diff --git a/docs/_build/doctrees/arbitrary.doctree b/docs/_build/doctrees/arbitrary.doctree new file mode 100644 index 00000000000..73b9621fcb9 Binary files /dev/null and b/docs/_build/doctrees/arbitrary.doctree differ diff --git a/docs/_build/doctrees/bandpass.doctree b/docs/_build/doctrees/bandpass.doctree new file mode 100644 index 00000000000..f8d618f6e8d Binary files /dev/null and b/docs/_build/doctrees/bandpass.doctree differ diff --git a/docs/_build/doctrees/bessel.doctree b/docs/_build/doctrees/bessel.doctree new file mode 100644 index 00000000000..a983e1ef4c9 Binary files /dev/null and b/docs/_build/doctrees/bessel.doctree differ diff --git a/docs/_build/doctrees/bounds.doctree b/docs/_build/doctrees/bounds.doctree new file mode 100644 index 00000000000..48f66b553a9 Binary files /dev/null and b/docs/_build/doctrees/bounds.doctree differ diff --git a/docs/_build/doctrees/catalog.doctree b/docs/_build/doctrees/catalog.doctree new file mode 100644 index 00000000000..a371d71fa1f Binary files /dev/null and b/docs/_build/doctrees/catalog.doctree differ diff --git a/docs/_build/doctrees/cd.doctree b/docs/_build/doctrees/cd.doctree new file mode 100644 index 00000000000..b974b70757f Binary files /dev/null and b/docs/_build/doctrees/cd.doctree differ diff --git a/docs/_build/doctrees/chromatic.doctree b/docs/_build/doctrees/chromatic.doctree new file mode 100644 index 00000000000..07ae27d203b Binary files /dev/null and b/docs/_build/doctrees/chromatic.doctree differ diff --git a/docs/_build/doctrees/chromaticobject.doctree b/docs/_build/doctrees/chromaticobject.doctree new file mode 100644 index 00000000000..3065ca0f09b Binary files /dev/null and b/docs/_build/doctrees/chromaticobject.doctree differ diff --git a/docs/_build/doctrees/composite.doctree b/docs/_build/doctrees/composite.doctree new file mode 100644 index 00000000000..d32ac675841 Binary files /dev/null and b/docs/_build/doctrees/composite.doctree differ diff --git a/docs/_build/doctrees/config.doctree b/docs/_build/doctrees/config.doctree new file mode 100644 index 00000000000..4ebdffdfb3b Binary files /dev/null and b/docs/_build/doctrees/config.doctree differ diff --git a/docs/_build/doctrees/config_galsim.doctree b/docs/_build/doctrees/config_galsim.doctree new file mode 100644 index 00000000000..52583e9d1c8 Binary files /dev/null and b/docs/_build/doctrees/config_galsim.doctree differ diff --git a/docs/_build/doctrees/config_image.doctree b/docs/_build/doctrees/config_image.doctree new file mode 100644 index 00000000000..f3433fc2ea8 Binary files /dev/null and b/docs/_build/doctrees/config_image.doctree differ diff --git a/docs/_build/doctrees/config_input.doctree b/docs/_build/doctrees/config_input.doctree new file mode 100644 index 00000000000..a49ca13bd3d Binary files /dev/null and b/docs/_build/doctrees/config_input.doctree differ diff --git a/docs/_build/doctrees/config_objects.doctree b/docs/_build/doctrees/config_objects.doctree new file mode 100644 index 00000000000..a7cd1efd418 Binary files /dev/null and b/docs/_build/doctrees/config_objects.doctree differ diff --git a/docs/_build/doctrees/config_output.doctree b/docs/_build/doctrees/config_output.doctree new file mode 100644 index 00000000000..79ccfd7e6fa Binary files /dev/null and b/docs/_build/doctrees/config_output.doctree differ diff --git a/docs/_build/doctrees/config_process.doctree b/docs/_build/doctrees/config_process.doctree new file mode 100644 index 00000000000..959749d53ad Binary files /dev/null and b/docs/_build/doctrees/config_process.doctree differ diff --git a/docs/_build/doctrees/config_special.doctree b/docs/_build/doctrees/config_special.doctree new file mode 100644 index 00000000000..863a5669e69 Binary files /dev/null and b/docs/_build/doctrees/config_special.doctree differ diff --git a/docs/_build/doctrees/config_stamp.doctree b/docs/_build/doctrees/config_stamp.doctree new file mode 100644 index 00000000000..93f4c1e3b38 Binary files /dev/null and b/docs/_build/doctrees/config_stamp.doctree differ diff --git a/docs/_build/doctrees/config_top.doctree b/docs/_build/doctrees/config_top.doctree new file mode 100644 index 00000000000..215f3e16418 Binary files /dev/null and b/docs/_build/doctrees/config_top.doctree differ diff --git a/docs/_build/doctrees/config_values.doctree b/docs/_build/doctrees/config_values.doctree new file mode 100644 index 00000000000..7708f69efc8 Binary files /dev/null and b/docs/_build/doctrees/config_values.doctree differ diff --git a/docs/_build/doctrees/corr_noise.doctree b/docs/_build/doctrees/corr_noise.doctree new file mode 100644 index 00000000000..aa3505f31f9 Binary files /dev/null and b/docs/_build/doctrees/corr_noise.doctree differ diff --git a/docs/_build/doctrees/cpp.doctree b/docs/_build/doctrees/cpp.doctree new file mode 100644 index 00000000000..ada38c49737 Binary files /dev/null and b/docs/_build/doctrees/cpp.doctree differ diff --git a/docs/_build/doctrees/cpp_bounds.doctree b/docs/_build/doctrees/cpp_bounds.doctree new file mode 100644 index 00000000000..6c9442c6b77 Binary files /dev/null and b/docs/_build/doctrees/cpp_bounds.doctree differ diff --git a/docs/_build/doctrees/cpp_hsm.doctree b/docs/_build/doctrees/cpp_hsm.doctree new file mode 100644 index 00000000000..efbfe7cae9a Binary files /dev/null and b/docs/_build/doctrees/cpp_hsm.doctree differ diff --git a/docs/_build/doctrees/cpp_image.doctree b/docs/_build/doctrees/cpp_image.doctree new file mode 100644 index 00000000000..6487df27db4 Binary files /dev/null and b/docs/_build/doctrees/cpp_image.doctree differ diff --git a/docs/_build/doctrees/cpp_interp.doctree b/docs/_build/doctrees/cpp_interp.doctree new file mode 100644 index 00000000000..7506a26713d Binary files /dev/null and b/docs/_build/doctrees/cpp_interp.doctree differ diff --git a/docs/_build/doctrees/cpp_math.doctree b/docs/_build/doctrees/cpp_math.doctree new file mode 100644 index 00000000000..c20640058d8 Binary files /dev/null and b/docs/_build/doctrees/cpp_math.doctree differ diff --git a/docs/_build/doctrees/cpp_noise.doctree b/docs/_build/doctrees/cpp_noise.doctree new file mode 100644 index 00000000000..871a9414842 Binary files /dev/null and b/docs/_build/doctrees/cpp_noise.doctree differ diff --git a/docs/_build/doctrees/cpp_photon.doctree b/docs/_build/doctrees/cpp_photon.doctree new file mode 100644 index 00000000000..622c0c3eee9 Binary files /dev/null and b/docs/_build/doctrees/cpp_photon.doctree differ diff --git a/docs/_build/doctrees/cpp_sb.doctree b/docs/_build/doctrees/cpp_sb.doctree new file mode 100644 index 00000000000..719c977eeba Binary files /dev/null and b/docs/_build/doctrees/cpp_sb.doctree differ diff --git a/docs/_build/doctrees/dcr.doctree b/docs/_build/doctrees/dcr.doctree new file mode 100644 index 00000000000..bfad3098073 Binary files /dev/null and b/docs/_build/doctrees/dcr.doctree differ diff --git a/docs/_build/doctrees/des.doctree b/docs/_build/doctrees/des.doctree new file mode 100644 index 00000000000..4b598196d62 Binary files /dev/null and b/docs/_build/doctrees/des.doctree differ diff --git a/docs/_build/doctrees/deviate.doctree b/docs/_build/doctrees/deviate.doctree new file mode 100644 index 00000000000..d724e6b71ff Binary files /dev/null and b/docs/_build/doctrees/deviate.doctree differ diff --git a/docs/_build/doctrees/environment.pickle b/docs/_build/doctrees/environment.pickle new file mode 100644 index 00000000000..7c8ce08fe66 Binary files /dev/null and b/docs/_build/doctrees/environment.pickle differ diff --git a/docs/_build/doctrees/errors.doctree b/docs/_build/doctrees/errors.doctree new file mode 100644 index 00000000000..330e3adda2a Binary files /dev/null and b/docs/_build/doctrees/errors.doctree differ diff --git a/docs/_build/doctrees/fft.doctree b/docs/_build/doctrees/fft.doctree new file mode 100644 index 00000000000..3a3b4f2fe2b Binary files /dev/null and b/docs/_build/doctrees/fft.doctree differ diff --git a/docs/_build/doctrees/fits.doctree b/docs/_build/doctrees/fits.doctree new file mode 100644 index 00000000000..93bc2b583d5 Binary files /dev/null and b/docs/_build/doctrees/fits.doctree differ diff --git a/docs/_build/doctrees/gal.doctree b/docs/_build/doctrees/gal.doctree new file mode 100644 index 00000000000..3a118725777 Binary files /dev/null and b/docs/_build/doctrees/gal.doctree differ diff --git a/docs/_build/doctrees/gsobject.doctree b/docs/_build/doctrees/gsobject.doctree new file mode 100644 index 00000000000..ded801c95e7 Binary files /dev/null and b/docs/_build/doctrees/gsobject.doctree differ diff --git a/docs/_build/doctrees/gsparams.doctree b/docs/_build/doctrees/gsparams.doctree new file mode 100644 index 00000000000..24c3e1347cd Binary files /dev/null and b/docs/_build/doctrees/gsparams.doctree differ diff --git a/docs/_build/doctrees/history.doctree b/docs/_build/doctrees/history.doctree new file mode 100644 index 00000000000..894861e4cf3 Binary files /dev/null and b/docs/_build/doctrees/history.doctree differ diff --git a/docs/_build/doctrees/hsm.doctree b/docs/_build/doctrees/hsm.doctree new file mode 100644 index 00000000000..456b84f49b0 Binary files /dev/null and b/docs/_build/doctrees/hsm.doctree differ diff --git a/docs/_build/doctrees/image.doctree b/docs/_build/doctrees/image.doctree new file mode 100644 index 00000000000..189e78fbd9b Binary files /dev/null and b/docs/_build/doctrees/image.doctree differ diff --git a/docs/_build/doctrees/image_class.doctree b/docs/_build/doctrees/image_class.doctree new file mode 100644 index 00000000000..6eb13c4d6f2 Binary files /dev/null and b/docs/_build/doctrees/image_class.doctree differ diff --git a/docs/_build/doctrees/index.doctree b/docs/_build/doctrees/index.doctree new file mode 100644 index 00000000000..571c9b863ba Binary files /dev/null and b/docs/_build/doctrees/index.doctree differ diff --git a/docs/_build/doctrees/install.doctree b/docs/_build/doctrees/install.doctree new file mode 100644 index 00000000000..399e0d52e2e Binary files /dev/null and b/docs/_build/doctrees/install.doctree differ diff --git a/docs/_build/doctrees/install_conda.doctree b/docs/_build/doctrees/install_conda.doctree new file mode 100644 index 00000000000..6487bab9e6e Binary files /dev/null and b/docs/_build/doctrees/install_conda.doctree differ diff --git a/docs/_build/doctrees/install_pip.doctree b/docs/_build/doctrees/install_pip.doctree new file mode 100644 index 00000000000..c0bc11e1098 Binary files /dev/null and b/docs/_build/doctrees/install_pip.doctree differ diff --git a/docs/_build/doctrees/integ.doctree b/docs/_build/doctrees/integ.doctree new file mode 100644 index 00000000000..9ab67377954 Binary files /dev/null and b/docs/_build/doctrees/integ.doctree differ diff --git a/docs/_build/doctrees/interpolant.doctree b/docs/_build/doctrees/interpolant.doctree new file mode 100644 index 00000000000..fa8b74835c7 Binary files /dev/null and b/docs/_build/doctrees/interpolant.doctree differ diff --git a/docs/_build/doctrees/misc.doctree b/docs/_build/doctrees/misc.doctree new file mode 100644 index 00000000000..2d1fc88429c Binary files /dev/null and b/docs/_build/doctrees/misc.doctree differ diff --git a/docs/_build/doctrees/nfwhalo.doctree b/docs/_build/doctrees/nfwhalo.doctree new file mode 100644 index 00000000000..675aa3fcd96 Binary files /dev/null and b/docs/_build/doctrees/nfwhalo.doctree differ diff --git a/docs/_build/doctrees/noise.doctree b/docs/_build/doctrees/noise.doctree new file mode 100644 index 00000000000..1baf6f5c9cb Binary files /dev/null and b/docs/_build/doctrees/noise.doctree differ diff --git a/docs/_build/doctrees/older.doctree b/docs/_build/doctrees/older.doctree new file mode 100644 index 00000000000..8b847256491 Binary files /dev/null and b/docs/_build/doctrees/older.doctree differ diff --git a/docs/_build/doctrees/overview.doctree b/docs/_build/doctrees/overview.doctree new file mode 100644 index 00000000000..b23d200f644 Binary files /dev/null and b/docs/_build/doctrees/overview.doctree differ diff --git a/docs/_build/doctrees/phase_psf.doctree b/docs/_build/doctrees/phase_psf.doctree new file mode 100644 index 00000000000..d3d1e298aca Binary files /dev/null and b/docs/_build/doctrees/phase_psf.doctree differ diff --git a/docs/_build/doctrees/photon.doctree b/docs/_build/doctrees/photon.doctree new file mode 100644 index 00000000000..089328f4629 Binary files /dev/null and b/docs/_build/doctrees/photon.doctree differ diff --git a/docs/_build/doctrees/photon_array.doctree b/docs/_build/doctrees/photon_array.doctree new file mode 100644 index 00000000000..edaa453e484 Binary files /dev/null and b/docs/_build/doctrees/photon_array.doctree differ diff --git a/docs/_build/doctrees/photon_ops.doctree b/docs/_build/doctrees/photon_ops.doctree new file mode 100644 index 00000000000..0ef70dc3fa4 Binary files /dev/null and b/docs/_build/doctrees/photon_ops.doctree differ diff --git a/docs/_build/doctrees/pos.doctree b/docs/_build/doctrees/pos.doctree new file mode 100644 index 00000000000..e14d29403c1 Binary files /dev/null and b/docs/_build/doctrees/pos.doctree differ diff --git a/docs/_build/doctrees/powerspectrum.doctree b/docs/_build/doctrees/powerspectrum.doctree new file mode 100644 index 00000000000..5d5ef3235dc Binary files /dev/null and b/docs/_build/doctrees/powerspectrum.doctree differ diff --git a/docs/_build/doctrees/pse.doctree b/docs/_build/doctrees/pse.doctree new file mode 100644 index 00000000000..a2fcfa37dfa Binary files /dev/null and b/docs/_build/doctrees/pse.doctree differ diff --git a/docs/_build/doctrees/psf.doctree b/docs/_build/doctrees/psf.doctree new file mode 100644 index 00000000000..b567258a270 Binary files /dev/null and b/docs/_build/doctrees/psf.doctree differ diff --git a/docs/_build/doctrees/random.doctree b/docs/_build/doctrees/random.doctree new file mode 100644 index 00000000000..04fa850e65c Binary files /dev/null and b/docs/_build/doctrees/random.doctree differ diff --git a/docs/_build/doctrees/real_gal.doctree b/docs/_build/doctrees/real_gal.doctree new file mode 100644 index 00000000000..184532c9258 Binary files /dev/null and b/docs/_build/doctrees/real_gal.doctree differ diff --git a/docs/_build/doctrees/roman.doctree b/docs/_build/doctrees/roman.doctree new file mode 100644 index 00000000000..f0da9d38ee4 Binary files /dev/null and b/docs/_build/doctrees/roman.doctree differ diff --git a/docs/_build/doctrees/sb.doctree b/docs/_build/doctrees/sb.doctree new file mode 100644 index 00000000000..241e8cc2122 Binary files /dev/null and b/docs/_build/doctrees/sb.doctree differ diff --git a/docs/_build/doctrees/sed.doctree b/docs/_build/doctrees/sed.doctree new file mode 100644 index 00000000000..d33295f40f9 Binary files /dev/null and b/docs/_build/doctrees/sed.doctree differ diff --git a/docs/_build/doctrees/sensor.doctree b/docs/_build/doctrees/sensor.doctree new file mode 100644 index 00000000000..4e5ec6e29a1 Binary files /dev/null and b/docs/_build/doctrees/sensor.doctree differ diff --git a/docs/_build/doctrees/shared.doctree b/docs/_build/doctrees/shared.doctree new file mode 100644 index 00000000000..9ba77af82a2 Binary files /dev/null and b/docs/_build/doctrees/shared.doctree differ diff --git a/docs/_build/doctrees/shear.doctree b/docs/_build/doctrees/shear.doctree new file mode 100644 index 00000000000..cb081b68113 Binary files /dev/null and b/docs/_build/doctrees/shear.doctree differ diff --git a/docs/_build/doctrees/simple.doctree b/docs/_build/doctrees/simple.doctree new file mode 100644 index 00000000000..fb19d911ff1 Binary files /dev/null and b/docs/_build/doctrees/simple.doctree differ diff --git a/docs/_build/doctrees/spectral.doctree b/docs/_build/doctrees/spectral.doctree new file mode 100644 index 00000000000..43d22a3f7f8 Binary files /dev/null and b/docs/_build/doctrees/spectral.doctree differ diff --git a/docs/_build/doctrees/table.doctree b/docs/_build/doctrees/table.doctree new file mode 100644 index 00000000000..f231c5f86a0 Binary files /dev/null and b/docs/_build/doctrees/table.doctree differ diff --git a/docs/_build/doctrees/transform.doctree b/docs/_build/doctrees/transform.doctree new file mode 100644 index 00000000000..94a0705ed51 Binary files /dev/null and b/docs/_build/doctrees/transform.doctree differ diff --git a/docs/_build/doctrees/tutorials.doctree b/docs/_build/doctrees/tutorials.doctree new file mode 100644 index 00000000000..bdd526b1de0 Binary files /dev/null and b/docs/_build/doctrees/tutorials.doctree differ diff --git a/docs/_build/doctrees/units.doctree b/docs/_build/doctrees/units.doctree new file mode 100644 index 00000000000..8b1690a410c Binary files /dev/null and b/docs/_build/doctrees/units.doctree differ diff --git a/docs/_build/doctrees/utilities.doctree b/docs/_build/doctrees/utilities.doctree new file mode 100644 index 00000000000..2adda5136eb Binary files /dev/null and b/docs/_build/doctrees/utilities.doctree differ diff --git a/docs/_build/doctrees/wcs.doctree b/docs/_build/doctrees/wcs.doctree new file mode 100644 index 00000000000..908a540ab82 Binary files /dev/null and b/docs/_build/doctrees/wcs.doctree differ diff --git a/docs/_build/doctrees/wl.doctree b/docs/_build/doctrees/wl.doctree new file mode 100644 index 00000000000..7098940e448 Binary files /dev/null and b/docs/_build/doctrees/wl.doctree differ diff --git a/docs/_build/doctrees/zernike.doctree b/docs/_build/doctrees/zernike.doctree new file mode 100644 index 00000000000..f042fb8ec24 Binary files /dev/null and b/docs/_build/doctrees/zernike.doctree differ diff --git a/docs/_build/html/.buildinfo b/docs/_build/html/.buildinfo new file mode 100644 index 00000000000..b629ffc11e9 --- /dev/null +++ b/docs/_build/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 74c0871fd363725cd29535f6d4d3dc20 +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/docs/_build/html/_modules/coord/angle.html b/docs/_build/html/_modules/coord/angle.html new file mode 100644 index 00000000000..40e1cfdcfd9 --- /dev/null +++ b/docs/_build/html/_modules/coord/angle.html @@ -0,0 +1,646 @@ + + + + + + coord.angle — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for coord.angle

+# Copyright (c) 2013-2017 LSST Dark Energy Science Collaboration (DESC)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+import math
+import numpy as np
+
+from .angleunit import AngleUnit, radians, degrees, hours, arcmin, arcsec
+
+
[docs]class Angle(object): + """A class representing an Angle. Angles are a value with an AngleUnit. + + **Initialization:** + + You typically create an Angle by multiplying a number by a coord.AngleUnit, for example: + + .. + + >>> pixel = 0.27 * arcsec + >>> ra = 13.4 * hours + >>> dec = -32 * degrees + >>> from math import pi + >>> theta = pi/2. * radians + + You can also initialize explicitly, taking a value and a unit: + + :meth:`coord.Angle.__init__` + + >>> unit = AngleUnit(math.pi / 100) # gradians + >>> phi = Angle(90, unit) + + **Built-in units:** + + There are five built-in AngleUnits which are always available for use: + + :coord.radians: coord.AngleUnit(1.) + :coord.degrees: coord.AngleUnit(pi / 180.) + :coord.hours: coord.AngleUnit(pi / 12.) + :coord.arcmin: coord.AngleUnit(pi / 180. / 60.) + :coord.arcsec: coord.AngleUnit(pi / 180. / 3600.) + + **Attribute:** + + Since extracting the value in radians is extremely common, we have a read-only attribute + to do this quickly: + + :rad: The measure of the unit in radians. + + For example: + + .. + + >>> theta = 90 * degrees + >>> print(theta.rad) + 1.5707963267948966 + + It is equivalent to the more verbose: + + .. + + >>> x = theta / radians + >>> print(x) + 1.5707963267948966 + + but without actually requiring the floating point operation of dividing by 1. + + **Arithmetic:** + + Allowed arithmetic with Angles include the following. + In the list below, + + - ``x`` is an arbitrary ``float`` value + - ``unit1`` and ``unit2`` are arbitrary `AngleUnit` instances + - ``theta1`` and ``theta2`` are arbitrary `Angle` instances + + >>> x = 37.8 + >>> unit1 = arcmin + >>> unit2 = degrees + + >>> theta1 = x * unit1 + >>> theta2 = x * unit2 + >>> x2 = theta1 / unit2 + >>> theta = theta1 + theta2 + >>> theta = theta1 - theta2 + >>> theta = theta1 * x + >>> theta = x * theta1 + >>> theta = theta1 / x + >>> theta = -theta1 + >>> theta += theta1 + >>> theta -= theta1 + >>> theta *= x + >>> theta /= x + >>> x = unit1 / unit2 # equivalent to x = (1 * unit1) / unit2 + + The above operations on NumPy arrays containing Angles are permitted as well. + + **Trigonometry:** + + There are convenience function for getting the sin, cos, and tan of an angle, along with + one for getting sin and cos together, which should be more efficient than doing sin and + cos separately: + + | :meth:`coord.Angle.sin` + | :meth:`coord.Angle.cos` + | :meth:`coord.Angle.tan` + | :meth:`coord.Angle.sincos` + + >>> sint = theta.sin() # equivalent to sint = math.sin(theta.rad) + >>> cost = theta.cos() # equivalent to cost = math.cos(theta.rad) + >>> tant = theta.tan() # equivalent to tant = math.tan(theta.rad) + >>> sint, cost = theta.sincos() + + These functions mean that numpy trig functions will work on Angles or arrays of Angles: + + .. + + >>> sint = np.sin(theta) + >>> cost = np.cos(theta) + >>> tant = np.tan(theta) + + **Wrapping:** + + Depending on the context, theta = 2pi radians and theta = 0 radians may mean the same thing. + If you want your angles to be wrapped to [-pi,pi) radians, you can do this by calling + + :meth:`coord.Angle.wrap` + + >>> theta = theta.wrap() + + This could be appropriate before testing for the equality of two angles for example, or + calculating the difference between them. + + There is also an option to wrap into a different 2 pi range if so desired by specifying + the center of the range. + """ + def __init__(self, theta, unit=None): + """ + :param theta: The numerical value of the angle. + :param unit: The units theta is measured in. + """ + # We also want to allow angle1 = Angle(angle2) as a copy, so check for that. + if isinstance(theta,Angle): + if unit is not None: + raise TypeError("Cannot provide unit if theta is already an Angle instance") + self._rad = theta._rad + elif unit is None: + raise TypeError("Must provide unit for Angle.__init__") + elif not isinstance(unit, AngleUnit): + raise TypeError("Invalid unit %s of type %s"%(unit,type(unit))) + else: + # Normal case + self._rad = float(theta) * unit.value + + @property + def rad(self): + """Return the Angle in radians. + + Equivalent to angle / coord.radians + """ + return self._rad + + @property + def deg(self): + """Return the Angle in degrees. + + Equivalent to angle / coord.degrees + """ + return self / degrees + + def __neg__(self): + return _Angle(-self._rad) + + def __pos__(self): + return self + + def __abs__(self): + return _Angle(abs(self._rad)) + + def __add__(self, other): + if not isinstance(other, Angle): + raise TypeError("Cannot add %s of type %s to an Angle"%(other,type(other))) + return _Angle(self._rad + other._rad) + + def __sub__(self, other): + if not isinstance(other, Angle): + raise TypeError("Cannot subtract %s of type %s from an Angle"%(other,type(other))) + return _Angle(self._rad - other._rad) + + def __mul__(self, other): + if other != float(other): + raise TypeError("Cannot multiply Angle by %s of type %s"%(other,type(other))) + return _Angle(self._rad * other) + + __rmul__ = __mul__ + + def __div__(self, other): + if isinstance(other, AngleUnit): + return self._rad / other.value + elif other == float(other): + return _Angle(self._rad / other) + else: + raise TypeError("Cannot divide Angle by %s of type %s"%(other,type(other))) + + __truediv__ = __div__ + +
[docs] def wrap(self, center=None): + """Wrap Angle to lie in the range [-pi, pi) radians (or other range of 2pi radians) + + Depending on the context, theta = 2pi radians and theta = 0 radians are the same thing. + If you want your angles to be wrapped to [-pi, pi) radians, you can do this as follows: + + .. + + >>> theta = Angle(700 * degrees) + >>> theta = theta.wrap() + >>> print(theta.deg) + -19.99999999999998 + + This could be appropriate before testing for the equality of two angles for example, or + calculating the difference between them. + + If you want to wrap to a different range than [-pi, pi), you can set the ``center`` argument + to be the desired center of the the range. e.g. for return values to fall in [0, 2pi), + you could call + + .. + + >>> theta = theta.wrap(center=180. * degrees) + >>> print(theta / degrees) + 340.0 + + :param center: The center point of the wrapped range. [default: 0 radians] + + :returns: the equivalent angle within the range [center-pi, center+pi) + """ + if center is None: center = _Angle(0.) + start = center._rad - math.pi + offset = (self._rad - start) // (2.*math.pi) # How many full cycles to subtract + return _Angle(self._rad - offset * 2.*math.pi)
+ +
[docs] def sin(self): + """Return the sin of an Angle.""" + return math.sin(self._rad)
+ +
[docs] def cos(self): + """Return the cos of an Angle.""" + return math.cos(self._rad)
+ +
[docs] def tan(self): + """Return the tan of an Angle.""" + return math.tan(self._rad)
+ +
[docs] def sincos(self): + """Return both the sin and cos of an Angle as a numpy array [sint, cost]. + """ + sin = math.sin(self._rad) + cos = math.cos(self._rad) + return sin, cos
+ + def __str__(self): + return str(self._rad) + ' radians' + + def __repr__(self): + return 'coord.Angle(%r, coord.radians)'%self.rad + + def __eq__(self, other): + return isinstance(other,Angle) and self.rad == other.rad + + def __ne__(self, other): + return not self.__eq__(other) + + def __le__(self, other): + if not isinstance(other, Angle): + raise TypeError("Cannot compare %s of type %s to an Angle"%(other,type(other))) + return self._rad <= other._rad + + def __lt__(self, other): + if not isinstance(other, Angle): + raise TypeError("Cannot compare %s of type %s to an Angle"%(other,type(other))) + return self._rad < other._rad + + def __ge__(self, other): + if not isinstance(other, Angle): + raise TypeError("Cannot compare %s of type %s to an Angle"%(other,type(other))) + return self._rad >= other._rad + + def __gt__(self, other): + if not isinstance(other, Angle): + raise TypeError("Cannot compare %s of type %s to an Angle"%(other,type(other))) + return self._rad > other._rad + + def __hash__(self): + return hash(('coord.Angle', self._rad)) + + @staticmethod + def _make_dms_string(decimal, sep, prec, pad, plus_sign): + # Account for the sign properly + if decimal < 0: + sign = '-' + decimal = -decimal + elif plus_sign: + sign = '+' + else: + sign = '' + + # Figure out the 3 sep tokens + sep1 = sep2 = '' + sep3 = None + if len(sep) == 1: + sep1 = sep2 = sep + elif len(sep) == 2: + sep1, sep2 = sep + elif len(sep) == 3: + sep1, sep2, sep3 = sep + + # Round to nearest 1.e-8 seconds (or 10**-prec if given) + round_prec = 8 if prec is None else prec + digits = 10**round_prec + + decimal = int(3600 * digits * decimal + 0.5) + + d = decimal // (3600 * digits) + decimal -= d * (3600 * digits) + m = decimal // (60 * digits) + decimal -= m * (60 * digits) + s = decimal // digits + decimal -= s * digits + + # Make the string + if pad: + d_str = '%02d'%d + m_str = '%02d'%m + s_str = '%02d'%s + else: + d_str = '%d'%d + m_str = '%d'%m + s_str = '%d'%s + string = '%s%s%s%s%s%s.%0*d'%(sign,d_str,sep1,m_str,sep2,s_str,round_prec,decimal) + if not prec: + string = string.rstrip('0') + string = string.rstrip('.') + if sep3: + string = string + sep3 + return string + +
[docs] def hms(self, sep=":", prec=None, pad=True, plus_sign=False): + """Return an HMS representation of the angle as a string: +-hh:mm:ss.decimal. + + An optional ``sep`` parameter can change the : to something else (e.g. a space or + nothing at all). + + Note: the reverse process is effected by :meth:`Angle.from_hms`: + + .. + + >>> angle = -5.357 * hours + >>> hms = angle.hms() + >>> print(hms) + -05:21:25.2 + >>> angle2 = Angle.from_hms(hms) + >>> print(angle2 / hours) + -5.356999999999999 + + :param sep: The token to put between the hh and mm and beteen mm and ss. This may + also be a string of 2 or 3 items, e.g. 'hm' or 'hms'. Or even a + tuple of strings such as ('hours ', 'minutes ', 'seconds'). + [default: ':'] + :param prec: The number of digits of precision after the decimal point. + [default: None] + :param pad: Whether to pad with a leading 0 if necessary to make h,m,s 2 digits. + [default: True] + :param plus_sign: Whether to use a plus sign for positive angles. [default: False] + + :returns: a string of the HMS representation of the angle. + """ + if not len(sep) <= 3: + raise ValueError("sep must be a string or tuple of length <= 3") + if prec is not None and not prec >= 0: + raise ValueError("prec must be >= 0") + return self._make_dms_string(self/hours, sep, prec, pad, plus_sign)
+ +
[docs] def dms(self, sep=":", prec=None, pad=True, plus_sign=False): + """Return a DMS representation of the angle as a string: +-dd:mm:ss.decimal + An optional ``sep`` parameter can change the : to something else (e.g. a space or + nothing at all). + + Note: the reverse process is effected by :meth:`Angle.from_dms`: + + .. + + >>> angle = -(5 * degrees + 21 * arcmin + 25.2 * arcsec) + >>> dms = angle.dms() + >>> print(dms) + -05:21:25.2 + >>> angle2 = Angle.from_dms(dms) + >>> print(angle2 / degrees) + -5.356999999999999 + + :param sep: The token to put between the hh and mm and beteen mm and ss. This may + also be a string of 2 or 3 items, e.g. 'dm' or 'dms'. Or even a + tuple of strings such as ('degrees ', 'minutes ', 'seconds'). + [default: ':'] + :param prec: The number of digits of precision after the decimal point. + [default: None] + :param pad: Whether to pad with a leading 0 if necessary to make h 2 digits. + [default: True] + :param plus_sign: Whether to use a plus sign for positive angles. [default: False] + + :returns: a string of the DMS representation of the angle. + """ + if not len(sep) <= 3: + raise ValueError("sep must be a string or tuple of length <= 3") + if prec is not None and not prec >= 0: + raise ValueError("prec must be >= 0") + return self._make_dms_string(self/degrees, sep, prec, pad, plus_sign)
+ +
[docs] @staticmethod + def from_hms(str): + """Convert a string of the form hh:mm:ss.decimal into an Angle. + + There may be an initial + or - (or neither), then two digits for the hours, two for the + minutes, and two for the seconds. Then there may be a decimal point followed by more + digits. There may be a colon separating hh, mm, and ss, or whitespace, or nothing at all. + In fact, the code will ignore any non-digits between the hours, minutes, and seconds. + + Note: the reverse process is effected by Angle.hms(): + + .. + + >>> angle = -5.357 * hours + >>> hms = angle.hms() + >>> print(hms) + -05:21:25.2 + >>> angle2 = Angle.from_hms(hms) + >>> print(angle2 / hours) + -5.356999999999999 + + :param str: The string to parse. + + :returns: the corresponding Angle instance + """ + return Angle._parse_dms(str) * hours
+ +
[docs] @staticmethod + def from_dms(str): + """Convert a string of the form dd:mm:ss.decimal into an Angle. + + There may be an initial + or - (or neither), then two digits for the degrees, two for the + minutes, and two for the seconds. Then there may be a decimal point followed by more + digits. There may be a colon separating dd, mm, and ss, or whitespace, or nothing at all. + In fact, the code will ignore any non-digits between the degrees, minutes, and seconds. + + Note: the reverse process is effected by Angle.dms(): + + .. + + >>> angle = -(5 * degrees + 21 * arcmin + 25.2 * arcsec) + >>> dms = angle.dms() + >>> print(dms) + -05:21:25.2 + >>> angle2 = Angle.from_dms(dms) + >>> print(angle2 / degrees) + -5.356999999999999 + + :param str: The string to parse. + + :returns: the corresponding Angle instance + """ + return Angle._parse_dms(str) * degrees
+ + @staticmethod + def _parse_dms(dms): + """Convert a string of the form dd:mm:ss.decimal into decimal degrees. + """ + import re + tokens = tuple(filter(None, re.split('([\.\d]+)', dms.strip()))) + if len(tokens) <= 1: + raise ValueError("string is not of the expected format") + sign = 1 + try: + dd = float(tokens[0]) + except ValueError: + if tokens[0].strip() == '-': + sign = -1 + tokens = tokens[1:] + dd = float(tokens[0]) + if len(tokens) <= 1: + raise ValueError("string is not of the expected format") + if len(tokens) <= 2: + return sign * dd + mm = float(tokens[2]) + if len(tokens) <= 4: + return sign * (dd + mm/60) + if len(tokens) >= 7: + raise ValueError("string is not of the expected format") + ss = float(tokens[4]) + return sign * (dd + mm/60. + ss/3600.)
+ +
[docs]def _Angle(theta): + """Equivalent to ``Angle(theta, coord.radians)``, but without the normal overhead (which isn't + much to be honest, but this is nonetheless slightly quicker). + + :param theta: The numerical value of the angle in radians. + """ + ret = Angle.__new__(Angle) + ret._rad = theta + return ret
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/coord/angleunit.html b/docs/_build/html/_modules/coord/angleunit.html new file mode 100644 index 00000000000..1d8aa7f9467 --- /dev/null +++ b/docs/_build/html/_modules/coord/angleunit.html @@ -0,0 +1,275 @@ + + + + + + coord.angleunit — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for coord.angleunit

+# Copyright (c) 2013-2017 LSST Dark Energy Science Collaboration (DESC)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from __future__ import print_function
+import math
+
+
[docs]class AngleUnit(object): + """ + A class for defining angular units used by Angle objects. + + **Initialization:** + + An AngleUnit takes a single argument for initialization, a float that specifies the size + of the desired angular unit in radians. For example: + + :meth:`coord.AngleUnit.__init__` + + >>> gradian = AngleUnit(2. * math.pi / 400.) + >>> print(gradian) + coord.AngleUnit(0.015707963267948967) + + **Built-in units:** + + There are five built-in AngleUnits which are always available for use: + + :coord.radians: coord.AngleUnit(1.) + :coord.degrees: coord.AngleUnit(pi / 180.) + :coord.hours: coord.AngleUnit(pi / 12.) + :coord.arcmin: coord.AngleUnit(pi / 180. / 60.) + :coord.arcsec: coord.AngleUnit(pi / 180. / 3600.) + + **Attribute:** + + An AngleUnit as the following (read-only) attribute: + + :value: The measure of the unit in radians. + """ + valid_names = ['rad', 'deg', 'hr', 'hour', 'arcmin', 'arcsec'] + + def __init__(self, value): + """ + :param value: The measure of the unit in radians. + """ + self._value = float(value) + + @property + def value(self): + """A read-only attribute giving the measure of the AngleUnit in radians.""" + return self._value + + def __rmul__(self, theta): + """float * AngleUnit returns an Angle""" + from .angle import Angle # Can't do this at the top, since circular reference. + return Angle(theta, self) + + def __div__(self, unit): + """AngleUnit / AngleUnit returns a float giving the relative scaling. + + Note: At least to within machine precision, it is the case that + + (x * angle_unit1) / angle_unit2 == x * (angle_unit1 / angle_unit2) + """ + if not isinstance(unit, AngleUnit): + raise TypeError("Cannot divide AngleUnit by %s"%unit) + return self.value / unit.value + + __truediv__ = __div__ + +
[docs] @staticmethod + def from_name(unit): + """Convert a string into the corresponding AngleUnit. + + Only the start of the string is checked, so for instance 'radian' or 'radians' is + equivalent to 'rad'. + + Valid options are: + + :rad: AngleUnit(1.) + :deg: AngleUnit(pi / 180.) + :hour or hr: AngleUnit(pi / 12.) + :arcmin: AngleUnit(pi / 180. / 60.) + :arcsec: AngleUnit(pi / 180. / 3600.) + + Note: these valid names are listed in AngleUnit.valid_names. + + :param unit: The string name of the unit to return + + :returns: an AngleUnit + """ + unit = unit.strip().lower() + if unit.startswith('rad') : + return radians + elif unit.startswith('deg') : + return degrees + elif unit.startswith('hour') : + return hours + elif unit.startswith('hr') : + return hours + elif unit.startswith('arcmin') : + return arcmin + elif unit.startswith('arcsec') : + return arcsec + else : + raise ValueError("Unknown Angle unit: %s"%unit)
+ + def __repr__(self): + if self == radians: + return 'coord.radians' + elif self == degrees: + return 'coord.degrees' + elif self == hours: + return 'coord.hours' + elif self == arcmin: + return 'coord.arcmin' + elif self == arcsec: + return 'coord.arcsec' + else: + return 'coord.AngleUnit(%r)'%self.value + + def __eq__(self, other): + return isinstance(other,AngleUnit) and self.value == other.value + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(('coord.AngleUnit', self.value))
+ +# Convenient pre-set built-in units +# (These are typically the only ones we will use.) + +radians = AngleUnit(1.) +hours = AngleUnit(math.pi / 12.) +degrees = AngleUnit(math.pi / 180.) +arcmin = AngleUnit(math.pi / 10800.) +arcsec = AngleUnit(math.pi / 648000.) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/coord/celestial.html b/docs/_build/html/_modules/coord/celestial.html new file mode 100644 index 00000000000..5c5f8eb7319 --- /dev/null +++ b/docs/_build/html/_modules/coord/celestial.html @@ -0,0 +1,1252 @@ + + + + + + coord.celestial — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for coord.celestial

+# Copyright (c) 2013-2017 LSST Dark Energy Science Collaboration (DESC)
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+
+from __future__ import print_function
+import numpy as np
+import math
+import datetime
+import warnings
+
+from .angle import Angle, _Angle
+from .angleunit import radians, degrees, hours, arcsec
+from . import util
+
+
[docs]class CelestialCoord(object): + """This class defines a position on the celestial sphere, normally given by two angles, + ``ra`` and ``dec``. + + This class can be used to perform various calculations in spherical coordinates, such + as finding the angular distance between two points in the sky, calculating the angles in + spherical triangles, projecting from sky coordinates onto a Euclidean tangent plane, etc. + + **Initialization:** + + A `CelestialCoord` object is constructed from the right ascension and declination: + + :meth:`coord.CelestialCoord.__init__` + + >>> c = CelestialCoord(ra=12*hours, dec=31*degrees) + >>> print(c) + coord.CelestialCoord(3.141592653589793 radians, 0.5410520681182421 radians) + + **Attributes:** + + A CelestialCoord has the following (read-only) attributes: + + :ra: The right ascension (an Angle instance) + :dec: The declination (an Angle instance) + + >>> print(c.ra / degrees, c.dec / degrees) + 180.0 31.0 + + In addition there is a convenience access property that returns ra and dec in radians. + + :rad: A tuple (ra.rad, dec.rad) + + >>> print(c.rad) + (3.141592653589793, 0.5410520681182421) + + **Spherical Geometry:** + + The basic spherical geometry operations are available to work with spherical triangles + + For three coordinates cA, cB, cC making a spherical triangle, one can calculate the + sides and angles via: + + | :meth:`coord.CelestialCoord.distanceTo` + | :meth:`coord.CelestialCoord.angleBetween` + + >>> cA = CelestialCoord(0 * degrees, 0 * degrees) + >>> cB = CelestialCoord(0 * degrees, 10 * degrees) + >>> cC = CelestialCoord(10 * degrees, 0 * degrees) + >>> a = cB.distanceTo(cC) + >>> b = cC.distanceTo(cA) + >>> c = cA.distanceTo(cB) + >>> print(a.deg, b.deg, c.deg) + 14.106044260566366 10.0 10.0 + >>> A = cA.angleBetween(cB, cC) + >>> B = cB.angleBetween(cC, cA) + >>> C = cC.angleBetween(cA, cB) + >>> print(A.deg, B.deg, C.deg) + 90.0 45.43854858674231 45.43854858674231 + + + **Projections:** + + Local tangent plane projections of an area of the sky can be performed using the project + method: + + :meth:`coord.CelestialCoord.project` + + >>> center = CelestialCoord(ra=10*hours, dec=30*degrees) + >>> sky_coord = CelestialCoord(ra=10.5*hours, dec=31*degrees) + >>> print(sky_coord) + coord.CelestialCoord(2.748893571891069 radians, 0.5410520681182421 radians) + >>> u, v = center.project(sky_coord) + >>> print(u.deg, v.deg) + -6.452371275343261 1.21794987288635 + + and back: + + :meth:`coord.CelestialCoord.deproject` + + >>> sky_coord = center.deproject(u,v) + >>> print(sky_coord) + coord.CelestialCoord(2.748893571891069 radians, 0.5410520681182421 radians) + + where u and v are Angles and center and sky_coord are CelestialCoords. + """ + def __init__(self, ra, dec=None): + """ + :param ra: The right ascension. Must be an Angle instance. + :param dec: The declination. Must be an Angle instance. + """ + if isinstance(ra, CelestialCoord) and dec is None: + # Copy constructor + self._ra = ra._ra + self._dec = ra._dec + self._x = None + elif ra is None or dec is None: + raise TypeError("ra and dec are both required") + elif not isinstance(ra, Angle): + raise TypeError("ra must be a coord.Angle") + elif not isinstance(dec, Angle): + raise TypeError("dec must be a coord.Angle") + elif dec/degrees > 90. or dec/degrees < -90.: + raise ValueError("dec must be between -90 deg and +90 deg.") + else: + # Normal case + self._ra = ra + self._dec = dec + self._x = None # Indicate that x,y,z are not set yet. + + @property + def ra(self): + """A read-only attribute, giving the Right Ascension as an Angle""" + return self._ra + + @property + def dec(self): + """A read-only attribute, giving the Declination as an Angle""" + return self._dec + + @property + def rad(self): + """A convenience property, giving a tuple (ra.rad, dec.rad) + """ + return (self._ra.rad, self._dec.rad) + + def _set_aux(self): + if self._x is None: + self._sindec, self._cosdec = self._dec.sincos() + self._sinra, self._cosra = self._ra.sincos() + self._x = self._cosdec * self._cosra + self._y = self._cosdec * self._sinra + self._z = self._sindec + +
[docs] def get_xyz(self): + """Get the (x,y,z) coordinates on the unit sphere corresponding to this (RA, Dec). + + .. math:: + + x &= \\cos(dec) \\cos(ra) \\\\ + y &= \\cos(dec) \\sin(ra) \\\\ + z &= \\sin(dec) + + :returns: a tuple (x,y,z) + """ + self._set_aux() + return self._x, self._y, self._z
+ +
[docs] @staticmethod + def from_xyz(x, y, z): + """Construct a CelestialCoord from a given (x,y,z) position in three dimensions. + + The 3D (x,y,z) position does not need to fall on the unit sphere. The RA, Dec will + be inferred from the relations: + + .. math:: + + x &= r \\cos(dec) \\cos(ra) \\\\ + y &= r \\cos(dec) \\sin(ra) \\\\ + z &= r \\sin(dec) + + where :math:`r` is arbitrary. + + :param x: The x position in 3 dimensions. Corresponds to r cos(dec) cos(ra) + :param y: The y position in 3 dimensions. Corresponds to r cos(dec) sin(ra) + :param z: The z position in 3 dimensions. Corresponds to r sin(dec) + + :returns: a CelestialCoord instance + """ + norm = np.sqrt(x*x + y*y + z*z) + if norm == 0.: + raise ValueError("CelestialCoord for position (0,0,0) is undefined.") + ret = CelestialCoord.__new__(CelestialCoord) + ret._x = x / norm + ret._y = y / norm + ret._z = z / norm + ret._sindec = ret._z + ret._cosdec = np.sqrt(ret._x*ret._x + ret._y*ret._y) + if ret._cosdec == 0.: + ret._sinra = 0. + ret._cosra = 1. + else: + ret._sinra = ret._y / ret._cosdec + ret._cosra = ret._x / ret._cosdec + ret._ra = (np.arctan2(ret._sinra, ret._cosra) * radians).wrap(_Angle(math.pi)) + ret._dec = np.arctan2(ret._sindec, ret._cosdec) * radians + return ret
+ +
[docs] @staticmethod + def radec_to_xyz(ra, dec, r=1.): + """Convert ra, dec (in radians) to 3D x,y,z coordinates on the unit sphere. + + The connection between (ra,dec) and (x,y,z) are given by the following formulae: + .. math:: + + x &= r \\cos(dec) \\cos(ra) \\\\ + y &= r \\cos(dec) \\sin(ra) \\\\ + z &= r \\sin(dec) + + For a single ra,dec pair, the following are essentially equivalent: + + >>> ra = 12*hours/radians # May be any angle measured + >>> dec = 31*degrees/radians # in radians + + >>> CelestialCoord.radec_to_xyz(ra, dec) + (-0.8571673007021123, 1.0497271911386187e-16, 0.5150380749100542) + + >>> CelestialCoord(ra * radians, dec * radians).get_xyz() + (-0.8571673007021123, 1.0497271911386187e-16, 0.5150380749100542) + + However, the advantage of this function is that the input values may be numpy + arrays, in which case, the return values will also be numpy arrays. + + :param ra: The right ascension(s) in radians. May be a numpy array. + :param dec: The declination(s) in radians. May be a numpy array. + :param r: The distance(s) from Earth (default 1.). May be a numpy array. + + :returns: x, y, z as a tuple. + """ + cosdec = np.cos(dec) + x = cosdec * np.cos(ra) * r + y = cosdec * np.sin(ra) * r + z = np.sin(dec) * r + return x,y,z
+ +
[docs] @staticmethod + def xyz_to_radec(x, y, z, return_r=False): + """Convert 3D x,y,z coordinates to ra, dec (in radians). + + The connection between (ra,dec) and (x,y,z) are given by the following formulae: + .. math:: + + x &= r \\cos(dec) \\cos(ra) \\\\ + y &= r \\cos(dec) \\sin(ra) \\\\ + z &= r \\sin(dec) + + For a single (x,y,z) position, the following are essentially equivalent: + + >>> x = 0.839 # May be any any 3D location + >>> y = 0.123 # Not necessarily on unit sphere + >>> z = 0.530 + + >>> CelestialCoord.xyz_to_radec(x, y, z) + (0.14556615088111796, 0.558616191048523) + + >>> c = CelestialCoord.from_xyz(x, y, z) + >>> c.ra.rad, c.dec.rad + (0.145566150881118, 0.558616191048523) + + However, the advantage of this function is that the input values may be numpy + arrays, in which case, the return values will also be numpy arrays. + + :param x: The x position(s) in 3 dimensions. May be a numpy array. + :param y: The y position(s) in 3 dimensions. May be a numpy array. + :param z: The z position(s) in 3 dimensions. May be a numpy array. + :param return_r: Whether to return r as well as ra, dec. (default: False) + + :returns: ra, dec as a tuple. Or if return_r is True, (ra, dec, r). + """ + xy2 = x**2 + y**2 + ra = np.arctan2(y, x) + # Note: We don't need arctan2, since always quadrant 1 or 4. + # Using plain arctan is slightly faster. About 10% for the whole function. + # However, if any points have x=y=0, then this will raise a numpy warning. + # It still gives the right answer, but we catch and ignore the warning here. + with warnings.catch_warnings(): + warnings.filterwarnings("ignore",category=RuntimeWarning) + dec = np.arctan(z/np.sqrt(xy2)) + if return_r: + return ra, dec, np.sqrt(xy2 + z**2) + else: + return ra, dec
+ +
[docs] def normal(self): + """Return the coordinate in the "normal" convention of having 0 <= ra < 24 hours. + + This convention is not enforced on construction, so this function exists to make it + easy to convert if desired. + + Functions such as `from_galactic` and `from_xyz` will return normal coordinates. + """ + return _CelestialCoord(self.ra.wrap(_Angle(math.pi)), self.dec)
+ + @staticmethod + def _raw_dsq(c1, c2): + # Compute the raw dsq between two coordinates. + # Both are expected to already have _set_aux() called. + return (c1._x-c2._x)**2 + (c1._y-c2._y)**2 + (c1._z-c2._z)**2 + + @staticmethod + def _raw_cross(c1, c2): + # Compute the raw cross product between two coordinates. + # Both are expected to already have _set_aux() called. + return (c1._y * c2._z - c2._y * c1._z, + c1._z * c2._x - c2._z * c1._x, + c1._x * c2._y - c2._x * c1._y) + +
[docs] def distanceTo(self, coord2): + """Returns the great circle distance between this coord and another one. + The return value is an Angle object + + :param coord2: The CelestialCoord to calculate the distance to. + + :returns: the great circle distance to ``coord2``. + """ + # The easiest way to do this in a way that is stable for small separations + # is to calculate the (x,y,z) position on the unit sphere corresponding to each + # coordinate position. + # + # x = cos(dec) cos(ra) + # y = cos(dec) sin(ra) + # z = sin(dec) + + self._set_aux() + coord2._set_aux() + + # The direct distance between the two points is + # + # d^2 = (x1-x2)^2 + (y1-y2)^2 + (z1-z2)^2 + dsq = self._raw_dsq(self, coord2) + + if dsq < 3.99: + # (The usual case. This formula is perfectly stable here.) + # This direct distance can then be converted to a great circle distance via + # + # sin(theta/2) = d/2 + theta = 2. * math.asin(0.5 * math.sqrt(dsq)) + + else: + # Points are nearly antipodes where the accuracy of this formula starts to break down. + # But in this case, the cross product provides an accurate distance. + cx, cy, cz = self._raw_cross(self, coord2) + crosssq = cx**2 + cy**2 + cz**2 + theta = math.pi - math.asin(math.sqrt(crosssq)) + + return _Angle(theta)
+ +
[docs] def greatCirclePoint(self, coord2, theta): + """Returns a point on the great circle connecting self and coord2. + + Two points, c1 and c2, on the unit sphere define a great circle (so long as the two points + are not either coincident or antipodal). We can define points on this great circle by + their angle from c1, such that the angle for c2 has 0 < theta2 < pi. I.e. theta increases + from 0 as the points move from c1 towards c2. + + This function then returns the coordinate on this great circle (where c1 is ``self`` and + c2 is ``coord2``) that corresponds to the given angle ``theta``. + + :param coord2: Another CelestialCoord defining the great circle to use. + :param theta: The Angle along the great circle corresponding to the desired point. + + :returns: the corresponding CelestialCoord + """ + self._set_aux() + coord2._set_aux() + + # Define u = self + # v = coord2 + # w = (u x v) x u + # The great circle through u and v is then + # + # R(t) = u cos(t) + w sin(t) + # + # Rather than directly calculate (u x v) x u, let's do some simplification first. + # u x v = ( uy vz - uz vy ) + # ( uz vx - ux vz ) + # ( ux vy - uy vx ) + # wx = (u x v)_y uz - (u x v)_z uy + # = (uz vx - ux vz) uz - (ux vy - uy vx) uy + # = vx uz^2 - vz ux uz - vy ux uy + vx uy^2 + # = vx (1 - ux^2) - ux (uz vz + uy vy) + # = vx - ux (u . v) + # = vx - ux (1 - d^2/2) + # = vx - ux + ux d^2/2 + # wy = vy - uy + uy d^2/2 + # wz = vz - uz + uz d^2/2 + + dsq = self._raw_dsq(self, coord2) + + # These are unnormalized yet. + wx = coord2._x - self._x + self._x * dsq/2. + wy = coord2._y - self._y + self._y * dsq/2. + wz = coord2._z - self._z + self._z * dsq/2. + + # Normalize + wr = (wx**2 + wy**2 + wz**2)**0.5 + if wr == 0.: + raise ValueError("coord2 does not define a unique great circle with self.") + wx /= wr + wy /= wr + wz /= wr + + # R(theta) + s, c = theta.sincos() + rx = self._x * c + wx * s + ry = self._y * c + wy * s + rz = self._z * c + wz * s + return CelestialCoord.from_xyz(rx,ry,rz)
+ + + def _triple(self, coord2, coord3): + """Compute the scalar triple product of the three vectors: + + (A x C). B = sina sinb sinC + + where C = self, A = coord2, B = coord3. This is used by both angleBetween and area. + (Although note that the triple product is invariant to the ordering modulo a sign.) + """ + # Note, the scalar triple product, (AxC).B, is the determinant of the 3x3 matrix + # [ xA yA zA ] + # [ xC yC zC ] + # [ xB yB zB ] + # Furthermore, it is more stable to calculate it that way than computing the cross + # product by hand and then dotting it to the other vector. + return np.linalg.det([ [ coord2._x, coord2._y, coord2._z ], + [ self._x, self._y, self._z ], + [ coord3._x, coord3._y, coord3._z ] ]) + + + def _alt_triple(self, coord2, coord3): + """Compute a different triple product of the three vectors: + + (A x C). (B x C) = sina sinb cosC + + where C = self, A = coord2, B = coord3. This is used by both angleBetween and area. + """ + # We can simplify (AxC).(BxC) as follows: + # (A x C) . (B x C) + # = (C x (BxC)) . A Rotation of triple product with (BxC) one of the vectors + # = ((C.C)B - (C.B)C) . A Vector triple product identity + # = A.B - (A.C) (B.C) C.C = 1 + # Dot products for nearby coordinates are not very accurate. Better to use the distances + # between the points: A.B = 1 - d_AB^2/2 + # = 1 - d_AB^2/2 - (1-d_AC^2/2) (1-d_BC^2/2) + # = d_AC^2 / 2 + d_BC^2 / 2 - d_AB^2 / 2 - d_AC^2 d_BC^2 / 4 + dsq_AC = (self._x-coord2._x)**2 + (self._y-coord2._y)**2 + (self._z-coord2._z)**2 + dsq_BC = (self._x-coord3._x)**2 + (self._y-coord3._y)**2 + (self._z-coord3._z)**2 + dsq_AB = (coord3._x-coord2._x)**2 + (coord3._y-coord2._y)**2 + (coord3._z-coord2._z)**2 + return 0.5 * (dsq_AC + dsq_BC - dsq_AB - 0.5 * dsq_AC * dsq_BC) + + +
[docs] def angleBetween(self, coord2, coord3): + """Find the open angle at the location of the current coord between ``coord2`` and ``coord3``. + + The current coordinate along with the two other coordinates form a spherical triangle + on the sky. This function calculates the angle between the two sides at the location of + the current coordinate. + + Note that this returns a signed angle. The angle is positive if the sweep direction from + ``coord2`` to ``coord3`` is counter-clockwise (as observed from Earth). It is negative if + the direction is clockwise. + + :param coord2: A second CelestialCoord + :param coord3: A third CelestialCoord + + :returns: the angle between the great circles joining the other two coordinates to the + current coordinate. + """ + # Call A = coord2, B = coord3, C = self + # Then we are looking for the angle ACB. + # If we treat each coord as a (x,y,z) vector, then we can use the following spherical + # trig identities: + # + # (A x C) . B = sina sinb sinC + # (A x C) . (B x C) = sina sinb cosC + # + # Then we can just use atan2 to find C, and atan2 automatically gets the sign right. + # And we only need 1 trig call, assuming that x,y,z are already set up, which is often + # the case. + + self._set_aux() + coord2._set_aux() + coord3._set_aux() + + sinC = self._triple(coord2, coord3) + cosC = self._alt_triple(coord2, coord3) + + C = math.atan2(sinC, cosC) + return _Angle(C)
+ +
[docs] def area(self, coord2, coord3): + """Find the area of a spherical triangle in steradians. + + The current coordinate along with the two other coordinates form a spherical triangle + on the sky. This function calculates the area of that spherical triangle, which is + measured in steradians (i.e. surface area of the triangle on the unit sphere). + + :param coord2: A second CelestialCoord + :param coord3: A third CelestialCoord + + :returns: the area in steradians of the given spherical triangle. + """ + # The area of a spherical triangle is defined by the "spherical excess", E. + # There are several formulae for E: + # (cf. http://en.wikipedia.org/wiki/Spherical_trigonometry#Area_and_spherical_excess) + # + # E = A + B + C - pi + # tan(E/4) = sqrt(tan(s/2) tan((s-a)/2) tan((s-b)/2) tan((s-c)/2) + # tan(E/2) = tan(a/2) tan(b/2) sin(C) / (1 + tan(a/2) tan(b/2) cos(C)) + # + # We use the last formula, which is stable both for small triangles and ones that are + # nearly degenerate (which the middle formula may have trouble with). + # + # Furthermore, we can use some of the math for angleBetween and distanceTo to simplify + # this further: + # + # In angleBetween, we have formulae for sina sinb sinC and sina sinb cosC. + # In distanceTo, we have formulae for sin(a/2) and sin(b/2). + # + # Define: F = sina sinb sinC + # G = sina sinb cosC + # da = 2 sin(a/2) + # db = 2 sin(b/2) + # + # tan(E/2) = sin(a/2) sin(b/2) sin(C) / (cos(a/2) cos(b/2) + sin(a/2) sin(b/2) cos(C)) + # = sin(a) sin(b) sin(C) / (4 cos(a/2)^2 cos(b/2)^2 + sin(a) sin(b) cos(C)) + # = F / (4 (1-sin(a/2)^2) (1-sin(b/2)^2) + G) + # = F / (4-da^2) (4-db^2)/4 + G) + + self._set_aux() + coord2._set_aux() + coord3._set_aux() + + F = self._triple(coord2, coord3) + G = self._alt_triple(coord2, coord3) + dasq = (self._x-coord2._x)**2 + (self._y-coord2._y)**2 + (self._z-coord2._z)**2 + dbsq = (self._x-coord3._x)**2 + (self._y-coord3._y)**2 + (self._z-coord3._z)**2 + + tanEo2 = F / ( 0.25 * (4.-dasq) * (4.-dbsq) + G) + E = 2. * math.atan( abs(tanEo2) ) + return E
+ + _valid_projections = [None, 'gnomonic', 'stereographic', 'lambert', 'postel'] + +
[docs] def project(self, coord2, projection=None): + """Use the currect coord as the center point of a tangent plane projection to project + the ``coord2`` coordinate onto that plane. + + This function return a tuple (u,v) in the Euclidean coordinate system defined by + a tangent plane projection around the current coordinate, with +v pointing north and + +u pointing west. (i.e. to the right on the sky if +v is up.) + + There are currently four options for the projection, which you can specify with the + optional ``projection`` keyword argument: + + :gnomonic: [default] uses a gnomonic projection (i.e. a projection from the center of + the sphere, which has the property that all great circles become straight + lines. For more information, see + http://mathworld.wolfram.com/GnomonicProjection.html + This is the usual TAN projection used by most FITS images. + :stereographic: uses a stereographic proejection, which preserves angles, but + not area. For more information, see + http://mathworld.wolfram.com/StereographicProjection.html + :lambert: uses a Lambert azimuthal projection, which preserves area, but not angles. + For more information, see + http://mathworld.wolfram.com/LambertAzimuthalEqual-AreaProjection.html + :postel: uses a Postel equidistant proejection, which preserves distances from + the projection point, but not area or angles. For more information, see + http://mathworld.wolfram.com/AzimuthalEquidistantProjection.html + + The distance or angle errors increase with distance from the projection point of course. + + :param coord2: The coordinate to project onto the tangent plane. + :param projection: The name of the projection to be used. [default: gnomonic, see above + for other options] + + :returns: (u,v) as Angle instances + """ + if projection not in CelestialCoord._valid_projections: + raise ValueError('Unknown projection: %s'%projection) + + self._set_aux() + coord2._set_aux() + + # The core calculation is done in a helper function: + u, v = self._project(coord2._cosra, coord2._sinra, coord2._cosdec, coord2._sindec, + projection) + + return u * radians, v * radians
+ +
[docs] def project_rad(self, ra, dec, projection=None): + """This is basically identical to the project() function except that the input ``ra``, ``dec`` + are given in radians rather than packaged as a CelestialCoord object and the returned + u,v are given in radians. + + The main advantage to this is that it will work if ``ra`` and ``dec`` are NumPy arrays, in which + case the output ``u``, ``v`` will also be NumPy arrays. + + :param ra: The right ascension in radians to project onto the tangent plane. + :param dec: The declination in radians to project onto the tangent plane. + :param projection: The name of the projection to be used. [default: gnomonic, see ``project`` + docstring for other options] + + :returns: (u,v) in radians + """ + if projection not in CelestialCoord._valid_projections: + raise ValueError('Unknown projection: %s'%projection) + + self._set_aux() + + cosra = np.cos(ra) + sinra = np.sin(ra) + cosdec = np.cos(dec) + sindec = np.sin(dec) + + return self._project(cosra, sinra, cosdec, sindec, projection)
+ + def _project(self, cosra, sinra, cosdec, sindec, projection): + # The equations are given at the above mathworld websites. They are the same except + # for the definition of k: + # + # x = k cos(dec) sin(ra-ra0) + # y = k ( cos(dec0) sin(dec) - sin(dec0) cos(dec) cos(ra-ra0) ) + # + # Lambert: + # k = sqrt( 2 / ( 1 + cos(c) ) ) + # Stereographic: + # k = 2 / ( 1 + cos(c) ) + # Gnomonic: + # k = 1 / cos(c) + # Postel: + # k = c / sin(c) + # where cos(c) = sin(dec0) sin(dec) + cos(dec0) cos(dec) cos(ra-ra0) + + # cos(dra) = cos(ra-ra0) = cos(ra0) cos(ra) + sin(ra0) sin(ra) + cosdra = self._cosra * cosra + cosdra += self._sinra * sinra + + # sin(dra) = -sin(ra - ra0) + # Note: - sign here is to make +x correspond to -ra, + # so x increases for decreasing ra. + # East is to the left on the sky! + # sin(dra) = -cos(ra0) sin(ra) + sin(ra0) cos(ra) + sindra = self._sinra * cosra + sindra -= self._cosra * sinra + + # Calculate k according to which projection we are using + cosc = cosdec * cosdra + cosc *= self._cosdec + cosc += self._sindec * sindec + if projection is None or projection[0] == 'g': + k = 1. / cosc + elif projection[0] == 's': + k = 2. / (1. + cosc) + elif projection[0] == 'l': + k = np.sqrt( 2. / (1.+cosc) ) + else: + c = np.arccos(cosc) + # k = c / np.sin(c) + # np.sinc is defined as sin(pi x) / (pi x) + # So need to divide by pi first. + k = 1. / np.sinc(c / np.pi) + + # u = k * cosdec * sindra + # v = k * ( self._cosdec * sindec - self._sindec * cosdec * cosdra ) + u = cosdec * sindra + v = cosdec * cosdra + v *= -self._sindec + v += self._cosdec * sindec + u *= k + v *= k + + return u, v + +
[docs] def deproject(self, u, v, projection=None): + """Do the reverse process from the project() function. + + i.e. This takes in a position (u,v) and returns the corresponding celestial + coordinate, using the current coordinate as the center point of the tangent plane + projection. + + :param u: The u position on the tangent plane to deproject (must be an Angle + instance) + :param v: The v position on the tangent plane to deproject (must be an Angle + instance) + :param projection: The name of the projection to be used. [default: gnomonic, see ``project`` + docstring for other options] + + :returns: the corresponding CelestialCoord for that position. + """ + if projection not in CelestialCoord._valid_projections: + raise ValueError('Unknown projection: %s'%projection) + + # Again, do the core calculations in a helper function + ra, dec = self._deproject(u / radians, v / radians, projection) + + return CelestialCoord(_Angle(ra), _Angle(dec))
+ +
[docs] def deproject_rad(self, u, v, projection=None): + """This is basically identical to the deproject() function except that the output ``ra``, + ``dec`` are returned as a tuple (ra, dec) in radians rather than packaged as a CelestialCoord + object and ``u`` and ``v`` are in radians rather than Angle instances. + + The main advantage to this is that it will work if ``u`` and ``v`` are NumPy arrays, in which + case the output ``ra``, ``dec`` will also be NumPy arrays. + + :param u: The u position in radians on the tangent plane to deproject + :param v: The v position in radians on the tangent plane to deproject + :param projection: The name of the projection to be used. [default: gnomonic, see ``project`` + docstring for other options] + + :returns: the corresponding RA, Dec in radians + """ + if projection not in CelestialCoord._valid_projections: + raise ValueError('Unknown projection: %s'%projection) + + return self._deproject(u, v, projection)
+ + def _deproject(self, u, v, projection): + # The inverse equations are also given at the same web sites: + # + # sin(dec) = cos(c) sin(dec0) + v sin(c) cos(dec0) / r + # tan(ra-ra0) = u sin(c) / (r cos(dec0) cos(c) - v sin(dec0) sin(c)) + # + # where + # + # r = sqrt(u^2+v^2) + # c = tan^(-1)(r) for gnomonic + # c = 2 tan^(-1)(r/2) for stereographic + # c = 2 sin^(-1)(r/2) for lambert + # c = r for postel + + # Note that we can rewrite the formulae as: + # + # sin(dec) = cos(c) sin(dec0) + v (sin(c)/r) cos(dec0) + # tan(ra-ra0) = u (sin(c)/r) / (cos(dec0) cos(c) - v sin(dec0) (sin(c)/r)) + # + # which means we only need cos(c) and sin(c)/r. For most of the projections, + # this saves us from having to take sqrt(rsq). + + rsq = u*u + rsq += v*v + if projection is None or projection[0] == 'g': + # c = arctan(r) + # cos(c) = 1 / sqrt(1+r^2) + # sin(c) = r / sqrt(1+r^2) + cosc = sinc_over_r = 1./np.sqrt(1.+rsq) + elif projection[0] == 's': + # c = 2 * arctan(r/2) + # Some trig manipulations reveal: + # cos(c) = (4-r^2) / (4+r^2) + # sin(c) = 4r / (4+r^2) + cosc = (4.-rsq) / (4.+rsq) + sinc_over_r = 4. / (4.+rsq) + elif projection[0] == 'l': + # c = 2 * arcsin(r/2) + # Some trig manipulations reveal: + # cos(c) = 1 - r^2/2 + # sin(c) = r sqrt(4-r^2) / 2 + cosc = 1. - rsq/2. + sinc_over_r = np.sqrt(4.-rsq) / 2. + else: + r = np.sqrt(rsq) + cosc = np.cos(r) + sinc_over_r = np.sinc(r/np.pi) + + # Compute sindec, tandra + # Note: more efficient to use numpy op= as much as possible to avoid temporary arrays. + self._set_aux() + # sindec = cosc * self._sindec + v * sinc_over_r * self._cosdec + sindec = v * sinc_over_r + sindec *= self._cosdec + sindec += cosc * self._sindec + # Remember the - sign so +dra is -u. East is left. + tandra_num = u * sinc_over_r + tandra_num *= -1. + # tandra_denom = cosc * self._cosdec - v * sinc_over_r * self._sindec + tandra_denom = v * sinc_over_r + tandra_denom *= -self._sindec + tandra_denom += cosc * self._cosdec + + dec = np.arcsin(sindec) + ra = self.ra.rad + np.arctan2(tandra_num, tandra_denom) + + return ra, dec + +
[docs] def jac_deproject(self, u, v, projection=None): + """Return the jacobian of the deprojection. + + i.e. if the input position is (u,v) then the return matrix is + + .. math:: + + J \\equiv \\begin{bmatrix} J00 & J01 \\\\ J10 & J11 \\end{bmatrix} + = \\begin{bmatrix} + d\\textrm{ra}/du \\cos(\\textrm{dec}) & d\\textrm{ra}/dv \\cos(\\textrm{dec}) \\\\ + d\\textrm{dec}/du & d\\textrm{dec}/dv + \\end{bmatrix} + + :param u: The u position (as an Angle instance) on the tangent plane + :param v: The v position (as an Angle instance) on the tangent plane + :param projection: The name of the projection to be used. [default: gnomonic, see ``project`` + docstring for other options] + + :returns: the Jacobian as a 2x2 numpy array [[J00, J01], [J10, J11]] + """ + if projection not in CelestialCoord._valid_projections: + raise ValueError('Unknown projection: %s'%projection) + return self._jac_deproject(u.rad, v.rad, projection)
+ +
[docs] def jac_deproject_rad(self, u, v, projection=None): + """Equivalent to ``jac_deproject``, but the inputs are in radians and may be numpy + arrays. + + :param u: The u position (in radians) on the tangent plane + :param v: The v position (in radians) on the tangent plane + :param projection: The name of the projection to be used. [default: gnomonic, see `project` + docstring for other options] + + :returns: the Jacobian as a 2x2 numpy array [[J00, J01], [J10, J11]] + """ + if projection not in CelestialCoord._valid_projections: + raise ValueError('Unknown projection: %s'%projection) + return self._jac_deproject(u, v, projection)
+ + def _jac_deproject(self, u, v, projection): + # sin(dec) = cos(c) sin(dec0) + v sin(c)/r cos(dec0) + # tan(ra-ra0) = u sin(c)/r / (cos(dec0) cos(c) - v sin(dec0) sin(c)/r) + # + # d(sin(dec)) = cos(dec) ddec = s0 dc + (v ds + s dv) c0 + # dtan(ra-ra0) = sec^2(ra-ra0) dra + # = ( (u ds + s du) A - u s (dc c0 - (v ds + s dv) s0 ) )/A^2 + # where s = sin(c) / r + # c = cos(c) + # s0 = sin(dec0) + # c0 = cos(dec0) + # A = c c0 - v s s0 + + rsq = u*u + v*v + rsq1 = (u+1.e-4)**2 + v**2 + rsq2 = u**2 + (v+1.e-4)**2 + if projection is None or projection[0] == 'g': + c = s = 1./np.sqrt(1.+rsq) + s3 = s*s*s + dcdu = dsdu = -u*s3 + dcdv = dsdv = -v*s3 + elif projection[0] == 's': + s = 4. / (4.+rsq) + c = 2.*s-1. + ssq = s*s + dcdu = -u * ssq + dcdv = -v * ssq + dsdu = 0.5*dcdu + dsdv = 0.5*dcdv + elif projection[0] == 'l': + c = 1. - rsq/2. + s = np.sqrt(4.-rsq) / 2. + dcdu = -u + dcdv = -v + dsdu = -u/(4.*s) + dsdv = -v/(4.*s) + else: + r = np.sqrt(rsq) + if r == 0.: + c = s = 1 + dcdu = -u + dcdv = -v + dsdu = dsdv = 0 + else: + c = np.cos(r) + s = np.sin(r)/r + dcdu = -s*u + dcdv = -s*v + dsdu = (c-s)*u/rsq + dsdv = (c-s)*v/rsq + + self._set_aux() + s0 = self._sindec + c0 = self._cosdec + sindec = c * s0 + v * s * c0 + cosdec = np.sqrt(1.-sindec*sindec) + dddu = ( s0 * dcdu + v * dsdu * c0 ) / cosdec + dddv = ( s0 * dcdv + (v * dsdv + s) * c0 ) / cosdec + + tandra_num = u * s + tandra_denom = c * c0 - v * s * s0 + # Note: A^2 sec^2(dra) = denom^2 (1 + tan^2(dra) = denom^2 + num^2 + A2sec2dra = tandra_denom**2 + tandra_num**2 + drdu = ((u * dsdu + s) * tandra_denom - u * s * ( dcdu * c0 - v * dsdu * s0 ))/A2sec2dra + drdv = (u * dsdv * tandra_denom - u * s * ( dcdv * c0 - (v * dsdv + s) * s0 ))/A2sec2dra + + drdu *= cosdec + drdv *= cosdec + return np.array([[drdu, drdv], [dddu, dddv]]) + +
[docs] def precess(self, from_epoch, to_epoch): + """This function precesses equatorial ra and dec from one epoch to another. + It is adapted from a set of fortran subroutines based on (a) pages 30-34 of + the Explanatory Supplement to the AE, (b) Lieske, et al. (1977) A&A 58, 1-16, + and (c) Lieske (1979) A&A 73, 282-284. + + :param from_epoch: The epoch of the current coordinate + :param to_epoch: The epoch of the returned precessed coordinate + + :returns: a CelestialCoord object corresponding to the precessed position. + """ + if from_epoch == to_epoch: return self + + # t0, t below correspond to Lieske's big T and little T + t0 = (from_epoch-2000.)/100. + t = (to_epoch-from_epoch)/100. + t02 = t0*t0 + t2 = t*t + t3 = t2*t + + # a,b,c below correspond to Lieske's zeta_A, z_A and theta_A + a = ( (2306.2181 + 1.39656*t0 - 0.000139*t02) * t + + (0.30188 - 0.000344*t0) * t2 + 0.017998 * t3 ) * arcsec + b = ( (2306.2181 + 1.39656*t0 - 0.000139*t02) * t + + (1.09468 + 0.000066*t0) * t2 + 0.018203 * t3 ) * arcsec + c = ( (2004.3109 - 0.85330*t0 - 0.000217*t02) * t + + (-0.42665 - 0.000217*t0) * t2 - 0.041833 * t3 ) * arcsec + sina, cosa = a.sincos() + sinb, cosb = b.sincos() + sinc, cosc = c.sincos() + + # This is the precession rotation matrix: + xx = cosa*cosc*cosb - sina*sinb + yx = -sina*cosc*cosb - cosa*sinb + zx = -sinc*cosb + xy = cosa*cosc*sinb + sina*cosb + yy = -sina*cosc*sinb + cosa*cosb + zy = -sinc*sinb + xz = cosa*sinc + yz = -sina*sinc + zz = cosc + + # Perform the rotation: + self._set_aux() + x2 = xx*self._x + yx*self._y + zx*self._z + y2 = xy*self._x + yy*self._y + zy*self._z + z2 = xz*self._x + yz*self._y + zz*self._z + + return CelestialCoord.from_xyz(x2, y2, z2).normal()
+ +
[docs] def galactic(self, epoch=2000.): + """Get the longitude and latitude in galactic coordinates corresponding to this position. + + :param epoch: The epoch of the current coordinate. [default: 2000.] + + :returns: the longitude and latitude as a tuple (el, b), given as Angle instances. + """ + # cf. Lang, Astrophysical Formulae, page 13 + # cos(b) cos(el-33) = cos(dec) cos(ra-282.25) + # cos(b) sin(el-33) = sin(dec) sin(62.6) + cos(dec) sin(ra-282.25) cos(62.6) + # sin(b) = sin(dec) cos(62.6) - cos(dec) sin(ra-282.25) sin(62.6) + # + # Those formulae were for the 1950 epoch. The corresponding numbers for J2000 are: + # (cf. https://arxiv.org/pdf/1010.3773.pdf) + el0 = 32.93191857 * degrees + r0 = 282.859481208 * degrees + d0 = 62.8717488056 * degrees + sind0, cosd0 = d0.sincos() + + sind, cosd = self.dec.sincos() + sinr, cosr = (self.ra-r0).sincos() + + cbcl = cosd*cosr + cbsl = sind*sind0 + cosd*sinr*cosd0 + sb = sind*cosd0 - cosd*sinr*sind0 + + b = _Angle(math.asin(sb)) + el = (_Angle(math.atan2(cbsl,cbcl)) + el0).wrap(_Angle(math.pi)) + + return (el, b)
+ + +
[docs] @staticmethod + def from_galactic(el, b, epoch=2000.): + """Create a CelestialCoord from the given galactic coordinates + + :param el: The longitude in galactic coordinates (an Angle instance) + :param b: The latitude in galactic coordinates (an Angle instance) + :param epoch: The epoch of the returned coordinate. [default: 2000.] + + :returns: the CelestialCoord corresponding to these galactic coordinates. + """ + el0 = 32.93191857 * degrees + r0 = 282.859481208 * degrees + d0 = 62.8717488056 * degrees + sind0, cosd0 = d0.sincos() + + sinb, cosb = b.sincos() + sinl, cosl = (el-el0).sincos() + x1 = cosb*cosl + y1 = cosb*sinl + z1 = sinb + + x2 = x1 + y2 = y1 * cosd0 - z1 * sind0 + z2 = y1 * sind0 + z1 * cosd0 + + temp = CelestialCoord.from_xyz(x2, y2, z2) + return CelestialCoord(temp.ra + r0, temp.dec).normal()
+ + +
[docs] def ecliptic(self, epoch=2000., date=None): + """Get the longitude and latitude in ecliptic coordinates corresponding to this position. + + The ``epoch`` parameter is used to get an accurate value for the (time-varying) obliquity of + the ecliptic. The formulae for this are quite straightforward. It requires just a single + parameter for the transformation, the obliquity of the ecliptic (the Earth's axial tilt). + + :param epoch: The epoch to be used for estimating the obliquity of the ecliptic, if + ``date`` is None. But if ``date`` is given, then use that to determine the + epoch. [default: 2000.] + :param date: If a date is given as a python datetime object, then return the + position in ecliptic coordinates with respect to the sun position at + that date. If None, then return the true ecliptic coordiantes. + [default: None] + + :returns: the longitude and latitude as a tuple (lambda, beta), given as Angle instances. + """ + # We are going to work in terms of the (x, y, z) projections. + self._set_aux() + + # Get the obliquity of the ecliptic. + if date is not None: + epoch = date.year + ep = util.ecliptic_obliquity(epoch) + sin_ep, cos_ep = ep.sincos() + + # Coordinate transformation here, from celestial to ecliptic: + x_ecl = self._x + y_ecl = cos_ep*self._y + sin_ep*self._z + z_ecl = -sin_ep*self._y + cos_ep*self._z + + beta = _Angle(math.asin(z_ecl)) + lam = _Angle(math.atan2(y_ecl, x_ecl)) + + if date is not None: + # Find the sun position in ecliptic coordinates on this date. We have to convert to + # Julian day in order to use our helper routine to find the Sun position in ecliptic + # coordinates. + lam_sun = util.sun_position_ecliptic(date) + # Subtract it off, to get ecliptic coordinates relative to the sun. + lam -= lam_sun + + return (lam.wrap(), beta)
+ +
[docs] @staticmethod + def from_ecliptic(lam, beta, epoch=2000., date=None): + """Create a CelestialCoord from the given ecliptic coordinates + + :param lam: The longitude in ecliptic coordinates (an Angle instance) + :param beta: The latitude in ecliptic coordinates (an Angle instance) + :param epoch: The epoch to be used for estimating the obliquity of the ecliptic, if + ``date`` is None. But if ``date`` is given, then use that to determine the + epoch. [default: 2000.] + :param date: If a date is given as a python datetime object, then return the + position in ecliptic coordinates with respect to the sun position at + that date. If None, then return the true ecliptic coordiantes. + [default: None] + + :returns: the CelestialCoord corresponding to these ecliptic coordinates. + """ + if date is not None: + lam += util.sun_position_ecliptic(date) + + # Get the (x, y, z)_ecliptic from (lam, beta). + sinbeta, cosbeta = beta.sincos() + sinlam, coslam = lam.sincos() + x_ecl = cosbeta*coslam + y_ecl = cosbeta*sinlam + z_ecl = sinbeta + + # Get the obliquity of the ecliptic. + if date is not None: + epoch = date.year + ep = util.ecliptic_obliquity(epoch) + + # Transform to (x, y, z)_equatorial. + sin_ep, cos_ep = ep.sincos() + x_eq = x_ecl + y_eq = cos_ep*y_ecl - sin_ep*z_ecl + z_eq = sin_ep*y_ecl + cos_ep*z_ecl + + return CelestialCoord.from_xyz(x_eq, y_eq, z_eq)
+ + + def __repr__(self): return 'coord.CelestialCoord(%r, %r)'%(self._ra,self._dec) + def __str__(self): return 'coord.CelestialCoord(%s, %s)'%(self._ra,self._dec) + def __hash__(self): return hash(repr(self)) + + def __eq__(self, other): + return (isinstance(other, CelestialCoord) and + self.ra == other.ra and self.dec == other.dec) + def __ne__(self, other): return not self.__eq__(other)
+ +def _CelestialCoord(ra, dec): + """ + Equivalent to CeletialCoord(ra,dec), but without the normal sanity checks. + + :param ra: The right ascension. Must be an Angle instance. + :param dec: The declination. Must be an Angle instance. + """ + ret = CelestialCoord.__new__(CelestialCoord) + ret._ra = ra + ret._dec = dec + ret._x = None + return ret +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/_utilities.html b/docs/_build/html/_modules/galsim/_utilities.html new file mode 100644 index 00000000000..2213f70d149 --- /dev/null +++ b/docs/_build/html/_modules/galsim/_utilities.html @@ -0,0 +1,448 @@ + + + + + + galsim._utilities — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim._utilities

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+# This file is logically part of utilities.py, but to avoid circular imports, we
+# put a few things in here so files can import _utilties without triggering an ImportError.
+
+import os
+import functools
+import weakref
+
+from .errors import GalSimError, GalSimValueError
+
+# Python 2/3 compatible definition of basestring without past.builtins
+# (Based on this SO answer: https://stackoverflow.com/a/33699705/1332281)
+basestring = ("".__class__, u"".__class__, b"".__class__)
+
+
[docs]class lazy_property: + """ + This decorator will act similarly to @property, but will be efficient for multiple access + to values that require some significant calculation. + + It works by replacing the attribute with the computed value, so after the first access, + the property (an attribute of the class) is superseded by the new attribute of the instance. + + Note that is should only be used for non-mutable data, since the calculation will not be + repeated if anything about the instance changes. + + Usage:: + + @lazy_property + def slow_function_to_be_used_as_a_property(self): + x = ... # Some slow calculation. + return x + + Base on an answer from http://stackoverflow.com/a/6849299 + """ + def __init__(self, fget): + self.fget = fget + self.func_name = fget.__name__ + + def __get__(self, obj, cls): + if obj is None: + return self + value = self.fget(obj) + setattr(obj, self.func_name, value) + return value
+ +
[docs]class doc_inherit: + ''' + This decorator will grab a doc string from a base class version of a method. + Useful if the subclass doesn't change anything about the method API, but just has + a specialized implementation. This lets the documentation live only in one place. + + Usage:: + + class Base: + def some_method(self): + """A nice description of the functionality + """ + pass + + class Sub(Base): + + @doc_inherit + def some_method(self): + # Don't bother with any doc string here. + pass + + Based on the Docstring Inheritance Decorator at: + + https://github.com/ActiveState/code/wiki/Python_index_1 + + Although I (MJ) modified it slightly, since the original recipe there had a bug that made it + not work properly with 2 levels of sub-classing (e.g. Pixel <- Box <- GSObject). + ''' + def __init__(self, mthd): + self.mthd = mthd + self.name = mthd.__name__ + + def __get__(self, obj, cls): + for parent in cls.__bases__: # pragma: no branch + parfunc = getattr(parent, self.name, None) + if parfunc and getattr(parfunc, '__doc__', None): # pragma: no branch + break + + if obj: + return self.get_with_inst(obj, cls, parfunc) + else: + return self.get_no_inst(cls, parfunc) + + def get_with_inst(self, obj, cls, parfunc): + @functools.wraps(self.mthd, assigned=('__name__','__module__')) + def f(*args, **kwargs): + return self.mthd(obj, *args, **kwargs) + return self.use_parent_doc(f, parfunc) + + def get_no_inst(self, cls, parfunc): + @functools.wraps(self.mthd, assigned=('__name__','__module__')) + def f(*args, **kwargs): # pragma: no cover (without inst, this is not normally called.) + return self.mthd(*args, **kwargs) + return self.use_parent_doc(f, parfunc) + + def use_parent_doc(self, func, source): + if source is None: # pragma: no cover + raise NameError("Can't find '%s' in parents"%self.name) + func.__doc__ = source.__doc__ + return func
+ + +
[docs]def isinteger(value): + """Check if a value is an integer type (including np.int64, long, etc.) + + Specifically, it checks whether value == int(value). + + Parameter: + value: The value to be checked whether it is an integer + + Returns: + True if the value is an integer type, False otherwise. + """ + try: + return value == int(value) + except TypeError: + return False
+ +
[docs]def ensure_dir(target): + """ + Make sure the directory for the target location exists, watching for a race condition + + In particular check if the OS reported that the directory already exists when running + makedirs, which can happen if another process creates it before this one can + + Parameter: + target: The file name for which to ensure that all necessary directories exist. + """ + _ERR_FILE_EXISTS=17 + dir = os.path.dirname(target) + if dir == '': return + + exists = os.path.exists(dir) + if not exists: + try: + os.makedirs(dir) + except OSError as err: # pragma: no cover + # check if the file now exists, which can happen if some other + # process created the directory between the os.path.exists call + # above and the time of the makedirs attempt. This is OK + if err.errno != _ERR_FILE_EXISTS: + raise err + + elif exists and not os.path.isdir(dir): + raise OSError("tried to make directory '%s' " + "but a non-directory file of that " + "name already exists" % dir)
+ + +
[docs]class LRU_Cache: + """Simplified Least Recently Used Cache. + + Mostly stolen from http://code.activestate.com/recipes/577970-simplified-lru-cache/, + but added a method for dynamic resizing. The least recently used cached item is + overwritten on a cache miss. + + Parameters: + user_function: A python function to cache. + maxsize: Maximum number of inputs to cache. [Default: 1024] + + Example:: + + >>> def slow_function(*args) # A slow-to-evaluate python function + >>> ... + >>> + >>> v1 = slow_function(*k1) # Calling function is slow + >>> v1 = slow_function(*k1) # Calling again with same args is still slow + >>> cache = galsim.utilities.LRU_Cache(slow_function) + >>> v1 = cache(*k1) # Returns slow_function(*k1), slowly the first time + >>> v1 = cache(*k1) # Returns slow_function(*k1) again, but fast this time. + """ + def __init__(self, user_function, maxsize=1024): + # Link layout: [PREV, NEXT, KEY, RESULT] + self.root = [None, None, None, None] + self.user_function = user_function + self.cache = {} + self.maxsize = maxsize + self.clear() + + def clear(self): + self.root = [None, None, None, None] + root = self.root + cache = self.cache + cache.clear() + maxsize = self.maxsize + last = root + for i in range(maxsize): + key = object() + cache[key] = last[1] = last = [last, root, key, None] + root[0] = last + + def __call__(self, *key): + cache = self.cache + root = self.root + link = cache.get(key) + if link is not None: + # Cache hit: move link to last position + link_prev, link_next, _, result = link + link_prev[1] = link_next + link_next[0] = link_prev + last = root[0] + last[1] = root[0] = link + link[0] = last + link[1] = root + return result + # Cache miss: evaluate and insert new key/value at root, then increment root + # so that just-evaluated value is in last position. + result = self.user_function(*key) + root = self.root # re-establish root in case user_function modified it due to recursion + root[2] = key + root[3] = result + oldroot = root + root = self.root = root[1] + root[2], oldkey = None, root[2] + root[3], oldvalue = None, root[3] + del cache[oldkey] + cache[key] = oldroot + return result + +
[docs] def resize(self, maxsize): + """Resize the cache. + + Increasing the size of the cache is non-destructive, i.e., previously cached inputs remain + in the cache. Decreasing the size of the cache will necessarily remove items from the + cache if the cache is already filled. Items are removed in least recently used order. + + Parameters: + maxsize: The new maximum number of inputs to cache. + """ + oldsize = self.maxsize + if maxsize == oldsize: + return + else: + root = self.root + cache = self.cache + if maxsize <= 0: + raise GalSimValueError("Invalid maxsize", maxsize) + if maxsize < oldsize: + for i in range(oldsize - maxsize): + # Delete root.next + current_next_link = root[1] + new_next_link = root[1] = root[1][1] + new_next_link[0] = root + del cache[current_next_link[2]] + else: # maxsize > oldsize: + for i in range(maxsize - oldsize): + # Insert between root and root.next + key = object() + cache[key] = link = [root, root[1], key, None] + root[1][0] = link + root[1] = link + self.maxsize = maxsize
+ + +
[docs]def math_eval(str, other_modules=()): + """Evaluate a string that may include numpy, np, or math commands. + + Parameters: + str: The string to evaluate + other_modules. Other modules in addition to numpy, np, math to import as well. + Should be given as a list of strings. [default: None] + + Returns: + Whatever the string evaluates to. + """ + gdict = globals().copy() + exec('import galsim', gdict) + exec('import numpy', gdict) + exec('import numpy as np', gdict) + + exec('import math', gdict) + exec('import coord', gdict) + for m in other_modules: # pragma: no cover (We don't use this.) + exec('import ' + m, gdict) + + # A few other things that show up in reprs, so useful to import here. + exec('from numpy import array, uint16, uint32, int16, int32, float32, float64, complex64, complex128, ndarray', + gdict) + exec('from astropy.units import Unit', gdict) + + return eval(str, gdict)
+ +
[docs]class WeakMethod: + """Wrap a method in a weakref. + + This is useful if you want to specialize a function if certain conditions hold. + You can check those conditions and return one of several possible implementations as + a `lazy_property`. + + Using just a normal ``weakref`` doesn't work, but this class will work. + + From http://code.activestate.com/recipes/81253-weakmethod/ + """ + def __init__(self, f): + self.f = f.__func__ + self.c = weakref.ref(f.__self__) + def __call__(self, *args): + try: + # If the reference is dead, self.c() will be None, so this will raise an + # AttributeError: 'NoneType' object has no attribute ... + # Hopefully the method itself won't raise an AttributeError for anything else. + return self.f(self.c(), *args) + except AttributeError: # pragma: no cover + raise GalSimError('Method called on dead object')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/airy.html b/docs/_build/html/_modules/galsim/airy.html new file mode 100644 index 00000000000..a1d1b4ed3ea --- /dev/null +++ b/docs/_build/html/_modules/galsim/airy.html @@ -0,0 +1,371 @@ + + + + + + galsim.airy — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.airy

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Airy' ]
+
+import math
+import astropy.units as u
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from ._utilities import lazy_property, doc_inherit
+from .errors import GalSimIncompatibleValuesError, GalSimNotImplementedError
+from .angle import arcsec, radians, AngleUnit
+
+
+
[docs]class Airy(GSObject): + """A class describing the surface brightness profile for an Airy disk (perfect + diffraction-limited PSF for a circular aperture), with an optional central obscuration. + + For more information, refer to + + http://en.wikipedia.org/wiki/Airy_disc + + The Airy profile is defined in terms of the diffraction angle, which is a function of the + ratio lambda / D, where lambda is the wavelength of the light (say in the middle of the + bandpass you are using) and D is the diameter of the telescope. + + The natural units for this value is radians, which is not normally a convenient unit to use for + other `GSObject` dimensions. Assuming that the other sky coordinates you are using are all in + arcsec (e.g. the pixel scale when you draw the image, the size of the galaxy, etc.), then you + should convert this to arcsec as well:: + + >>> lam = 700 # nm + >>> diam = 4.0 # meters + >>> lam_over_diam = (lam * 1.e-9) / diam # radians + >>> lam_over_diam *= 206265 # Convert to arcsec + >>> airy = galsim.Airy(lam_over_diam) + + To make this process a bit simpler, we recommend instead providing the wavelength and diameter + separately using the parameters ``lam`` (in nm) and ``diam`` (in m). GalSim will then convert + this to any of the normal kinds of angular units using the ``scale_unit`` parameter:: + + >>> airy = galsim.Airy(lam=lam, diam=diam, scale_unit=galsim.arcsec) + + When drawing images, the scale_unit should match the unit used for the pixel scale or the WCS. + e.g. in this case, a pixel scale of 0.2 arcsec/pixel would be specified as ``pixel_scale=0.2``. + + Parameters: + lam_over_diam: The parameter that governs the scale size of the profile. + See above for details about calculating it. + lam: Lambda (wavelength) either as an astropy Quantity, or as a float in units + of nanometers. Must be supplied with ``diam``, and in this case, image + scales (``scale``) should be specified in units of ``scale_unit``. + diam: Telescope diameter either as an astropy Quantity, or as a float in units of + meters. Must be supplied with ``lam``, and in this case, image scales + (``scale``) should be specified in units of ``scale_unit``. + obscuration: The linear dimension of a central obscuration as a fraction of the + pupil dimension. [default: 0] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + scale_unit: Units to use for the sky coordinates when calculating lam/diam if these + are supplied separately. Note that the results of using properties like + `fwhm` will be returned in units of ``scale_unit`` as well. Should + be either a `galsim.AngleUnit` or a string that can be used to construct + one (e.g., 'arcsec', 'radians', etc.). [default: galsim.arcsec] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { } + _opt_params = { "flux" : float , + "obscuration" : float, + "diam" : (float, u.Quantity), + "scale_unit" : str + } + # Note that this is not quite right; it's true that either lam_over_diam or lam should be + # supplied, but if lam is supplied then diam is required. Errors in which parameters are used + # may be caught either by config or by the python code itself, depending on the particular + # error. + _single_params = [{ "lam_over_diam" : float , "lam" : (float, u.Quantity) } ] + + # For an unobscured Airy, we have the following factor which can be derived using the + # integral result given in the Wikipedia page (http://en.wikipedia.org/wiki/Airy_disk), + # solved for half total flux using the free online tool Wolfram Alpha. + # At www.wolframalpha.com: + # Type "Solve[BesselJ0(x)^2+BesselJ1(x)^2=1/2]" ... and divide the result by pi + _hlr_factor = 0.5348321477242647 + _fwhm_factor = 1.028993969962188 + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, lam_over_diam=None, lam=None, diam=None, obscuration=0., flux=1., + scale_unit=None, gsparams=None): + + self._obscuration = float(obscuration) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + + if isinstance(lam, u.Quantity): + lam = lam.to_value(u.nm) + if isinstance(diam, u.Quantity): + diam = diam.to_value(u.m) + + # Parse arguments: either lam_over_diam in arbitrary units, or lam in nm and diam in m. + # If the latter, then get lam_over_diam in units of scale_unit, as specified in + # docstring. + if lam_over_diam is not None: + if lam is not None or diam is not None: + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify lam or diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) + self._lod = float(lam_over_diam) + else: + if lam is None or diam is None: + raise GalSimIncompatibleValuesError( + "If not specifying lam_over_diam, then specify lam AND diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) + # In this case we're going to use scale_unit, so parse it in case of string input: + if isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + elif scale_unit is None: + scale_unit = arcsec + self._lod = (1.e-9*float(lam)/float(diam))*(radians/scale_unit) + + @lazy_property + def _sbp(self): + return _galsim.SBAiry(self._lod, self._obscuration, self._flux, self.gsparams._gsp) + + @property + def lam_over_diam(self): + """The input lambda/diam value. + """ + return self._lod + @property + def obscuration(self): + """The input obscuration. + """ + return self._obscuration + + @property + def half_light_radius(self): + """The half light radius of this Airy profile (only supported for obscuration = 0.). + """ + if self.obscuration == 0.: + return self.lam_over_diam * Airy._hlr_factor + else: + # In principle can find the half light radius as a function of lam_over_diam and + # obscuration too, but it will be much more involved...! + raise GalSimNotImplementedError( + "Half light radius calculation not implemented for Airy " + "objects with non-zero obscuration.") + + @property + def fwhm(self): + """The FWHM of this Airy profile (only supported for obscuration = 0.). + """ + # As above, likewise, FWHM only easy to define for unobscured Airy + if self.obscuration == 0.: + return self.lam_over_diam * Airy._fwhm_factor + else: + # In principle can find the FWHM as a function of lam_over_diam and obscuration too, + # but it will be much more involved...! + raise GalSimNotImplementedError( + "FWHM calculation not implemented for Airy " + "objects with non-zero obscuration.") + + def __eq__(self, other): + return (self is other or + (isinstance(other, Airy) and + self.lam_over_diam == other.lam_over_diam and + self.obscuration == other.obscuration and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Airy", self.lam_over_diam, self.obscuration, self.flux, + self.gsparams)) + + def __repr__(self): + return 'galsim.Airy(lam_over_diam=%r, obscuration=%r, flux=%r, gsparams=%r)'%( + self.lam_over_diam, self.obscuration, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Airy(lam_over_diam=%s'%self.lam_over_diam + if self.obscuration != 0.: + s += ', obscuration=%s'%self.obscuration + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return 2.*math.pi / self._lod + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Airy(lam_over_diam=self.lam_over_diam, obscuration=self.obscuration, + flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/bandpass.html b/docs/_build/html/_modules/galsim/bandpass.html new file mode 100644 index 00000000000..523f9920390 --- /dev/null +++ b/docs/_build/html/_modules/galsim/bandpass.html @@ -0,0 +1,785 @@ + + + + + + galsim.bandpass — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.bandpass

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Bandpass' ]
+
+import numpy as np
+import os
+from astropy import units
+from numbers import Real
+from pathlib import PosixPath
+
+from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError
+from .table import LookupTable, _LookupTable
+from ._utilities import WeakMethod, basestring
+from . import integ
+from . import meta_data
+from . import utilities
+from .sed import SED
+
+
[docs]class Bandpass: + """Simple bandpass object, which models the transmission fraction of incident light as a + function of wavelength, for either an entire optical path (e.g., atmosphere, reflecting and + refracting optics, filters, CCD quantum efficiency), or some intermediate piece thereof. + Bandpasses representing individual components may be combined through the ``*`` operator to form + a new Bandpass object representing the composite optical path. + + Bandpasses are callable, returning dimensionless throughput as a function of wavelength in nm. + + Bandpasses are immutable; all transformative methods return *new* Bandpasses, and leave their + originating Bandpasses unaltered. + + Bandpasses require ``blue_limit`` and ``red_limit`` attributes, which may either be explicitly + set at initialization, or are inferred from the initializing `LookupTable` or 2-column file. + + Outside of the wavelength interval between ``blue_limit`` and ``red_limit``, the throughput is + returned as zero, regardless of the ``throughput`` input parameter. + + Bandpasses may be multiplied by other Bandpasses, functions, scalars, or `SED` instances. + The product of a Bandpass with an `SED` is a new `SED`. + + The Bandpass effective wavelength is stored in the python property ``effective_wavelength``. We + use throughput-weighted average wavelength (which is independent of any `SED`) as our + definition for effective wavelength. + + For Bandpasses defined using a `LookupTable`, a numpy.array of wavelengths, ``wave_list``, + defining the table is maintained. Bandpasses defined as products of two other Bandpasses will + define their ``wave_list`` as the union of multiplicand ``wave_list`` values, although limited + to the range between the new product ``blue_limit`` and ``red_limit``. (This implementation + detail may affect the choice of integrator used to draw a `ChromaticObject`.) + + The input parameter, throughput, may be one of several possible forms: + + 1. a regular python function (or an object that acts like a function) + 2. a `LookupTable` + 3. a file from which a `LookupTable` can be read in + 4. a string which can be evaluated to a function of ``wave`` via + ``eval('lambda wave : '+throughput)``, e.g.:: + + throughput = '0.8 + 0.2 * (wave-800)' + + The argument of ``throughput`` will be the wavelength in units specified by ``wave_type``. (See + below.) The output should be the dimensionless throughput at that wavelength. (Note we use + ``wave`` rather than ``lambda``, since ``lambda`` is a python reserved word.) + + The argument ``wave_type`` specifies the units to assume for wavelength and must be one of + 'nm', 'nanometer', 'nanometers', 'A', 'Ang', 'Angstrom', or 'Angstroms', or an astropy + distance unit. (For the string values, case is unimportant.) If given as floats, blue_limit + and red_limit are taken to be in these units as well. (If given as an astropy Quantity, then + the units are taken directly and converted to ``wave_type``.) + + Note that the ``wave_type`` parameter does not propagate into other methods of Bandpass. + For instance, `Bandpass.__call__` assumes its input argument is in nanometers. + + Finally, a Bandpass may have zeropoint attribute, which is a float used to convert flux + (in photons/s/cm^2) to magnitudes:: + + mag = -2.5*log10(flux) + zeropoint + + You can either set the zeropoint at initialization, or via the `withZeropoint` method. Note + that the zeropoint attribute does not propagate if you get a new Bandpass by multiplying or + dividing an old Bandpass. + + Parameters: + throughput: Function defining the throughput at each wavelength. See above for + valid options for this parameter. + wave_type: The units to use for the wavelength argument of the ``throughput`` + function. See above for details. + blue_limit: Hard cut off of bandpass on the blue side. [default: None, but required + if throughput is not a `LookupTable` or file. See above.] + red_limit: Hard cut off of bandpass on the red side. [default: None, but required + if throughput is not a `LookupTable` or file. See above.] + zeropoint: Set the zero-point for this Bandpass. Here, this can only be a float + value. See the method `withZeropoint` for other options for how to + set this using a particular spectrum (AB, Vega, etc.) [default: None] + interpolant: If reading from a file, what interpolant to use. [default: 'linear'] + """ + def __init__(self, throughput, wave_type, blue_limit=None, red_limit=None, + zeropoint=None, interpolant='linear', _wave_list=None, _tp=None): + # Note that `_wave_list` acts as a private construction variable that overrides the way that + # `wave_list` is normally constructed (see `Bandpass.__mul__` below) + + self._orig_tp = throughput # Save this for pickling. + self._tp = _tp # This will normally become orig_tp turned into an actual + # function (see _initialize_tp()), although in some cases, + # it can be supplied directly as a constructor argument. + + # Parse the various options for wave_type + if isinstance(wave_type, str): + if wave_type.lower() in ('nm', 'nanometer', 'nanometers'): + self.wave_type = 'nm' + self.wave_factor = 1. + elif wave_type.lower() in ('a', 'ang', 'angstrom', 'angstroms'): + self.wave_type = 'Angstrom' + self.wave_factor = 10. + else: + raise GalSimValueError("Invalid wave_type.", wave_type, ('nm', 'Angstrom')) + else: + self.wave_type = wave_type + try: + self.wave_factor = (1*units.nm).to(self.wave_type).value + if self.wave_factor == 1.: + self.wave_type = 'nm' + elif abs(self.wave_factor-10.) < 2.e-15: # This doesn't come out exactly 10. + self.wave_type = 'Angstrom' + self.wave_factor = 10. + except units.UnitConversionError: + # Unlike in SED, we require a distance unit for wave_type + raise GalSimValueError("Invalid wave_type. Must be a distance.", wave_type) + + if isinstance(blue_limit, units.Quantity): + blue_limit = blue_limit.to_value(units.Unit(self.wave_type)) + if isinstance(red_limit, units.Quantity): + red_limit = red_limit.to_value(units.Unit(self.wave_type)) + + if blue_limit is not None and red_limit is not None and blue_limit >= red_limit: + raise GalSimRangeError("blue_limit must be less than red_limit", + blue_limit, None, red_limit) + self.blue_limit = blue_limit # These may change as we go through this. + self.red_limit = red_limit + self.zeropoint = zeropoint + self.interpolant = interpolant + + # Convert string input into a real function (possibly a LookupTable) + self._initialize_tp() + + if _wave_list is not None: + # Manual override! Be careful! + self.wave_list = _wave_list + # This also means that red_limit and blue_limit are already set correctly. + # Don't change them. + #assert self.blue_limit is not None + #assert self.red_limit is not None + self._setup_func() + return + + # Account for wave_factor in wavelength limits + if self.wave_factor != 1.0: + if self.blue_limit is not None: + self.blue_limit /= self.wave_factor + if self.red_limit is not None: + self.red_limit /= self.wave_factor + + # Assign blue and red limits of bandpass + if isinstance(self._tp, LookupTable): + if self.blue_limit is None: + self.blue_limit = float(self._tp.x_min)/self.wave_factor + if self.red_limit is None: + self.red_limit = float(self._tp.x_max)/self.wave_factor + else: + if self.blue_limit is None or self.red_limit is None: + raise GalSimIncompatibleValuesError( + "red_limit and blue_limit are required if throughput is not a LookupTable.", + blue_limit=blue_limit, red_limit=red_limit, throughput=throughput) + + # Sanity check blue/red limit and create self.wave_list + if isinstance(self._tp, LookupTable): + self.wave_list = np.array(self._tp.getArgs())/self.wave_factor + # Make sure that blue_limit and red_limit are within LookupTable region of support. + if self.blue_limit < (self._tp.x_min/self.wave_factor): + raise GalSimRangeError("Cannot set blue_limit to be less than throughput x_min", + self.blue_limit, self._tp.x_min, self._tp.x_max) + if self.red_limit > (self._tp.x_max/self.wave_factor): + raise GalSimRangeError("Cannot set red_limit to be greater than throughput x_max", + self.red_limit, self._tp.x_min, self._tp.x_max) + # Remove any values that are outside the limits + self.wave_list = self.wave_list[np.logical_and(self.wave_list >= self.blue_limit, + self.wave_list <= self.red_limit) ] + # Make sure that blue_limit and red_limit are part of wave_list. + if self.red_limit not in self.wave_list: + np.append(self.wave_list, self.red_limit) + if self.blue_limit not in self.wave_list: + np.insert(self.wave_list, 0, self.blue_limit) + else: + self.wave_list = np.array([], dtype=float) + + self._setup_func() + + def _setup_func(self): + if self.wave_factor == 1.: + self.func = WeakMethod(self._func_trivial) + else: + self.func = WeakMethod(self._func_factor) + + def _func_trivial(self, wave): + return self._tp(np.asarray(wave,dtype=float)) + + def _func_factor(self, wave): + return self._tp(np.asarray(wave) * self.wave_factor) + + def _initialize_tp(self): + # Turn the input tp into a real function self._tp. + # The function cannot be pickled, so will need to do this in setstate as well as init. + + if self._tp is not None: + pass + elif isinstance(self._orig_tp, (basestring, PosixPath)): + isfile, filename = utilities.check_share_file(self._orig_tp, 'bandpasses') + if isfile: + self._tp = LookupTable.from_file(filename, interpolant=self.interpolant) + else: + if self.blue_limit is None or self.red_limit is None: + raise GalSimIncompatibleValuesError( + "red_limit and blue_limit are required if throughput is not a LookupTable.", + blue_limit=None, red_limit=None, throughput=self._orig_tp) + test_wave = self.blue_limit + try: + self._tp = utilities.math_eval('lambda wave : ' + self._orig_tp) + test_value = self._tp(test_wave) + except Exception as e: + raise GalSimValueError( + "String throughput must either be a valid filename or something that " + "can eval to a function of wave.\n Caught error: %s."%(e), + self._orig_tp) + if not isinstance(test_value, Real): + raise GalSimValueError( + "The given throughput function did not return a valid " + "number at test wavelength %s: got %s."%(test_wave, test_value), + self._orig_tp) + else: + self._tp = self._orig_tp + + def __mul__(self, other): + # Watch out for 4 types of `other`: + # 1. SED: delegate to SED.__mul__(bandpass) + # 2. Bandpass: return a Bandpass, but carefully propagate blue/red limit and wave_list. + # 3. Callable: return a Bandpass + # 4. Scalar: return a Bandpass + + # Delegate SED * Bandpass to SED.__mul__: + if isinstance(other, SED): + return other.__mul__(self) + + # Bandpass * Bandpass -> Bandpass + if isinstance(other, Bandpass): + wave_list, blue_limit, red_limit = utilities.combine_wave_list([self, other]) + tp = lambda w: self(w) * other(w) + return Bandpass(tp, 'nm', blue_limit=blue_limit, red_limit=red_limit, zeropoint=None, + _wave_list=wave_list) + + # Product of Bandpass with generic callable or scalar is a rescaled Bandpass. + wave_type = 'nm' + if hasattr(other, '__call__'): + tp = lambda w: self.func(w) * other(w) + elif isinstance(self._tp, LookupTable): + # If other is not a function, then there is no loss of accuracy by applying the + # factor directly to the LookupTable, if that's what we are using. + # Make sure to keep the same properties about the table, wave_type. + wave_type = self.wave_type + x = self._tp.getArgs() + f = [ val * other for val in self._tp.getVals() ] + tp = LookupTable(x, f, x_log=self._tp.x_log, f_log=self._tp.f_log, + interpolant=self._tp.interpolant) + else: + tp = lambda w: self.func(w) * other + + return Bandpass(tp, wave_type, self.blue_limit, self.red_limit, _wave_list=self.wave_list) + + def __rmul__(self, other): + return self*other + + # Doesn't check for divide by zero, so be careful. + def __div__(self, other): + # Watch out for 4 types of `other`: + # 1. SED: prohibit. + # 2. Bandpass: return a Bandpass, but carefully propagate blue/red limit and wave_list. + # 3. Callable: return a Bandpass + # 4. Scalar: return a Bandpass + + if isinstance(other, SED): + raise TypeError("Cannot divide Bandpass by SED.") + + # Bandpass / Bandpass -> Bandpass + if isinstance(other, Bandpass): + wave_list, blue_limit, red_limit = utilities.combine_wave_list([self, other]) + tp = lambda w: self(w) / other(w) + return Bandpass(tp, 'nm', blue_limit=blue_limit, red_limit=red_limit, zeropoint=None, + _wave_list=wave_list) + + # Quotient of Bandpass with generic callable or scalar is a rescaled Bandpass. + wave_type = 'nm' + if hasattr(other, '__call__'): + tp = lambda w: self.func(w) / other(w) + elif isinstance(self._tp, LookupTable): + # If other is not a function, then there is no loss of accuracy by applying the + # factor directly to the LookupTable, if that's what we are using. + # Make sure to keep the same properties about the table, wave_type. + wave_type = self.wave_type + x = self._tp.getArgs() + f = [ val / other for val in self._tp.getVals() ] + tp = LookupTable(x, f, x_log=self._tp.x_log, f_log=self._tp.f_log, + interpolant=self._tp.interpolant) + else: + tp = lambda w: self.func(w) / other + + return Bandpass(tp, wave_type, self.blue_limit, self.red_limit, _wave_list=self.wave_list) + + __truediv__ = __div__ + +
[docs] def __call__(self, wave): + """Return dimensionless throughput of bandpass at given wavelength in nanometers. + + Note that outside of the wavelength range defined by the ``blue_limit`` and ``red_limit`` + attributes, the throughput is assumed to be zero. + + Parameters: + wave: Wavelength in nanometers. (Either a scalar or a numpy array) + + Returns: + the dimensionless throughput. + """ + wave = np.asarray(wave) + if wave.shape == (): + if (wave >= self.blue_limit and wave <= self.red_limit): + return self.func(float(wave)) + else: + return 0.0 + else: + wgood = (wave >= self.blue_limit) & (wave <= self.red_limit) + ret = np.zeros(wave.shape, dtype=float) + np.place(ret, wgood, self.func(wave[wgood])) + return ret
+ + @property + def effective_wavelength(self): + """The effective wavelength of the `Bandpass`. + + An alias for ``self.calculateEffectiveWavelength()``. + """ + return self.calculateEffectiveWavelength() + +
[docs] def calculateEffectiveWavelength(self, precise=False): + """Calculate, store, and return the effective wavelength for this bandpass. + + We define the effective wavelength as the throughput-weighted average wavelength, which is + SED-independent. Units are nanometers. + + Parameters: + precise: Optionally use a more precise integration method when the bandpass uses + a `LookupTable` rather than the normal trapezoid rule. [default: False] + """ + if not hasattr(self, '_effective_wavelength') or precise: + if len(self.wave_list) > 0 and not precise: + num = self._tp.integrate_product(lambda w:w, + self.blue_limit, self.red_limit, + x_factor=self.wave_factor) + denom = self._tp.integrate(self.blue_limit*self.wave_factor, + self.red_limit*self.wave_factor) / self.wave_factor + else: + num = integ.int1d(lambda w: self.func(w) * w, + self.blue_limit, self.red_limit) + denom = integ.int1d(self.func, self.blue_limit, self.red_limit) + + self._effective_wavelength = num / denom + + return self._effective_wavelength
+ +
[docs] def withZeropoint(self, zeropoint): + """Assign a zeropoint to this `Bandpass`. + + A bandpass zeropoint is a float used to convert flux (in photons/s/cm^2) to magnitudes:: + + mag = -2.5*log10(flux) + zeropoint + + Note that the zeropoint attribute does not propagate if you get a new `Bandpass` by + multiplying or dividing an old `Bandpass`. + + The ``zeropoint`` argument can take a variety of possible forms: + + 1. a number, which will be the zeropoint + 2. a `galsim.SED`. In this case, the zeropoint is set such that the magnitude of the + supplied `SED` through the `Bandpass` is 0.0 + 3. the string 'AB'. In this case, use an AB zeropoint. + 4. the string 'Vega'. Use a Vega zeropoint. + 5. the string 'ST'. Use a HST STmag zeropoint. + + Parameters: + zeropoint: See above for valid input options + + Returns: + new `Bandpass` with zeropoint set. + """ + # Convert `zeropoint` from str to galsim.SED. + if isinstance(zeropoint, str): + if zeropoint.upper()=='AB': + AB_source = 3631e-23 # 3631 Jy in units of erg/s/Hz/cm^2 + sed = SED(lambda wave: AB_source, wave_type='nm', flux_type='fnu') + elif zeropoint.upper()=='ST': + # Use HST STmags: http://www.stsci.edu/hst/acs/analysis/zeropoints + ST_flambda = 3.63e-8 # erg/s/cm^2/nm + sed = SED(lambda wave: ST_flambda, wave_type='nm', flux_type='flambda') + elif zeropoint.upper()=='VEGA': + # Use vega spectrum for SED + vegafile = os.path.join(meta_data.share_dir, "SEDs", "vega.txt") + sed = SED(vegafile, wave_type='nm', flux_type='flambda') + else: + raise GalSimValueError("Unrecognized Zeropoint string.", zeropoint, + ('AB', 'ST', 'VEGA')) + zeropoint = sed + + # Convert `zeropoint` from galsim.SED to float + if isinstance(zeropoint, SED): + flux = zeropoint.calculateFlux(self) + zeropoint = 2.5 * np.log10(flux) + + # Should be a float now (or maybe an int). If not, raise an exception. + if not isinstance(zeropoint, (float, int)): + raise TypeError( + "Don't know how to handle zeropoint of type: {0}".format(type(zeropoint))) + + return Bandpass(self._orig_tp, self.wave_type, self.blue_limit, self.red_limit, + zeropoint=zeropoint, interpolant=self.interpolant, + _wave_list=self.wave_list, _tp=self._tp)
+ +
[docs] def truncate(self, blue_limit=None, red_limit=None, relative_throughput=None, + preserve_zp='auto'): + """Return a bandpass with its wavelength range truncated. + + This function truncate the range of the bandpass either explicitly (with ``blue_limit`` or + ``red_limit`` or both) or automatically, just trimming off leading and trailing wavelength + ranges where the relative throughput is less than some amount (``relative_throughput``). + + This second option using relative_throughput is only available for bandpasses initialized + with a `LookupTable` or from a file, not when using a regular python function or a string + evaluation. + + This function does not remove any intermediate wavelength ranges, but see thin() for + a method that can thin out the intermediate values. + + When truncating a bandpass that already has an assigned zeropoint, there are several + possibilities for what should happen to the new (returned) bandpass by default. If red + and/or blue limits are given, then the new bandpass will have no assigned zeropoint because + it is difficult to predict what should happen if the bandpass is being arbitrarily + truncated. If ``relative_throughput`` is given, often corresponding to low-level truncation + that results in little change in observed quantities, then the new bandpass is assigned the + same zeropoint as the original. This default behavior is called 'auto'. The user can also + give boolean True or False values. + + Parameters: + blue_limit: Truncate blue side of bandpass at this wavelength in nm. + [default: None] + red_limit: Truncate red side of bandpass at this wavelength in nm. + [default: None] + relative_throughput: Truncate leading or trailing wavelengths that are below + this relative throughput level. (See above for details.) + Either ``blue_limit`` and/or ``red_limit`` should be supplied, + or ``relative_throughput`` should be supplied -- but + ``relative_throughput`` should not be combined with one of the + limits. + [default: None] + preserve_zp: If True, the new truncated `Bandpass` will be assigned the same + zeropoint as the original. If False, the new truncated + `Bandpass` will have a zeropoint of None. If 'auto', the new + truncated `Bandpass` will have the same zeropoint as the + original when truncating using ``relative_throughput``, but + will have a zeropoint of None when truncating using + 'blue_limit' and/or 'red_limit'. [default: 'auto'] + + Returns: + the truncated `Bandpass`. + """ + # Enforce the choice of a single mode of truncation. + if relative_throughput is not None: + if blue_limit is not None or red_limit is not None: + raise GalSimIncompatibleValuesError( + "Truncate using relative_throughput or red/blue_limit, not both!", + blue_limit=blue_limit, red_limit=red_limit, + relative_throughput=relative_throughput) + + if preserve_zp == 'auto': + if relative_throughput is not None: preserve_zp = True + else: preserve_zp = False + # Check for weird input + if preserve_zp is not True and preserve_zp is not False: + raise GalSimValueError("Unrecognized input for preserve_zp.",preserve_zp) + + if blue_limit is None: + blue_limit = self.blue_limit + else: + if blue_limit < self.blue_limit: + raise GalSimRangeError("Supplied blue_limit may not be bluer than the original.", + blue_limit, self.blue_limit, self.red_limit) + if red_limit is None: + red_limit = self.red_limit + else: + if red_limit > self.red_limit: + raise GalSimRangeError("Supplied red_limit may not be redder than the original.", + red_limit, self.blue_limit, self.red_limit) + + wave_list = self.wave_list + if len(self.wave_list) > 0: + wave = np.array(self.wave_list) + tp = self.func(wave) + if relative_throughput is not None: + w = (tp >= tp.max()*relative_throughput).nonzero() + blue_limit = max([np.min(wave[w]), blue_limit]) + red_limit = min([np.max(wave[w]), red_limit]) + wave_list = wave_list[np.logical_and(wave_list >= blue_limit, + wave_list <= red_limit) ] + elif relative_throughput is not None: + raise GalSimIncompatibleValuesError( + "Can only truncate with relative_throughput argument if throughput is " + "a LookupTable", relative_throughput=relative_throughput, throughput=self._orig_tp) + + if preserve_zp: + return Bandpass(self._orig_tp, self.wave_type, blue_limit, red_limit, + zeropoint=self.zeropoint, interpolant=self.interpolant, + _wave_list=wave_list, _tp=self._tp) + else: + return Bandpass(self._orig_tp, self.wave_type, blue_limit, red_limit, + interpolant=self.interpolant, + _wave_list=wave_list, _tp=self._tp)
+ +
[docs] def thin(self, rel_err=1.e-4, trim_zeros=True, preserve_range=True, fast_search=True, + preserve_zp=True): + """Thin out the internal wavelengths of a `Bandpass` that uses a `LookupTable`. + + If the bandpass was initialized with a `LookupTable` or from a file (which internally + creates a `LookupTable`), this function removes tabulated values while keeping the integral + over the set of tabulated values still accurate to the given relative error. + + That is, the integral of the bandpass function is preserved to a relative precision + of ``rel_err``, while eliminating as many internal wavelength values as possible. This + process will usually help speed up integrations using this bandpass. You should weigh + the speed improvements against your fidelity requirements for your particular use + case. + + By default, this routine will preserve the zeropoint of the original bandpass by assigning + it to the new thinned bandpass. The justification for this choice is that when using an AB + zeropoint, a typical optical bandpass, and the default thinning ``rel_err`` value, the + zeropoint for the new and thinned bandpasses changes by 10^-6. However, if you are thinning + a lot, and/or want to do extremely precise tests, you can set ``preserve_zp=False`` and then + recalculate the zeropoint after thinning. + + Parameters: + rel_err: The relative error allowed in the integral over the throughput + function. [default: 1.e-4] + trim_zeros: Remove redundant leading and trailing points where f=0? (The last + leading point with f=0 and the first trailing point with f=0 will + be retained). Note that if both trim_leading_zeros and + preserve_range are True, then the only the range of ``x`` *after* + zero trimming is preserved. [default: True] + preserve_range: Should the original range (``blue_limit`` and ``red_limit``) of the + `Bandpass` be preserved? (True) Or should the ends be trimmed to + include only the region where the integral is significant? (False) + [default: True] + fast_search: If set to True, then the underlying algorithm will use a + relatively fast O(N) algorithm to select points to include in the + thinned approximation. If set to False, then a slower O(N^2) + algorithm will be used. We have found that the slower algorithm + tends to yield a thinned representation that retains fewer samples + while still meeting the relative error requirement, and may also + be somewhat more robust when computing an `SED` flux through + a `Bandpass` when a significant fraction of the integrated flux + passes through low throughput bandpass light leaks. + [default: True] + preserve_zp: If True, the new thinned `Bandpass` will be assigned the same + zeropoint as the original. If False, the new thinned `Bandpass` + will have a zeropoint of None. [default: True] + + Returns: + the thinned `Bandpass`. + """ + if len(self.wave_list) > 0: + x = self.wave_list + f = self(x) + interpolant = (self.interpolant if not isinstance(self._tp, LookupTable) + else self._tp.interpolant) + newx, newf = utilities.thin_tabulated_values(x, f, rel_err=rel_err, + trim_zeros=trim_zeros, + preserve_range=preserve_range, + fast_search=fast_search, + interpolant=interpolant) + tp = _LookupTable(newx, newf, interpolant) + blue_limit = np.min(newx) + red_limit = np.max(newx) + wave_list = np.array(newx) + if preserve_zp: + return Bandpass(tp, 'nm', blue_limit, red_limit, _wave_list=wave_list, + zeropoint=self.zeropoint) + else: + return Bandpass(tp, 'nm', blue_limit, red_limit, _wave_list=wave_list) + else: + return self
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Bandpass) and + self._orig_tp == other._orig_tp and + self.blue_limit == other.blue_limit and + self.red_limit == other.red_limit and + self.wave_factor == other.wave_factor and + self.zeropoint == other.zeropoint and + np.array_equal(self.wave_list, other.wave_list))) + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): + # Cache this in case self._orig_tp or self.wave_list is long. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.Bandpass", self._orig_tp, self.blue_limit, self.red_limit, + self.wave_factor, self.zeropoint, tuple(self.wave_list))) + return self._hash + + def __repr__(self): + return ('galsim.Bandpass(%r, wave_type=%r, blue_limit=%r, red_limit=%r, zeropoint=%r, ' + 'interpolant=%r, _wave_list=array(%r))')%( + self._orig_tp, self.wave_type, self.blue_limit, self.red_limit, + self.zeropoint, self.interpolant, self.wave_list.tolist()) + + def __str__(self): + orig_tp = repr(self._orig_tp) + if len(orig_tp) > 80: + orig_tp = str(self._orig_tp) + return 'galsim.Bandpass(%s)'%self._orig_tp + + def __getstate__(self): + d = self.__dict__.copy() + if not isinstance(d['_tp'], LookupTable): + del d['_tp'] + del d['func'] + return d + + def __setstate__(self, d): + self.__dict__ = d + if '_tp' not in d: + self._tp = None + # If _tp is already set, this is will just set func. + self._initialize_tp() + self._setup_func()
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/bounds.html b/docs/_build/html/_modules/galsim/bounds.html new file mode 100644 index 00000000000..74aca530d4e --- /dev/null +++ b/docs/_build/html/_modules/galsim/bounds.html @@ -0,0 +1,604 @@ + + + + + + galsim.bounds — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.bounds

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Bounds', 'BoundsI', 'BoundsD', '_BoundsI', '_BoundsD', ]
+
+import math
+
+from . import _galsim
+from .position import *
+from .errors import GalSimUndefinedBoundsError
+
+
[docs]class Bounds: + """A class for representing image bounds as 2D rectangles. + + Bounds is a base class for two slightly different kinds of bounds: + `BoundsD` describes bounds with floating point values in x and y. + `BoundsI` described bounds with integer values in x and y. + + The bounds are stored as four numbers in each instance, (xmin, xmax, ymin, ymax), with an + additional boolean switch to say whether or not the Bounds rectangle has been defined. The + rectangle is undefined if the min value > the max value in either direction. + + *Initialization*: + + A `BoundsI` or `BoundsD` instance can be initialized in a variety of ways. The most direct is + via four scalars:: + + >>> bounds = galsim.BoundsD(xmin, xmax, ymin, ymax) + >>> bounds = galsim.BoundsI(imin, imax, jmin, jmax) + + In the `BoundsI` example above, ``imin``, ``imax``, ``jmin`` and ``jmax`` must all be integers + to avoid a TypeError exception. + + Another way to initialize a `Bounds` instance is using two `Position` instances, the first + for ``(xmin,ymin)`` and the second for ``(xmax,ymax)``:: + + >>> bounds = galsim.BoundsD(galsim.PositionD(xmin, ymin), galsim.PositionD(xmax, ymax)) + >>> bounds = galsim.BoundsI(galsim.PositionI(imin, jmin), galsim.PositionI(imax, jmax)) + + In both the examples above, the I/D type of `PositionI`/`PositionD` must match that of + `BoundsI`/`BoundsD`. + + Finally, there are a two ways to lazily initialize a bounds instance with ``xmin = xmax``, + ``ymin = ymax``, which will have an undefined rectangle and the instance method isDefined() + will return False. The first sets ``xmin = xmax = ymin = ymax = 0``:: + + >>> bounds = galsim.BoundsD() + >>> bounds = galsim.BoundsI() + + The second method sets both upper and lower rectangle bounds to be equal to some position:: + + >>> bounds = galsim.BoundsD(galsim.PositionD(xmin, ymin)) + >>> bounds = galsim.BoundsI(galsim.PositionI(imin, jmin)) + + Once again, the I/D type of `PositionI`/`PositionD` must match that of `BoundsI`/`BoundsD`. + + For the latter two initializations, you would typically then add to the bounds with:: + + >>> bounds += pos1 + >>> bounds += pos2 + >>> [etc.] + + Then the bounds will end up as the bounding box of all the positions that were added to it. + + You can also find the intersection of two bounds with the & operator:: + + >>> overlap = bounds1 & bounds2 + + This is useful for adding one image to another when part of the first image might fall off + the edge of the other image:: + + >>> overlap = stamp.bounds & image.bounds + >>> image[overlap] += stamp[overlap] + + """ + def __init__(self): + raise NotImplementedError("Cannot instantiate the base class. " + "Use either BoundsD or BoundsI.") + + def _parse_args(self, *args, **kwargs): + if len(kwargs) == 0: + if len(args) == 4: + self._isdefined = True + self.xmin, self.xmax, self.ymin, self.ymax = args + elif len(args) == 0: + self._isdefined = False + self.xmin = self.xmax = self.ymin = self.ymax = 0 + elif len(args) == 1: + if isinstance(args[0], (Bounds, _galsim.BoundsD, _galsim.BoundsI)): + self._isdefined = True + self.xmin = args[0].xmin + self.xmax = args[0].xmax + self.ymin = args[0].ymin + self.ymax = args[0].ymax + elif isinstance(args[0], (Position, _galsim.PositionD, _galsim.PositionI)): + self._isdefined = True + self.xmin = self.xmax = args[0].x + self.ymin = self.ymax = args[0].y + else: + raise TypeError("Single argument to %s must be either a Bounds or a Position"%( + self.__class__.__name__)) + self._isdefined = True + elif len(args) == 2: + if (isinstance(args[0], (Position, _galsim.PositionD, _galsim.PositionI)) and + isinstance(args[1], (Position, _galsim.PositionD, _galsim.PositionI))): + self._isdefined = True + self.xmin = min(args[0].x, args[1].x) + self.xmax = max(args[0].x, args[1].x) + self.ymin = min(args[0].y, args[1].y) + self.ymax = max(args[0].y, args[1].y) + else: + raise TypeError("Two arguments to %s must be Positions"%( + self.__class__.__name__)) + else: + raise TypeError("%s takes either 1, 2, or 4 arguments (%d given)"%( + self.__class__.__name__,len(args))) + elif len(args) != 0: + raise TypeError("Cannot provide both keyword and non-keyword arguments to %s"%( + self.__class__.__name__)) + else: + try: + self._isdefined = True + self.xmin = kwargs.pop('xmin') + self.xmax = kwargs.pop('xmax') + self.ymin = kwargs.pop('ymin') + self.ymax = kwargs.pop('ymax') + except KeyError: + raise TypeError("Keyword arguments, xmin, xmax, ymin, ymax are required for %s"%( + self.__class__.__name__)) from None + if kwargs: + raise TypeError("Got unexpected keyword arguments %s"%kwargs.keys()) + + if not (float(self.xmin) <= float(self.xmax) and float(self.ymin) <= float(self.ymax)): + self._isdefined = False + +
[docs] def area(self): + """Return the area of the enclosed region. + + The area is a bit different for integer-type `BoundsI` and float-type `BoundsD` instances. + For floating point types, it is simply ``(xmax-xmin)*(ymax-ymin)``. However, for integer + types, we add 1 to each size to correctly count the number of pixels being described by the + bounding box. + """ + return self._area()
+ +
[docs] def withBorder(self, dx, dy=None): + """Return a new `Bounds` object that expands the current bounds by the specified width. + + If two arguments are given, then these are separate dx and dy borders. + """ + self._check_scalar(dx, "dx") + if dy is None: + dy = dx + else: + self._check_scalar(dy, "dy") + return self.__class__(self.xmin-dx, self.xmax+dx, self.ymin-dy, self.ymax+dy)
+ + @property + def origin(self): + "The lower left position of the `Bounds`." + return self._pos_class(self.xmin, self.ymin) + + @property + def center(self): + """The central position of the `Bounds`. + + For a `BoundsI`, this will return an integer `PositionI`, which will be above and/or to + the right of the true center if the x or y ranges have an even number of pixels. + + For a `BoundsD`, this is equivalent to true_center. + """ + if not self.isDefined(): + raise GalSimUndefinedBoundsError("center is invalid for an undefined Bounds") + return self._center + + @property + def true_center(self): + """The central position of the `Bounds` as a `PositionD`. + + This is always (xmax + xmin)/2., (ymax + ymin)/2., even for integer `BoundsI`, where + this may not necessarily be an integer `PositionI`. + """ + if not self.isDefined(): + raise GalSimUndefinedBoundsError("true_center is invalid for an undefined Bounds") + return _PositionD((self.xmax + self.xmin)/2., (self.ymax + self.ymin)/2.) + +
[docs] def includes(self, *args): + """Test whether a supplied ``(x,y)`` pair, `Position`, or `Bounds` lie within a defined + `Bounds` rectangle of this instance. + + Examples:: + + >>> bounds = galsim.BoundsD(0., 100., 0., 100.) + >>> bounds.includes(50., 50.) + True + >>> bounds.includes(galsim.PositionD(50., 50.)) + True + >>> bounds.includes(galsim.BoundsD(-50., -50., 150., 150.)) + False + + The type of the `PositionI`/`PositionD` and `BoundsI`/`BoundsD` instances (i.e. integer or + float type) should match that of the bounds instance. + """ + if len(args) == 1: + if isinstance(args[0], Bounds): + b = args[0] + return (self.isDefined() and b.isDefined() and + self.xmin <= b.xmin and + self.xmax >= b.xmax and + self.ymin <= b.ymin and + self.ymax >= b.ymax) + elif isinstance(args[0], Position): + p = args[0] + return (self.isDefined() and + self.xmin <= p.x <= self.xmax and + self.ymin <= p.y <= self.ymax) + else: + raise TypeError("Invalid argument %s"%args[0]) + elif len(args) == 2: + x, y = args + return (self.isDefined() and + self.xmin <= float(x) <= self.xmax and + self.ymin <= float(y) <= self.ymax) + elif len(args) == 0: + raise TypeError("include takes at least 1 argument (0 given)") + else: + raise TypeError("include takes at most 2 arguments (%d given)"%len(args))
+ +
[docs] def expand(self, factor_x, factor_y=None): + """Grow the `Bounds` by the supplied factor about the center. + + If two arguments are given, then these are separate x and y factors to + expand by. + """ + if factor_y is None: + factor_y = factor_x + dx = (self.xmax - self.xmin) * 0.5 * (factor_x-1.) + dy = (self.ymax - self.ymin) * 0.5 * (factor_y-1.) + if isinstance(self, BoundsI): + dx = int(math.ceil(dx)) + dy = int(math.ceil(dy)) + return self.withBorder(dx,dy)
+ +
[docs] def isDefined(self): + "Test whether `Bounds` rectangle is defined." + return self._isdefined
+ +
[docs] def getXMin(self): + "Get the value of xmin." + return self.xmin
+ +
[docs] def getXMax(self): + "Get the value of xmax." + return self.xmax
+ +
[docs] def getYMin(self): + "Get the value of ymin." + return self.ymin
+ +
[docs] def getYMax(self): + "Get the value of ymax." + return self.ymax
+ +
[docs] def shift(self, delta): + """Shift the `Bounds` instance by a supplied `Position`. + + Examples: + + The shift method takes either a `PositionI` or `PositionD` instance, which must match + the type of the `Bounds` instance:: + + >>> bounds = BoundsI(1,32,1,32) + >>> bounds = bounds.shift(galsim.PositionI(3, 2)) + >>> bounds = BoundsD(0, 37.4, 0, 49.9) + >>> bounds = bounds.shift(galsim.PositionD(3.9, 2.1)) + """ + if not isinstance(delta, self._pos_class): + raise TypeError("delta must be a %s instance"%self._pos_class) + return self.__class__(self.xmin + delta.x, self.xmax + delta.x, + self.ymin + delta.y, self.ymax + delta.y)
+ + def __and__(self, other): + if not isinstance(other, self.__class__): + raise TypeError("other must be a %s instance"%self.__class__.__name__) + if not self.isDefined() or not other.isDefined(): + return self.__class__() + else: + xmin = max(self.xmin, other.xmin) + xmax = min(self.xmax, other.xmax) + ymin = max(self.ymin, other.ymin) + ymax = min(self.ymax, other.ymax) + if xmin > xmax or ymin > ymax: + return self.__class__() + else: + return self.__class__(xmin, xmax, ymin, ymax) + + def __add__(self, other): + if isinstance(other, self.__class__): + if not other.isDefined(): + return self + elif self.isDefined(): + xmin = min(self.xmin, other.xmin) + xmax = max(self.xmax, other.xmax) + ymin = min(self.ymin, other.ymin) + ymax = max(self.ymax, other.ymax) + return self.__class__(xmin, xmax, ymin, ymax) + else: + return other + elif isinstance(other, self._pos_class): + if self.isDefined(): + xmin = min(self.xmin, other.x) + xmax = max(self.xmax, other.x) + ymin = min(self.ymin, other.y) + ymax = max(self.ymax, other.y) + return self.__class__(xmin, xmax, ymin, ymax) + else: + return self.__class__(other) + else: + raise TypeError("other must be either a %s or a %s"%( + self.__class__.__name__, self._pos_class.__name__)) + + def __repr__(self): + if self.isDefined(): + return "galsim.%s(xmin=%r, xmax=%r, ymin=%r, ymax=%r)"%( + self.__class__.__name__, self.xmin, self.xmax, self.ymin, self.ymax) + else: + return "galsim.%s()"%(self.__class__.__name__) + + def __str__(self): + if self.isDefined(): + return "galsim.%s(%s,%s,%s,%s)"%( + self.__class__.__name__, self.xmin, self.xmax, self.ymin, self.ymax) + else: + return "galsim.%s()"%(self.__class__.__name__) + + def _getinitargs(self): + if self.isDefined(): + return (self.xmin, self.xmax, self.ymin, self.ymax) + else: + return () + + def __eq__(self, other): + return (self is other or + (isinstance(other, self.__class__) and self._getinitargs() == other._getinitargs())) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.__class__.__name__, self._getinitargs()))
+ + +
[docs]class BoundsD(Bounds): + """A `Bounds` that takes floating point values. + + See the `Bounds` doc string for more details. + """ + _pos_class = PositionD + + def __init__(self, *args, **kwargs): + self._parse_args(*args, **kwargs) + self.xmin = float(self.xmin) + self.xmax = float(self.xmax) + self.ymin = float(self.ymin) + self.ymax = float(self.ymax) + + @property + def _b(self): + return _galsim.BoundsD(float(self.xmin), float(self.xmax), + float(self.ymin), float(self.ymax)) + + def _check_scalar(self, x, name): + try: + if x == float(x): return + except (TypeError, ValueError): + pass + raise TypeError("%s must be a float value"%name) + + def _area(self): + return (self.xmax - self.xmin) * (self.ymax - self.ymin) + + @property + def _center(self): + return _PositionD( (self.xmax + self.xmin)/2., (self.ymax + self.ymin)/2. )
+ + +
[docs]class BoundsI(Bounds): + """A `Bounds` that takes only integer values. + + Typically used to define the bounding box of an image. + + See the `Bounds` doc string for more details. + """ + _pos_class = PositionI + + def __init__(self, *args, **kwargs): + self._parse_args(*args, **kwargs) + if (self.xmin != int(self.xmin) or self.xmax != int(self.xmax) or + self.ymin != int(self.ymin) or self.ymax != int(self.ymax)): + raise TypeError("BoundsI must be initialized with integer values") + # Now make sure they are all ints + self.xmin = int(self.xmin) + self.xmax = int(self.xmax) + self.ymin = int(self.ymin) + self.ymax = int(self.ymax) + + @property + def _b(self): + return _galsim.BoundsI(self.xmin, self.xmax, self.ymin, self.ymax) + + def _check_scalar(self, x, name): + try: + if x == int(x): return + except (TypeError, ValueError): + pass + raise TypeError("%s must be an integer value"%name) + +
[docs] def numpyShape(self): + "A simple utility function to get the numpy shape that corresponds to this `Bounds` object." + if self.isDefined(): + return self.ymax-self.ymin+1, self.xmax-self.xmin+1 + else: + return 0,0
+ + def _area(self): + # Remember the + 1 this time to include the pixels on both edges of the bounds. + if not self.isDefined(): + return 0 + else: + return (self.xmax - self.xmin + 1) * (self.ymax - self.ymin + 1) + + @property + def _center(self): + # Write it this way to make sure the integer rounding goes the same way regardless + # of whether the values are positive or negative. + # e.g. (1,10,1,10) -> (6,6) + # (-10,-1,-10,-1) -> (-5,-5) + # Just up and to the right of the true center in both cases. + return _PositionI(self.xmin + (self.xmax - self.xmin + 1)//2, + self.ymin + (self.ymax - self.ymin + 1)//2)
+ + +
[docs]def _BoundsD(xmin, xmax, ymin, ymax): + """Equivalent to `BoundsD` constructor, but skips some sanity checks and argument parsing. + This requires that the four values be float types. + """ + ret = BoundsD.__new__(BoundsD) + ret._isdefined = True + ret.xmin = float(xmin) + ret.xmax = float(xmax) + ret.ymin = float(ymin) + ret.ymax = float(ymax) + return ret
+ + +
[docs]def _BoundsI(xmin, xmax, ymin, ymax): + """Equivalent to `BoundsI` constructor, but skips some sanity checks and argument parsing. + This requires that the four values be int types. + """ + ret = BoundsI.__new__(BoundsI) + ret._isdefined = True + ret.xmin = int(xmin) + ret.xmax = int(xmax) + ret.ymin = int(ymin) + ret.ymax = int(ymax) + return ret
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/box.html b/docs/_build/html/_modules/galsim/box.html new file mode 100644 index 00000000000..80eee324863 --- /dev/null +++ b/docs/_build/html/_modules/galsim/box.html @@ -0,0 +1,398 @@ + + + + + + galsim.box — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.box

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Pixel', 'Box', 'TopHat' ]
+
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+
+
+
[docs]class Box(GSObject): + """A class describing a box profile. This is just a 2D top-hat function, where the + width and height are allowed to be different. + + Parameters: + width: The width of the Box. + height: The height of the Box. + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "width" : float, "height" : float } + _opt_params = { "flux" : float } + + _has_hard_edges = True + _is_axisymmetric = False + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, width, height, flux=1., gsparams=None): + self._width = float(width) + self._height = float(height) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + self._norm = self._flux / (self._width * self._height) + + @lazy_property + def _sbp(self): + return _galsim.SBBox(self._width, self._height, self._flux, self.gsparams._gsp) + + @property + def width(self): + """The width of the `Box`. + """ + return self._width + @property + def height(self): + """The height of the `Box`. + """ + return self._height + + def __eq__(self, other): + return (self is other or + (isinstance(other, Box) and + self.width == other.width and + self.height == other.height and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Box", self.width, self.height, self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.Box(width=%r, height=%r, flux=%r, gsparams=%r)'%( + self.width, self.height, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Box(width=%s, height=%s'%(self.width, self.height) + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return 2. / (self.gsparams.maxk_threshold * min(self.width, self.height)) + + @property + def _stepk(self): + return math.pi / max(self.width, self.height) + + @property + def _max_sb(self): + return self._norm + + def _xValue(self, pos): + if 2.*abs(pos.x) < self._width and 2.*abs(pos.y) < self._height: + return self._norm + else: + return 0. + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Box(width=self.width, height=self.height, flux=flux, gsparams=self.gsparams)
+ +
[docs]class Pixel(Box): + """A class describing a pixel profile. This is just a 2D square top-hat function. + + This class is typically used to represent a pixel response function. It is used internally by + the `GSObject.drawImage` function, but there may be cases where the user would want to use + this profile directly. + + Parameters: + scale: The linear scale size of the pixel. Typically given in arcsec. + flux: The flux (in photons/cm^2/s) of the profile. This should almost + certainly be left at the default value of 1. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "scale" : float } + _opt_params = { "flux" : float } + + def __init__(self, scale, flux=1., gsparams=None): + super(Pixel, self).__init__(width=scale, height=scale, flux=flux, gsparams=gsparams) + + @property + def scale(self): + """The linear scale size of the `Pixel`. + """ + return self.width + + def __repr__(self): + return 'galsim.Pixel(scale=%r, flux=%r, gsparams=%r)'%( + self.scale, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Pixel(scale=%s'%self.scale + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + +
[docs] @doc_inherit + def withFlux(self, flux): + return Pixel(scale=self.scale, flux=flux, gsparams=self.gsparams)
+ + +
[docs]class TopHat(GSObject): + """A class describing a radial tophat profile. This profile is a constant value within some + radius, and zero outside this radius. + + Parameters: + radius: The radius of the TopHat, where the surface brightness drops to 0. + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "radius" : float } + _opt_params = { "flux" : float } + + _has_hard_edges = True + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, radius, flux=1., gsparams=None): + self._radius = float(radius) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + self._rsq = self._radius**2 + self._norm = self._flux / (math.pi * self._rsq) + + @lazy_property + def _sbp(self): + return _galsim.SBTopHat(self._radius, self._flux, self.gsparams._gsp) + + @property + def radius(self): + """The radius of the `TopHat` profile. + """ + return self._radius + + def __eq__(self, other): + return (self is other or + (isinstance(other, TopHat) and + self.radius == other.radius and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.TopHat", self.radius, self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.TopHat(radius=%r, flux=%r, gsparams=%r)'%( + self.radius, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.TopHat(radius=%s'%self.radius + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return math.pi / self._radius + + @property + def _max_sb(self): + return self._norm + + def _xValue(self, pos): + rsq = pos.x**2 + pos.y**2 + if rsq < self._rsq: + return self._norm + else: + return 0. + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return TopHat(radius=self.radius, flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/catalog.html b/docs/_build/html/_modules/galsim/catalog.html new file mode 100644 index 00000000000..4178eb86ed6 --- /dev/null +++ b/docs/_build/html/_modules/galsim/catalog.html @@ -0,0 +1,713 @@ + + + + + + galsim.catalog — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.catalog

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Catalog', 'Dict', 'OutputCatalog' ]
+
+import os
+import numpy as np
+import pickle
+import yaml
+import json
+
+from .errors import GalSimValueError, GalSimKeyError, GalSimIndexError
+from ._utilities import lazy_property
+from ._pyfits import pyfits
+from .angle import Angle
+from .position import PositionD, PositionI
+from .shear import Shear
+
+
[docs]class Catalog: + """A class storing the data from an input catalog. + + Each row corresponds to a different object to be built, and each column stores some item of + information about that object (e.g. flux or half_light_radius). + + Parameters: + file_name: Filename of the input catalog. (Required) + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. + file_type: Either 'ASCII' or 'FITS'. If None, infer from ``file_name`` ending. + [default: None] + comments: The character used to indicate the start of a comment in an + ASCII catalog. [default: '#'] + hdu: Which hdu to use for FITS files. [default: 1] + + After construction, the following attributes are available: + + Attributes: + nobjects: The number of objects in the catalog. + ncols: The number of columns in the catalog. + isfits: Whether the catalog is a fits catalog. + names: For a fits catalog, the valid column names. + """ + _req_params = { 'file_name' : str } + _opt_params = { 'dir' : str , 'file_type' : str , 'comments' : str , 'hdu' : int } + + def __init__(self, file_name, dir=None, file_type=None, comments='#', hdu=1): + + # First build full file_name + self.file_name = file_name.strip() + if dir is not None: + self.file_name = os.path.join(dir,self.file_name) + + if file_type is None: + name, ext = os.path.splitext(file_name) + if ext.lower().startswith('.fit'): + file_type = 'FITS' + else: + file_type = 'ASCII' + file_type = file_type.upper() + if file_type not in ('FITS', 'ASCII'): + raise GalSimValueError("file_type must be either FITS or ASCII if specified.", + file_type, ('FITS', 'ASCII')) + self.file_type = file_type + if comments == '': comments = None # loadtxt actually wants None, not '' + self.comments = comments + self.hdu = hdu + + if file_type == 'FITS': + self.readFits() + else: # file_type == 'ASCII': + self.readAscii() + + def finishRead(self): + if self.file_type == 'ASCII': + self.finishReadAscii() + # FITS is already finished. + + # A couple things only get set for ASCII if finishRead has been run. + @lazy_property + def data(self): + self.finishRead() + return self._data + + @lazy_property + def ncols(self): + self.finishRead() + return self._ncols + + # When we make a proxy of this class (cf. galsim/config/stamp.py), the attributes + # don't get proxied. Only callable methods are. So make method versions of these. + def getNObjects(self) : return self.nobjects + def isFits(self) : return self.isfits + def __len__(self) : return self.nobjects + +
[docs] def readAscii(self): + """Read in an input catalog from an ASCII file. + """ + if self.comments is not None and len(self.comments) > 1: + raise GalSimValueError('Invalid comments character', self.comments) + + # In the first pass, just figure out nobjects. We may not need to go past this. + # See the script devel/testlinecounting.py that tests several possibilities. + # An even faster version using buffering is possible although it requires some care + # around edge cases, so we use this one instead, which is "correct by inspection". + with open(self.file_name) as f: + if self.comments is not None: + c = self.comments[0] + self.nobjects = sum(1 for line in f if line[0] != c) + else: # comments == None. No comments. + self.nobjects = sum(1 for line in f) + self.isfits = False
+ + def finishReadAscii(self): + # Read in the data using the numpy convenience function + # Note: we leave the data as str, rather than convert to float, so that if + # we have any str fields, they don't give an error here. They'll only give an + # error if one tries to convert them to float at some point. + self._data = np.loadtxt(self.file_name, comments=self.comments, dtype=bytes, ndmin=2) + # Convert the bytes to str. For Py2, this is a no op. + self._data = self._data.astype(str) + + assert self.nobjects == self._data.shape[0] + self._ncols = self._data.shape[1] + +
[docs] def readFits(self): + """Read in an input catalog from a FITS file. + """ + with pyfits.open(self.file_name) as fits: + self._data = fits[self.hdu].data.copy() + self.names = self._data.columns.names + self.nobjects = len(self._data) + self._ncols = len(self.names) + self.isfits = True
+ +
[docs] def get(self, index, col): + """Return the data for the given ``index`` and ``col`` in its native type. + + For ASCII catalogs, ``col`` is the column number. + For FITS catalogs, ``col`` is a string giving the name of the column in the FITS table. + + Also, for ASCII catalogs, the "native type" is always str. For FITS catalogs, it is + whatever type is specified for each field in the binary table. + """ + if self.isfits: + if col not in self.names: + raise GalSimKeyError("Column is invalid for catalog %s"%self.file_name, col) + if not isinstance(index, int): + raise GalSimIndexError("Index must be an int for catalog %s"%self.file_name, index) + if index < 0 or index >= self.nobjects: + raise GalSimIndexError("Index is invalid for catalog %s"%self.file_name, index) + return self.data[col][index] + else: + if not isinstance(col, int): + raise GalSimIndexError("Column must an int for ASCII catalog %s"%self.file_name, + col) + if col < 0 or col >= self.ncols: + raise GalSimIndexError("Column is invalid for catalog %s"%self.file_name, col) + if not isinstance(index, int): + raise GalSimIndexError("Index must be an int for catalog %s"%self.file_name, index) + if index < 0 or index >= self.nobjects: + raise GalSimIndexError("Index is invalid for catalog %s"%self.file_name, col) + return self.data[index, col]
+ +
[docs] def getFloat(self, index, col): + """Return the data for the given ``index`` and ``col`` as a float if possible + """ + return float(self.get(index,col))
+ +
[docs] def getInt(self, index, col): + """Return the data for the given ``index`` and ``col`` as an int if possible + """ + return int(self.get(index,col))
+ + def __repr__(self): + s = "galsim.Catalog(file_name=%r, file_type=%r"%(self.file_name, self.file_type) + if self.comments != '#': s += ', comments=%r'%self.comments + if self.hdu != 1: s += ', hdu=%r'%self.hdu + s += ')' + return s + + def __str__(self): return "galsim.Catalog(file_name=%r)"%self.file_name + + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self))
+ + +
[docs]class Dict: + """A class that reads a python dict from a file. + + After construction, it behaves like a regular python dict, with one exception. + In order to facilitate getting values in a hierarchy of fields, we allow the '.' + character to chain keys together for the get() method. So,:: + + >>> d.get('noise.properties.variance') + + is expanded into:: + + >>> d['noise']['properties']['variance'] + + Furthermore, if a "key" is really an integer, then it is used as such, which accesses + the corresponding element in a list. e.g.:: + + >>> d.get('noise_models.2.variance') + + is equivalent to:: + + >>> d['noise_models'][2]['variance'] + + This makes it much easier to access arbitrary elements within parameter files. + + Caveat: The above prescription means that an element whose key really has a '.' in it + won't be accessed correctly. This is probably a rare occurrence, but the workaround is + to set ``key_split`` to a different character or string and use that to chain the keys. + + + Parameters: + file_name: Filename storing the dict. + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + file_type: Options are 'Pickle', 'YAML', or 'JSON' or None. If None, infer from + ``file_name`` extension ('.p*', '.y*', '.j*' respectively). + [default: None] + key_split: The character (or string) to use to split chained keys. (cf. the + description of this feature above.) [default: '.'] + """ + _req_params = { 'file_name' : str } + _opt_params = { 'dir' : str , 'file_type' : str, 'key_split' : str } + _single_params = [] + _takes_rng = False + + def __init__(self, file_name, dir=None, file_type=None, key_split='.'): + + # First build full file_name + self.file_name = file_name.strip() + if dir is not None: + self.file_name = os.path.join(dir,self.file_name) + + if file_type is None: + name, ext = os.path.splitext(self.file_name) + if ext.lower().startswith('.p'): + file_type = 'PICKLE' + elif ext.lower().startswith('.y'): + file_type = 'YAML' + elif ext.lower().startswith('.j'): + file_type = 'JSON' + else: + raise GalSimValueError('Unable to determine file_type from file_name ending', + file_name, ('*.p*', '*.y*', '*.j*')) + + file_type = file_type.upper() + if file_type not in ('PICKLE','YAML','JSON'): + raise GalSimValueError("Invalid file_type", file_type, ('Pickle', 'YAML', 'JSON')) + self.file_type = file_type + + self.key_split = key_split + + if file_type == 'PICKLE': + with open(self.file_name, 'rb') as f: + self.dict = pickle.load(f) + elif file_type == 'YAML': + with open(self.file_name, 'r') as f: + self.dict = yaml.safe_load(f) + else: # JSON + with open(self.file_name, 'r') as f: + self.dict = json.load(f) + + def get(self, key, default=None): + # Make a list of keys according to our key_split parameter + chain = key.split(self.key_split) + d = self.dict + while len(chain): + k = chain.pop(0) + + # Try to convert to an integer: + try: k = int(k) + except ValueError: pass + + # If there are more keys, just set d to the next in the chanin. + if chain: d = d[k] + # Otherwise, return the result. + else: + if k not in d and default is None: + raise GalSimKeyError("key not found in dictionary.",key) + return d.get(k,default) + raise GalSimKeyError("Invalid key given to Dict.get()",key) + + # The rest of the functions are typical non-mutating functions for a dict, for which we just + # pass the request along to self.dict. + def __len__(self): + return len(self.dict) + + def __getitem__(self, key): + return self.dict[key] + + def __contains__(self, key): + return key in self.dict + + def __iter__(self): + return self.dict.__iter__() + + def keys(self): + return self.dict.keys() + + def values(self): + return self.dict.values() + + def items(self): + return self.dict.items() + + def iterkeys(self): + return self.keys() + + def itervalues(self): + return self.values() + + def iteritems(self): + return self.items() + + def __repr__(self): + s = "galsim.Dict(file_name=%r, file_type=%r"%(self.file_name, self.file_type) + if self.key_split != '.': + s += ', key_split=%r'%self.key_split + s += ')' + return s + + def __str__(self): return "galsim.Dict(file_name=%r)"%self.file_name + + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self))
+ + + +
[docs]class OutputCatalog: + """A class for building up a catalog for output, typically storing truth information + about a simulation. + + Each row corresponds to a different object, and each column stores some item of + information about that object (e.g. flux or half_light_radius). + + Note: no type checking is done when the data are added in addRow(). It is up to + the user to make sure that the values added for each row are compatible with the + types given here in the ``types`` parameter. + + Parameters: + names: A list of names for the output columns. + types: A list of types for the output columns. [default: None, which assumes all + columns are float] + + After construction, the following attributes are available: + + Attributes: + nobjects: The number of objects so far in the catalog. + ncols: The number of columns in the catalog. + names: The names of the columns. + types: The types of the columns. + rows: The rows of data that have been accumulated so far. + """ + # Watch out for this "Gotcha". Using _rows=[] as the default argument doesn't work! + # https://pythonconquerstheuniverse.wordpress.com/2012/02/15/mutable-default-arguments/ + def __init__(self, names, types=None, _rows=(), _sort_keys=()): + self.names = names + if types is None: + self.types = [ float for i in names ] + else: + self.types = types + self.rows = list(_rows) + self.sort_keys = list(_sort_keys) + + @property + def nobjects(self): + """The number of objects in the `OutputCatalog`. + """ + return len(self.rows) + @property + def ncols(self): + """The number of columns in the `OutputCatalog`. + """ + return len(self.names) + def __len__(self): return self.nobjects + + # Again, when we use this through a proxy, we need getters for the attributes. +
[docs] def getNames(self): + """Equivalent to sef.names.""" + return self.names
+
[docs] def getTypes(self): + """Equivalent to sef.types.""" + return self.types
+
[docs] def setTypes(self, types): + """Equivalent to sef.types = types.""" + self.types = types
+
[docs] def getNObjects(self): + """Equivalent to sef.nobjects.""" + return self.nobjects
+
[docs] def getNCols(self): + """Equivalent to sef.ncols.""" + return self.ncols
+ +
[docs] def addRow(self, row, sort_key=None): + """Add a row of data to the catalog. + + Warning: no type checking is done at this point. If the values in the row do not + match the column types, you may get an error when writing, or you may lose precision, + depending on the nature of the mismatch. + + Parameters: + row: A list with one item per column in the same order as the names list. + sort_key: If the rows may be added out of order, you can provide a sort_key, + which will be used at the end to re-sort the rows. + """ + if len(row) != self.ncols: + raise GalSimValueError("Length of row does not match the number of columns = %d"%( + self.ncols), len(row)) + self.rows.append(tuple(row)) + if sort_key is None: + self.sort_keys.append(self.nobjects) + else: + self.sort_keys.append(sort_key)
+ +
[docs] def write(self, file_name, dir=None, file_type=None, prec=8): + """Write the catalog to a file. + + Parameters: + file_name: The name of the file to write to. + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + file_type: Which kind of file to write to. [default: determine from the file_name + extension] + prec: Output precision for ASCII. [default: 8] + """ + if dir is not None: + file_name = os.path.join(dir,file_name) + + # Figure out which file type the catalog is + if file_type is None: + name, ext = os.path.splitext(file_name) + if ext.lower().startswith('.fit'): + file_type = 'FITS' + else: + file_type = 'ASCII' + file_type = file_type.upper() + if file_type not in ('FITS', 'ASCII'): + raise GalSimValueError("Invalid file_type.", file_type, ('FITS', 'ASCII')) + + if file_type == 'FITS': + self.writeFits(file_name) + else: # file_type == 'ASCII': + self.writeAscii(file_name, prec)
+ +
[docs] def makeData(self): + """Returns a numpy array of the data as it should be written to an output file. + """ + + cols = zip(*self.rows) + + dtypes = [] + new_cols = [] + for col, name, t in zip(cols, self.names, self.types): + name = str(name) # numpy will barf if the name is a unicode string + dt = np.dtype(t) # just used to categorize the type into int, float, str + if dt.kind in np.typecodes['AllInteger']: + dtypes.append( (name, int) ) + new_cols.append(col) + elif dt.kind in np.typecodes['AllFloat']: + dtypes.append( (name, float) ) + new_cols.append(col) + elif t == Angle: + dtypes.append( (name + ".rad", float) ) + new_cols.append( [ val.rad for val in col ] ) + elif t == PositionI: + dtypes.append( (name + ".x", int) ) + dtypes.append( (name + ".y", int) ) + new_cols.append( [ val.x for val in col ] ) + new_cols.append( [ val.y for val in col ] ) + elif t == PositionD: + dtypes.append( (name + ".x", float) ) + dtypes.append( (name + ".y", float) ) + new_cols.append( [ val.x for val in col ] ) + new_cols.append( [ val.y for val in col ] ) + elif t == Shear: + dtypes.append( (name + ".g1", float) ) + dtypes.append( (name + ".g2", float) ) + new_cols.append( [ val.g1 for val in col ] ) + new_cols.append( [ val.g2 for val in col ] ) + else: + col = [ str(s).encode() for s in col ] + maxlen = np.max([ len(s) for s in col ]) + dtypes.append( (name, str, maxlen) ) + new_cols.append(col) + + data = np.array(list(zip(*new_cols)), dtype=dtypes) + + sort_index = np.argsort(self.sort_keys) + data = data[sort_index] + + return data
+ +
[docs] def writeAscii(self, file_name, prec=8): + """Write catalog to an ASCII file. + + Parameters: + file_name: The name of the file to write to. + prec: Output precision for floats. [default: 8] + """ + data = self.makeData() + + width = prec+8 + header_form = "" + for i in range(len(data.dtype.names)): + header_form += "{%d:^%d} "%(i,width) + header = header_form.format(*data.dtype.names) + + fmt = [] + for name in data.dtype.names: + dt = data.dtype[name] + if dt.kind in np.typecodes['AllInteger']: + fmt.append('%%%dd'%(width)) + elif dt.kind in np.typecodes['AllFloat']: + fmt.append('%%%d.%de'%(width,prec)) + else: + fmt.append('%%%ds'%(width)) + + np.savetxt(file_name, data, fmt=fmt, header=header)
+ +
[docs] def writeFits(self, file_name): + """Write catalog to a FITS file. + + Parameters: + file_name: The name of the file to write to. + """ + from .fits import writeFile + tbhdu = self.writeFitsHdu() + writeFile(file_name, tbhdu)
+ +
[docs] def writeFitsHdu(self): + """Write catalog to a FITS hdu. + + Returns: + an HDU with the FITS binary table of the catalog. + """ + # Note to developers: Because of problems with pickling in older pyfits versions, this + # code is duplicated in galsim/config/extra_truth.py, BuildTruthHDU. If you change + # this function, you should update BuildTruthHDU as well. + + data = self.makeData() + + cols = [] + for name in data.dtype.names: + dt = data.dtype[name] + if dt.kind in np.typecodes['AllInteger']: + cols.append(pyfits.Column(name=name, format='J', array=data[name])) + elif dt.kind in np.typecodes['AllFloat']: + cols.append(pyfits.Column(name=name, format='D', array=data[name])) + else: + cols.append(pyfits.Column(name=name, format='%dA'%dt.itemsize, array=data[name])) + + cols = pyfits.ColDefs(cols) + tbhdu = pyfits.BinTableHDU.from_columns(cols) + return tbhdu
+ + def __repr__(self): + def make_type_str(t): + s = repr(t) + if s[1:6] == 'class': return s[8:-2] + else: return s + type_str = "( " + ", ".join([ make_type_str(t) for t in self.types ]) + " )" + return "galsim.OutputCatalog(names=%r, types=%s, _rows=%r)"%( + self.names, type_str, self.rows) + + def __str__(self): + return "galsim.OutputCatalog(names=%r)"%self.names + + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self))
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/cdmodel.html b/docs/_build/html/_modules/galsim/cdmodel.html new file mode 100644 index 00000000000..39fb121d177 --- /dev/null +++ b/docs/_build/html/_modules/galsim/cdmodel.html @@ -0,0 +1,355 @@ + + + + + + galsim.cdmodel — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.cdmodel

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+
+from .image import Image
+from . import _galsim
+from .errors import GalSimValueError
+
+
[docs]class BaseCDModel: + """Base class for the most generic, i.e. no with symmetries or distance scaling relationships + assumed, pixel boundary charge deflection model (as per Antilogus et al 2014). + """ + +
[docs] def __init__(self, a_l, a_r, a_b, a_t): + """Initialize a generic CDModel (charge deflection model). + + Usually this class will not be instantiated directly, but there is nothing to prevent you + from doing so. Each of the input a_l, a_r, a_b & a_t matrices must have the same shape and + be odd-dimensioned. + + The model implemented here is described in Antilogus et al. (2014). The effective border + of a pixel shifts to an extent proportional to the flux in a pixel at separation (dx,dy) + and a coefficient a(dx,dy). Contributions of all neighbouring pixels are superposed. Border + shifts are calculated for each (l=left, r=right (=positive x), b=bottom, t=top (=pos. y)) + border and the resulting change in flux in a pixel is the shift times the mean of its flux + and the flux in the pixel on the opposite side of the border (caveat: in Antilogus et al. + 2014 the sum is used instead of the mean, making the a(dx,dy) a factor of 2 smaller). + + The parameters of the model are the a_l/r/b/t matrices, whose entry at (dy,dx) gives the + respective shift coefficient. Note that for a realistic model, the matrices have a number + of symmetries, as described in Antilogus et al. (2014). Use derived classes like PowerLawCD + to have a model that automatically fulfills the symmetry conditions. + + Note that there is a gain factor included in the coefficients. When the a_* are measured + from flat fields according to eqn. 4.10 in Antilogus et. al (2014) and applied to images + that have the same gain as the flats, the correction is as intended. If the gain in the + images is different, this can be accounted for with the gain_ratio parameter when calling + applyForward or applyBackward. + + Parameters: + a_l: NumPy array containing matrix of deflection coefficients of left pixel border + a_r: NumPy array containing matrix of deflection coefficients of right pixel border + a_b: NumPy array containing matrix of deflection coefficients of bottom pixel border + a_t: NumPy array containing matrix of deflection coefficients of top pixel border + """ + # Some basic sanity checking + if (a_l.shape[0] % 2 != 1): + raise GalSimValueError("Input array must be odd-dimensioned", a_l.shape) + for a in (a_l, a_r, a_b, a_t): + if a.shape[0] != a.shape[1]: + raise GalSimValueError("Input array is not square", a.shape) + if a.shape[0] != a_l.shape[0]: + raise GalSimValueError("Input arrays not all the same dimensions", a.shape) + # Save the relevant dimension and the matrices storing deflection coefficients + self.n = a_l.shape[0] // 2 + if (self.n < 1): + raise GalSimValueError("Input arrays must be at least 3x3", a_l.shape) + + self.a_l = Image(a_l, dtype=np.float64, make_const=True) + self.a_r = Image(a_r, dtype=np.float64, make_const=True) + self.a_b = Image(a_b, dtype=np.float64, make_const=True) + self.a_t = Image(a_t, dtype=np.float64, make_const=True)
+ +
[docs] def applyForward(self, image, gain_ratio=1.): + """Apply the charge deflection model in the forward direction. + + Returns an image with the forward charge deflection transformation applied. The input image + is not modified, but its WCS is included in the returned image. + + Parameters: + gain_ratio: Ratio of gain_image/gain_flat when shift coefficients were derived from + flat fields; default value is 1., which assumes the common case that your + flat and science images have the same gain value + """ + ret = image.copy() + _galsim.ApplyCDModel( + ret._image, image._image, self.a_l._image, self.a_r._image, self.a_b._image, + self.a_t._image, int(self.n), float(gain_ratio)) + return ret
+ +
[docs] def applyBackward(self, image, gain_ratio=1.): + """Apply the charge deflection model in the backward direction (accurate to linear order). + + Returns an image with the backward charge deflection transformation applied. The input + image is not modified, but its WCS is included in the returned image. + + Parameters: + gain_ratio: Ratio of gain_image/gain_flat when shift coefficients were derived from + flat fields; default value is 1., which assumes the common case that your + flat and science images have the same gain value + """ + retimage = self.applyForward(image, gain_ratio=-gain_ratio) + return retimage
+ + def __repr__(self): + return 'galsim.cdmodel.BaseCDModel(array(%r),array(%r),array(%r),array(%r))'%( + self.a_l.array.tolist(), self.a_r.array.tolist(), + self.a_b.array.tolist(), self.a_t.array.tolist()) + + # Quick and dirty. Just check reprs are equal. + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self))
+ +# The _modelShiftCoeffX functions are used by the PowerLawCD class +def _modelShiftCoeffR(x, y, r0, t0, rx, tx, r, t, alpha): + """Calculate the model shift coeff of right pixel border as a function of int pixel position + (x, y). + """ + # Invoke symmetry + if y < 0: return _modelShiftCoeffR(x, -y, r0, t0, rx, tx, r, t, alpha) + if x < 0: return -_modelShiftCoeffR(1 - x, y, r0, t0, rx, tx, r, t, alpha) + # Invoke special immediate neighbour cases + if x == 0 and y == 0: return -r0 + if x == 1 and y == 0: return +r0 + if x == 0 and y == 1: return -rx + if x == 1 and y == 1: return +rx + # Then, for remainder, apply power law model + rr = np.sqrt((float(x) - .5)**2 + float(y)**2) + cc = (x - 0.5) / rr # projection onto relevant axis + return cc * r * rr**(-alpha) + +def _modelShiftCoeffL(x, y, r0, t0, rx, tx, r, t, alpha): + """Calculate the model shift coeff of left pixel border as a function of int pixel + position (x, y). + + Equivalent to ``-_modelShiftCoeffR(x+1, y, *args)``. + """ + return -_modelShiftCoeffR(x+1, y, r0, t0, rx, tx, r, t, alpha) + +def _modelShiftCoeffT(x, y, r0, t0, rx, tx, r, t, alpha): + """Calculate the model shift coeff of top pixel border as a function of int pixel + position (x, y). + """ + # Invoke symmetry + if x < 0: return _modelShiftCoeffT(-x, y, r0, t0, rx, tx, r, t, alpha) + if y < 0: return -_modelShiftCoeffT(x, 1 - y, r0, t0, rx, tx, r, t, alpha) + # Invoke special immediate neighbour cases + if x == 0 and y == 0: return -t0 + if x == 0 and y == 1: return +t0 + if x == 1 and y == 0: return -tx + if x == 1 and y == 1: return +tx + # Then, for remainder, apply power law model + rr = np.sqrt((float(y) - .5)**2 + float(x)**2) + cc = (y - 0.5) / rr # projection onto relevant axis + return cc * t * rr**(-alpha) + +def _modelShiftCoeffB(x, y, r0, t0, rx, tx, r, t, alpha): + """Calculate the model shift coeff of bottom pixel border as a function of int pixel + position (x, y) + + Equivalent to ``-_modelShiftCoeffT(x, y+1, *args)``. + """ + return -_modelShiftCoeffT(x, y+1, r0, t0, rx, tx, r, t, alpha) + +
[docs]class PowerLawCD(BaseCDModel): + """Class for parametrizing charge deflection coefficient strengths as a power law in distance + from affected pixel border. + """ + +
[docs] def __init__(self, n, r0, t0, rx, tx, r, t, alpha): + """Initialize a power-law charge deflection model. + + The deflections from charges in the six pixels directly neighbouring a pixel border are + modelled independently by the parameters ``r0``, ``t0`` (directly adjacent to borders + between two pixels in the same row=y / column=x) and ``rx``, ``tx`` (pixels on the corner + of pixel borders). + + Deflections due to charges further away are modelled as a power-law:: + + a = A * numpy.sin(theta) * (r_distance)**(-alpha) + + where A is a power-law amplitude (``r`` for a_l / a_b and ``t`` for a_b / a_t), theta is + the angle between the pixel border line and the line from border center to the other pixel + center. + + Sign conventions are such that positive ``r0``, ``t0``, ``rx``, ``tx``, ``r``, ``t`` + correspond to physical deflection of equal charges (this is also how the theta above is + defined). + + Parameters: + n: Maximum separation [pix] out to which charges contribute to deflection + r0: a_l(0,-1)=a_r(0,+1) deflection coefficient along x direction + t0: a_b(-1,0)=a_t(+1,0) deflection coefficient along y direction + rx: a_l(-1,-1)=a_r(+1,+1) diagonal contribution to deflection along x direction + tx: a_b(-1,-1)=a_t(+1,+1) diagonal contribution to deflection along y direction + r: Power-law amplitude for contribution to deflection along x from further away + t: Power-law amplitude for contribution to deflection along y from further away + alpha: Power-law exponent for deflection from further away + """ + n = int(n) + # First define x and y coordinates in a square grid of ints of shape (2n + 1) * (2n + 1) + x, y = np.meshgrid(np.arange(2 * n + 1) - n, np.arange(2 * n + 1) - n) + + # prepare a_* matrices + a_l = np.zeros((2 * n + 1, 2 * n + 1), dtype=np.float64) + a_r = np.zeros((2 * n + 1, 2 * n + 1), dtype=np.float64) + a_b = np.zeros((2 * n + 1, 2 * n + 1), dtype=np.float64) + a_t = np.zeros((2 * n + 1, 2 * n + 1), dtype=np.float64) + + # Fill with power law model (slightly clunky loop but not likely a big time sink) + # See https://github.com/GalSim-developers/GalSim/pull/592#discussion_r17027766 for a + # discussion of the speeding up possibilities / timing results for this loop + for ix in np.arange(0, 2 * n + 1): + + for iy in np.arange(0, 2 * n + 1): + + if(ix<2*n): # need to keep the other elements zero for flux conservation + a_l[iy, ix] = _modelShiftCoeffL(ix-n, iy-n, r0, t0, rx, tx, r, t, alpha) + if(ix>0): + a_r[iy, ix] = _modelShiftCoeffR(ix-n, iy-n, r0, t0, rx, tx, r, t, alpha) + if(iy<2*n): + a_b[iy, ix] = _modelShiftCoeffB(ix-n, iy-n, r0, t0, rx, tx, r, t, alpha) + if(iy>0): + a_t[iy, ix] = _modelShiftCoeffT(ix-n, iy-n, r0, t0, rx, tx, r, t, alpha) + + BaseCDModel.__init__(self, a_l, a_r, a_b, a_t)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/chromatic.html b/docs/_build/html/_modules/galsim/chromatic.html new file mode 100644 index 00000000000..366274d1f5c --- /dev/null +++ b/docs/_build/html/_modules/galsim/chromatic.html @@ -0,0 +1,4047 @@ + + + + + + galsim.chromatic — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.chromatic

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'ChromaticObject', 'ChromaticAtmosphere', 'ChromaticSum',
+            'ChromaticConvolution', 'ChromaticDeconvolution',
+            'ChromaticAutoConvolution', 'ChromaticAutoCorrelation',
+            'ChromaticTransformation', 'SimpleChromaticTransformation',
+            'ChromaticFourierSqrtProfile', 'ChromaticOpticalPSF',
+            'ChromaticAiry', 'InterpolatedChromaticObject', ]
+
+import math
+import numpy as np
+from astropy import units
+import copy
+
+from .gsobject import GSObject
+from .sed import SED
+from .bandpass import Bandpass
+from .position import Position, _PositionD
+from ._utilities import lazy_property, doc_inherit
+from .gsparams import GSParams
+from .phase_psf import OpticalPSF, Aperture
+from .table import _LookupTable
+from .sum import Add
+from .errors import GalSimError, GalSimRangeError, GalSimSEDError, GalSimValueError
+from .errors import GalSimIncompatibleValuesError, GalSimNotImplementedError, galsim_warn
+from .photon_array import WavelengthSampler, PhotonArray, PhotonOp, ScaleFlux
+from .random import BaseDeviate, UniformDeviate, BinomialDeviate, PoissonDeviate
+from .shear import Shear, _Shear
+from .interpolatedimage import InterpolatedImage
+from .angle import AngleUnit, arcsec, radians
+from .airy import Airy
+from .deltafunction import DeltaFunction
+from . import utilities
+from . import integ
+from . import dcr
+from .image import Image ##change
+
+
+
[docs]class ChromaticObject: + """Base class for defining wavelength-dependent objects. + + This class primarily serves as the base class for chromatic subclasses. See the docstrings for + subclasses for more details. + + A ChromaticObject can be instantiated directly from an existing `GSObject`. In this case, the + newly created ChromaticObject will act in nearly the same way as the original `GSObject` works, + except that it has access to the ChromaticObject transformation methods described below (e.g., + expand(), dilate(), shift(), withFlux(), ...) These can all take functions as arguments to + describe wavelength-dependent transformations. E.g.,:: + + >>> gsobj = galsim.Gaussian(fwhm=1) + >>> chrom_obj = galsim.ChromaticObject(gsobj).dilate(lambda wave: (wave/500.)**(-0.2)) + + In this and similar cases, the argument to the transformation method should be a python callable + that accepts wavelength in nanometers and returns whatever type the transformation method + normally accepts (so an int or float above). + + One caveat to creating a ChromaticObject directly from a `GSObject` like this is that even + though the source `GSObject` instance has flux units in photons/s/cm^2, the newly formed + ChromaticObject will be interpreted as dimensionless, i.e., it will have a dimensionless `SED` + (and have its .dimensionless attribute set to True). See below for more discussion about the + dimensions of ChromaticObjects. + + Another way to instantiate a ChromaticObject from a `GSObject` is to multiply by an `SED`. + This can be useful to consistently generate the same galaxy observed through different filters, + or, with `ChromaticSum`, to construct multi-component galaxies, each component with a different + `SED`. For example, a bulge+disk galaxy could be constructed:: + + >>> bulge_sed = user_function_to_get_bulge_spectrum() + >>> disk_sed = user_function_to_get_disk_spectrum() + >>> bulge_mono = galsim.DeVaucouleurs(half_light_radius=1.0) + >>> disk_mono = galsim.Exponential(half_light_radius=2.0) + >>> bulge = bulge_mono * bulge_sed + >>> disk = disk_mono * disk_sed + >>> gal = bulge + disk + + The ``sed`` instances above describe the flux density in photons/nm/cm^2/s of an object, possibly + normalized with either the `SED.withFlux` or `SED.withMagnitude` methods (see their docstrings + for details about these and other normalization options). Note that for dimensional + consistency, in this case, the ``flux`` attribute of the multiplied `GSObject` is interpreted + as being dimensionless instead of in its normal units of [photons/s/cm^2]. The photons/s/cm^2 + units are (optionally) carried by the `SED` instead, or even left out entirely if the `SED` is + dimensionless itself (see discussion on ChromaticObject dimensions below). The `GSObject` + ``flux`` attribute *does* still contribute to the ChromaticObject normalization, though. + + For example, the following are equivalent:: + + >>> chrom_obj = (sed * 3.0) * gsobj + >>> chrom_obj2 = sed * (gsobj * 3.0) + + Subclasses that instantiate a ChromaticObject directly, such as `ChromaticAtmosphere`, also + exist. Even in this case, however, the underlying implementation always eventually wraps one + or more `GSObject` instances. + + **Dimensions**: + + ChromaticObjects can generally be sorted into two distinct types: those that represent galaxies + or stars and have dimensions of [photons/wavelength-interval/area/time/solid-angle], and those + that represent other types of wavelength dependence besides flux, like chromatic PSFs (these + have dimensions of [1/solid-angle]). The former category of ChromaticObjects will have their + ``.spectral`` attribute set to True, while the latter category of ChromaticObjects will have + their ``.dimensionless`` attribute set to True. These two classes of ChromaticObjects have + different restrictions associated with them. For example, only spectral ChromaticObjects can + be drawn using `drawImage`, only ChromaticObjects of the same type can be added together, and + at most one spectral ChromaticObject can be part of a `ChromaticConvolution`. + + Multiplying a dimensionless ChromaticObject a spectral `SED` produces a spectral ChromaticObject + (though note that the new object's `SED` may not be equal to the SED being multiplied by since + the original ChromaticObject may not have had unit normalization.) + + **Methods**: + + `evaluateAtWavelength` returns the monochromatic surface brightness profile (as a `GSObject`) + at a given wavelength (in nanometers). + + `interpolate` can be used for non-separable ChromaticObjects to expedite the image rendering + process. See the docstring of that method for more details and discussion of when this is a + useful tool (and the interplay between interpolation, object transformations, and convolutions). + + Also, ChromaticObject has most of the same methods as `GSObject` with the following exceptions: + + The `GSObject` access methods (e.g. `GSObject.xValue`, `GSObject.maxk`, etc.) are not available. + Instead, you would need to evaluate the profile at a particular wavelength and access what you + want from that. + + The `withFlux`, `withFluxDensity`, and `withMagnitude` methods will return a new chromatic + object with the appropriate spatially integrated flux, flux density, or magnitude. + + The `drawImage` method draws the object as observed through a particular bandpass, so the + arguments are somewhat different. See the docstring of `drawImage` for more details. + """ + + # ChromaticObjects should adhere to the following invariants: + # - Objects should define the attributes/properties: + # * .sed, .separable, .wave_list, .interpolated, .deinterpolated, .spectral, .dimensionless + # - obj.evaluateAtWavelength(lam).drawImage().array.sum() == obj.sed(lam) + # == obj.evaluateAtWavelength(lam).flux + # - if obj.spectral: + # obj.sed.calculateFlux(bandpass) == obj.calculateFlux(bandpass) + # == obj.drawImage(bandpass).array.sum() + # - .separable is a boolean indicating whether or not the profile can be factored into a + # spatial part and a spectral part. + # - .wave_list is a numpy array indicating wavelengths of particular interest, for instance, the + # wavelengths at which the sed is explicitly defined via a LookupTable. These are the + # wavelengths that will be used (in addition to those in bandpass.wave_list) when drawing an + # image of the chromatic profile. + # - .interpolated is a boolean indicating whether any part of the object hierarchy includes an + # InterpolatedChromaticObject. + # - .spectral indicates obj.sed.spectral + # - .dimensionless indicates obj.sed.dimensionless + + def __init__(self, obj): + self._obj = obj + if not isinstance(obj, GSObject) and not isinstance(obj, ChromaticObject): + raise TypeError("Can only directly instantiate ChromaticObject with a GSObject " + "or ChromaticObject argument.") + self.separable = obj.separable + self.interpolated = obj.interpolated + self.deinterpolated = obj.deinterpolated + + @property + def sed(self): + return self._obj.sed + + @property + def SED(self): + from .deprecated import depr + depr('obj.SED', 2.5, 'obj.sed') + return self.sed + + @property + def wave_list(self): + return self.sed.wave_list + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._obj.gsparams + + @property + def redshift(self): + """The redshift of the object. + """ + return self.sed.redshift + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) + those component objects will also have their gsparams updated to the new value. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._obj = self._obj.withGSParams(gsparams, **kwargs) + return ret
+ + @staticmethod + def _get_multiplier(sed, bandpass, wave_list): + """Cached integral of product of sed and bandpass.""" + wave_list = np.array(wave_list) + if len(wave_list) > 0: + bp = _LookupTable(wave_list, bandpass(wave_list), 'linear') + multiplier = bp.integrate_product(sed) + else: + multiplier = integ.int1d(lambda w: sed(w) * bandpass(w), + bandpass.blue_limit, bandpass.red_limit) + return multiplier + +
[docs] @staticmethod + def resize_multiplier_cache(maxsize): + """Resize the cache (default size=10) containing the integral over the product of an `SED` + and a `Bandpass`, which is used by `ChromaticObject.drawImage`. + + Parameters: + maxsize: The new number of products to cache. + """ + ChromaticObject._multiplier_cache.resize(maxsize)
+ + def _fiducial_profile(self, bandpass): + """ + Return a fiducial achromatic profile of a chromatic object that can be used to estimate + default output image characteristics, or in the case of separable profiles, can be scaled to + give the monochromatic profile at any wavelength or the wavelength-integrated profile. + """ + bpwave = bandpass.effective_wavelength + bpwave, prof0 = self._approxWavelength(bpwave) + if prof0.flux != 0: + return bpwave, prof0 + + candidate_waves = np.concatenate( + [np.array([0.5 * (bandpass.blue_limit + bandpass.red_limit)]), + bandpass.wave_list, + self.wave_list]) + # Prioritize wavelengths near the bandpass effective wavelength. + candidate_waves = candidate_waves[np.argsort(np.abs(candidate_waves - bpwave))] + for w in candidate_waves: + if bandpass.blue_limit <= w <= bandpass.red_limit: + prof0 = self.evaluateAtWavelength(w) + if prof0.flux != 0: + return w, prof0 + + raise GalSimError("Could not locate fiducial wavelength where SED * Bandpass is nonzero.") + + def _approxWavelength(self, wave): + # If a class doesn't have any more appropriate choice, just use evaluateAtWavelength + # InterpolatedChromaticObject has a better choice when phot=True, so overrides this. + return wave, self.evaluateAtWavelength(wave) + + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticObject) and + hasattr(other, '_obj') and # not all ChromaticObjects have an _obj attribute. + self._obj == other._obj)) + + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): return hash(("galsim.ChromaticObject", self._obj)) + + def __repr__(self): + return 'galsim.ChromaticObject(%r)'%self._obj + + def __str__(self): + return 'galsim.ChromaticObject(%s)'%self._obj + +
[docs] def interpolate(self, waves, **kwargs): + """Build interpolation images to (possibly) speed up subsequent `drawImage` calls. + + This method is used as a pre-processing step that can expedite image rendering using objects + that have to be built up as sums of `GSObject` instances with different parameters at each + wavelength, by interpolating between images at each wavelength instead of making a more + costly instantiation of the relevant `GSObject` at each value of wavelength at which the + bandpass is defined. + + This routine does a costly initialization process to build up a grid of `Image` instances to + be used for the interpolation later on. However, the object can get reused with different + bandpasses, so there should not be any need to make many versions of this object, and there + is a significant savings each time it is drawn into an image. + + As a general rule of thumb, chromatic objects that are separable do not benefit from this + particular optimization, whereas those that involve making `GSObject` instances with + wavelength-dependent keywords or transformations do benefit from it. + + Note that the interpolation scheme is simple linear interpolation in wavelength, and no + extrapolation beyond the originally-provided range of wavelengths is permitted. However, + the overall flux at each wavelength will use the exact `SED` at that wavelength to give + more accurate final flux values. You can disable this feature by setting + ``use_exact_sed = False``. + + The speedup involved in using interpolation depends in part on the bandpass used for + rendering (since that determines how many full profile evaluations are involved in rendering + the image). However, for `ChromaticAtmosphere` with simple profiles like `Kolmogorov`, the + speedup in some simple example cases is roughly a factor of three, whereas for more + expensive to render profiles like the `ChromaticOpticalPSF`, the speedup is more typically a + factor of 10-50. + + Achromatic transformations can be applied either before or after setting up interpolation, + with the best option depending on the application. For example, when rendering many times + with the same achromatic transformation applied, it is typically advantageous to apply the + transformation before setting up the interpolation. But there is no value in this when + applying different achromatic transformation to each object. Chromatic transformations + should be applied before setting up interpolation; attempts to render images of + `ChromaticObject` instances with interpolation followed by a chromatic transformation will + result in the interpolation being unset and the full calculation being done. + + Because of the clever way that the `ChromaticConvolution` routine works, convolutions of + separable chromatic objects with non-separable ones that use interpolation will still + benefit from these optimizations. For example, a non-separable chromatic PSF that uses + interpolation, when convolved with a sum of two separable galaxy components each with their + own `SED`, will be able to take advantage of this optimization. In contrast, when + convolving two non-separable profiles that already have interpolation set up, there is no + way to take advantage of that interpolation optimization, so it will be ignored and the + full calculation will be done. However, interpolation can be set up for the convolution of + two non-separable profiles, after the convolution step. This could be beneficial for + example when convolving a chromatic optical PSF and chromatic atmosphere, before convolving + with multiple galaxy profiles. + + For use cases requiring a high level of precision, we recommend a comparison between the + interpolated and the more accurate calculation for at least one case, to ensure that the + required precision has been reached. + + The input parameter ``waves`` determines the input grid on which images are precomputed. It + is difficult to give completely general guidance as to how many wavelengths to choose or how + they should be spaced; some experimentation compared with the exact calculation is warranted + for each particular application. The best choice of settings might depend on how strongly + the parameters of the object depend on wavelength. + + Parameters: + waves: The list, tuple, or NumPy array of wavelengths to be used when + building up the grid of images for interpolation. The wavelengths + should be given in nanometers, and they should span the full range + of wavelengths covered by any bandpass to be used for drawing an + `Image` (i.e., this class will not extrapolate beyond the given + range of wavelengths). They can be spaced any way the user likes, + not necessarily linearly, though interpolation will be linear in + wavelength between the specified wavelengths. + oversample_fac: Factor by which to oversample the stored profiles compared to the + default, which is to sample them at the Nyquist frequency for + whichever wavelength has the highest Nyquist frequency. + ``oversample_fac``>1 results in higher accuracy but costlier + pre-computations (more memory and time). [default: 1] + use_exact_sed: If true, then rescale the interpolated image for a given wavelength + by the ratio of the exact `SED` at that wavelength to the linearly + interpolated `SED` at that wavelength. Thus, the flux of the + interpolated object should be correct, at the possible expense of + other features. [default: True] + + Returns: + the version of the Chromatic object that uses interpolation + (This will be an `InterpolatedChromaticObject` instance.) + """ + if 'use_exact_SED' in kwargs: + from .deprecated import depr + depr('use_exact_SED', 2.5, 'use_exact_sed') + kwargs['use_exact_sed'] = kwargs.pop('use_exact_SED') + return InterpolatedChromaticObject(self, waves, **kwargs)
+ + @property + def spectral(self): + """Boolean indicating if the `ChromaticObject` has units compatible with a spectral density. + """ + return self.sed.spectral + + @property + def dimensionless(self): + """Boolean indicating if the `ChromaticObject` is dimensionless. + """ + return self.sed.dimensionless + + @staticmethod + def _get_integrator(integrator, wave_list): + # Decide on integrator. If the user passed one of the integrators from galsim.integ, that's + # fine. Otherwise we decide based on the adopted integration rule and the presence/absence + # of `wave_list`. + if isinstance(integrator, str): + if integrator == 'quadratic': + rule = integ.quadRule + elif integrator == 'trapezoidal': + rule = integ.trapzRule + elif integrator == 'midpoint': + rule = integ.midptRule + else: + raise GalSimValueError("Unrecognized integration rule", integrator, + ('trapezoidal', 'midpoint', 'quadratic')) + if len(wave_list) > 0: + integrator = integ.SampleIntegrator(rule) + else: + integrator = integ.ContinuousIntegrator(rule) + if not isinstance(integrator, integ.ImageIntegrator): + raise TypeError("Invalid type passed in for integrator!") + return integrator + +
[docs] def drawImage(self, bandpass, image=None, integrator='quadratic', **kwargs): + """Base implementation for drawing an image of a `ChromaticObject`. + + Some subclasses may choose to override this for specific efficiency gains. For instance, + most GalSim use cases will probably finish with a convolution, in which case + `ChromaticConvolution.drawImage` will be used. + + The task of drawImage() in a chromatic context is to integrate a chromatic surface + brightness profile multiplied by the throughput of ``bandpass``, over the wavelength + interval indicated by ``bandpass``. + + Several integrators are available in galsim.integ to do this integration when using the + first method (non-interpolated integration). By default, `galsim.integ.SampleIntegrator` + will be used if either ``bandpass.wave_list`` or ``self.wave_list`` have len() > 0. + + If lengths of both are zero, which may happen if both the bandpass throughput and the SED + associated with ``self`` are analytic python functions for example, then + `galsim.integ.ContinuousIntegrator` will be used instead. This latter case by default will + evaluate the integrand at 250 equally-spaced wavelengths between ``bandpass.blue_limit`` + and ``bandpass.red_limit``. + + By default, the above two integrators will use the ``rule`` `galsim.integ.quadRule` + for integration. The midpoint rule for integration can be specified instead by passing an + integrator that has been initialized with the ``rule`` set to `galsim.integ.midptRule`. + When creating a `ContinuousIntegrator`, the number of samples ``N`` is also an argument. + For example:: + + >>> integrator = galsim.integ.ContinuousIntegrator(rule=galsim.integ.midptRule, N=100) + >>> image = chromatic_obj.drawImage(bandpass, integrator=integrator) + + Finally, this method uses a cache to avoid recomputing the integral over the product of + the bandpass and object SED when possible (i.e., for separable profiles). Because the + cache size is finite, users may find that it is more efficient when drawing many images + to group images using the same SEDs and bandpasses together in order to hit the cache more + often. The default cache size is 10, but may be resized using the + `ChromaticObject.resize_multiplier_cache` method. + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to + integrate. + image: Optionally, the Image to draw onto. (See `GSObject.drawImage` + for details.) [default: None] + integrator: When doing the exact evaluation of the profile, this argument should + be one of the image integrators from galsim.integ, or a string + 'trapezoidal', 'midpoint', or 'quadratic', in which case the routine + will use a `SampleIntegrator` or `ContinuousIntegrator` depending on + whether or not the object has a ``wave_list``. [default: 'quadratic', + which will try to select an appropriate integrator using the + quadratic integration rule automatically.] + **kwargs: For all other kwarg options, see `GSObject.drawImage` + + Returns: + the drawn `Image`. + """ + # Store the last bandpass used and any extra kwargs. + self._last_bp = bandpass + if self.sed.dimensionless: + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.sed) + + # setup output image using fiducial profile + wave0, prof0 = self._fiducial_profile(bandpass) + image = prof0.drawImage(image=image, setup_only=True, **kwargs) + _remove_setup_kwargs(kwargs) + + # determine combined self.wave_list and bandpass.wave_list + wave_list, _, _ = utilities.combine_wave_list(self, bandpass) + + # If there are photon ops, they'll probably need valid wavelengths, so add + # WavelengthSampler as the first op in the list (if one isn't already present). + if (kwargs.get('photon_ops', None) + and not any([isinstance(p,WavelengthSampler) for p in kwargs['photon_ops']])): + wave_sampler = WavelengthSampler(self.sed, bandpass) + kwargs['photon_ops'] = [wave_sampler] + kwargs['photon_ops'] + + if self.separable: + multiplier = ChromaticObject._multiplier_cache(self.sed, bandpass, tuple(wave_list)) + prof0 *= multiplier/self.sed(wave0) + image = prof0.drawImage(image=image, **kwargs) + return image + + # Note to developers, if we get to this point, then the implementaion is to integrate + # drawn images as a function of wavelength. This is quite slow. + # Moreover, for method=phot, it cannot implement some features like save_photons=True. + # The guards to avoid this happening for method=phot are mostly in the drawImage methods + # of subclasses. So if you find this happening, the solution is most likely to + # specialize something in the subclass's drawImage method. + assert kwargs.get('method', None) != 'phot' + + integrator = self._get_integrator(integrator, wave_list) + + # merge self.wave_list into bandpass.wave_list if using a sampling integrator + if isinstance(integrator, integ.SampleIntegrator): + if len(wave_list) < 2: + raise GalSimIncompatibleValuesError( + "Cannot use SampleIntegrator when Bandpass and SED are both analytic.", + integrator=integrator, bandpass=bandpass, sed=self.sed) + bandpass = Bandpass(_LookupTable(wave_list, bandpass(wave_list), 'linear'), 'nm') + + add_to_image = kwargs.pop('add_to_image', False) + integral = integrator(self.evaluateAtWavelength, bandpass, image, kwargs) + + # For performance profiling, store the number of evaluations used for the last integration + # performed. Note that this might not be very useful for ChromaticSum instances, which are + # drawn one profile at a time, and hence _last_n_eval will only represent the final + # component drawn. + self._last_n_eval = integrator.last_n_eval + + # Apply integral to the initial image appropriately. + # Note: Don't do image = integral and return that for add_to_image==False. + # Remember that python doesn't actually do assignments, so this won't update the + # original image if the user provided one. The following procedure does work. + if not add_to_image: + image.setZero() + image += integral + self._last_wcs = image.wcs + return image
+ +
[docs] def drawKImage(self, bandpass, image=None, integrator='quadratic', **kwargs): + """Base implementation for drawing the Fourier transform of a `ChromaticObject`. + + The task of drawKImage() in a chromatic context is exactly analogous to the task of + `drawImage` in a chromatic context: to integrate the ``sed * bandpass`` weighted Fourier + profiles over wavelength. + + See `drawImage` for details on integration options. + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to integrate. + image: If provided, this will be the complex `Image` onto which to draw the + k-space image. If ``image`` is None, then an automatically-sized image + will be created. If ``image`` is given, but its bounds are undefined, + then it will be resized appropriately based on the profile's size. + [default: None] + integrator: When doing the exact evaluation of the profile, this argument should be + one of the image integrators from galsim.integ, or a string + 'trapezoidal', 'midpoint', or 'quadratic', in which case the routine will + use a `SampleIntegrator` or `ContinuousIntegrator` depending on whether or + not the object has a ``wave_list``. [default: 'quadratic', which will try + to select an appropriate integrator using the quadratic integration rule + automatically.] + **kwargs: For all other kwarg options, see `GSObject.drawKImage`. + + Returns: + a complex `Image` instance (created if necessary) + """ + if self.sed.dimensionless: + raise GalSimSEDError("Can only drawK ChromaticObjects with spectral SEDs.", self.sed) + + # setup output image (semi-arbitrarily using the bandpass effective wavelength) + prof0 = self.evaluateAtWavelength(bandpass.effective_wavelength) + image = prof0.drawKImage(image=image, setup_only=True, **kwargs) + _remove_setup_kwargs(kwargs) + + # determine combined self.wave_list and bandpass.wave_list + wave_list, _ , _ = utilities.combine_wave_list(self, bandpass) + + if self.separable: + multiplier = ChromaticObject._multiplier_cache(self.sed, bandpass, tuple(wave_list)) + prof0 *= multiplier/self.sed(bandpass.effective_wavelength) + image = prof0.drawKImage(image=image, **kwargs) + return image + + integrator = self._get_integrator(integrator, wave_list) + + # merge self.wave_list into bandpass.wave_list if using a sampling integrator + if isinstance(integrator, integ.SampleIntegrator): + bandpass = Bandpass(_LookupTable(wave_list, bandpass(wave_list), 'linear'), 'nm') + + add_to_image = kwargs.pop('add_to_image', False) + image_int = integrator(self.evaluateAtWavelength, bandpass, image, kwargs, doK=True) + + # For performance profiling, store the number of evaluations used for the last integration + # performed. Note that this might not be very useful for ChromaticSum instances, which are + # drawn one profile at a time, and hence _last_n_eval will only represent the final + # component drawn. + self._last_n_eval = integrator.last_n_eval + + # Apply integral to the initial image appropriately. + # Note: Don't do image = integral and return that for add_to_image==False. + # Remember that python doesn't actually do assignments, so this won't update the + # original image if the user provided one. The following procedure does work. + if add_to_image: + image += image_int + else: + image._copyFrom(image_int) + return image
+ +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + # Subclasses all override this. + return self._obj.evaluateAtWavelength(wave)
+ + def _shoot(self, photons, rng): + self._obj._shoot(photons, rng) + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Apply the chromatic profile as a convolution to an existing photon array. + + This method allows instances of this class to duck type as a PhotonOp, so one can apply it + in a photon_ops list. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use to effect the convolution. + [default: None] + """ + if not photon_array.hasAllocatedWavelengths(): + raise GalSimError("Using ChromaticObject as a PhotonOp requires wavelengths be set") + p1 = PhotonArray(len(photon_array)) + p1._copyFrom(photon_array, slice(None), slice(None), do_xy=False, do_flux=False) + obj = local_wcs.toImage(self) if local_wcs is not None else self + rng = BaseDeviate(rng) + obj._shoot(p1, rng) + photon_array.convolve(p1, rng)
+ + # Make op* and op*= work to adjust the flux of the object +
[docs] def __mul__(self, flux_ratio): + """Scale the flux of the object by the given flux ratio, which may be an `SED`, a float, or + a univariate callable function (of wavelength in nanometers) that returns a float. + + The normalization of a `ChromaticObject` is tracked through its ``.sed`` attribute, which + may have dimensions of either [photons/wavelength-interval/area/time/solid-angle] or + [1/solid-angle]. + + If ``flux_ratio`` is a spectral `SED` (i.e., ``flux_ratio.spectral==True``), then self.sed + must be dimensionless for dimensional consistency. The returned object will have a + spectral sed attribute. On the other hand, if ``flux_ratio`` is a dimensionless `SED`, + float, or univariate callable function, then the returned object will have ``.spectral`` + and ``.dimensionless`` matching ``self.spectral`` and ``self.dimensionless``. + + Parameters: + flux_ratio: The factor by which to scale the normalization of the object. + ``flux_ratio`` may be a float, univariate callable function, in which + case the argument should be wavelength in nanometers and return value + the flux ratio for that wavelength, or an `SED`. + + Returns: + a new object with scaled flux. + """ + return self.withScaledFlux(flux_ratio)
+ + __rmul__ = __mul__ + + # Likewise for op/ and op/= + def __div__(self, other): + return self.__mul__(1./other) + + __truediv__ = __div__ + +
[docs] def withScaledFlux(self, flux_ratio): + """Multiply the flux of the object by ``flux_ratio`` + + Parameters: + flux_ratio: The factor by which to scale the normalization of the object. + ``flux_ratio`` may be a float, univariate callable function, in which + case the argument should be wavelength in nanometers and return value + the flux ratio for that wavelength, or an `SED`. + + Returns: + a new object with scaled flux. + """ + return transform.Transform(self, flux_ratio=flux_ratio)
+ +
[docs] def withFlux(self, target_flux, bandpass): + """Return a new `ChromaticObject` with flux through the `Bandpass` ``bandpass`` set to + ``target_flux``. + + Parameters: + target_flux: The desired flux normalization of the `ChromaticObject`. + bandpass: A `Bandpass` object defining a filter bandpass. + + Returns: + the new normalized `ChromaticObject`. + """ + current_flux = self.calculateFlux(bandpass) + norm = target_flux/current_flux + return self * norm
+ +
[docs] def withMagnitude(self, target_magnitude, bandpass): + """Return a new `ChromaticObject` with magnitude through ``bandpass`` set to + ``target_magnitude``. Note that this requires ``bandpass`` to have been assigned a + zeropoint using `Bandpass.withZeropoint`. + + Parameters: + target_magnitude: The desired magnitude of the `ChromaticObject`. + bandpass: A `Bandpass` object defining a filter bandpass. + + Returns: + the new normalized `ChromaticObject`. + """ + if bandpass.zeropoint is None: + raise GalSimError("Cannot call ChromaticObject.withMagnitude on this bandpass, because" + " it does not have a zeropoint. See `Bandpass.withZeropoint`") + current_magnitude = self.calculateMagnitude(bandpass) + norm = 10**(-0.4*(target_magnitude - current_magnitude)) + return self * norm
+ +
[docs] def withFluxDensity(self, target_flux_density, wavelength): + """Return a new `ChromaticObject` with flux density set to ``target_flux_density`` at + wavelength ``wavelength``. + + Parameters: + target_flux_density: The target normalization in photons/nm/cm^2/s. + wavelength: The wavelength, in nm, at which the flux density will be set. + + Returns: + the new normalized `SED`. + """ + _photons = units.astrophys.photon/(units.s * units.cm**2 * units.nm) + + if self.dimensionless: + raise GalSimSEDError("Cannot set flux density of dimensionless ChromaticObject.", self) + if isinstance(wavelength, units.Quantity): + wavelength_nm = wavelength.to(units.nm, units.spectral()) + current_flux_density = self.sed(wavelength_nm.value) + else: + wavelength_nm = wavelength * units.nm + current_flux_density = self.sed(wavelength) + if isinstance(target_flux_density, units.Quantity): + target_flux_density = target_flux_density.to( + _photons, units.spectral_density(wavelength_nm)).value + factor = target_flux_density / current_flux_density + return self * factor
+ +
[docs] def atRedshift(self, redshift): + """Create a version of the current object with a different redshift. + + This will both adjust the SED to have the given redshift and set a ``redshift`` attribute + with the given value. + + Returns: + the object with the new redshift + """ + from .deprecated import depr + depr('atRedshift', '2.5.3', 'SED.atRedshift', + 'We now require that the SED of an object be appropriately redshifted before being ' + 'used in a chromatic object. So rather than `(obj * sed).atRedshift(z)`, you ' + 'should now use `obj * sed.atRedshift(z)`. For more complicated chromatic objects, ' + 'it is not always clear what parts of the wavelength dependence should be shifted ' + 'by this method, so we no longer allow the ambiguous syntax.') + return self._atRedshift(redshift)
+ + def _atRedshift(self, redshift): + return ChromaticTransformation(self, _redshift=redshift) + +
[docs] def calculateCentroid(self, bandpass): + """Determine the centroid of the wavelength-integrated surface brightness profile. + + Parameters: + bandpass: The bandpass through which the observation is made. + + Returns: + the centroid of the integrated surface brightness profile, as a PositionD. + """ + # if either the Bandpass or self maintain a wave_list, evaluate integrand only at + # those wavelengths. + if len(bandpass.wave_list) > 0 or len(self.wave_list) > 0: + w, _, _ = utilities.combine_wave_list(self, bandpass) + objs = [self.evaluateAtWavelength(ww) for ww in w] + fluxes = np.array([o.flux for o in objs]) + centroids = [o.centroid for o in objs] + xcentroids = np.array([c.x for c in centroids]) + ycentroids = np.array([c.y for c in centroids]) + bp = _LookupTable(w, bandpass(w), 'linear') + flux = bp.integrate_product(_LookupTable(w, fluxes, 'linear')) + xcentroid = bp.integrate_product(_LookupTable(w, fluxes * xcentroids, 'linear')) / flux + ycentroid = bp.integrate_product(_LookupTable(w, fluxes * ycentroids, 'linear')) / flux + return _PositionD(xcentroid, ycentroid) + else: + flux_integrand = lambda w: self.evaluateAtWavelength(w).flux * bandpass(w) + def xcentroid_integrand(w): + mono = self.evaluateAtWavelength(w) + return mono.centroid.x * mono.flux * bandpass(w) + def ycentroid_integrand(w): + mono = self.evaluateAtWavelength(w) + return mono.centroid.y * mono.flux * bandpass(w) + flux = integ.int1d(flux_integrand, bandpass.blue_limit, bandpass.red_limit) + xcentroid = 1./flux * integ.int1d(xcentroid_integrand, + bandpass.blue_limit, + bandpass.red_limit) + ycentroid = 1./flux * integ.int1d(ycentroid_integrand, + bandpass.blue_limit, + bandpass.red_limit) + return _PositionD(xcentroid, ycentroid)
+ +
[docs] def calculateFlux(self, bandpass): + """Return the flux (photons/cm^2/s) of the `ChromaticObject` through a `Bandpass` bandpass. + + Parameters: + bandpass: A `Bandpass` object representing a filter, or None to compute the bolometric + flux. For the bolometric flux the integration limits will be set to + (0, infinity) unless overridden by non-None `SED` attributes ``blue_limit`` + or ``red_limit``. Note that an `SED` defined using a `LookupTable` + automatically has ``blue_limit`` and ``red_limit`` set. + + Returns: + the flux through the bandpass. + """ + if self.sed.dimensionless: + raise GalSimSEDError("Cannot calculate flux of dimensionless ChromaticObject.", + self.sed) + return self.sed.calculateFlux(bandpass)
+ +
[docs] def calculateMagnitude(self, bandpass): + """Return the `ChromaticObject` magnitude through a `Bandpass` ``bandpass``. + + Note that this requires ``bandpass`` to have been assigned a zeropoint using + `Bandpass.withZeropoint`. + + Parameters: + bandpass: A `Bandpass` object representing a filter, or None to compute the + bolometric magnitude. For the bolometric magnitude the integration + limits will be set to (0, infinity) unless overridden by non-None `SED` + attributes ``blue_limit`` or ``red_limit``. Note that an `SED` defined + using a `LookupTable` automatically has ``blue_limit`` and ``red_limit`` + set. + + Returns: + the bandpass magnitude. + """ + if self.sed.dimensionless: + raise GalSimSEDError("Cannot calculate magnitude of dimensionless ChromaticObject.", + self.sed) + return self.sed.calculateMagnitude(bandpass)
+ + # Add together ChromaticObjects and/or GSObjects + def __add__(self, other): + return ChromaticSum(self, other) + + # Subtract ChromaticObjects and/or GSObjects + def __sub__(self, other): + return ChromaticSum(self, -other) + + def __neg__(self): + return -1. * self + + # Following functions work to apply affine transformations to a ChromaticObject. + # +
[docs] def expand(self, scale): + """Expand the linear size of the profile by the given (possibly wavelength-dependent) + scale factor ``scale``, while preserving surface brightness. + + This doesn't correspond to either of the normal operations one would typically want to + do to a galaxy. The functions dilate() and magnify() are the more typical usage. But this + function is conceptually simple. It rescales the linear dimension of the profile, while + preserving surface brightness. As a result, the flux will necessarily change as well. + + See dilate() for a version that applies a linear scale factor while preserving flux. + + See magnify() for a version that applies a scale factor to the area while preserving surface + brightness. + + Parameters: + scale: The factor by which to scale the linear dimension of the object. In + addition, ``scale`` may be a callable function, in which case the argument + should be wavelength in nanometers and the return value the scale for that + wavelength. + + Returns: + the expanded object + """ + if hasattr(scale, '__call__'): + def buildScaleJac(w): + s = scale(w) + return np.array([[s,np.zeros_like(s)],[np.zeros_like(s),s]]) + jac = buildScaleJac + else: + jac = None if scale == 1 else np.diag([scale, scale]) + return transform.Transform(self, jac=jac)
+ +
[docs] def dilate(self, scale): + """Dilate the linear size of the profile by the given (possibly wavelength-dependent) + ``scale``, while preserving flux. + + e.g. ``half_light_radius`` <-- ``half_light_radius * scale`` + + See expand() and magnify() for versions that preserve surface brightness, and thus + change the flux. + + Parameters: + scale: The linear rescaling factor to apply. In addition, ``scale`` may be a + callable function, in which case the argument should be wavelength in + nanometers and the return value the scale for that wavelength. + + Returns: + the dilated object. + """ + if hasattr(scale, '__call__'): + return self.expand(scale).withScaledFlux(lambda w: 1./scale(w)**2) + else: + return self.expand(scale).withScaledFlux(1./scale**2)
+ +
[docs] def magnify(self, mu): + """Apply a lensing magnification, scaling the area and flux by ``mu`` at fixed surface + brightness. + + This process applies a lensing magnification ``mu``, which scales the linear dimensions of the + image by the factor sqrt(mu), i.e., ``half_light_radius`` <-- ``half_light_radius * sqrt(mu)`` + while increasing the flux by a factor of ``mu``. Thus, magnify() preserves surface + brightness. + + See dilate() for a version that applies a linear scale factor while preserving flux. + + Parameters: + mu: The lensing magnification to apply. In addition, ``mu`` may be a callable + function, in which case the argument should be wavelength in nanometers + and the return value the magnification for that wavelength. + + Returns: + the magnified object. + """ + if hasattr(mu, '__call__'): + return self.expand(lambda w: np.sqrt(mu(w))) + else: + return self.expand(math.sqrt(mu))
+ +
[docs] def shear(self, *args, **kwargs): + """Apply an area-preserving shear to this object, where arguments are either a `Shear`, + or arguments that will be used to initialize one. + + For more details about the allowed keyword arguments, see the `Shear` docstring. + + The shear() method precisely preserves the area. To include a lensing distortion with + the appropriate change in area, either use shear() with magnify(), or use lens(), which + combines both operations. + + Note that, while gravitational shear is monochromatic, the shear method may be used for + many other use cases including some which may be wavelength-dependent, such as + intrinsic galaxy shape, telescope dilation, atmospheric PSF shape, etc. Thus, the + shear argument is allowed to be a function of wavelength like other transformations. + + Parameters: + shear: The shear to be applied. Or, as described above, you may instead supply + parameters to construct a `Shear` directly. eg. ``obj.shear(g1=g1,g2=g2)``. + In addition, the ``shear`` parameter may be a callable function, in which + case the argument should be wavelength in nanometers and the return value + the shear for that wavelength, returned as a `galsim.Shear` instance. + + Returns: + the sheared object. + """ + if len(args) == 1: + if kwargs: + raise TypeError("Gave both unnamed and named arguments!") + if not hasattr(args[0], '__call__') and not isinstance(args[0], Shear): + raise TypeError("Unnamed argument is not a Shear or function returning Shear!") + shear = args[0] + elif len(args) > 1: + raise TypeError("Too many unnamed arguments!") + elif 'shear' in kwargs: + # Need to break this out specially in case it is a function of wavelength + shear = kwargs.pop('shear') + if kwargs: + raise TypeError("Too many kwargs provided!") + else: + shear = Shear(**kwargs) + if hasattr(shear, '__call__'): + jac = lambda w: (shear(w).getMatrix() + if np.isscalar(w) + # Note: The .T switches from shape=(N,2,2) to (2,2,N) + # Technically it transposes each 2x2 matrix along the way, but + # the shear matrices are symmetric, so that doesn't matter. + else np.array([shear(ww).getMatrix() for ww in w])).T + else: + jac = shear.getMatrix() + return transform.Transform(self, jac=jac)
+ + def _shear(self, shear): + """Equivalent to `ChromaticObject.shear`, but only valid for a galsim.Shear object, + not any of the possible wavelength-dependent options. + + Parameters: + shear: The `Shear` to be applied. + + Returns: + the sheared object. + """ + return transform.Transform(self, shear.getMatrix()) + +
[docs] def lens(self, g1, g2, mu): + """Apply a lensing shear and magnification to this object. + + This `ChromaticObject` method applies a lensing (reduced) shear and magnification. + The shear must be specified using the g1, g2 definition of shear (see `Shear` for details). + This is the same definition as the outputs of the `PowerSpectrum` and `NFWHalo` classes, + which compute shears according to some lensing power spectrum or lensing by an NFW dark + matter halo. The magnification determines the rescaling factor for the object area and + flux, preserving surface brightness. + + While gravitational lensing is achromatic, we do allow the parameters ``g1``, ``g2``, and + ``mu`` to be callable functions to be parallel to all the other transformations of + chromatic objects. In this case, the functions should take the wavelength in nanometers as + the argument, and the return values are the corresponding value at that wavelength. + + Parameters: + g1: First component of lensing (reduced) shear to apply to the object. + g2: Second component of lensing (reduced) shear to apply to the object. + mu: Lensing magnification to apply to the object. This is the factor by which + the solid angle subtended by the object is magnified, preserving surface + brightness. + + Returns: + the lensed object. + """ + if any(hasattr(g, '__call__') for g in (g1,g2)): + _g1 = g1 + _g2 = g2 + if not hasattr(g1, '__call__'): _g1 = lambda w: g1 + if not hasattr(g2, '__call__'): _g2 = lambda w: g2 + S = lambda w: Shear(g1=_g1(w), g2=_g2(w)) + sheared = self.shear(S) + else: + sheared = self.shear(g1=g1,g2=g2) + return sheared.magnify(mu)
+ + def _lens(self, g1, g2, mu): + """Equivalent to `ChromaticObject.lens`, but without the overhead of some of the sanity + checks or any of the possible wavelength-dependent options. + + Parameters: + g1: First component of lensing (reduced) shear to apply to the object. + g2: Second component of lensing (reduced) shear to apply to the object. + mu: Lensing magnification to apply to the object. This is the factor by which + the solid angle subtended by the object is magnified, preserving surface + brightness. + + Returns: + the lensed object. + """ + shear = _Shear(g1 + 1j*g2) + return transform.Transform(self, shear.getMatrix() * math.sqrt(mu)) + +
[docs] def rotate(self, theta): + """Rotate this object by an `Angle` ``theta``. + + Parameters: + theta: Rotation angle (`Angle` object, +ve anticlockwise). In addition, ``theta`` + may be a callable function, in which case the argument should be wavelength + in nanometers and the return value the rotation angle for that wavelength, + returned as a `galsim.Angle` instance. + + Returns: + the rotated object. + """ + if hasattr(theta, '__call__'): + def buildRMatrix(w): + sth = np.sin(theta(w)) + cth = np.cos(theta(w)) + R = np.array([[cth, -sth], + [sth, cth]], dtype=float) + return R + jac = buildRMatrix + else: + sth, cth = theta.sincos() + jac = np.array([[cth, -sth], + [sth, cth]], dtype=float) + return transform.Transform(self, jac=jac)
+ +
[docs] def transform(self, dudx, dudy, dvdx, dvdy): + """Apply a transformation to this object defined by an arbitrary Jacobian matrix. + + This works the same as `GSObject.transform`, so see that method's docstring for more + details. + + As with the other more specific chromatic trasnformations, dudx, dudy, dvdx, and dvdy + may be callable functions, in which case the argument should be wavelength in nanometers + and the return value the appropriate value for that wavelength. + + Parameters: + dudx: du/dx, where (x,y) are the current coords, and (u,v) are the new coords. + dudy: du/dy, where (x,y) are the current coords, and (u,v) are the new coords. + dvdx: dv/dx, where (x,y) are the current coords, and (u,v) are the new coords. + dvdy: dv/dy, where (x,y) are the current coords, and (u,v) are the new coords. + + Returns: + the transformed object. + """ + if any(hasattr(dd, '__call__') for dd in (dudx, dudy, dvdx, dvdy)): + _dudx = dudx + _dudy = dudy + _dvdx = dvdx + _dvdy = dvdy + if not hasattr(dudx, '__call__'): _dudx = lambda w: dudx + if not hasattr(dudy, '__call__'): _dudy = lambda w: dudy + if not hasattr(dvdx, '__call__'): _dvdx = lambda w: dvdx + if not hasattr(dvdy, '__call__'): _dvdy = lambda w: dvdy + jac = lambda w: np.array([[_dudx(w), _dudy(w)], + [_dvdx(w), _dvdy(w)]], dtype=float) + else: + jac = np.array([[dudx, dudy], + [dvdx, dvdy]], dtype=float) + return transform.Transform(self, jac=jac)
+ +
[docs] def shift(self, *args, **kwargs): + """Apply a (possibly wavelength-dependent) (dx, dy) shift to this chromatic object. + + For a wavelength-independent shift, you may supply ``dx,dy`` as either two arguments, as a + tuple, or as a PositionD or PositionI object. + + For a wavelength-dependent shift, you may supply two functions of wavelength in nanometers + which will be interpreted as ``dx(wave)`` and ``dy(wave)``, or a single function of + wavelength in nanometers that returns either a 2-tuple, PositionD, or PositionI. + + Parameters: + dx: Horizontal shift to apply (float or function). + dy: Vertical shift to apply (float or function). + + Returns: + the shifted object. + """ + # This follows along the galsim.utilities.pos_args function, but has some + # extra bits to account for the possibility of dx,dy being functions. + # First unpack args/kwargs + if len(args) == 0: + # Then dx,dy need to be kwargs + # If not, then python will raise an appropriate error. + try: + dx = kwargs.pop('dx') + dy = kwargs.pop('dy') + except KeyError: + raise TypeError('shift() requires exactly 2 arguments (dx, dy)') from None + offset = None + elif len(args) == 1: + if hasattr(args[0], '__call__'): + try: + args[0](700.).x + # If the function returns a Position, recast it as a function returning + # a numpy array. + def offset_func(w): + d = args[0](w) + return np.asarray( (d.x, d.y) ) + offset = offset_func + except AttributeError: + # Then it's a function returning a tuple or list or array. + # Just make sure it is actually an array to make our life easier later. + offset = lambda w: np.asarray(args[0](w)) + elif isinstance(args[0], Position): + offset = np.asarray( (args[0].x, args[0].y) ) + else: + # Let python raise the appropriate exception if this isn't valid. + dx, dy = args[0] + offset = np.asarray( (dx, dy) ) + elif len(args) == 2: + dx = args[0] + dy = args[1] + offset = None + else: + raise TypeError("Too many arguments supplied!") + if kwargs: + raise TypeError("Got unexpected keyword arguments: %s",kwargs.keys()) + + if offset is None: + offset = utilities.functionize(lambda x,y:(x,y))(dx, dy) + + return transform.Transform(self, offset=offset)
+ + def _shift(self, dx, dy): + """Equivalent to `ChromaticObject.shift`, but only valid for a scalar shift (dx, dy) + not any of the possible wavelength-dependent options. + + Parameters: + dx: The x-component of the shift to apply + dy: The y-component of the shift to apply + + Returns: + the shifted object. + """ + return transform.Transform(self, offset=_PositionD(dx,dy))
+ +ChromaticObject._multiplier_cache = utilities.LRU_Cache( + ChromaticObject._get_multiplier, maxsize=10) + + +
[docs]class InterpolatedChromaticObject(ChromaticObject): + """A `ChromaticObject` that uses interpolation of predrawn images to speed up subsequent + rendering. + + This class wraps another `ChromaticObject`, which is stored in the attribute ``deinterpolated``. + Any `ChromaticObject` can be used, although the interpolation procedure is most effective + for non-separable objects, which can sometimes be very slow to render. + + Normally, you would not create an InterpolatedChromaticObject directly. It is the + return type from `ChromaticObject.interpolate`. See the description of that function + for more details. + + Parameters: + original: The `ChromaticObject` to be interpolated. + waves: The list, tuple, or NumPy array of wavelengths to be used when + building up the grid of images for interpolation. The wavelengths + should be given in nanometers, and they should span the full range + of wavelengths covered by any bandpass to be used for drawing an `Image` + (i.e., this class will not extrapolate beyond the given range of + wavelengths). They can be spaced any way the user likes, not + necessarily linearly, though interpolation will be linear in + wavelength between the specified wavelengths. + oversample_fac: Factor by which to oversample the stored profiles compared to the + default, which is to sample them at the Nyquist frequency for + whichever wavelength has the highest Nyquist frequency. + ``oversample_fac``>1 results in higher accuracy but costlier + pre-computations (more memory and time). [default: 1] + use_exact_sed: If true, then rescale the interpolated image for a given wavelength by + the ratio of the exact `SED` at that wavelength to the linearly + interpolated `SED` at that wavelength. Thus, the flux of the interpolated + object should be correct, at the possible expense of other features. + [default: True] + """ + def __init__(self, original, waves, oversample_fac=1.0, use_exact_sed=True, + use_exact_SED=None): + if use_exact_SED is not None: + from .deprecated import depr + depr('use_exact_SED', 2.5, 'use_exact_sed') + use_exact_sed = use_exact_SED + + self.waves = np.sort(np.array(waves)) + self.oversample = oversample_fac + self.use_exact_sed = use_exact_sed + + self.separable = original.separable + self.interpolated = True + + # Don't interpolate an interpolation. Go back to the original. + self.deinterpolated = original.deinterpolated + +
[docs] @classmethod + def from_images(self, images, waves, _force_stepk = None, _force_maxk = None, **kwargs): + """ + Alternative initiazliation to InterpolatedChromaticObject from input images at specific wavelenghts. Any parameters accepted by + the InterpolatedImage class can be passed in kwargs. Note that stepk and maxk are parameters that can depend on the image size, + and therefore on the wavelength. If not given, as a list for every image, or a single number for all images, it will be caluclated + using the input image pixel scale and dimensions. This means it will be identical for all images. This will cause small differences + from the normal use of this class using chromatic objects whose stepk and maxk are wavelength-dependent. To avoid sanity checks + from method initialization, such as wavelength sorting, pixel scale and image dimensions compatibility, simply call the function + InterpolatedChromaticObject._from_images directly. + + Parameters: + images: list of Galsim Image objects to use as interpolated images. All images must have the same pixel scale and image + dimensions. + waves: list of wavelength values in nanometers. + _force_stepk: list of step_k values to pass to InterpolatedImages for each image. Can also be single value + to pass for all images alike. If not given stepk is calculated from image pixel scale and dimensions. [Default: None] + _force_maxk: list of max_k values to pass to InterpolatedImages for each image. Can also be single value + to pass for all images alike. If not given maxk is calculated from image pixel scale. [Default: None] + + Returns: + An InterpolatedChromaticObject intialized from input images. + """ + # check that all images have PixelScale wcs, the same pixel scale and image dimensions. + if images[0].wcs is None or not images[0].wcs._isPixelScale: + raise GalSimValueError("images must have a pixel scale wcs.", images[0].wcs ) + pix_scale = images[0].scale + img_dims = images[0].array.shape + for img in images[1:]: + if img.scale != pix_scale: + raise GalSimValueError("Cannot interpolate images with different pixel scales.",img.scale, pix_scale ) + if img.array.shape != img_dims: + raise GalSimValueError("Cannot interpolate images with different image dimensions.", img.array.shape , img_dims) + # sort wavelengths and apply sorting to image list + sorted_indices = np.argsort(waves) + sorted_waves = np.array(waves)[sorted_indices] + sorted_images = [images[i] for i in sorted_indices] + return self._from_images(sorted_images, sorted_waves, _force_stepk = _force_stepk, _force_maxk = _force_maxk, **kwargs)
+ + @classmethod + def _from_images(cls, images, waves, _force_stepk = None, _force_maxk = None, **kwargs): + """ + Equivalent to InterpolatedChromaticObject.from_images, but without the sanity checks. + Also, the waves must be given in sorted order. + """ + obj = cls.__new__(cls) # Does not call __init__ + # use_exact_sed set to false as input images won't have sed available. Ovsersample factor not relevant here, set to 1.0 + obj.oversample = 1.0 + obj.use_exact_sed = False + obj.separable = False + obj.interpolated = True + # image properties + obj.waves = np.array(waves) + pix_scale = images[0].scale + img_dims = images[0].array.shape + n_img = len(images) + + stepk = np.zeros(n_img) + maxk = np.zeros(n_img) + mean_stepk, mean_maxk = 0.0, 0.0 + # in the case _force_stepk and/or _force_maxk are passed as lists or single value, set up variables for dummy interpolated object + if _force_stepk is not None: + mean_stepk = np.mean(_force_stepk) + if _force_maxk is not None: + mean_maxk = np.mean(_force_maxk) + + # set deinterpolated to a dummy interpolated image. Galsim uses this object to get gsparams and other properties + # so setting it to None will cause issues. Only obj.ims and obj.objs will affect future calculations if use_exact_sed = False. + # In here we set it to be the average profile in order to calculate the default stepk and maxk to use for all images + avg_image = np.zeros(img_dims, dtype=float) + for img in images: + avg_image += img.array + avg_image /= n_img + avg_img = Image(avg_image, scale=pix_scale) + obj.deinterpolated = InterpolatedImage(avg_img, _force_stepk = mean_stepk, _force_maxk= mean_maxk, **kwargs) + stepk[:] = obj.deinterpolated._stepk + maxk[:] = obj.deinterpolated._maxk + + # if stepk and maxk are provided as arguments, overwrite default from average profile + if _force_stepk is not None: + stepk[:] = _force_stepk + if _force_maxk is not None: + maxk[:] = _force_maxk + + # set ims and objs manually using the input images + obj.ims = images + obj.objs = np.array([InterpolatedImage(images[i], _force_stepk=stepk[i], _force_maxk=maxk[i], **kwargs) for i in range(n_img) ]) + return obj + + + @property + def sed(self): + return self.deinterpolated.sed + + # Note: We don't always need all of these, so only calculate them if needed. + # E.g. if photon shooting, pretty much only need objs. + @lazy_property + def objs(self): + return [ self.deinterpolated.evaluateAtWavelength(wave) for wave in self.waves ] + + @lazy_property + def stepk_vals(self): + return np.array([ obj.stepk for obj in self.objs ]) + + @lazy_property + def maxk_vals(self): + return np.array([ obj.maxk for obj in self.objs ]) + + @lazy_property + def ims(self): + # Find the Nyquist scale for each, and to be safe, choose the minimum value to use for the + # array of images that is being stored. + nyquist_scale_vals = [ obj.nyquist_scale for obj in self.objs ] + scale = np.min(nyquist_scale_vals) / self.oversample + + # Find the suggested image size for each object given the choice of scale, and use the + # maximum just to be safe. + possible_im_sizes = [ obj.getGoodImageSize(scale) for obj in self.objs ] + im_size = np.max(possible_im_sizes) + + # Note that `no_pixel` is used (we want the object on its own, without a pixel response). + return [ obj.drawImage(scale=scale, nx=im_size, ny=im_size, method='no_pixel') + for obj in self.objs ] + + @lazy_property + def fluxes(self): + return [ obj.flux for obj in self.objs ] + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self.deinterpolated.gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret.deinterpolated = self.deinterpolated.withGSParams(gsparams, **kwargs) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, InterpolatedChromaticObject) and + self.deinterpolated == other.deinterpolated and + np.array_equal(self.waves, other.waves) and + self.oversample == other.oversample and + self.use_exact_sed == other.use_exact_sed)) + + def __hash__(self): + return hash(("galsim.InterpolatedChromaticObject", self.deinterpolated, tuple(self.waves), + self.oversample, self.use_exact_sed)) + + def __repr__(self): + s = 'galsim.InterpolatedChromaticObject(%r,%r'%(self.deinterpolated, self.waves) + if self.oversample != 1.0: + s += ', oversample_fac=%r'%self.oversample + if not self.use_exact_sed: + s += ', use_exact_sed=False' + s += ')' + return s + + def __str__(self): + return 'galsim.InterpolatedChromaticObject(%s,%s)'%(self.deinterpolated, self.waves) + + def _imageAtWavelength(self, wave): + """ + Get an image of the object at a particular wavelength, using linear interpolation between + the originally-stored images. Also returns values for step_k and max_k, to be used to + expedite the instantation of `InterpolatedImage`. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + an `Image` of the object at the given wavelength. + """ + # First, some wavelength-related sanity checks. + if wave < self.waves[0] or wave > self.waves[-1]: + raise GalSimRangeError("Requested wavelength is outside the allowed range.", + wave, self.waves[0], self.waves[-1]) + + # Figure out where the supplied wavelength is compared to the list of wavelengths on which + # images were originally tabulated. + lower_idx, frac = _findWave(self.waves, wave) + + # Actually do the interpolation for the image, stepk, and maxk. + im = _linearInterp(self.ims, frac, lower_idx) + stepk = _linearInterp(self.stepk_vals, frac, lower_idx) + maxk = _linearInterp(self.maxk_vals, frac, lower_idx) + + # Rescale to use the exact flux or normalization if requested. + if self.use_exact_sed: + interp_norm = _linearInterp(self.fluxes, frac, lower_idx) + exact_norm = self.sed(wave) + im *= exact_norm/interp_norm + + return im, stepk, maxk + + def _approxWavelength(self, wave): + # More efficient to use one of the original objects, not a new InterpolatedImage. + k = np.searchsorted(self.waves, wave) + if k >= len(self.waves) or (k > 0 and wave-self.waves[k-1] < self.waves[k]-wave): + k = k - 1 + return self.waves[k], self.objs[k] + +
[docs] def evaluateAtWavelength(self, wave): + """ + Evaluate this `ChromaticObject` at a particular wavelength using interpolation. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength, as a `GSObject`. + """ + im, stepk, maxk = self._imageAtWavelength(wave) + return InterpolatedImage(im, _force_stepk=stepk, _force_maxk=maxk)
+ + def _shoot(self, photons, rng): + w = photons.wavelength + if np.any((w < self.waves[0]) | (w > self.waves[-1])): + bad_waves = [w for w in photons.wavelength if w < self.waves[0] or w > self.waves[-1]] + raise GalSimRangeError("Shooting photons outside the interpolated wave_list", + bad_waves, self.waves[0], self.waves[-1]) + + k = np.searchsorted(self.waves, w) + k[k==0] = 1 # if k == 0, then w == min(waves). Using k=1 instead is fine for this. + #assert np.all(k > 0) + #assert np.all(k < len(self.waves)) + + # For each w, these are the wavelengthat that bracket w: + w0 = self.waves[k-1] + w1 = self.waves[k] + #assert np.all(w0 <= w) + #assert np.all(w <= w1) + + # If we could get away with averaging photons shot at each wavelength, + # these would be relative fractions. So e.g. x = x0 f0 + x1 f1 would be the + # right weighted average to use. + f0 = (w1-w) / (w1-w0) + #f1 = (w-w0) / (w1-w0) (We don't need this quantity below.) + + # Instead of averaging these, we can do the averaging probabilistically by selecting + # each photon with a probability equal to the relative weight we would have used + # in the average. + u = np.empty(len(photons)) + UniformDeviate(rng).generate(u) + use_k = k - (u<f0).astype(int) # The second term is either 0 or 1. + + # Draw photons from the saved profiles according to when we have selected to use each one. + for kk, obj in enumerate(self.objs): + use = (use_k == kk) # True for each photon where this is the object to shoot from + temp = PhotonArray(np.sum(use)) + temp._copyFrom(photons, slice(None), use, do_xy=False, do_flux=False) + obj._shoot(temp, rng) + # It will have tried to shoot the right total flux. But that's not correct. + # Rescale it down by the fraction of the total flux we actually want in this set. + temp.scaleFlux(len(temp)/len(photons)) + photons._copyFrom(temp, use, slice(None)) + + def _get_interp_image(self, bandpass, image=None, integrator='quadratic', + _flux_ratio=None, **kwargs): + if integrator == 'quadratic': + rule = integ.quadRule + elif integrator == 'trapezoidal': + rule = integ.trapzRule + elif integrator == 'midpoint': + rule = integ.midptRule + else: + raise GalSimValueError("Unrecognized integration rule", integrator, + ('trapezoidal', 'midpoint', 'quadratic')) + + if _flux_ratio is None: + _flux_ratio = lambda w: np.ones_like(w) + # _flux_ratio might be a constant float. For simplicity below, turn it into a function. + if not hasattr(_flux_ratio, '__call__'): + val = _flux_ratio + _flux_ratio = lambda w: np.full_like(w, val) + + # setup output image (semi-arbitrarily using the bandpass effective wavelength). + # Note: we cannot just use self._imageAtWavelength, because that routine returns an image + # with whatever pixel scale was required to sample all the images properly. We want to set + # up an output image that has the requested pixel scale, which might change the image size + # and so on. + _, prof0 = self._fiducial_profile(bandpass) + image = prof0.drawImage(image=image, setup_only=True, **kwargs) + _remove_setup_kwargs(kwargs) + + # determine combination of self.wave_list and bandpass.wave_list + wave_objs = [self, bandpass] + if isinstance(_flux_ratio, SED): + wave_objs += [_flux_ratio] + wave_list, _, _ = utilities.combine_wave_list(wave_objs) + + if np.any((wave_list < self.waves[0]) | (wave_list > self.waves[-1])): # pragma: no cover + # MJ: I'm pretty sure it's impossible to hit this. + # But just in case I'm wrong, I'm leaving it here but with pragma: no cover. + bad_waves = [w for w in wave_list if w < self.waves[0] or w > self.waves[-1]] + raise GalSimRangeError("Requested wavelength is outside the allowed range.", + bad_waves, self.waves[0], self.waves[-1]) + + # weights are the weights to use at each of the given wavelengths for the integration. + weights = rule.calculateWeights(wave_list, bandpass) + # im_weights are the weights for the stored images. + im_weights = np.zeros(len(self.waves)) + for w, wt in zip(wave_list, weights): + # Find where this is with respect to the wavelengths on which images are stored. + lower_idx, frac = _findWave(self.waves, w) + assert 0 <= lower_idx < len(self.waves)-1 + + # Rescale to use the exact flux or normalization if requested. + if self.use_exact_sed: + interp_norm = _linearInterp(self.fluxes, frac, lower_idx) + exact_norm = self.sed(w) + wt *= exact_norm/interp_norm + + im_weights[lower_idx] += (1.-frac) * wt * _flux_ratio(w) + im_weights[lower_idx+1] += frac * wt * _flux_ratio(w) + + # Do the integral as a weighted sum. + integral = sum(wt*im for wt,im in zip(im_weights, self.ims) if wt!=0) + + # Get the stepk, maxk using the same weights + stepk = np.average(self.stepk_vals, weights=im_weights) + maxk = np.average(self.maxk_vals, weights=im_weights) + + # Instantiate the InterpolatedImage, using these conservative stepk and maxk choices. + return InterpolatedImage(integral, _force_stepk=stepk, _force_maxk=maxk) + +
[docs] def drawImage(self, bandpass, image=None, integrator='quadratic', **kwargs): + """Draw an image as seen through a particular bandpass using the stored interpolated + images at the specified wavelengths. + + This integration will take place using interpolation between stored images that were + setup when the object was constructed. (See interpolate() for more details.) + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to + integrate. + image: Optionally, the `Image` to draw onto. (See `GSObject.drawImage` + for details.) [default: None] + integrator: The integration algorithm to use, given as a string. Either + 'midpoint', 'trapezoidal', or 'quadratic' is allowed. + [default: 'quadratic'] + **kwargs: For all other kwarg options, see `GSObject.drawImage`. + + Returns: + the drawn `Image`. + """ + # Store the last bandpass used. + self._last_bp = bandpass + if self.sed.dimensionless: + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.sed) + + int_im = self._get_interp_image(bandpass, image=image, integrator=integrator, **kwargs) + image = int_im.drawImage(image=image, **kwargs) + self._last_wcs = image.wcs + return image
+ + +
[docs]class ChromaticAtmosphere(ChromaticObject): + """A `ChromaticObject` implementing two atmospheric chromatic effects: differential + chromatic refraction (DCR) and wavelength-dependent seeing. + + Due to DCR, blue photons land closer to the zenith than red photons. Kolmogorov turbulence + also predicts that blue photons get spread out more by the atmosphere than red photons, + specifically FWHM is proportional to wavelength^(-0.2). Both of these effects can be + implemented by wavelength-dependent shifts and dilations. + + Since DCR depends on the zenith angle and the parallactic angle (which is the position angle of + the zenith measured from North through East) of the object being drawn, these must be specified + via keywords. There are four ways to specify these values: + + 1) explicitly provide ``zenith_angle`` as a keyword of type `Angle`, and + ``parallactic_angle`` will be assumed to be 0 by default. + 2) explicitly provide both ``zenith_angle`` and ``parallactic_angle`` as keywords of type + `Angle`. + 3) provide the coordinates of the object ``obj_coord`` and the coordinates of the zenith + ``zenith_coord`` as keywords of type `CelestialCoord`. + 4) provide the coordinates of the object ``obj_coord`` as a `CelestialCoord`, the hour angle + of the object ``HA`` as an `Angle`, and the latitude of the observer ``latitude`` as an + `Angle`. + + DCR also depends on temperature, pressure and water vapor pressure of the atmosphere. The + default values for these are expected to be appropriate for LSST at Cerro Pachon, Chile, but + they are broadly reasonable for most observatories. + + Note that a ChromaticAtmosphere by itself is NOT the correct thing to use to draw an image of a + star. Stars (and galaxies too, of course) have an `SED` that is not flat. To draw a real star, + you should either multiply the ChromaticAtmosphere object by an `SED`, or convolve it with a + point source multiplied by an `SED`:: + + >>> psf = galsim.ChromaticAtmosphere(...) + >>> star = galsim.DeltaFunction() * psf_sed + >>> final_star = galsim.Convolve( [psf, star] ) + >>> final_star.drawImage(bandpass = bp, ...) + + Parameters: + base_obj: Fiducial PSF, equal to the monochromatic PSF at ``base_wavelength`` + base_wavelength: Wavelength represented by the fiducial PSF, in nanometers. + scale_unit: Units used by base_obj for its linear dimensions. + [default: galsim.arcsec] + alpha: Power law index for wavelength-dependent seeing. [default: -0.2, + the prediction for Kolmogorov turbulence] + zenith_angle: `Angle` from object to zenith [default: 0] + parallactic_angle: Parallactic angle, i.e. the position angle of the zenith, measured + from North through East. [default: 0] + obj_coord: Celestial coordinates of the object being drawn as a + `CelestialCoord`. [default: None] + zenith_coord: Celestial coordinates of the zenith as a `CelestialCoord`. + [default: None] + HA: Hour angle of the object as an `Angle`. [default: None] + latitude: Latitude of the observer as an `Angle`. [default: None] + pressure: Air pressure in kiloPascals. [default: 69.328 kPa] + temperature: Temperature in Kelvins. [default: 293.15 K] + H2O_pressure: Water vapor pressure in kiloPascals. [default: 1.067 kPa] + """ + def __init__(self, base_obj, base_wavelength, scale_unit=None, **kwargs): + self.separable = False + self.interpolated = False + self.deinterpolated = self + + self.base_obj = base_obj + self.base_wavelength = base_wavelength + self._gsparams = base_obj.gsparams + + if scale_unit is None: + scale_unit = arcsec + elif isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + self.scale_unit = scale_unit + self.alpha = kwargs.pop('alpha', -0.2) + self.zenith_angle, self.parallactic_angle, self.kw = dcr.parse_dcr_angles(**kwargs) + + # Any remaining kwargs will get forwarded to galsim.dcr.get_refraction + # Check that they're valid + for kw in self.kw: + if kw not in ('temperature', 'pressure', 'H2O_pressure'): + raise TypeError("Got unexpected keyword: {0}".format(kw)) + + self.base_refraction = dcr.get_refraction(self.base_wavelength, self.zenith_angle, + **self.kw) + + @property + def sed(self): + return self.base_obj.sed + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self.base_obj.gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret.base_obj = self.base_obj.withGSParams(gsparams, **kwargs) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticAtmosphere) and + self.base_obj == other.base_obj and + self.base_wavelength == other.base_wavelength and + self.alpha == other.alpha and + self.zenith_angle == other.zenith_angle and + self.parallactic_angle == other.parallactic_angle and + self.scale_unit == other.scale_unit and + self.kw == other.kw)) + + def __hash__(self): + return hash(("galsim.ChromaticAtmosphere", self.base_obj, self.base_wavelength, + self.alpha, self.zenith_angle, self.parallactic_angle, self.scale_unit, + frozenset(self.kw.items()))) + + def __repr__(self): + s = 'galsim.ChromaticAtmosphere(%r, base_wavelength=%r, alpha=%r'%( + self.base_obj, self.base_wavelength, self.alpha) + s += ', zenith_angle=%r, parallactic_angle=%r'%(self.zenith_angle, self.parallactic_angle) + s += ', scale_unit=%r'%(self.scale_unit) + for k,v in self.kw.items(): + s += ', %s=%r'%(k,v) + s += ')' + return s + + def __str__(self): + return 'galsim.ChromaticAtmosphere(%s, base_wavelength=%s, alpha=%s)'%( + self.base_obj, self.base_wavelength, self.alpha) + +
[docs] def build_obj(self): + """Build a `ChromaticTransformation` object for this `ChromaticAtmosphere`. + + We don't do this right away to help make `ChromaticAtmosphere` objects be picklable. + Building this is quite fast, so we do it on the fly in `evaluateAtWavelength` and + `ChromaticObject.drawImage`. + """ + def shift_fn(w): + shift_magnitude = dcr.get_refraction(w, self.zenith_angle, **self.kw) + shift_magnitude -= self.base_refraction + shift_magnitude = shift_magnitude * radians / self.scale_unit + sinp, cosp = self.parallactic_angle.sincos() + shift = (-shift_magnitude * sinp, shift_magnitude * cosp) + return shift + + def jac_fn(w): + scale = (w/self.base_wavelength)**self.alpha + return np.diag([scale, scale]) + + flux_ratio = lambda w: (w/self.base_wavelength)**(-2.*self.alpha) + + return ChromaticTransformation(self.base_obj, jac=jac_fn, offset=shift_fn, + flux_ratio=flux_ratio)
+ + def _shoot(self, photons, rng): + # Start with the base PSF + self.base_obj._shoot(photons, rng) + + w = photons.wavelength + + # Apply the wavelength-dependent scaling + if self.alpha != 0.: + scale = (w/self.base_wavelength)**self.alpha + photons.x *= scale + photons.y *= scale + + # Apply DCR + shift_magnitude = dcr.get_refraction(w, self.zenith_angle, **self.kw) + shift_magnitude -= self.base_refraction + shift_magnitude *= radians / self.scale_unit + sinp, cosp = self.parallactic_angle.sincos() + photons.x += -shift_magnitude * sinp + photons.y += shift_magnitude * cosp + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return self.build_obj().evaluateAtWavelength(wave)
+ + +
[docs]class ChromaticTransformation(ChromaticObject): + """A class for modeling a wavelength-dependent affine transformation of a `ChromaticObject` + instance. + + Typically, you do not need to construct a ChromaticTransformation object explicitly. + This is the type returned by the various transformation methods of `ChromaticObject` such as + `ChromaticObject.shear`, `ChromaticObject.rotate`, `ChromaticObject.shift`, etc. + + All the various transformations can be described as a combination of a jacobian matrix + (i.e. `ChromaticObject.transform`) and a translation (`ChromaticObject.shift`), which are + described by (dudx,dudy,dvdx,dvdy) and (dx,dy) respectively. + + Parameters: + obj: The object to be transformed. + jac: A list or tuple (dudx, dudy, dvdx, dvdy), or a numpy.array object + [[dudx, dudy], [dvdx, dvdy]] describing the Jacobian to apply. May + also be a function of wavelength returning a numpy array. + Use None to indicate that the Jacobian is the 2x2 unit matrix. + [default: None] + offset: A galsim.PositionD or list or tuple or numpy array giving the offset + (dx,dy) by which to shift the profile. May also be a function of + wavelength returning a numpy array. [default: None] + flux_ratio: A factor by which to multiply the flux of the object. [default: 1] + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, jac=None, offset=(0.,0.), flux_ratio=1., redshift=None, + gsparams=None, propagate_gsparams=True, _redshift=None): + if isinstance(offset, Position): + offset = (offset.x, offset.y) + if not hasattr(jac,'__call__') and jac is not None: + jac = np.asarray(jac).reshape(2,2) + if not hasattr(offset,'__call__'): + offset = np.asarray(offset) + if redshift is not None: + from .deprecated import depr + depr('redshift', 2.5, 'SED.atRedshift', + 'You should now apply the redshift to the SED before using it in a ' + 'chromatic object.') + _redshift = redshift + + self.chromatic = hasattr(jac,'__call__') or hasattr(offset,'__call__') + # Technically, if the only chromatic transformation is a flux_ratio, and the original object + # is separable, then the transformation is still separable (for instance, galsim.Chromatic), + # but we'll ignore that here. + self.separable = obj.separable and not self.chromatic + + if obj.interpolated and self.chromatic: + galsim_warn("Cannot render image with chromatic transformation applied to it " + "using interpolation between stored images. Reverting to " + "non-interpolated version.") + obj = obj.deinterpolated + self.interpolated = obj.interpolated + self._redshift = _redshift + + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + + if (isinstance(obj, ChromaticTransformation) and not self.chromatic + and not obj.chromatic and self._redshift is None and obj._redshift is None): + # If both transformations are not chromatic, then it is useful to combine them. + # Especially if the original object is interpolated, since we have special handling + # for that case in drawImage. + # However, if either one is chromatic, then it's hard to combine them in a way that + # preserves the ability to call the functions on either numpy arrays or scalars, + # so just leave this as two consecutive transformations in that case. + # Similarly, if there are redshifts involved, then the flux_ratio function needs to + # be called on two different wavelengths, so again just leave it separate. + # (I think this last case could be remedied, so if there is a use case where it + # is important, we could try to implement it.) + self._original = obj.original + if jac is None: + self._jac = obj._jac + self._offset = obj._offset + offset + else: + self._jac = jac if obj._jac is None else jac.dot(obj._jac) + self._offset = jac.dot(obj._offset) + offset + if (not isinstance(flux_ratio, SED) and + (hasattr(flux_ratio, '__call__') or hasattr(obj._flux_ratio, '__call__'))): + self._flux_ratio = SED(flux_ratio, 'nm', '1') * obj._flux_ratio + else: + self._flux_ratio = obj._flux_ratio * flux_ratio + else: + self._original = obj + self._jac = jac + self._offset = offset + self._flux_ratio = flux_ratio + + if self._propagate_gsparams: + self._original = self._original.withGSParams(self._gsparams) + + if self.interpolated: + self.deinterpolated = ChromaticTransformation( + self._original.deinterpolated, + jac = self._jac, + offset = self._offset, + flux_ratio = self._flux_ratio, + redshift = self._redshift, + gsparams = self._gsparams, + propagate_gsparams = self._propagate_gsparams) + else: + self.deinterpolated = self + + @lazy_property + def sed(self): + sed = self._original.sed * self._flux_ratio + + if self._redshift is not None: + sed = sed.atRedshift(self._redshift) + + # Need to account for non-unit determinant jacobian in normalization. + if hasattr(self._jac, '__call__'): + @np.vectorize + def detjac(w): + return np.linalg.det(np.asarray(self._jac(w)).reshape(2,2)) + sed *= detjac + elif self._jac is not None: + sed *= np.linalg.det(np.asarray(self._jac).reshape(2,2)) + + return sed + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + + def _atRedshift(self, redshift): + ret = copy.copy(self) + ret._redshift = redshift + return ret + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._original = self._original.withGSParams(ret._gsparams) + if self.interpolated: + ret.deinterpolated = self.deinterpolated.withGSParams(ret._gsparams) + else: + ret.deinterpolated = ret + return ret
+ + @property + def original(self): + """The original object that was transformed. + """ + return self._original + + def __eq__(self, other): + if self is other: return True + if not (isinstance(other, ChromaticTransformation) and + self.original == other.original and + self._gsparams == other._gsparams and + self._propagate_gsparams == other._propagate_gsparams): + return False + # There's really no good way to check that two callables are equal, except if they literally + # point to the same object. So we'll just check for that for _jac, _offset, _flux_ratio. + for attr in ('_jac', '_offset', '_flux_ratio'): + selfattr = getattr(self, attr) + otherattr = getattr(other, attr) + # For this attr, either both need to be chromatic or neither. + if ((hasattr(selfattr, '__call__') and not hasattr(otherattr, '__call__')) or + (hasattr(otherattr, '__call__') and not hasattr(selfattr, '__call__'))): + return False + # If chromatic, then check that attrs compare equal + if hasattr(selfattr, '__call__'): + if selfattr != otherattr: + return False + else: # Otherwise, check that attr arrays are equal. + if not np.array_equal(selfattr, otherattr): + return False + return True + + def __hash__(self): + # This one's a bit complicated, so we'll go ahead and cache the hash. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.ChromaticTransformation", self.original, self._gsparams, + self._propagate_gsparams)) + # achromatic _jac and _offset are ndarrays, so need to be handled separately. + for attr in ('_jac', '_offset'): + selfattr = getattr(self, attr) + if hasattr(selfattr, '__call__'): + self._hash ^= hash(selfattr) + elif selfattr is not None: + self._hash ^= hash(tuple(selfattr.ravel().tolist())) + self._hash ^= hash(self._flux_ratio) + return self._hash + + def __repr__(self): + if hasattr(self._jac, '__call__'): + jac = self._jac + elif self._jac is None: + jac = None + else: + jac = self._jac.ravel().tolist() + if hasattr(self._offset, '__call__'): + offset = self._offset + else: + offset = _PositionD(*(self._offset.tolist())) + if self._redshift is not None: + redshift_str = 'redshift=%r, '%self._redshift + else: + redshift_str = '' + return ('galsim.ChromaticTransformation(%r, jac=%r, offset=%r, flux_ratio=%r, %s' + 'gsparams=%r, propagate_gsparams=%r)')%( + self.original, jac, offset, self._flux_ratio, redshift_str, + self._gsparams, self._propagate_gsparams) + + def __str__(self): + s = str(self.original) + if hasattr(self._jac, '__call__'): + s += '.transform(%s)'%self._jac + elif self._jac is not None: + s += transform.Transformation._str_from_jac(self._jac) + if hasattr(self._offset, '__call__'): + s += '.shift(%s)'%self._offset + elif not np.array_equal(self._offset,(0,0)): + s += '.shift(%s,%s)'%(self._offset[0],self._offset[1]) + if self._flux_ratio != 1.: + s += '.withScaledFlux(%s)'%self._flux_ratio + if self._redshift is not None: + s += '.atRedshift(%s)'%(self._redshift) + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('sed',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + def _getTransformations(self, wave): + if hasattr(self._jac, '__call__'): + jac = self._jac(wave) + else: + jac = self._jac + if hasattr(self._offset, '__call__'): + offset = self._offset(wave) + else: + offset = self._offset + if hasattr(self._flux_ratio, '__call__'): + flux_ratio = self._flux_ratio(wave) + else: + flux_ratio = self._flux_ratio + return jac, offset, flux_ratio + + def _approxWavelength(self, wave): + # Same as evaluateAtWavelength, except the starting point is also _approxWavelength + wave1 = wave / (1.+self._redshift) if self._redshift is not None else wave + wave2, ret = self.original._approxWavelength(wave1) + wave = wave2 * (1.+self.redshift) if self._redshift is not None else wave2 + jac, offset, flux_ratio = self._getTransformations(wave) + offset = _PositionD(*offset) + return wave, transform.Transformation(ret, jac=jac, offset=offset, flux_ratio=flux_ratio, + gsparams=self._gsparams, + propagate_gsparams=self._propagate_gsparams) + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + if self._redshift is not None: + wave1 = wave / (1.+self._redshift) + else: + wave1 = wave + ret = self.original.evaluateAtWavelength(wave1) + jac, offset, flux_ratio = self._getTransformations(wave) + offset = _PositionD(*offset) + return transform.Transformation(ret, jac=jac, offset=offset, flux_ratio=flux_ratio, + gsparams=self._gsparams, + propagate_gsparams=self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + self._original._shoot(photons, rng) + self._photon_op.applyTo(photons) + + @lazy_property + def _photon_op(self): + # A PhotonOp that just applies the transformation part, not self.original. + class ChromaticTransformationPhotonOp(PhotonOp): + def __init__(self, ct): + self.ct = ct + + def applyTo(self, photons, local_wcs=None, rng=None): + wave = photons.wavelength + if self.ct._redshift is not None: + wave /= (1+self.ct._redshift) + jac, offset, flux_ratio = self.ct._getTransformations(wave) + + # cf. Transformation._fwd_normal + if jac is not None: + temp = jac[0,1] * photons.y + photons.y *= jac[1,1] + photons.y += jac[1,0] * photons.x + photons.x *= jac[0,0] + photons.x += temp + + det = jac[0,0] * jac[1,1] - jac[0,1] * jac[1,0] + flux_ratio *= np.abs(det) + + photons.x += offset[0] + photons.y += offset[1] + photons.flux *= flux_ratio + + return ChromaticTransformationPhotonOp(self) + +
[docs] def drawImage(self, bandpass, image=None, integrator='quadratic', **kwargs): + """ + See `ChromaticObject.drawImage` for a full description. + + This version usually just calls that one, but if the transformed object (self.original) is + an `InterpolatedChromaticObject`, and the transformation is achromatic, then it will still + be able to use the interpolation. + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to + integrate. + image: Optionally, the `Image` to draw onto. (See `GSObject.drawImage` + for details.) [default: None] + integrator: When doing the exact evaluation of the profile, this argument should + be one of the image integrators from galsim.integ, or a string + 'trapezoidal', 'midpoint', 'quadratic', in which case the routine will + use a `SampleIntegrator` or `ContinuousIntegrator` depending on whether + or not the object has a ``wave_list``. [default: 'quadratic', + which will try to select an appropriate integrator using the + quadratic integration rule automatically.] + If the object being transformed is an `InterpolatedChromaticObject`, + then ``integrator`` can only be a string, either 'midpoint', + 'trapezoidal', or 'quadratic'. + **kwargs: For all other kwarg options, see `GSObject.drawImage`. + + Returns: + the drawn `Image`. + """ + # Store the last bandpass used. + self._last_bp = bandpass + if self.sed.dimensionless: + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.sed) + if isinstance(self.original, InterpolatedChromaticObject): + # Pass self._flux_ratio, which *could* depend on wavelength, to _get_interp_image, + # where it will be used to reweight the stored images. + int_im = self.original._get_interp_image(bandpass, image=image, integrator=integrator, + _flux_ratio=self._flux_ratio, **kwargs) + # Get shape transformations at bandpass.red_limit (they are achromatic so it doesn't + # matter where you get them). + jac, offset, _ = self._getTransformations(bandpass.red_limit) + offset = _PositionD(*offset) + int_im = transform.Transform(int_im, jac=jac, offset=offset, gsparams=self._gsparams, + propagate_gsparams=self._propagate_gsparams) + image = int_im.drawImage(image=image, **kwargs) + elif kwargs.get('method',None) == 'phot': + # Need to calculate n_photons now using the fiducial profile, not self.original, + # since the transformation may (often does) change the flux. + flux = self.calculateFlux(bandpass) + wave0, prof0 = self._fiducial_profile(bandpass) + prof1 = prof0.withFlux(flux) + n_photons = kwargs.pop('n_photons', 0) + poisson_flux = kwargs.pop('poisson_flux', n_photons == 0.) + max_extra_noise = kwargs.pop('max_extra_noise', 0.) + rng = BaseDeviate(kwargs.get('rng', None)) + n_photons, g = prof1._calculate_nphotons(n_photons, poisson_flux, max_extra_noise, rng) + kwargs['n_photons'] = n_photons + + if self.original.dimensionless: + # Then this is a PSF * SED. Things work better to convert it into a + # Convolution(delta * SED, PSF) instead. + psf = ChromaticTransformation(self.original, jac=self._jac, offset=self._offset, + gsparams=self._gsparams, propagate_gsparams=False) + sed = self._flux_ratio * g + assert sed.spectral # This must be true here. + if self._redshift is not None: + sed = sed.atRedshift(self._redshift) + kwargs['photon_ops'] = [psf] + kwargs.get('photon_ops', []) + image = (DeltaFunction() * sed).drawImage(bandpass, image=image, **kwargs) + else: + # If the original is spectral, then treat the transformation as a photon_op. + ops = [self._photon_op] + if g != 1.0: + ops = ops + [ScaleFlux(g)] + ops = ops + kwargs.pop('photon_ops', []) + gal = self.original + if self._redshift is not None: + gal = gal._atRedshift(self._redshift) + image = gal.drawImage(bandpass, image=image, photon_ops=ops, **kwargs) + else: + image = ChromaticObject.drawImage(self, bandpass, image, integrator, **kwargs) + self._last_wcs = image.wcs + return image
+ + @lazy_property + def noise(self): + """An estimate of the noise already in the profile. + """ + from .correlatednoise import BaseCorrelatedNoise + # Condition for being able to propagate noise: + # 1) All transformations are achromatic. + # 2) This ChromaticTransformation wraps a ChromaticConvolution with a valid noise property. + if (hasattr(self._jac, '__call__') or + hasattr(self._offset, '__call__') or + hasattr(self._flux_ratio, '__call__')): + raise GalSimError("Cannot propagate noise through chromatic transformation") + noise = self.original.noise + jac = self._jac + flux_ratio = self._flux_ratio + noise_prof = transform._Transform(noise._profile, jac, flux_ratio=flux_ratio**2) + return BaseCorrelatedNoise(noise.rng, noise_prof, noise.wcs)
+ + +class SimpleChromaticTransformation(ChromaticTransformation): + """A class for the simplest kind of chromatic object -- a GSObject times an SED. + + This is a subclass of ChromaticTransformation, which just skips some calculations + that are unnecessary in this simple, but fairly common special case. + + Parameters: + obj: The object to be transformed. + sed: An `SED` instance. + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, sed=1., gsparams=None, propagate_gsparams=True): + self.chromatic = False + self.separable = True + self.interpolated = False + self._redshift = None + + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + + self._original = obj + self._jac = None + self._offset = (0.,0.) + self._flux_ratio = sed + + if self._propagate_gsparams: + self._original = self._original.withGSParams(self._gsparams) + self.deinterpolated = self + + @lazy_property + def sed(self): + return self._flux_ratio * self.original.flux + + def _atRedshift(self, redshift): + return SimpleChromaticTransformation(self.original, self._flux_ratio.atRedshift(redshift), + self._gsparams, self._propagate_gsparams) + + def __hash__(self): + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.SimpleChromaticTransformation", self.original, + self._flux_ratio, self._gsparams, self._propagate_gsparams)) + return self._hash + + def __repr__(self): + return ('galsim.SimpleChromaticTransformation(%r, sed=%r, ' + 'gsparams=%r, propagate_gsparams=%r)')%( + self.original, self._flux_ratio, + self._gsparams, self._propagate_gsparams) + + def __str__(self): + return str(self.original) + ' * ' + str(self.sed) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('sed',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + def _getTransformations(self, wave): + flux_ratio = self._flux_ratio(wave) + return self._jac, self._offset, flux_ratio + + def _shoot(self, photons, rng): + self._original._shoot(photons, rng) + wave = photons.wavelength + flux_ratio = self._flux_ratio(wave) + photons.flux *= flux_ratio + + def drawImage(self, bandpass, image=None, integrator='quadratic', **kwargs): + """ + See `ChromaticObject.drawImage` for a full description. + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to + integrate. + image: Optionally, the `Image` to draw onto. (See `GSObject.drawImage` + for details.) [default: None] + integrator: When doing the exact evaluation of the profile, this argument should + be one of the image integrators from galsim.integ, or a string + 'trapezoidal', 'midpoint', 'quadratic', in which case the routine will + use a `SampleIntegrator` or `ContinuousIntegrator` depending on whether + or not the object has a ``wave_list``. [default: 'quadratic', + which will try to select an appropriate integrator using the + quadratic integration rule automatically.] + If the object being transformed is an `InterpolatedChromaticObject`, + then ``integrator`` can only be a string, either 'midpoint', + 'trapezoidal', or 'quadratic'. + **kwargs: For all other kwarg options, see `GSObject.drawImage`. + + Returns: + the drawn `Image`. + """ + # Store the last bandpass used. + self._last_bp = bandpass + if self.sed.dimensionless: + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.sed) + image = ChromaticObject.drawImage(self, bandpass, image, integrator, **kwargs) + self._last_wcs = image.wcs + return image + + +
[docs]class ChromaticSum(ChromaticObject): + """A sum of several `ChromaticObject` and/or `GSObject` instances. + + Any `GSObject` in the sum is assumed to have a flat `SED` with spectral density of 1 + photon/s/cm**2/nm. + + This is the type returned from `galsim.Add` if any of the objects are a `ChromaticObject`. + + Typically, you do not need to construct a ChromaticSum object explicitly. Normally, you + would just use the + operator, which returns a ChromaticSum when used with chromatic objects:: + + >>> bulge = galsim.Sersic(n=3, half_light_radius=0.8) * bulge_sed + >>> disk = galsim.Exponential(half_light_radius=1.4) * disk_sed + >>> gal = bulge + disk + + You can also use the `Add` factory function, which returns a ChromaticSum object if any of + the individual objects are chromatic:: + + >>> gal = galsim.Add([bulge,disk]) + + Parameters: + args: Unnamed args should be a list of objects to add. + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, *args, **kwargs): + # Check kwargs first: + gsparams = kwargs.pop("gsparams", None) + self._propagate_gsparams = kwargs.pop("propagate_gsparams", True) + + # Make sure there is nothing left in the dict. + if kwargs: + raise TypeError("Got unexpected keyword argument(s): %s"%kwargs.keys()) + + if len(args) == 0: + # No arguments. Could initialize with an empty list but draw then segfaults. Raise an + # exception instead. + raise TypeError("Must provide at least one GSObject or ChromaticObject.") + elif len(args) == 1: + # 1 argument. Should be either a GSObject, ChromaticObject or a list of these. + if isinstance(args[0], (GSObject, ChromaticObject)): + args = [args[0]] + elif isinstance(args[0], list): + args = args[0] + else: + raise TypeError("Single input argument must be a GSObject, a ChromaticObject," + " or list of them.") + # else args is already the list of objects + + # Figure out what gsparams to use + if gsparams is None: + # If none is given, take the most restrictive combination from the obj_list. + self._gsparams = GSParams.combine([obj.gsparams for obj in args]) + else: + # If something explicitly given, then use that. + self._gsparams = GSParams.check(gsparams) + + self.interpolated = any(arg.interpolated for arg in args) + if self.interpolated: + self.deinterpolated = ChromaticSum([arg.deinterpolated for arg in args], + gsparams=self._gsparams) + else: + self.deinterpolated = self + + # We can only add ChromaticObjects together if they're either all SED'd or all non-SED'd + dimensionless = all(a.dimensionless for a in args) + spectral = all(a.spectral for a in args) + if not (dimensionless or spectral): + raise GalSimIncompatibleValuesError( + "Cannot add dimensionless and spectral ChromaticObjects.", args=args) + + # We always consider ChromaticSums to be inseparable. + # Note that separable groups can only be identified if the constituent objects have the + # *same* SED even though a proportional SED is mathematically sufficient for separability. + # It's basically impossible to identify if two SEDs are proportional (or even equal) unless + # they point to the same memory, and in practice any sums that are like this would + # almost certainly have different relative fluxes, so they would end up with different + # SED instances. This implies that there is no point wasting time trying to pull out + # separable groups. + + self.separable = False + self._obj_list = [] + for obj in args: + if self._propagate_gsparams: + obj = obj.withGSParams(self._gsparams) + self._obj_list.append(obj) + + @lazy_property + def sed(self): + sed = self._obj_list[0].sed + for obj in self._obj_list[1:]: + sed += obj.sed + return sed + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + @property + def obj_list(self): + """The list of objects being added. + """ + return self._obj_list + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj_list = [ obj.withGSParams(ret._gsparams) for obj in self.obj_list ] + return ret
+ + def _atRedshift(self, redshift): + ret = copy.copy(self) + ret._obj_list = [ obj._atRedshift(redshift) for obj in self.obj_list ] + return ret + + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticSum) and + self.obj_list == other.obj_list and + self._gsparams == other._gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.ChromaticSum", tuple(self.obj_list), self._gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.ChromaticSum(%r, gsparams=%r, propagate_gsparams=%r)'%( + self.obj_list, self._gsparams, self._propagate_gsparams) + + def __str__(self): + str_list = [ str(obj) for obj in self.obj_list ] + return 'galsim.ChromaticSum([%s])'%', '.join(str_list) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('sed',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength ``wave``. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return Add([obj.evaluateAtWavelength(wave) for obj in self.obj_list], + gsparams=self._gsparams, propagate_gsparams=self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + w = photons.wavelength + + # We probabilistically choose a component for each photon based on the + # relative flux density of that component for the given wavelength. + comp_flux = np.array([obj.sed(w) for obj in self._obj_list]) + total_flux = np.sum(comp_flux, axis=0) + + prob = comp_flux / total_flux + cdf = np.cumsum(prob, axis=0) + + u = np.empty(len(photons)) + UniformDeviate(rng).generate(u) + use_k = np.sum((u >= cdf), axis=0) + n = len(self._obj_list) + use_k[use_k==n] = n-1 # This shouldn't be possible, but maybe with numerical rounding... + + # Draw photons from the components. + for kk, obj in enumerate(self._obj_list): + use = (use_k == kk) # True for each photon where this is the object to shoot from + this_n = np.sum(use) + if this_n == 0: + continue + temp = PhotonArray(this_n) + temp._copyFrom(photons, slice(None), use, do_xy=False, do_flux=False) + obj._shoot(temp, rng) + photons._copyFrom(temp, use, slice(None)) + +
[docs] def drawImage(self, bandpass, image=None, integrator='quadratic', **kwargs): + """Slightly optimized draw method for `ChromaticSum` instances. + + Draws each summand individually and add resulting images together. This might waste time if + two or more summands are separable and have the same `SED`, and another summand with a + different `SED` is also added, in which case the summands should be added together first and + the resulting `Sum` object can then be chromaticized. In general, however, drawing + individual sums independently can help with speed by identifying chromatic profiles that + are separable into spectral and spatial factors. + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to + integrate. + image: Optionally, the `Image` to draw onto. (See `GSObject.drawImage` + for details.) [default: None] + integrator: When doing the exact evaluation of the profile, this argument should + be one of the image integrators from galsim.integ, or a string + 'trapezoidal', 'midpoint', 'quadratic', in which case the routine will + use a `SampleIntegrator` or `ContinuousIntegrator` depending on whether + or not the object has a ``wave_list``. [default: 'quadratic', + which will try to select an appropriate integrator using the + quadratic integration rule automatically.] + **kwargs: For all other kwarg options, see `GSObject.drawImage`. + + Returns: + the drawn `Image`. + """ + # Store the last bandpass used. + self._last_bp = bandpass + if self.sed.dimensionless: + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.sed) + add_to_image = kwargs.pop('add_to_image', False) + + n_photons = kwargs.pop('n_photons', None) + save_photons = kwargs.get('save_photons', False) + all_photons = [] + if n_photons is None or kwargs.get('method', None) != 'phot': + # Use given add_to_image for the first one, then add_to_image=True for the rest. + image = self.obj_list[0].drawImage( + bandpass, image=image, add_to_image=add_to_image, **kwargs) + _remove_setup_kwargs(kwargs) + added_flux = image.added_flux + if save_photons: + all_photons.append(image.photons) + for obj in self.obj_list[1:]: + image = obj.drawImage(bandpass, image=image, add_to_image=True, **kwargs) + added_flux += image.added_flux + if save_photons: + all_photons.append(image.photons) + image.added_flux = added_flux + if save_photons: + image.photons = PhotonArray.concatenate(all_photons) + else: + # If n_photons is specified, need some special handling here. + rng = BaseDeviate(kwargs.get('rng', None)) + total_flux = self.calculateFlux(bandpass) + remaining_nphot = n_photons + remaining_flux = total_flux + if kwargs.pop('poisson_flux',False): + pd = PoissonDeviate(rng, total_flux) + flux_ratio = pd() / total_flux + else: + flux_ratio = 1 + added_flux = 0 + first = True + for i, obj in enumerate(self.obj_list): + this_flux = obj.calculateFlux(bandpass) + if i == len(self.obj_list)-1: + this_nphot = remaining_nphot + else: + bd = BinomialDeviate(rng, remaining_nphot, this_flux / remaining_flux) + this_nphot = int(bd()) + remaining_nphot -= this_nphot + remaining_flux -= this_flux + # Get the flux right based on the actual draw and possibly poisson_flux. + if this_nphot > 0: + obj *= (this_nphot * total_flux) / (n_photons * this_flux) * flux_ratio + image = obj.drawImage(bandpass, image=image, add_to_image=add_to_image, + n_photons=this_nphot, poisson_flux=False, **kwargs) + added_flux += image.added_flux + if save_photons: + all_photons.append(image.photons) + if first: + # Note: This might not be i==0. + # Do this after the first call we make to drawImage. + _remove_setup_kwargs(kwargs) + add_to_image = True + first = False + if not remaining_flux > 0: + break + image.added_flux = added_flux + if save_photons: + image.photons = PhotonArray.concatenate(all_photons) + self._last_wcs = image.wcs + return image
+ +
[docs] def withScaledFlux(self, flux_ratio): + """Multiply the flux of the object by ``flux_ratio`` + + Parameters: + flux_ratio: The factor by which to scale the flux. + + Returns: + the object with the new flux. + """ + new_obj = ChromaticSum([ obj.withScaledFlux(flux_ratio) for obj in self.obj_list ]) + if hasattr(self, 'covspec'): + new_covspec = self.covspec * flux_ratio**2 + new_obj.covspec = new_covspec + return new_obj
+ + +
[docs]class ChromaticConvolution(ChromaticObject): + """A convolution of several `ChromaticObject` and/or `GSObject` instances. + + Any `GSObject` in the convolution is assumed to have a flat `SED` with spectral density of 1 + photon/s/cm**2/nm. + + This is the type returned from `galsim.Convolve` if any of the objects is a `ChromaticObject`. + + The normal way to use this class is to use the `Convolve` factory function:: + + >>> gal = galsim.Sersic(n, half_light_radius) * galsim.SED(sed_file, 'nm', 'flambda') + >>> psf = galsim.ChromaticAtmosphere(...) + >>> final = galsim.Convolve([gal, psf]) + + The objects to be convolved may be provided either as multiple unnamed arguments (e.g. + ``Convolve(psf, gal, pix)``) or as a list (e.g. ``Convolve([psf, gal, pix])``). Any number of + objects may be provided using either syntax. (Well, the list has to include at least 1 item.) + + Parameters: + args: Unnamed args should be a list of objects to convolve. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the objects have hard + edges.] + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, *args, **kwargs): + # First check for number of arguments != 0 + if len(args) == 0: + # No arguments. Could initialize with an empty list but draw then segfaults. Raise an + # exception instead. + raise TypeError("Must provide at least one GSObject or ChromaticObject") + elif len(args) == 1: + if isinstance(args[0], (GSObject, ChromaticObject)): + args = [args[0]] + elif isinstance(args[0], list): + args = args[0] + else: + raise TypeError("Single input argument must be a GSObject, or a ChromaticObject," + " or list of them.") + # else args is already the list of objects + + # Check kwargs + # real space convolution is not implemented for chromatic objects. + real_space = kwargs.pop("real_space", None) + if real_space: + raise GalSimNotImplementedError( + "Real space convolution of chromatic objects not implemented.") + gsparams = kwargs.pop("gsparams", None) + self._propagate_gsparams = kwargs.pop("propagate_gsparams", True) + + # Figure out what gsparams to use + if gsparams is None: + # If none is given, take the most restrictive combination from the obj_list. + self._gsparams = GSParams.combine([obj.gsparams for obj in args]) + else: + # If something explicitly given, then use that. + self._gsparams = GSParams.check(gsparams) + + # Make sure there is nothing left in the dict. + if kwargs: + raise TypeError("Got unexpected keyword argument(s): %s"%kwargs.keys()) + + # Accumulate convolutant .seds. Check if more than one is spectral. + nspectral = sum(arg.spectral for arg in args) + if nspectral > 1: + raise GalSimIncompatibleValuesError( + "Cannot convolve more than one spectral ChromaticObject.", args=args) + + self._obj_list = [] + # Unfold convolution of convolution. + for obj in args: + if self._propagate_gsparams: + obj = obj.withGSParams(self._gsparams) + if isinstance(obj, ChromaticConvolution): + self._obj_list.extend(obj.obj_list) + else: + self._obj_list.append(obj) + + self.separable = all(obj.separable for obj in self._obj_list) + self.interpolated = any(obj.interpolated for obj in self._obj_list) + if self.interpolated: + self.deinterpolated = ChromaticConvolution( + [obj.deinterpolated for obj in self._obj_list], + gsparams=self._gsparams, propagate_gsparams=self._propagate_gsparams) + else: + self.deinterpolated = self + + # Check quickly whether we are convolving two non-separable things that aren't + # ChromaticSums, >1 of which uses interpolation. If so, emit a warning that the + # interpolation optimization is being ignored and full evaluation is necessary. + # For the case of ChromaticSums, as long as each object in the sum is separable (even if the + # entire object is not) then interpolation can still be used. So we do not warn about this + # here. + n_nonsep = 0 + n_interp = 0 + for obj in self._obj_list: + if not obj.separable and not isinstance(obj, ChromaticSum): n_nonsep += 1 + if obj.interpolated: n_interp += 1 + if n_nonsep>1 and n_interp>0: + galsim_warn( + "Image rendering for this convolution cannot take advantage of " + "interpolation-related optimization. Will use full profile evaluation.") + + @lazy_property + def sed(self): + sed = self._obj_list[0].sed + for obj in self._obj_list[1:]: + sed *= obj.sed + return sed + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + + @property + def obj_list(self): + """The list of objects being convolved. + """ + return self._obj_list + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj_list = [ obj.withGSParams(ret._gsparams) for obj in self.obj_list ] + return ret
+ + @staticmethod + def _get_effective_prof(insep_obj, bandpass, iimult, integrator, gsparams): + # Find scale at which to draw effective profile + # Use smallest nyquist scale among the fiducial profile and at the two limits of the bp. + _, prof0 = insep_obj._fiducial_profile(bandpass) + prof1 = insep_obj.evaluateAtWavelength(bandpass.red_limit) + prof2 = insep_obj.evaluateAtWavelength(bandpass.blue_limit) + iiscale = min(prof0.nyquist_scale, prof1.nyquist_scale, prof2.nyquist_scale) + iiscale /= 2 # This seems to be required to make test_monochromatic_sed to pass. + # Not sure why, since I thought straight nyquist should be good enough. + # But if it's needed there, it's probably worth always doing, rather than + # having that test use iimult=2. And definitions of Nyquist are somewhat + # confusing, so it's possible that we should expect to need a factor of + # 2 smaller than nyquist for the pixel scale. :-S + if iimult is not None: + iiscale /= iimult + + # Prevent infinite recursive loop by using ChromaticObject.drawImage() on a + # ChromaticConvolution. + if isinstance(insep_obj, ChromaticConvolution): + effective_prof_image = ChromaticObject.drawImage( + insep_obj, bandpass, scale=iiscale, + integrator=integrator, method='no_pixel') + else: + effective_prof_image = insep_obj.drawImage( + bandpass, scale=iiscale, integrator=integrator, + method='no_pixel') + + return InterpolatedImage(effective_prof_image, gsparams=gsparams) + +
[docs] @staticmethod + def resize_effective_prof_cache(maxsize): + """Resize the cache containing effective profiles. + + These are wavelength-integrated products of separable profile SEDs, inseparable profiles, + and Bandpasses) used by `ChromaticConvolution.drawImage`. + + Parameters: + maxsize: The new number of effective profiles to cache. + """ + ChromaticConvolution._effective_prof_cache.resize(maxsize)
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticConvolution) and + self.obj_list == other.obj_list and + self._gsparams == other._gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.ChromaticConvolution", tuple(self.obj_list), self._gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.ChromaticConvolution(%r, gsparams=%r, propagate_gsparams=%r)'%( + self.obj_list, self._gsparams, self._propagate_gsparams) + + def __str__(self): + str_list = [ str(obj) for obj in self.obj_list ] + return 'galsim.ChromaticConvolution([%s])'%', '.join(str_list) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('sed',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + def _approxWavelength(self, wave): + # If any of the components prefer a different wavelength, use that for all. + achrom_objs = [] + for k, obj in enumerate(self.obj_list): + new_wave, aobj = obj._approxWavelength(wave) + if new_wave != wave: + # Break the loop and use evaluateAtWavelength for everything else. + achrom_objs = ([o.evaluateAtWavelength(new_wave) for o in self.obj_list[:k]] + + [aobj] + + [o.evaluateAtWavelength(new_wave) for o in self.obj_list[k+1:]]) + break + else: + achrom_objs.append(aobj) + + return new_wave, convolve.Convolve(achrom_objs, gsparams=self._gsparams, + propagate_gsparams=self._propagate_gsparams) + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength ``wave``. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return convolve.Convolve([obj.evaluateAtWavelength(wave) for obj in self.obj_list], + gsparams=self._gsparams, propagate_gsparams=self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + raise GalSimNotImplementedError("ChromaticConvolution cannot be used as a PhotonOp") + +
[docs] def drawImage(self, bandpass, image=None, integrator='quadratic', iimult=None, **kwargs): + """Optimized draw method for the `ChromaticConvolution` class. + + Works by finding sums of profiles which include separable portions, which can then be + integrated before doing any convolutions, which are pushed to the end. + + This method uses a cache to avoid recomputing 'effective' profiles, which are the + wavelength-integrated products of inseparable profiles, the spectral components of + separable profiles, and the bandpass. Because the cache size is finite, users may find + that it is more efficient when drawing many images to group images using the same + SEDs, bandpasses, and inseparable profiles (generally PSFs) together in order to hit the + cache more often. The default cache size is 10, but may be resized using the + `ChromaticConvolution.resize_effective_prof_cache` method. + + Parameters: + bandpass: A `Bandpass` object representing the filter against which to + integrate. + image: Optionally, the `Image` to draw onto. (See `GSObject.drawImage` + for details.) [default: None] + integrator: When doing the exact evaluation of the profile, this argument should + be one of the image integrators from galsim.integ, or a string + 'trapezoidal', 'midpoint', or 'quadratic', in which case the routine + will use a `SampleIntegrator` or `ContinuousIntegrator` depending on + whether or not the object has a ``wave_list``. [default: 'quadratic', + which will try to select an appropriate integrator using the + quadratic integration rule automatically.] + iimult: Oversample any intermediate `InterpolatedImage` created to hold + effective profiles by this amount. [default: None] + **kwargs: For all other kwarg options, see `GSObject.drawImage`. + + Returns: + the drawn `Image`. + """ + # Store the last bandpass used. + self._last_bp = bandpass + if self.sed.dimensionless: + raise GalSimSEDError("Can only draw ChromaticObjects with spectral SEDs.", self.sed) + # `ChromaticObject.drawImage()` can just as efficiently handle separable cases. + if self.separable: + image = ChromaticObject.drawImage(self, bandpass, image=image, **kwargs) + self._last_wcs = image.wcs + return image + + # Now split up any `ChromaticSum`s: + # This is the tricky part. Some notation first: + # int(f(x,y,lambda)) denotes the integral over wavelength of chromatic surface + # brightness profile f(x,y,lambda). + # (f1 * f2) denotes the convolution of surface brightness profiles f1 & f2. + # (f1 + f2) denotes the addition of surface brightness profiles f1 & f2. + # + # In general, chromatic s.b. profiles can be classified as either separable or inseparable, + # depending on whether they can be factored into spatial and spectral components or not. + # Write separable profiles as g(x,y) * h(lambda), and leave inseparable profiles as + # f(x,y,lambda). + # We will suppress the arguments `x`, `y`, `lambda`, hereforward, but generally an `f` + # refers to an inseparable profile, a `g` refers to the spatial part of a separable + # profile, and an `h` refers to the spectral part of a separable profile. + # + # Now, analyze a typical scenario, a bulge+disk galaxy model (each of which is separable, + # e.g., an SED times an exponential profile for the disk, and a different SED times a DeV + # profile for the bulge). Suppose the PSF is inseparable. (Chromatic PSF's will generally + # be inseparable since we usually think of the spatial part of the PSF being normalized to + # unit integral for any fixed wavelength.) Say there's also an achromatic pixel to + # convolve with. + # The formula for this might look like: + # + # img = int((bulge + disk) * PSF * pix) + # = int((g1 h1 + g2 h2) * f3 * g4) # note pix is lambda-independent + # = int(g1 h1 * f3 * g4 + g2 h2 * f3 * g4) # distribute the + over the * + # = int(g1 h1 * f3 * g4) + int(g2 h2 * f3 * g4) # distribute the + over the int + # = g1 * g4 * int(h1 f3) + g2 * g4 * int(h2 f3) # move lambda-indep terms out of int + # + # The result is that the integral is now inside the convolution, meaning we only have to + # compute two convolutions instead of a convolution for each wavelength at which we evaluate + # the integrand. This technique, making an "effective" PSF profile for each of the bulge + # and disk, is a significant time savings in most cases. + # + # In general, we make effective profiles by splitting up ChromaticSum items and collecting + # the inseparable terms on which to do integration first, and then finish with convolution + # last. + + phot = kwargs.get('method', 'auto') == 'phot' + + # This optimization is not actually helpful when photon shooting. + if not phot: + # Here is the logic to turn + # int((g1 h1 + g2 h2) * f3) + # -> g1 * int(h1 f3) + g2 * int(h2 f3) + for i, obj in enumerate(self.obj_list): + if isinstance(obj, ChromaticSum): + # say obj.obj_list = [A,B,C], where obj is a ChromaticSum object + # Assemble temporary list of convolutants excluding the ChromaticSum in question. + tmplist = list(self.obj_list) + del tmplist[i] # remove ChromaticSum object from obj_list + tmplist.append(obj.obj_list[0]) # Append first summand, i.e., A, to convolutants + # now draw this image + tmpobj = ChromaticConvolution(tmplist) + add_to_image = kwargs.pop('add_to_image', False) + image = tmpobj.drawImage(bandpass, image=image, integrator=integrator, + iimult=iimult, add_to_image=add_to_image, **kwargs) + # Now add in the rest of the summands in turn, i.e., B and C + for summand in obj.obj_list[1:]: + tmplist = list(self.obj_list) + del tmplist[i] + tmplist.append(summand) + tmpobj = ChromaticConvolution(tmplist) + # add to previously started image + _remove_setup_kwargs(kwargs) + image = tmpobj.drawImage(bandpass, image=image, integrator=integrator, + iimult=iimult, add_to_image=True, **kwargs) + # Return the image here, breaking the loop early. If there are two ChromaticSum + # instances in obj_list, then the above procedure will repeat in the recursion, + # effectively distributing the multiplication over both sums. + self._last_wcs = image.wcs + return image + + # If program gets this far, the objects in obj_list should be atomic (non-ChromaticSum + # and non-ChromaticConvolution). (The latter case was dealt with in the constructor.) + + # setup output image (semi-arbitrarily using the bandpass effective wavelength) + wave0, prof0 = self._fiducial_profile(bandpass) + image = prof0.drawImage(image=image, setup_only=True, **kwargs) + _remove_setup_kwargs(kwargs) + + # If we are photon shooting, then we can move all non-spectral objects to the photon_ops + # list and deal with them that way. This both more accurate and more efficient for most + # chromatic PSFs. + if phot: + psfs = [obj for obj in self.obj_list if obj.dimensionless] + gals = [obj for obj in self.obj_list if obj.spectral] + assert len(gals) == 1 # Should have been checked by constructor. + gal = gals[0] + kwargs['photon_ops'] = psfs + kwargs.get('photon_ops', []) + + # Need to calculate n_photons now using the fiducial profile, not gal, in case the + # PSF has an interpolated image (e.g. OpticalPSF) which needs more photons. + flux = self.calculateFlux(bandpass) + prof1 = prof0.withFlux(flux) + n_photons = kwargs.pop('n_photons', 0) + poisson_flux = kwargs.pop('poisson_flux', n_photons == 0.) + max_extra_noise = kwargs.pop('max_extra_noise', 0.) + rng = BaseDeviate(kwargs.get('rng', None)) + n_photons, g = prof1._calculate_nphotons(n_photons, poisson_flux, max_extra_noise, rng) + gal *= g + return gal.drawImage(bandpass, image=image, integrator=integrator, + n_photons=n_photons, **kwargs) + + # Separate convolutants into a Convolution of inseparable profiles multiplied by the + # wavelength-dependent normalization of separable profiles, and the achromatic part of + # separable profiles. + insep_obj = [obj for obj in self.obj_list if not obj.separable] + + # Note that len(insep_obj) > 0, since purely separable ChromaticConvolutions were + # already handled above. + # Don't wrap in Convolution if not needed. Single item can draw itself better than + # Convolution can. + if len(insep_obj) == 1: + insep_obj = insep_obj[0] + else: + insep_obj = convolve.Convolve(insep_obj, gsparams=self._gsparams, + propagate_gsparams=self._propagate_gsparams) + + sep_profs = [] + for obj in self.obj_list: + if not obj.separable: + continue + wave0, prof0 = obj._fiducial_profile(bandpass) + sep_profs.append(prof0 / obj.sed(wave0)) + insep_obj *= obj.sed + + # Collapse inseparable profiles and chromatic normalizations into one effective profile + # Note that at this point, insep_obj.sed should *not* be None. + effective_prof = ChromaticConvolution._effective_prof_cache( + insep_obj, bandpass, iimult, integrator, self._gsparams) + + # append effective profile to separable profiles (which should all be GSObjects) + sep_profs.append(effective_prof) + # finally, convolve and draw. + final_prof = convolve.Convolve(sep_profs, gsparams=self._gsparams, + propagate_gsparams=self._propagate_gsparams) + image = final_prof.drawImage(image=image, **kwargs) + self._last_wcs = image.wcs + return image
+ + @lazy_property + def noise(self): + """An estimate of the noise already in the profile. + """ + # Condition for being able to propagate noise: + # Exactly one of the convolutants has a .covspec attribute. + covspecs = [ obj.covspec for obj in self.obj_list if hasattr(obj, 'covspec') ] + if len(covspecs) != 1: + raise GalSimError("Cannot compute noise for ChromaticConvolution for which number " + "of convolutants with covspec attribute is not 1.") + if not hasattr(self, '_last_bp'): + raise GalSimError("Cannot compute noise for ChromaticConvolution until after drawImage " + "has been called.") + covspec = covspecs[0] + other = convolve.Convolve([obj for obj in self.obj_list if not hasattr(obj, 'covspec')]) + return covspec.toNoise(self._last_bp, other, self._last_wcs) # rng=?
+ + +ChromaticConvolution._effective_prof_cache = utilities.LRU_Cache( + ChromaticConvolution._get_effective_prof, maxsize=10) + + +
[docs]class ChromaticDeconvolution(ChromaticObject): + """A class for deconvolving a `ChromaticObject`. + + The ChromaticDeconvolution class represents a wavelength-dependent deconvolution kernel. + + You may also specify a gsparams argument. See the docstring for `GSParams` for more + information about this option. Note: if ``gsparams`` is unspecified (or None), then the + ChromaticDeconvolution instance inherits the same `GSParams` as the object being deconvolved. + + This is the type returned from `galsim.Deconvolve` if the argument is a `ChromaticObject`. + This is the normal way to construct this class. + + Parameters: + obj: The object to deconvolve. + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, gsparams=None, propagate_gsparams=True): + if not obj.sed.dimensionless: + raise GalSimSEDError("Cannot deconvolve by spectral ChromaticObject.", obj.sed) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._obj = obj.withGSParams(self._gsparams) + else: + self._obj = obj + self.separable = obj.separable + self.interpolated = obj.interpolated + if self.interpolated: + self.deinterpolated = ChromaticDeconvolution(self._obj.deinterpolated, self._gsparams, + self._propagate_gsparams) + else: + self.deinterpolated = self + + @property + def sed(self): + return SED(lambda w: self._obj.sed(w)**-1, 'nm', '1') + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj = self._obj.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticDeconvolution) and + self._obj == other._obj and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.ChromaticDeconvolution", self._obj, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.ChromaticDeconvolution(%r, gsparams=%r, propagate_gsparams=%r)'%( + self._obj, self.gsparams, self._propagate_gsparams) + + def __str__(self): + return 'galsim.ChromaticDeconvolution(%s)'%self._obj + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength ``wave``. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return convolve.Deconvolve(self._obj.evaluateAtWavelength(wave), gsparams=self.gsparams, + propagate_gsparams=self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + raise GalSimNotImplementedError("ChromaticDeconvolution cannot use method='phot'")
+ + +
[docs]class ChromaticAutoConvolution(ChromaticObject): + """A special class for convolving a `ChromaticObject` with itself. + + It is equivalent in functionality to ``galsim.Convolve([obj,obj])``, but takes advantage of + the fact that the two profiles are the same for some efficiency gains. + + This is the type returned from `galsim.AutoConvolve` if the argument is a `ChromaticObject`. + This is the normal way to construct this class. + + Parameters: + obj: The object to be convolved with itself. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the objects have hard + edges.] + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, real_space=None, gsparams=None, propagate_gsparams=True): + if not obj.sed.dimensionless: + raise GalSimSEDError("Cannot autoconvolve spectral ChromaticObject.", obj.sed) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._obj = obj.withGSParams(self._gsparams) + else: + self._obj = obj + self._real_space = real_space + self.separable = obj.separable + self.interpolated = obj.interpolated + if self.interpolated: + self.deinterpolated = ChromaticAutoConvolution(self._obj.deinterpolated, real_space, + self._gsparams, self._propagate_gsparams) + else: + self.deinterpolated = self + + @lazy_property + def sed(self): + return self._obj.sed * self._obj.sed + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj = self._obj.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticAutoConvolution) and + self._obj == other._obj and + self._real_space == other._real_space and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.ChromaticAutoConvolution", self._obj, self._real_space, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return ('galsim.ChromaticAutoConvolution(%r, real_space=%r, gsparams=%r, ' + 'propagate_gsparams=%r)')%( + self._obj, self._real_space, self.gsparams, self._propagate_gsparams) + + def __str__(self): + return 'galsim.ChromaticAutoConvolution(%s)'%self._obj + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('sed',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength ``wave``. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return convolve.AutoConvolve(self._obj.evaluateAtWavelength(wave), self._real_space, + self._gsparams, self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + raise GalSimNotImplementedError("ChromaticAutoConvolution cannot be used as a PhotonOp")
+ + +
[docs]class ChromaticAutoCorrelation(ChromaticObject): + """A special class for correlating a `ChromaticObject` with itself. + + It is equivalent in functionality to:: + + galsim.Convolve([obj,obj.rotate(180.*galsim.degrees)]) + + but takes advantage of the fact that the two profiles are the same for some efficiency gains. + + This is the type returned from `galsim.AutoCorrelate` if the argument is a `ChromaticObject`. + This is the normal way to construct this class. + + Parameters: + obj: The object to be convolved with itself. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the objects have hard + edges.] + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, real_space=None, gsparams=None, propagate_gsparams=True): + if not obj.sed.dimensionless: + raise GalSimSEDError("Cannot autocorrelate spectral ChromaticObject.", obj.sed) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._obj = obj.withGSParams(self._gsparams) + else: + self._obj = obj + self._real_space = real_space + self.separable = obj.separable + self.interpolated = obj.interpolated + if self.interpolated: + self.deinterpolated = ChromaticAutoCorrelation(self._obj.deinterpolated, + self._real_space, self._gsparams, + self._propagate_gsparams) + else: + self.deinterpolated = self + + @lazy_property + def sed(self): + return self._obj.sed * self._obj.sed + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj = self._obj.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticAutoCorrelation) and + self._obj == other._obj and + self._real_space == other._real_space and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.ChromaticAutoCorrelation", self._obj, self._real_space, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return ('galsim.ChromaticAutoCorrelation(%r, real_space=%r, gsparams=%r, ' + 'propagate_gsparams=%r)')%( + self._obj, self._real_space, self.gsparams, self._propagate_gsparams) + + def __str__(self): + return 'galsim.ChromaticAutoCorrelation(%s)'%self._obj + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('sed',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength ``wave``. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return convolve.AutoCorrelate(self._obj.evaluateAtWavelength(wave), self._real_space, + self.gsparams, self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + raise GalSimNotImplementedError("ChromaticAutoCorrelation cannot be used as a PhotonOp")
+ + +
[docs]class ChromaticFourierSqrtProfile(ChromaticObject): + """A class for computing the Fourier-space square root of a `ChromaticObject`. + + The ChromaticFourierSqrtProfile class represents a wavelength-dependent Fourier-space square + root of a profile. + + You may also specify a gsparams argument. See the docstring for `GSParams` for more + information about this option. Note: if ``gsparams`` is unspecified (or None), then the + ChromaticFourierSqrtProfile inherits the same `GSParams` as the object being operated on. + + The normal way to use this class is to use the `FourierSqrt` factory function:: + + >>> fourier_sqrt = galsim.FourierSqrt(chromatic_obj) + + If ``chromatic_obj`` is indeed a `ChromaticObject`, then that function will create a + ChromaticFourierSqrtProfile object. + + Parameters: + obj: The object to compute the Fourier-space square root of. + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, gsparams=None, propagate_gsparams=True): + if not obj.sed.dimensionless: + raise GalSimSEDError("Cannot take Fourier sqrt of spectral ChromaticObject.", obj.sed) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._obj = obj.withGSParams(self._gsparams) + else: + self._obj = obj + self.separable = obj.separable + self.interpolated = obj.interpolated + if self.interpolated: + self.deinterpolated = ChromaticFourierSqrtProfile( + self._obj.deinterpolated, self._gsparams, self._propagate_gsparams) + else: + self.deinterpolated = self + + @property + def sed(self): + return SED(lambda w: self._obj.sed(w)**0.5, 'nm', '1') + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj = self._obj.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticFourierSqrtProfile) and + self._obj == other._obj and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.ChromaticFourierSqrtProfile", self._obj, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.ChromaticFourierSqrtProfile(%r, gsparams=%r, propagate_gsparams=%r)'%( + self._obj, self.gsparams, self._propagate_gsparams) + + def __str__(self): + return 'galsim.ChromaticFourierSqrtProfile(%s)'%self._obj + +
[docs] def evaluateAtWavelength(self, wave): + """Evaluate this chromatic object at a particular wavelength ``wave``. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + the monochromatic object at the given wavelength. + """ + return fouriersqrt.FourierSqrt(self._obj.evaluateAtWavelength(wave), self.gsparams, + self._propagate_gsparams)
+ + def _shoot(self, photons, rng): + raise GalSimNotImplementedError("ChromaticFourierSqrtProfile cannot use method='phot'")
+ + +
[docs]class ChromaticOpticalPSF(ChromaticObject): + """A subclass of ChromaticObject meant to represent chromatic optical PSFs. + + Chromaticity plays two roles in optical PSFs. First, it determines the diffraction limit, via + the wavelength/diameter factor. Second, aberrations such as defocus, coma, etc. are typically + defined in physical distances, but their impact on the PSF depends on their size in units of + wavelength. Other aspects of the optical PSF do not require explicit specification of their + chromaticity, e.g., once the obscuration and struts are specified in units of the aperture + diameter, their chromatic dependence gets taken care of automatically. Note that the + ChromaticOpticalPSF implicitly defines diffraction limits in units of ``scale_units``, which by + default are arcsec, but can in principle be set to any of our GalSim angle units. + + When using interpolation to speed up image rendering (see the `ChromaticObject.interpolate` + method for details), the ideal number of wavelengths to use across a given bandpass depends on + the application and accuracy requirements. In general it will be necessary to do a test in + comparison with a more exact calculation to ensure convergence. However, a typical calculation + might use ~10-15 samples across a typical optical bandpass, with ``oversample_fac`` in the range + 1.5-2; for moderate accuracy, ~5 samples across the bandpass and ``oversample_fac=1`` may + suffice. All of these statements assume that aberrations are not very large (typically <~0.25 + waves, which is commonly satisfied by space telescopes); if they are larger than that, then more + stringent settings are required. + + Note that a ChromaticOpticalPSF by itself is NOT the correct thing to use to draw an image of a + star. Stars (and galaxies too, of course) have an `SED` that is not flat. To draw a real star, + you should either multiply the ChromaticOpticalPSF object by an `SED`, or convolve it with a + point source multiplied by an `SED`:: + + >>> psf = galsim.ChromaticOpticalPSF(...) + >>> star = galsim.DeltaFunction() * psf_sed + >>> final_star = galsim.Convolve( [psf, star] ) + >>> final_star.drawImage(bandpass = bp, ...) + + .. note:: + + When geometric_shooting is False (the default), the photon shooting implementation is + only approximately correct with respect to the wavelength dependence. It is also + not particularly fast, since it generates three optical screens to span the wavelength + range and shoots from these (with a subsequent adjustment to improve the accuracy + of this approximation). We expect that most users who want to use photon shooting in + conjunction with this class will prefer to make an InterpolatedChromaticObject + (by calling ``psf.interpolate(...)``), especially if it is a good approximation to + use the same optical PSF for a whole exposure or CCD image, so the setup time for + the interpolation is able to be amortized for many objects. + + Parameters: + lam: Fiducial wavelength for which diffraction limit and aberrations are + initially defined, in nanometers. + diam: Telescope diameter in meters. Either ``diam`` or ``lam_over_diam`` must be + specified. + lam_over_diam: Ratio of (fiducial wavelength) / telescope diameter in units of + ``scale_unit``. Either ``diam`` or ``lam_over_diam`` must be specified. + aberrations: An array of aberrations, in units of fiducial wavelength ``lam``. The + size and format of this array is described in the OpticalPSF docstring. + scale_unit: Units used to define the diffraction limit and draw images. + [default: galsim.arcsec] + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + geometric_shooting: If True, then when drawing using photon shooting, use geometric + optics approximation where the photon angles are derived from the + phase screen gradient. If False, then first draw using Fourier + optics and then shoot from the derived InterpolatedImage. [default: False] + **kwargs: Any other keyword arguments to be passed to OpticalPSF, for example, + related to struts, obscuration, oversampling, etc. See OpticalPSF + docstring for a complete list of options. + """ + _req_params = { 'lam' : float } + _opt_params = { k:v for k,v in OpticalPSF._opt_params.items() if k != 'diam' } + _single_params = [ {'diam' : float, 'lam_over_diam' : float} ] + + def __init__(self, lam, diam=None, lam_over_diam=None, aberrations=None, + scale_unit=None, gsparams=None, **kwargs): + # First, take the basic info. + if scale_unit is None: + scale_unit = arcsec + elif isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + self.scale_unit = scale_unit + self._gsparams = GSParams.check(gsparams) + + # We have to require either diam OR lam_over_diam: + if ( (diam is None and lam_over_diam is None) or + (diam is not None and lam_over_diam is not None) ): + raise GalSimIncompatibleValuesError( + "Need to specify telescope diameter OR wavelength/diam ratio", + diam=diam, lam_over_diam=lam_over_diam) + if diam is not None: + self.lam_over_diam = (1.e-9*lam/diam)*radians/self.scale_unit + self.diam = diam + else: + self.lam_over_diam = lam_over_diam + self.diam = (lam*1e-9/lam_over_diam)*radians/self.scale_unit + self.lam = lam + + if aberrations is not None: + self.aberrations = np.asarray(aberrations) + if len(self.aberrations) < 12: + self.aberrations = np.append(self.aberrations, [0] * (12-len(self.aberrations))) + else: + self.aberrations = np.zeros(12) + + # Pop named aberrations from kwargs so aberrations=[0,0,0,0,1] means the same as + # defocus=1 (w/ all other named aberrations 0). + for i, ab in enumerate(['tip', 'tilt', 'defocus', 'astig1', 'astig2', 'coma1', 'coma2', + 'trefoil1', 'trefoil2', 'spher']): + if ab in kwargs: + self.aberrations[i+2] = kwargs.pop(ab) + + self.fft_sign = kwargs.pop('fft_sign', '+') + if self.fft_sign not in ['+', '-']: + raise GalSimValueError("Invalid fft_sign", self.fft_sign, allowed_values=['+','-']) + + self.geometric_shooting = kwargs.pop('geometric_shooting', False) + + # All kwargs left should be relevant for the Aperture. + # If the pupil plane image is given, then we can make the aperture now to use at all + # wavelengths. Otherwise, we need to make it anew for each wavelength. + if 'aper' in kwargs: + # Then the aperture is given explicitly. + self._aper = kwargs.pop('aper') + if kwargs: + raise TypeError("Invalid kwargs provided in conjunction with aper: %s."%( + tuple(kwargs.keys()))) + self._kwargs = {} + elif 'pupil_plane_im' in kwargs: + # Then the aperture is not wavelength dependent. Make it now. + self._aper = Aperture(self.diam, lam=self.lam, gsparams=self._gsparams, **kwargs) + self._kwargs = {} + else: + self._aper = None + self._kwargs = kwargs + + # This will be the stepk and maxk values at self.lam. But wait until the first time + # we call evaluateAtWavelength to compute them. + self._stepk = self._maxk = None + + # Define the necessary attributes for this ChromaticObject. + self.separable = False + self.interpolated = False + self.deinterpolated = self + + @property + def sed(self): + return SED(1, 'nm', '1') + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticOpticalPSF) and + self.lam == other.lam and + self.lam_over_diam == other.lam_over_diam and + np.array_equal(self.aberrations, other.aberrations) and + self.scale_unit == other.scale_unit and + self.gsparams == other.gsparams and + self.fft_sign == other.fft_sign and + self.geometric_shooting == other.geometric_shooting and + self._aper == other._aper and + self._kwargs == other._kwargs)) + + def __hash__(self): + return hash(("galsim.ChromaticOpticalPSF", self.lam, self.lam_over_diam, + tuple(self.aberrations), self.scale_unit, self.gsparams, + self.fft_sign, self.geometric_shooting, self._aper, + frozenset(self._kwargs.items()))) + + def __repr__(self): + s = 'galsim.ChromaticOpticalPSF(lam=%r, lam_over_diam=%r, aberrations=%r'%( + self.lam, self.lam_over_diam, self.aberrations.tolist()) + if self.scale_unit != arcsec: + s += ', scale_unit=%r'%self.scale_unit + if self.fft_sign == '-': + s += ', fft_sign="-"' + if self.geometric_shooting: + s += ', geometric_shooting=True' + if self._aper is not None: + s += ', aper=%r'%self._aper + else: + for k,v in self._kwargs.items(): + s += ', %s=%r'%(k,v) + s += ', gsparams=%r'%self.gsparams + s += ')' + return s + + def __str__(self): + return 'galsim.ChromaticOpticalPSF(lam=%s, lam_over_diam=%s, aberrations=%s)'%( + self.lam, self.lam_over_diam, self.aberrations.tolist()) + +
[docs] def evaluateAtWavelength(self, wave): + """ + Method to directly instantiate a monochromatic instance of this object. + + Parameters: + wave: Wavelength in nanometers. + """ + if self._aper is not None: + # If aperture is not wavelength-dependent (i.e. given as an image) then we can use + # the same aperture for each wavelength and save making gratuitous copies of a + # large numpy array, which will be identical each time. + aper = self._aper + else: + # Otherwise we need to make the apeture anew each time. + # XXX: This is pretty slow. Maybe should provide an option to use a single + # apeture at the canonical wavelength when using geometric apertures? + # Note: oversampling and pad_factor need different defaults than Aperture defaults. + oversampling = self._kwargs.pop('oversampling', 1.5) + pad_factor = self._kwargs.pop('oversampling', 1.5) + aper = Aperture(diam=self.diam, lam=wave, gsparams=self._gsparams, + oversampling=oversampling, pad_factor=pad_factor, **self._kwargs) + + # The aberrations were in units of wavelength for the fiducial wavelength, so we have to + # convert to units of waves for *this* wavelength. + wave_factor = self.lam / wave + + # stepk and maxk also scale basically with this ratio, and they are fairly slow to + # calculate, so once we've done this once, store the results and just rescale all future + # versions with this factor. + if self._stepk is not None: + return OpticalPSF( + lam=wave, diam=self.diam, + aberrations=self.aberrations*wave_factor, scale_unit=self.scale_unit, + _force_stepk=self._stepk*wave_factor, _force_maxk=self._maxk*wave_factor, + gsparams=self.gsparams, aper=aper) + else: + ret = OpticalPSF( + lam=wave, diam=self.diam, + aberrations=self.aberrations*wave_factor, scale_unit=self.scale_unit, + gsparams=self.gsparams, aper=aper) + self._stepk = ret.stepk / wave_factor + self._maxk = ret.maxk / wave_factor + return ret
+ + def _shoot(self, photons, rng): + if self.geometric_shooting: + # In the geometric shooting approximation, the lambda factors out, and this + # becomes the same kind of calculation we did for ChromaticAiry. + # Use the mean wavelength for the base profile. + mean_wave = np.mean(photons.wavelength) + obj = self.evaluateAtWavelength(mean_wave) + obj._shoot(photons, rng) + factor = photons.wavelength / mean_wave + photons.scaleXY(factor) + else: + # When not using geometric shooting, the following isn't exact. + # The exact method would involve doing the fourier transform for each wavelength + # in the photon list. Obviously, that's not tenable. + # So instead, we shoot with the same random seed for 3 different profiles: + # The minimum wavelength, the mean, and the maximum. + # Then interpolate between the results for each photon. + # This should (hopefully!) be good enough for most use cases if the bandpass + # isn't extremely wide and the wavelength dependence is modest over the range. + + wave1 = np.min(photons.wavelength) + wave2 = np.mean(photons.wavelength) + wave3 = np.max(photons.wavelength) + + prof1 = self.evaluateAtWavelength(wave1) + if wave1 == wave3: + # Interjection at this point -- if min=mean=max, then this is easy. + return prof1._shoot(photons, rng) + else: + # Otherwise we're ok dividing by wave2-wave1 and wave3-wave2 below. + assert wave2 != wave1 + assert wave3 != wave2 + prof2 = self.evaluateAtWavelength(wave2) + prof3 = self.evaluateAtWavelength(wave3) + # For each photon, shoot using one of these profiles according to the given + # wavelength. + # For wavelenghts with w1 < w < w2, select from prof1 or prof2 with probabilities + # P(use prof1) = (w2-w)/(w2-w1) + # P(use prof2) = (w-w1)/(w2-w1) + # Likewise when w2 < w < w3: + # P(use prof2) = (w3-w)/(w3-w2) + # P(use prof3) = (w-w2)/(w3-w2) + u = np.empty(len(photons)) + UniformDeviate(rng).generate(u) + w = photons.wavelength + use_p1 = (wave1 <= w) & (w < wave2) & (u <= (wave2-w)/(wave2-wave1)) + use_p2 = (wave1 <= w) & (w < wave2) & (u > (wave2-w)/(wave2-wave1)) + use_p2 |= (wave2 <= w) & (w <= wave3) & (u <= (wave3-w)/(wave3-wave2)) + use_p3 = (wave2 <= w) & (w <= wave3) & (u > (wave3-w)/(wave3-wave2)) + assert np.all(use_p1 | use_p2 | use_p3) + assert not np.any(use_p1 & use_p2) + assert not np.any(use_p2 & use_p3) + assert not np.any(use_p1 & use_p3) + + temp1 = PhotonArray(np.sum(use_p1)) + temp2 = PhotonArray(np.sum(use_p2)) + temp3 = PhotonArray(np.sum(use_p3)) + temp1._copyFrom(photons, slice(None), use_p1, do_xy=False, do_flux=False) + temp2._copyFrom(photons, slice(None), use_p2, do_xy=False, do_flux=False) + temp3._copyFrom(photons, slice(None), use_p3, do_xy=False, do_flux=False) + prof1._shoot(temp1, rng) + prof2._shoot(temp2, rng) + prof3._shoot(temp3, rng) + temp1.scaleXY(w[use_p1]/wave1) + temp1.scaleFlux(len(temp1)/len(photons)) + temp2.scaleXY(w[use_p2]/wave2) + temp2.scaleFlux(len(temp2)/len(photons)) + temp3.scaleXY(w[use_p3]/wave3) + temp3.scaleFlux(len(temp3)/len(photons)) + photons._copyFrom(temp1, use_p1, slice(None)) + photons._copyFrom(temp2, use_p2, slice(None)) + photons._copyFrom(temp3, use_p3, slice(None))
+ + +
[docs]class ChromaticAiry(ChromaticObject): + """A subclass of `ChromaticObject` meant to represent chromatic Airy profiles. + + For more information about the basics of Airy profiles, please see `galsim.Airy`. + + This class is a chromatic representation of Airy profiles, including the wavelength-dependent + diffraction limit. One can also get this functionality using the `ChromaticOpticalPSF` class, + but that class includes additional complications beyond a simple Airy profile, and thus has a + more complicated internal representation. For users who only want a (possibly obscured) Airy + profile, the ChromaticAiry class is likely to be a less computationally expensive and more + accurate option. + + Parameters: + lam: Fiducial wavelength for which diffraction limit is initially defined, in + nanometers. + diam: Telescope diameter in meters. Either ``diam`` or ``lam_over_diam`` must be + specified. + lam_over_diam: Ratio of (fiducial wavelength) / telescope diameter in units of + ``scale_unit``. Either ``diam`` or ``lam_over_diam`` must be specified. + scale_unit: Units used to define the diffraction limit and draw images. + [default: galsim.arcsec] + gsparams: An optional `GSParams` argument. See the docstring for `GSParams` for + details. [default: None] + **kwargs: Any other keyword arguments to be passed to `Airy`: either flux, or + gsparams. See `galsim.Airy` docstring for a complete description of these + options. + """ + _req_params = { 'lam' : float } + _opt_params = { 'scale_unit' : str } + _single_params = [ {'diam' : float, 'lam_over_diam' : float} ] + + def __init__(self, lam, diam=None, lam_over_diam=None, scale_unit=None, gsparams=None, + **kwargs): + # First, take the basic info. + # We have to require either diam OR lam_over_diam: + if scale_unit is None: + scale_unit = arcsec + elif isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + self.scale_unit = scale_unit + self._gsparams = GSParams.check(gsparams) + + if ( (diam is None and lam_over_diam is None) or + (diam is not None and lam_over_diam is not None) ): + raise GalSimIncompatibleValuesError( + "Need to specify telescope diameter OR wavelength/diam ratio", + diam=diam, lam_over_diam=lam_over_diam) + if diam is not None: + self.lam_over_diam = (1.e-9*lam/diam)*radians/self.scale_unit + else: + self.lam_over_diam = float(lam_over_diam) + self.lam = float(lam) + + self.kwargs = kwargs + + # Define the necessary attributes for this ChromaticObject. + self.separable = False + self.interpolated = False + self.deinterpolated = self + + @property + def sed(self): + return SED(1, 'nm', '1') + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._gsparams + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticAiry) and + self.lam == other.lam and + self.lam_over_diam == other.lam_over_diam and + self.scale_unit == other.scale_unit and + self.gsparams == other.gsparams and + self.kwargs == other.kwargs)) + + def __hash__(self): + return hash(("galsim.ChromaticAiry", self.lam, self.lam_over_diam, self.scale_unit, + self.gsparams, frozenset(self.kwargs.items()))) + + def __repr__(self): + s = 'galsim.ChromaticAiry(lam=%r, lam_over_diam=%r'%(self.lam, self.lam_over_diam) + if self.scale_unit != arcsec: + s += ', scale_unit=%r'%self.scale_unit + for k,v in self.kwargs.items(): + s += ', %s=%r'%(k,v) + s += ', gsparams=%r'%self.gsparams + s += ')' + return s + + def __str__(self): + return 'galsim.ChromaticAiry(lam=%s, lam_over_diam=%s)'%(self.lam, self.lam_over_diam) + +
[docs] def evaluateAtWavelength(self, wave): + """ + Method to directly instantiate a monochromatic instance of this object. + + Parameters: + wave: Wavelength in nanometers. + """ + # We need to rescale the stored lam/diam by the ratio of input wavelength to stored fiducial + # wavelength. + ret = Airy( + lam_over_diam=self.lam_over_diam*(wave/self.lam), scale_unit=self.scale_unit, + gsparams=self.gsparams, **self.kwargs) + return ret
+ + def _shoot(self, photons, rng): + # Start with the convolution at the reference wavelength + obj = Airy(lam_over_diam=self.lam_over_diam, scale_unit=self.scale_unit, + gsparams=self.gsparams, **self.kwargs) + obj._shoot(photons, rng) + + # Now adjust the positions according to the wavelengths + factor = photons.wavelength / self.lam + photons.scaleXY(factor)
+ + +def _findWave(wave_list, wave): + # Helper routine to search a sorted NumPy array of wavelengths (not necessarily evenly spaced) + # to find where a particular wavelength ``wave`` would fit in, and return the index below along + # with the fraction of the way to the next entry in the array. + lower_idx = np.searchsorted(wave_list, wave)-1 + # There can be edge issues, so watch out for that: + if lower_idx < 0: lower_idx = 0 + if lower_idx > len(wave_list)-1: lower_idx = len(wave_list)-1 + + frac = (wave-wave_list[lower_idx]) / (wave_list[lower_idx+1]-wave_list[lower_idx]) + return lower_idx, frac + +def _linearInterp(list, frac, lower_idx): + # Helper routine for linear interpolation between values in lists (which could be lists of + # images, just not numbers, hence the need to avoid a LookupTable). Not really worth + # splitting out on its own now, but could be useful to have separate routines for the + # interpolation later on if we want to enable something other than linear interpolation. + return frac*list[lower_idx+1] + (1.-frac)*list[lower_idx] + +def _remove_setup_kwargs(kwargs): + # Helper function to remove from kwargs anything that is only used for setting up image and that + # might otherwise interfere with drawImage. + kwargs.pop('dtype', None) + kwargs.pop('scale', None) + kwargs.pop('wcs', None) + kwargs.pop('nx', None) + kwargs.pop('ny', None) + kwargs.pop('bounds', None) + +# Put these at the bottom to avoid circular import errors. +from . import convolve +from . import fouriersqrt +from . import transform +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/bandpass.html b/docs/_build/html/_modules/galsim/config/bandpass.html new file mode 100644 index 00000000000..9bb8685de1e --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/bandpass.html @@ -0,0 +1,298 @@ + + + + + + galsim.config.bandpass — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.bandpass

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+from astropy.units import Quantity, Unit
+
+from .util import LoggerWrapper
+from .value import ParseValue, GetAllParams, GetIndex
+from .input import RegisterInputConnectedType
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..bandpass import Bandpass
+from ..utilities import basestring
+
+# This module-level dict will store all the registered Bandpass types.
+# See the RegisterBandpassType function at the end of this file.
+# The keys are the (string) names of the Bandpass types, and the values will be builders that know
+# how to build the Bandpass object.
+valid_bandpass_types = {}
+
+def BuildBandpass(config, key, base, logger=None):
+    """Read the Bandpass parameters from config[key] and return a constructed Bandpass object.
+
+    Parameters:
+        config:     A dict with the configuration information.
+        key:        The key name in config indicating which object to build.
+        base:       The base dict of the configuration.
+        logger:     Optionally, provide a logger for logging debug statements. [default: None]
+
+    Returns:
+        (bandpass, safe) where bandpass is a Bandpass instance, and safe is whether it is safe to
+                         reuse.
+    """
+    logger = LoggerWrapper(logger)
+    logger.debug('obj %d: Start BuildBandpass key = %s',base.get('obj_num',0),key)
+
+    param = config[key]
+
+    # Check for direct value, else get the bandpass type
+    if isinstance(param, Bandpass):
+        return param, True
+    elif isinstance(param, basestring) and (param[0] == '$' or param[0] == '@'):
+        return ParseValue(config, key, base, None)
+    elif isinstance(param, dict):
+        bandpass_type = param.get('type', 'FileBandpass')
+    else:
+        raise GalSimConfigError("%s must be either a Bandpass or a dict"%key)
+
+    # For these two, just do the usual ParseValue function.
+    if bandpass_type in ('Eval', 'Current'):
+        return ParseValue(config, key, base, None)
+
+    # Check if we can use the current cached object
+    index, index_key = GetIndex(param, base)
+    if 'current' in param:
+        cbandpass, csafe, cvalue_type, cindex, cindex_key = param['current']
+        if cindex == index:
+            logger.debug('obj %d: The Bandpass object is already current', base.get('obj_num',0))
+            logger.debug('obj %d: index_key = %s, index = %d',base.get('obj_num',0),
+                         cindex_key, cindex)
+            return cbandpass, csafe
+
+    if bandpass_type not in valid_bandpass_types:
+        raise GalSimConfigValueError("Invalid bandpass.type.", bandpass_type,
+                                     list(valid_bandpass_types.keys()))
+    logger.debug('obj %d: Building bandpass type %s', base.get('obj_num',0), bandpass_type)
+    builder = valid_bandpass_types[bandpass_type]
+    bandpass, safe = builder.buildBandpass(param, base, logger)
+    logger.debug('obj %d: bandpass = %s', base.get('obj_num',0), bandpass)
+
+    param['current'] = bandpass, safe, Bandpass, index, index_key
+
+    return bandpass, safe
+
+
+
[docs]class BandpassBuilder: + """A base class for building Bandpass objects. + + The base class defines the call signatures of the methods that any derived class should follow. + """ +
[docs] def buildBandpass(self, config, base, logger): + """Build the Bandpass based on the specifications in the config dict. + + Note: Sub-classes must override this function with a real implementation. + + Parameters: + config: The configuration dict for the bandpass type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed Bandpass object. + """ + raise NotImplementedError("The %s class has not overridden buildBandpass"%self.__class__)
+ + +class FileBandpassBuilder(BandpassBuilder): + """A class for loading a Bandpass from a file + + FileBandpass expects the following parameters: + + file_name (str) The file to load (required) + wave_type (str or Quantity) The units (nm or Ang) of the wavelengths in the file (required) + thin (float) A relative error to use for thinning the file (default: None) + blue_limit (float or Quantity) A cutoff wavelength on the blue side (default: None) + red_limit (float or Quantity) A cutoff wavelength on the red side (default: None) + zeropoint (float or str) A zeropoint to use (default: None) + """ + def buildBandpass(self, config, base, logger): + """Build the Bandpass based on the specifications in the config dict. + + Parameters: + config: The configuration dict for the bandpass type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed Bandpass object. + """ + logger = LoggerWrapper(logger) + + req = { + 'file_name': str, + 'wave_type': (str, Unit), + } + opt = { + 'thin' : float, + 'blue_limit' : (float, Quantity), + 'red_limit' : (float, Quantity), + 'zeropoint': (float, str) + } + + kwargs, safe = GetAllParams(config, base, req=req, opt=opt) + + file_name = kwargs.pop('file_name') + thin = kwargs.pop('thin', None) + zeropoint = kwargs.pop('zeropoint', None) + + logger.info("Reading Bandpass file: %s",file_name) + bandpass = Bandpass(file_name, **kwargs) + if zeropoint: + bandpass = bandpass.withZeropoint(zeropoint) + if thin: + bandpass = bandpass.thin(thin) + + return bandpass, safe + +
[docs]def RegisterBandpassType(bandpass_type, builder, input_type=None): + """Register a bandpass type for use by the config apparatus. + + Parameters: + bandpass_type: The name of the type in the config dict. + builder: A builder object to use for building the Bandpass object. It should + be an instance of a subclass of BandpassBuilder. + input_type: If the Bandpass builder utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_bandpass_types[bandpass_type] = builder + RegisterInputConnectedType(input_type, bandpass_type)
+ +RegisterBandpassType('FileBandpass', FileBandpassBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/extra.html b/docs/_build/html/_modules/galsim/config/extra.html new file mode 100644 index 00000000000..3f0bc403090 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/extra.html @@ -0,0 +1,652 @@ + + + + + + galsim.config.extra — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.extra

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import os
+from multiprocessing.managers import ListProxy, DictProxy
+
+from .util import LoggerWrapper, SetDefaultExt, RetryIO, SafeManager, single_threaded
+from .value import ParseValue
+from .image import GetNObjForImage
+from ..utilities import ensure_dir
+from ..errors import GalSimConfigValueError, GalSimConfigError
+from ..fits import writeMulti
+
+# This file handles the processing of extra output items in addition to the primary output file
+# in config['output']. The ones that are defined natively in GalSim are psf, weight, badpix,
+# and truth.  See extra_*.py for the specific functions for each of these.
+
+# This module-level dict will store all the registered "extra" output types.
+# See the RegisterExtraOutput function at the end of this file.
+# The keys will be the (string) names of the extra output types, and the values will be
+# builder classes that will perform the different processing functions.
+valid_extra_outputs = {}
+
+
[docs]def SetupExtraOutput(config, logger=None): + """ + Set up the extra output items as necessary, including building Managers for the work + space so they can work safely in multi-processing mode. Each builder will be placed in + config['extra_builder'][key] where key is the key in galsim.config.valid_extra_outputs. + + Parameters: + config: The configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + output = config['output'] + file_num = config.get('file_num',0) + + # We'll iterate through this list of keys a few times + all_keys = [ k for k in valid_extra_outputs.keys() if k in output ] + + # We don't need the manager stuff if we (a) are already in a multiprocessing Process, or + # (b) config.image.nproc == 1. + use_manager = ( + 'current_nproc' not in config and + 'image' in config and 'nproc' in config['image'] and + ParseValue(config['image'], 'nproc', config, int)[0] != 1 ) + + if use_manager and 'output_manager' not in config: + class OutputManager(SafeManager): pass + + # We'll use a list and a dict as work space to do the extra output processing. + OutputManager.register('dict', dict, DictProxy) + OutputManager.register('list', list, ListProxy) + # Start up the output_manager + config['output_manager'] = OutputManager() + with single_threaded(): + config['output_manager'].start() + + if 'extra_builder' not in config: + config['extra_builder'] = {} + + # Keep track of any skipped obj_nums, since usually need to treat them differently. + # Note: it would be slightly nicer to use a set here, but there isn't a pre-defined + # multiprocessing.managers.SetProxy type, so we just use a dict like a set by giving + # each item the value None. + if '_skipped_obj_nums' in config: + config['_skipped_obj_nums'].clear() + elif use_manager: + config['_skipped_obj_nums'] = config['output_manager'].dict() + else: + config['_skipped_obj_nums'] = dict() + + for key in all_keys: + logger.debug('file %d: Setup output item %s',file_num,key) + + # Make the work space structures + if use_manager: + data = config['output_manager'].list() + scratch = config['output_manager'].dict() + else: + data = list() + scratch = dict() + + # Make the data list the right length now to avoid issues with multiple + # processes trying to append at the same time. + nimages = config.get('nimages', 1) + for k in range(nimages): + data.append(None) + + # Create the builder, giving it the data and scratch objects as work space. + field = config['output'][key] + builder = valid_extra_outputs[key] + builder.initialize(data, scratch, field, config, logger) + # And store it in the config dict + config['extra_builder'][key] = builder + + logger.debug('file %d: Setup output %s object',file_num,key)
+ + +
[docs]def SetupExtraOutputsForImage(config, logger=None): + """Perform any necessary setup for the extra output items at the start of a new image. + + Parameters: + config: The configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + if 'output' in config: + if 'extra_builder' not in config: + SetupExtraOutput(config, logger) + for key, builder in config['extra_builder'].items(): + field = config['output'][key] + builder.setupImage(field, config, logger)
+ +
[docs]def ProcessExtraOutputsForStamp(config, skip, logger=None): + """Run the appropriate processing code for any extra output items that need to do something + at the end of building each object. + + This gets called after all the object flux is added to the stamp, but before the sky level + and noise are added. + + Parameters: + config: The configuration dict. + skip: Was the drawing of this object skipped? + logger: If given, a logger object to log progress. [default: None] + """ + if 'output' in config: + obj_num = config['obj_num'] + for key, builder in config.get('extra_builder',{}).items(): + field = config['output'][key] + if skip: + config['_skipped_obj_nums'][obj_num] = None + builder.processSkippedStamp(obj_num, field, config, logger) + else: + builder.processStamp(obj_num, field, config, logger)
+ + +
[docs]def ProcessExtraOutputsForImage(config, logger=None): + """Run the appropriate processing code for any extra output items that need to do something + at the end of building each image + + Parameters: + config: The configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + if 'output' in config: + obj_nums = None + for key, builder in config.get('extra_builder',{}).items(): + image_num = config.get('image_num',0) + start_image_num = config.get('start_image_num',0) + if obj_nums is None: + # Figure out which obj_nums were used for this image. + file_num = config.get('file_num',0) + start_obj_num = config.get('start_obj_num',0) + nobj = config.get('nobj', [GetNObjForImage(config,image_num)]) + k = image_num - start_image_num + for i in range(k): + start_obj_num += nobj[i] + obj_nums = range(start_obj_num, start_obj_num+nobj[k]) + # Omit skipped obj_nums + skipped = config['_skipped_obj_nums'] + obj_nums = [ n for n in obj_nums if n not in skipped ] + field = config['output'][key] + index = image_num - start_image_num + builder.processImage(index, obj_nums, field, config, logger)
+ + +
[docs]def WriteExtraOutputs(config, main_data, logger=None): + """Write the extra output objects to files. + + This gets run at the end of the functions for building the regular output files. + + Parameters: + config: The configuration dict. + main_data: The main file data in case it is needed. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + output = config['output'] + if 'retry_io' in output: + ntries = ParseValue(config['output'],'retry_io',config,int)[0] + # This is how many retries. Do at least 1, so ntries is 1 more than this. + ntries = ntries + 1 + else: + ntries = 1 + + if 'dir' in output: + default_dir = ParseValue(output,'dir',config,str)[0] + else: + default_dir = None + + if 'noclobber' in output: + noclobber = ParseValue(output,'noclobber',config,bool)[0] + else: + noclobber = False + + if 'extra_last_file' not in config: + config['extra_last_file'] = {} + + for key, builder in config['extra_builder'].items(): + field = output[key] + if 'file_name' in field: + SetDefaultExt(field, '.fits') + file_name = ParseValue(field,'file_name',config,str)[0] + else: # pragma: no cover This is covered, but codecov thinks it isn't. + # If no file_name, then probably writing to hdu + continue + if 'dir' in field: + dir = ParseValue(field,'dir',config,str)[0] + else: + dir = default_dir + + if dir is not None: + file_name = os.path.join(dir,file_name) + + ensure_dir(file_name) + + if noclobber and os.path.isfile(file_name): + logger.warning('Not writing %s file %d = %s because output.noclobber = True ' + 'and file exists',key,config['file_num'],file_name) + continue + + if config['extra_last_file'].get(key, None) == file_name: + # If we already wrote this file, skip it this time around. + # (Typically this is applicable for psf, where we may only want 1 psf file.) + logger.info('Not writing %s file %d = %s because already written', + key,config['file_num'],file_name) + continue + + # Do any final processing that needs to happen. + builder.ensureFinalized(field, config, main_data, logger) + + # Call the write function, possibly multiple times to account for IO failures. + write_func = builder.writeFile + args = (file_name,field,config,logger) + RetryIO(write_func, args, ntries, file_name, logger) + config['extra_last_file'][key] = file_name + logger.debug('file %d: Wrote %s to %r',config['file_num'],key,file_name)
+ + +
[docs]def AddExtraOutputHDUs(config, main_data, logger=None): + """Write the extra output objects to either HDUS or images as appropriate and add them + to the existing data. + + This gets run at the end of the functions for building the regular output files. + + Note: the extra items must have hdu numbers ranging continuously (in any order) starting + at len(data). Typically first = 1, since the main image is the primary HDU, numbered 0. + + Parameters: + config: The configuration dict. + main_data: The main file data as a list of images. Usually just [image] where + image is the primary image to be written to the output file. + logger: If given, a logger object to log progress. [default: None] + + Returns: + data with additional hdus added + """ + output = config['output'] + hdus = {} + for key, builder in config['extra_builder'].items(): + field = output[key] + if 'hdu' in field: + hdu = ParseValue(field,'hdu',config,int)[0] + else: # pragma: no cover This is covered, but codecov thinks it isn't. + # If no hdu, then probably writing to file + continue + if hdu <= 0 or hdu in hdus: + raise GalSimConfigValueError("hdu is invalid or a duplicate.",hdu) + + # Do any final processing that needs to happen. + builder.ensureFinalized(field, config, main_data, logger) + + # Build the HDU for this output object. + hdus[hdu] = builder.writeHdu(field,config,logger) + + first = len(main_data) + for h in range(first,len(hdus)+first): + if h not in hdus: + raise GalSimConfigError("Cannot skip hdus. No output found for hdu %d"%h) + # Turn hdus into a list (in order) + hdulist = [ hdus[k] for k in range(first,len(hdus)+first) ] + return main_data + hdulist
+ +
[docs]def CheckNoExtraOutputHDUs(config, output_type, logger=None): + """Check that none of the extra output objects want to add to the HDU list. + + Raises an exception if one of them has an hdu field. + + Parameters: + config: The configuration dict. + output_type: A string to use in the error message to indicate which output type + had a problem. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + output = config['output'] + for key in config['extra_builder'].keys(): + field = output[key] + if 'hdu' in field: + hdu = ParseValue(field,'hdu',config,int)[0] + logger.error("Extra output %s requesting to write to hdu %d", key, hdu) + raise GalSimConfigError( + "Output type %s cannot add extra images as HDUs"%output_type)
+ + +
[docs]def GetFinalExtraOutput(key, config, main_data=[], logger=None): + """Get the finalized output object for the given extra output key + + Parameters: + key: The name of the output field in config['output'] + config: The configuration dict. + main_data: The main file data in case it is needed. [default: []] + logger: If given, a logger object to log progress. [default: None] + + Returns: + the final data to be output. + """ + field = config['output'][key] + return config['extra_builder'][key].ensureFinalized(field, config, main_data, logger)
+ +
[docs]class ExtraOutputBuilder: + """A base class for building some kind of extra output object along with the main output. + + The base class doesn't do anything, but it defines the function signatures that a derived + class can override to perform specific processing at any of several steps in the processing. + + The builder gets initialized with a list and and dict to use as work space. + The typical work flow is to save something in scratch[obj_num] for each object built, and then + process them all at the end of each image into data[k]. Then finalize may do something + additional at the end of the processing to prepare the data to be written. + + It's worth remembering that the objects could potentially be processed in a random order if + multiprocessing is being used. The above work flow will thus work regardless of the order + that the stamps and/or images are processed. + + Also, because of how objects are duplicated across processes during multiprocessing, you + should not count on attributes you set in the builder object during the stamp or image + processing stages to be present in the later finalize or write stages. You should write + any information you want to persist into the scratch or data objects, which are set up + to handle the multiprocessing communication properly. + """ +
[docs] def initialize(self, data, scratch, config, base, logger): + """Do any initial setup for this builder at the start of a new output file. + + The base class implementation saves two work space items into self.data and self.scratch + that can be used to safely communicate across multiple processes. + + Parameters: + data: An empty list of length nimages to use as work space. + scratch: An empty dict that can be used as work space. + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + self.data = data + self.scratch = scratch + self.final_data = None
+ +
[docs] def setupImage(self, config, base, logger): + """Perform any necessary setup at the start of an image. + + This function will be called at the start of each image to allow for any setup that + needs to happen at this point in the processing. + + Parameters: + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + pass
+ +
[docs] def processStamp(self, obj_num, config, base, logger): + """Perform any necessary processing at the end of each stamp construction. + + This function will be called after each stamp is built, but before the noise is added, + so the existing stamp image has the true surface brightness profile (unless photon shooting + was used, in which case there will necessarily be noise from that process). + + Remember, these stamps may be processed out of order. Saving data to the scratch dict + is safe, even if multiprocessing is being used. + + Parameters: + obj_num: The object number + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + pass # pragma: no cover (all our ExtraBuilders override this function.)
+ +
[docs] def processSkippedStamp(self, obj_num, config, base, logger): + """Perform any necessary processing for stamps that were skipped in the normal processing. + + This function will be called for stamps that are not built because they were skipped + for some reason. Normally, you would not want to do anything for the extra outputs in + these cases, but in case some module needs to do something in these cases as well, this + method can be overridden. + + Parameters: + obj_num: The object number + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + pass
+ +
[docs] def processImage(self, index, obj_nums, config, base, logger): + """Perform any necessary processing at the end of each image construction. + + This function will be called after each full image is built. + + Remember, these images may be processed out of order. But if using the default + constructor, the data list is already set to be the correct size, so it is safe to + access self.data[k], where k = base['image_num'] - base['start_image_num'] is the + appropriate index to use for this image. + + Parameters: + index: The index in self.data to use for this image. This isn't the image_num + (which can be accessed at base['image_num'] if needed), but rather + an index that starts at 0 for the first image being worked on and + goes up to nimages-1. + obj_nums: The object numbers that were used for this image. + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + pass
+ +
[docs] def ensureFinalized(self, config, base, main_data, logger): + """A helper function in the base class to make sure finalize only gets called once by the + different possible locations that might need it to have been called. + + Parameters: + config: The configuration field for this output object. + base: The base configuration dict. + main_data: The main file data in case it is needed. + logger: If given, a logger object to log progress. [default: None] + + Returns: + the final version of the object. + """ + if self.final_data is None: + self.final_data = self.finalize(config, base, main_data, logger) + return self.final_data
+ +
[docs] def finalize(self, config, base, main_data, logger): + """Perform any final processing at the end of all the image processing. + + This function will be called after all images have been built. + + It returns some sort of final version of the object. In the base class, it just returns + self.data, but depending on the meaning of the output object, something else might be + more appropriate. + + Parameters: + config: The configuration field for this output object. + base: The base configuration dict. + main_data: The main file data in case it is needed. + logger: If given, a logger object to log progress. [default: None] + + Returns: + The final version of the object. + """ + return self.data
+ +
[docs] def writeFile(self, file_name, config, base, logger): + """Write this output object to a file. + + The base class implementation is appropriate for the cas that the result of finalize + is a list of images to be written to a FITS file. + + Parameters: + file_name: The file to write to. + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + writeMulti(self.final_data, file_name)
+ +
[docs] def writeHdu(self, config, base, logger): + """Write the data to a FITS HDU with the data for this output object. + + The base class implementation is appropriate for the cas that the result of finalize + is a list of images of length 1 to be written to a FITS file. + + Parameters: + config: The configuration field for this output object. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + + Returns: + an HDU with the output data. + """ + if len(self.data) != 1: # pragma: no cover (Not sure if this is possible.) + raise GalSimConfigError( + "%d %s images were created. Expecting 1."%(n,self._extra_output_key)) + return self.data[0]
+ + +
[docs]def RegisterExtraOutput(key, builder): + """Register an extra output field for use by the config apparatus. + + The builder parameter should be a subclass of galsim.config.ExtraOutputBuilder. + See that class for the functions that should be defined and their signatures. + Not all functions need to be overridden. If nothing needs to be done at a particular place + in the processing, you can leave the base class function, which doesn't do anything. + + Parameters: + key: The name of the output field in config['output'] + builder: A builder object to use for building the extra output object. + It should be an instance of a subclass of ExtraOutputBuilder. + """ + builder._extra_output_key = key + valid_extra_outputs[key] = builder
+ +# Nothing is registered here. The appropriate items are registered in extra_*.py. +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/extra_badpix.html b/docs/_build/html/_modules/galsim/config/extra_badpix.html new file mode 100644 index 00000000000..cb88d676860 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/extra_badpix.html @@ -0,0 +1,186 @@ + + + + + + galsim.config.extra_badpix — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.extra_badpix

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from .extra import ExtraOutputBuilder, RegisterExtraOutput
+from ..image import ImageS
+
+# The badpix extra output type is currently just a placeholder for when we eventually add
+# defects, saturation, etc.  Now it always just builds an Image with all 0's.
+
+
[docs]class BadPixBuilder(ExtraOutputBuilder): + """This builds a bad pixel mask image to go along with each regular data image. + + There's not much here currently, since GalSim doesn't yet have any image artifacts that + would be appropriate to do something with here. So this is mostly just a placeholder for + when we eventually add defects, saturation, etc. + """ + + # The function to call at the end of building each stamp + def processStamp(self, obj_num, config, base, logger): + # Note: This is just a placeholder for now. Once we implement defects, saturation, etc., + # these features should be marked in the badpix mask. For now though, all pixels = 0. + if base['do_noise_in_stamps']: + badpix_im = ImageS(base['current_stamp'].bounds, wcs=base['wcs'], init_value=0) + self.scratch[obj_num] = badpix_im + + # The function to call at the end of building each image + def processImage(self, index, obj_nums, config, base, logger): + image = ImageS(base['image_bounds'], wcs=base['wcs'], init_value=0) + if len(self.scratch) > 0.: + # If we have been accumulating the variance on the stamps, build the total from them. + # Make sure to only use the stamps for objects in this image. + for obj_num in obj_nums: + stamp = self.scratch[obj_num] + b = stamp.bounds & image.bounds + if b.isDefined(): # pragma: no branch + # This next line is equivalent to: + # image[b] |= stamp[b] + # except that this doesn't work through the proxy. We can only call methods + # that don't start with _. Hence using the more verbose form here. + image.setSubImage(b, image.subImage(b) | stamp[b]) + else: + # Otherwise, build the bad pixel mask here. + # Again, nothing here yet. + pass + self.data[index] = image
+ + +# Register this as a valid extra output +RegisterExtraOutput('badpix', BadPixBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/extra_psf.html b/docs/_build/html/_modules/galsim/config/extra_psf.html new file mode 100644 index 00000000000..e79ab3cd059 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/extra_psf.html @@ -0,0 +1,271 @@ + + + + + + galsim.config.extra_psf — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.extra_psf

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import math
+import numpy as np
+import logging
+
+from .extra import ExtraOutputBuilder, RegisterExtraOutput
+from .stamp import valid_draw_methods
+from .value import ParseValue, GetCurrentValue
+from .util import GetRNG
+from .noise import CalculateNoiseVariance, AddNoise
+from ..image import Image
+from ..position import PositionD
+from ..errors import GalSimConfigValueError, GalSimConfigError
+
+# The psf extra output type builds an Image of the PSF at the same locations as the galaxies.
+
+# The code the actually draws the PSF on a postage stamp.
+def DrawPSFStamp(psf, config, base, bounds, offset, method, logger):
+    """
+    Draw an image using the given psf profile.
+
+    Returns:
+        the resulting image.
+    """
+    if 'draw_method' in config:
+        method = ParseValue(config,'draw_method',base,str)[0]
+        if method not in valid_draw_methods:
+            raise GalSimConfigValueError("Invalid draw_method.", method, valid_draw_methods)
+    else:
+        method = 'auto'
+
+    if 'flux' in config:
+        flux = ParseValue(config,'flux',base,float)[0]
+        psf = psf.withFlux(flux)
+
+    if method == 'phot':
+        rng = GetRNG(config, base)
+        n_photons = psf.flux
+    else:
+        rng = None
+        n_photons = 0
+
+    wcs = base['wcs'].local(base['image_pos'])
+    im = Image(bounds, wcs=wcs, dtype=base['current_stamp'].dtype)
+    im = psf.drawImage(image=im, offset=offset, method=method, rng=rng, n_photons=n_photons)
+
+    if 'signal_to_noise' in config:
+        if 'flux' in config:
+            raise GalSimConfigError(
+                "Cannot specify both flux and signal_to_noise for psf output")
+        if method == 'phot':
+            raise GalSimConfigError(
+                "signal_to_noise option not implemented for draw_method = phot")
+
+        if 'image' in base and 'noise' in base['image']:
+            noise_var = CalculateNoiseVariance(base)
+        else:
+            raise GalSimConfigError(
+                "Need to specify noise level when using psf.signal_to_noise")
+
+        sn_target = ParseValue(config, 'signal_to_noise', base, float)[0]
+
+        sn_meas = np.sqrt( np.sum(im.array**2, dtype=float) / noise_var )
+        flux = sn_target / sn_meas
+        im *= flux
+
+    return im
+
+
+# The function to call at the end of building each stamp
+
[docs]class ExtraPSFBuilder(ExtraOutputBuilder): + """Build an image that draws the PSF at the same location as each object on the main image. + + This makes the most sense when the main image consists of non-overlapping stamps, such as + a TiledImage, since you wouldn't typically want the PSF images to overlap. But it just + follows whatever pattern of stamp locations the main image has. + """ + def processStamp(self, obj_num, config, base, logger): + # If this doesn't exist, an appropriate exception will be raised. + psf = base['psf']['current'][0] + draw_method = GetCurrentValue('draw_method', base['stamp'], str, base) + bounds = base['current_stamp'].bounds + + # Check if we should shift the psf: + if 'shift' in config: + # Special: output.psf.shift = 'galaxy' means use the galaxy shift. + if config['shift'] == 'galaxy': + # This shift value might be in either stamp or gal. + b = base['stamp'] if 'shift' in base['stamp'] else base['gal'] + # This will raise an appropriate error if there is no gal.shift or stamp.shift. + shift = GetCurrentValue('shift', b, PositionD, base) + else: + shift = ParseValue(config, 'shift', base, PositionD)[0] + logger.debug('obj %d: psf shift: %s',base.get('obj_num',0),str(shift)) + psf = psf.shift(shift) + + # Start with the offset required just due to the stamp size/shape. + offset = base['stamp_offset'] + # Check if we should apply any additional offset: + if 'offset' in config: + # Special: output.psf.offset = 'galaxy' means use the same offset as in the galaxy + # image, which is actually in config.stamp, not config.gal. + if config['offset'] == 'galaxy': + offset += GetCurrentValue('offset', base['stamp'], PositionD, base) + else: + offset += ParseValue(config, 'offset', base, PositionD)[0] + logger.debug('obj %d: psf offset: %s',base.get('obj_num',0),str(offset)) + + psf_im = DrawPSFStamp(psf,config,base,bounds,offset,draw_method,logger) + if 'signal_to_noise' in config: + base['current_noise_image'] = base['current_stamp'] + AddNoise(base,psf_im,current_var=0,logger=logger) + self.scratch[obj_num] = psf_im + + # The function to call at the end of building each image + def processImage(self, index, obj_nums, config, base, logger): + image = Image(base['image_bounds'], wcs=base['wcs'], init_value=0., + dtype=base['current_image'].dtype) + # Make sure to only use the stamps for objects in this image. + for obj_num in obj_nums: + stamp = self.scratch[obj_num] + b = stamp.bounds & image.bounds + logger.debug('image %d: psf image at b = %s = %s & %s', + base['image_num'],b,stamp.bounds,image.bounds) + if b.isDefined(): # pragma: no branch (We normally guard against this already.) + image[b] += stamp[b] + logger.debug('obj %d: added psf image to main image',base.get('obj_num',0)) + self.data[index] = image
+ + +# Register this as a valid extra output +RegisterExtraOutput('psf', ExtraPSFBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/extra_truth.html b/docs/_build/html/_modules/galsim/config/extra_truth.html new file mode 100644 index 00000000000..2d58a450e9e --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/extra_truth.html @@ -0,0 +1,239 @@ + + + + + + galsim.config.extra_truth — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.extra_truth

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import sys
+import numpy as np
+from .extra import ExtraOutputBuilder, RegisterExtraOutput
+from .value import ParseValue, GetCurrentValue
+from ..errors import GalSimConfigError
+from ..catalog import OutputCatalog
+from ..utilities import basestring
+
+# The truth extra output type builds an OutputCatalog with truth information about each of the
+# objects being built by the configuration processing.  It stores the appropriate row information
+# in scratch space for each stamp and then adds them in order at the end of the file processing.
+# This means that the stamps can be built out of order by the multiprocessing and still show
+# up in the correct order in the output catalog.
+
+# Note that the order of the column names in the output catalog is taken from
+# config['output']['truth']['columns'].keys().  So if config is a regular dict, the order
+# of the keys is semi-arbitrary.  However, if config is an OrderedDict, the keys come out
+# in the order specified.  The standard galsim executable reads the config file into an
+# OrderedDict for precisely this reason.
+
+
[docs]class TruthBuilder(ExtraOutputBuilder): + """Build an output truth catalog with user-defined columns, typically taken from + current values of various quantities for each constructed object. + """ + # The function to call at the end of building each stamp + def processStamp(self, obj_num, config, base, logger): + cols = config['columns'] + row = [] + types = [] + for name in cols: + key = cols[name] + if isinstance(key, dict): + # Then the "key" is actually something to be parsed in the normal way. + # Caveat: We don't know the value_type here, so we give None. This allows + # only a limited subset of the parsing. Usually enough for truth items, but + # not fully featured. + value = ParseValue(cols,name,base,None)[0] + elif not isinstance(key,basestring): + # The item can just be a constant value. + value = key + elif key[0] == '$': + # This can also be handled by ParseValue + value = ParseValue(cols,name,base,None)[0] + elif key[0] == '@': + # Pop off an initial @ if there is one. + value = GetCurrentValue(str(key[1:]), base) + else: + # str(key) handles the possibility of unicode. In particular, this happens with + # JSON files. + value = GetCurrentValue(str(key), base) + row.append(value) + types.append(self._type(value)) + if 'types' not in self.scratch: + self.scratch['types'] = types + elif self.scratch['types'] != types: + logger.error("Type mismatch found when building truth catalog at object %d", + base['obj_num']) + for name, t1, t2 in zip(cols, types, self.scratch['types']): + if t1 != t2: + logger.error("%s has type %s, but previously had type %s"%( + name,t1.__name__,t2.__name__)) + raise GalSimConfigError("Type mismatch found when building truth catalog.") + self.scratch[obj_num] = row + + def _type(self, v): + if isinstance(v, np.floating): + return float + elif isinstance(v, np.integer): + return int + else: + return type(v) + + # The function to call at the end of building each file to finalize the truth catalog + def finalize(self, config, base, main_data, logger): + # Make the OutputCatalog + cols = config['columns'] + # Note: Provide a default here, because if all items were skipped it would otherwise + # lead to a KeyError. + types = self.scratch.pop('types', [float] * len(cols)) + cat = OutputCatalog(names=list(cols.keys()), types=types) + + # Add all the rows in order to the OutputCatalog + # Note: types was popped above, so only the obj_num keys are left. + obj_nums = sorted(self.scratch.keys()) + for obj_num in obj_nums: + row = self.scratch[obj_num] + cat.addRow(row) + return cat # This becomes self.final_data + + # Write the catalog to a file + def writeFile(self, file_name, config, base, logger): + self.final_data.write(file_name) + + # Create an HDU of the FITS binary table. + def writeHdu(self, config, base, logger): + return self.final_data.writeFitsHdu()
+ +# Register this as a valid extra output +RegisterExtraOutput('truth', TruthBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/extra_weight.html b/docs/_build/html/_modules/galsim/config/extra_weight.html new file mode 100644 index 00000000000..9b8df86f9f1 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/extra_weight.html @@ -0,0 +1,206 @@ + + + + + + galsim.config.extra_weight — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.extra_weight

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from .extra import ExtraOutputBuilder, RegisterExtraOutput
+from .value import ParseValue
+from .noise import AddNoiseVariance
+from ..image import ImageF
+
+# The weight extra output type builds an ImageF of the inverse noise variance in the image.
+# It builds up the variance either from the stamp information if noise is being added then
+# or at the end from the full image if that is when noise is added.  Then at the end of
+# the image processing, it inverts the image to get the appropriate weight map.
+
+
[docs]class WeightBuilder(ExtraOutputBuilder): + """This builds a weight map image to go along with each regular data image. + + The weight is the inverse variance of the noise in the image. + + There is a option called 'include_obj_var' that governs whether the weight should include the + Poisson variance of the signal. In real data, you don't know the true signal, and estimating + the Poisson noise from the realized image can lead to biases. As such, different applications + may or may not want this included. + """ + + # The function to call at the end of building each stamp + def processStamp(self, obj_num, config, base, logger): + if base['do_noise_in_stamps']: + weight_im = ImageF(base['current_stamp'].bounds, wcs=base['wcs'], init_value=0.) + if 'include_obj_var' in config: + include_obj_var = ParseValue(config, 'include_obj_var', base, bool)[0] + else: + include_obj_var = False + base['current_noise_image'] = base['current_stamp'] + AddNoiseVariance(base,weight_im,include_obj_var,logger) + self.scratch[obj_num] = weight_im + + # The function to call at the end of building each image + def processImage(self, index, obj_nums, config, base, logger): + image = ImageF(base['image_bounds'], wcs=base['wcs'], init_value=0.) + if len(self.scratch) > 0.: + # If we have been accumulating the variance on the stamps, build the total from them. + for obj_num in obj_nums: + stamp = self.scratch[obj_num] + b = stamp.bounds & image.bounds + if b.isDefined(): # pragma: no branch + # This next line is equivalent to: + # image[b] += stamp[b] + # except that this doesn't work through the proxy. We can only call methods + # that don't start with _. Hence using the more verbose form here. + image.setSubImage(b, image.subImage(b) + stamp[b]) + else: + # Otherwise, build the variance map now. + if 'include_obj_var' in config: + include_obj_var = ParseValue(config, 'include_obj_var', base, bool)[0] + else: + include_obj_var = False + base['current_noise_image'] = base['current_image'] + AddNoiseVariance(base,image,include_obj_var,logger) + + # Now invert the variance image to get weight map. + # Note that any zeros present in the image are maintained as zeros after inversion. + # So it is ok to set bad pixels to have zero variance above, and they will invert to have + # zero weight. + image.invertSelf() + self.data[index] = image
+ + +# Register this as a valid extra output +RegisterExtraOutput('weight', WeightBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/gsobject.html b/docs/_build/html/_modules/galsim/config/gsobject.html new file mode 100644 index 00000000000..be545e43de6 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/gsobject.html @@ -0,0 +1,761 @@ + + + + + + galsim.config.gsobject — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.gsobject

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+import inspect
+
+from .util import LoggerWrapper, GetIndex, GetRNG, get_cls_params, CleanConfig
+from .value import ParseValue, GetCurrentValue, GetAllParams, CheckAllParams, SetDefaultIndex
+from .input import RegisterInputConnectedType
+from .sed import BuildSED
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..position import PositionD
+from ..sum import Add
+from ..convolve import Convolve
+from ..phase_psf import OpticalPSF
+from ..shear import Shear
+from ..angle import Angle
+from ..gsobject import GSObject
+from ..chromatic import ChromaticObject, ChromaticOpticalPSF
+from ..gsparams import GSParams
+from ..utilities import basestring
+from ..chromatic import ChromaticAtmosphere
+from ..celestial import CelestialCoord
+
+# This file handles the building of GSObjects in the config['psf'] and config['gal'] fields.
+# This file includes many of the simple object types.  Additional types are defined in
+# gsobject_ring.py, input_real.py, and input_cosmos.py.
+
+# This module-level dict will store all the registered gsobject types.
+# See the RegisterObjectType function at the end of this file.
+# The keys will be the (string) names of the object types, and the values are the function
+# to call to build an object of that type.
+valid_gsobject_types = {}
+
+
[docs]class SkipThisObject(Exception): + """ + A class that a builder can throw to indicate that nothing went wrong, but for some + reason, this particular object should be skipped and just move onto the next object. + The constructor takes an optional message that will be output to the logger if + logging is active. + """ + def __init__(self, message=None): + # Using self.message gives a deprecation warning. Avoid this by using a different name. + self.msg = message
+ + +
[docs]def BuildGSObject(config, key, base=None, gsparams={}, logger=None): + """Build a GSObject from the parameters in config[key]. + + Parameters: + config: A dict with the configuration information. + key: The key name in config indicating which object to build. + base: The base dict of the configuration. [default: config] + gsparams: Optionally, provide non-default GSParams items. Any ``gsparams`` specified + at this level will be added to the list. This should be a dict with + whatever kwargs should be used in constructing the GSParams object. + [default: {}] + logger: Optionally, provide a logger for logging debug statements. + [default: None] + + Returns: + the tuple (gsobject, safe), where ``gsobject`` is the built object, and ``safe`` is + a bool that says whether it is safe to use this object again next time. + """ + from .. import __dict__ as galsim_dict + + logger = LoggerWrapper(logger) + if base is None: + base = config + + logger.debug('obj %d: Start BuildGSObject %s',base.get('obj_num',0),key) + + # If key isn't in config, then just return None. + try: + param = config[key] + except KeyError: + return None, True + + # Check what index key we want to use for this object. + # Note: this call will also set base['index_key'] and base['rng'] to the right values + index, index_key = GetIndex(param, base) + + # Get the type to be parsed. + if not 'type' in param: + raise GalSimConfigError("type attribute required in config.%s"%key) + type_name = param['type'] + + # If we are repeating, then we get to use the current object for repeat times. + if 'repeat' in param: + repeat = ParseValue(param, 'repeat', base, int)[0] + else: + repeat = 1 + + # Check if we need to skip this object + if 'skip' in param: + skip = ParseValue(param, 'skip', base, bool)[0] + if skip: + logger.debug('obj %d: Skipping because field skip=True',base.get('obj_num',0)) + raise SkipThisObject() + + # Check if we can use the current cached object + if 'current' in param: + # NB. "current" tuple is (obj, safe, None, index, index_type) + cobj, csafe, cvalue_type, cindex, cindex_type = param['current'] + if csafe or cindex//repeat == index//repeat: + # If logging, explain why we are using the current object. + if logger: + if csafe: + logger.debug('obj %d: current is safe',base.get('obj_num',0)) + elif repeat > 1: + logger.debug('obj %d: repeat = %d, index = %d, use current object', + base.get('obj_num',0),repeat,index) + else: + logger.debug('obj %d: This object is already current', base.get('obj_num',0)) + + return cobj, csafe + + # Set up the initial default list of attributes to ignore while building the object: + ignore = [ + 'dilate', 'dilation', 'ellip', 'rotate', 'rotation', 'scale_flux', + 'magnify', 'magnification', 'shear', 'lens', 'shift', 'sed', + 'gsparams', 'skip', + 'current', 'index_key', 'repeat' + ] + # There are a few more that are specific to which key we have. + # Note: some custom stamp builders may have fields besides just gal and psf. + # Using 'gal' in key rather than key == 'gal', we make it easier for them, since the + # keys can be e.g. blue_gal, red_gal, or halo_gal, field_gal, etc. Anything with gal + # somewhere in the name will be treated as a gal. Likewise ground_psf, space_psf or + # similar will all be treated as psf. + if isinstance(key, basestring) and 'gal' in key: + ignore += [ 'resolution', 'signal_to_noise', 'redshift', 're_from_res' ] + elif isinstance(key, basestring) and 'psf' in key: + ignore += [ 'saved_re' ] + else: + # As long as key isn't psf, allow resolution. + # Ideally, we'd like to check that it's something within the gal hierarchy, but + # I don't know an easy way to do that. + ignore += [ 'resolution' , 're_from_res' ] + + # Allow signal_to_noise for PSFs only if there is not also a galaxy. + if 'gal' not in base and isinstance(key, basestring) and 'psf' in key: + ignore += [ 'signal_to_noise'] + + # If we are specifying the size according to a resolution, then we + # need to get the PSF's half_light_radius. + if 'resolution' in param: + if 'psf' not in base: + raise GalSimConfigError("Cannot use gal.resolution if no psf is set.") + if 'saved_re' not in base['psf']: + raise GalSimConfigError( + 'Cannot use gal.resolution with psf.type = %s'%base['psf']['type']) + psf_re = base['psf']['saved_re'] + resolution = ParseValue(param, 'resolution', base, float)[0] + gal_re = resolution * psf_re + if 're_from_res' not in param: + # The first time, check that half_light_radius isn't also specified. + if 'half_light_radius' in param: + raise GalSimConfigError( + 'Cannot specify both gal.resolution and gal.half_light_radius') + param['re_from_res'] = True + param['half_light_radius'] = gal_re + + if 'gsparams' in param: + gsparams = UpdateGSParams(gsparams, param['gsparams'], base) + + # See if this type is registered as a valid type. + if type_name in valid_gsobject_types: + build_func = valid_gsobject_types[type_name] + elif type_name in galsim_dict: + gdict = globals().copy() + exec('import galsim', gdict) + build_func = eval("galsim."+type_name, gdict) + else: + raise GalSimConfigValueError("Unrecognised gsobject type", type_name) + + if inspect.isclass(build_func) and issubclass(build_func, (GSObject, ChromaticObject)): + gsobject, safe = _BuildSimple(build_func, param, base, ignore, gsparams, logger) + else: + gsobject, safe = build_func(param, base, ignore, gsparams, logger) + + # Apply any SED and redshift that might be present. + if 'redshift' in param: + if 'sed' in param: + from ..deprecated import depr + depr('gal.redshift', '2.5.3', 'gal.sed.redshift', + 'For chromatic objects, the redshift parameter should be given in the ' + 'sed field.') + param['sed']['redshift'] = param.pop('redshift') + + gsobject, safe1 = ApplySED(gsobject, param, base, logger) + safe = safe and safe1 + + if 'redshift' in param: + redshift, safe1 = ParseValue(param, 'redshift', base, float) + safe = safe and safe1 + if isinstance(gsobject, ChromaticObject): + from ..deprecated import depr + depr('gal.redshift', '2.5.3', 'gal.sed.redshift', + 'For chromatic objects, the redshift parameter should be given in the ' + 'sed field.') + gsobject = gsobject._atRedshift(redshift) + else: + # If not chromatic, then redshift is just an attribute for reference. + gsobject.redshift = redshift + + if 'flux' in param: + flux, safe1 = ParseValue(param, 'flux', base, float) + logger.debug('obj %d: flux == %f',base.get('obj_num',0),flux) + if 'sed' in param and 'bandpass' in base: + gsobject = gsobject.withFlux(flux, bandpass=base['bandpass']) + else: + gsobject = gsobject.withFlux(flux) + safe = safe and safe1 + + # If this is a psf, try to save the half_light_radius in case gal uses resolution. + if key == 'psf': + try: + param['saved_re'] = gsobject.half_light_radius + except (AttributeError, NotImplementedError, TypeError): + pass + + # Apply any dilation, ellip, shear, etc. modifications. + gsobject, safe1 = TransformObject(gsobject, param, base, logger) + safe = safe and safe1 + + # Re-get index and index_key in case something changed when building the object. + # (cf. Roman PSF for an example of why this might be useful.) + index, index_key = GetIndex(param, base) + param['current'] = gsobject, safe, None, index, index_key + + return gsobject, safe
+ + +
[docs]def UpdateGSParams(gsparams, config, base): + """Add additional items to the ``gsparams`` dict based on config['gsparams']. + + Parameters: + gsparams: A dict with whatever kwargs should be used in constructing the GSParams object. + config: A dict with the configuration information. + base: The base dict of the configuration. + + Returns: + an updated gsparams dict + """ + opt = GSObject._gsparams_opt + kwargs, safe = GetAllParams(config, base, opt=opt) + # When we update gsparams, we don't want to corrupt the original, so we need to + # make a copy first, then update with kwargs. + ret = {} + ret.update(gsparams) + ret.update(kwargs) + return ret
+ +def ApplySED(gsobject, config, base, logger): + """Read and apply an SED to the base gsobject + + Parameters: + gsobject: The base GSObject + config: A dict with the configuration information. + base: The base dict of the configuration. + logger: A logger for logging debug statements. + """ + if 'sed' in config: + sed, safe = BuildSED(config, 'sed', base, logger) + return gsobject * sed, safe + else: + return gsobject, True + +# +# The following are private functions to implement the simpler GSObject types. +# These are not imported into galsim.config namespace. +# + +def _BuildSimple(build_func, config, base, ignore, gsparams, logger): + """Build a simple GSObject (i.e. one without a specialized _Build function) or + any other GalSim object that defines _req_params, _opt_params and _single_params. + """ + # Build the kwargs according to the various params objects in the class definition. + type_name = config['type'] + logger.debug('obj %d: BuildSimple for type = %s',base.get('obj_num',0),type_name) + + req, opt, single, takes_rng = get_cls_params(build_func) + kwargs, safe = GetAllParams(config, base, req=req, opt=opt, single=single, ignore=ignore) + if gsparams: kwargs['gsparams'] = GSParams(**gsparams) + + if takes_rng: + kwargs['rng'] = GetRNG(config, base, logger, type_name) + safe = False + + logger.debug('obj %d: kwargs = %s',base.get('obj_num',0),kwargs) + + # Finally, after pulling together all the params, try making the GSObject. + return build_func(**kwargs), safe + + +def _BuildNone(config, base, ignore, gsparams, logger): + """Special type=None returns None. + """ + return None, True + + +
[docs]def _BuildAdd(config, base, ignore, gsparams, logger): + """Build a Sum object. + """ + req = { 'items' : list } + opt = { 'flux' : float } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt, ignore=ignore) + + gsobjects = [] + items = config['items'] + if not isinstance(items,list): + raise GalSimConfigError("items entry for type=Add is not a list.") + safe = True + + for i in range(len(items)): + gsobject, safe1 = BuildGSObject(items, i, base, gsparams, logger) + # Skip items with flux=0 + if 'flux' in items[i] and GetCurrentValue('flux',items[i],float,base) == 0.: + logger.debug('obj %d: Not including component with flux == 0',base.get('obj_num',0)) + continue + safe = safe and safe1 + gsobjects.append(gsobject) + + if len(gsobjects) == 0: + raise GalSimConfigError("No valid items for type=Add") + elif len(gsobjects) == 1: + gsobject = gsobjects[0] + else: + # Special: if the last item in a Sum doesn't specify a flux, we scale it + # to bring the total flux up to 1. + if ('flux' not in items[-1]) and all('flux' in item for item in items[0:-1]): + sum_flux = 0 + for item in items[0:-1]: + sum_flux += GetCurrentValue('flux',item,float,base) + f = 1. - sum_flux + if (f < 0): + logger.warning( + "Warning: Automatic flux for the last item in Sum (to make the total flux=1) " + "resulted in negative flux = %f for that item"%f) + logger.debug('obj %d: Rescaling final object in sum to have flux = %f', + base.get('obj_num',0), f) + gsobjects[-1] = gsobjects[-1].withFlux(f) + if gsparams: gsparams = GSParams(**gsparams) + else: gsparams = None + gsobject = Add(gsobjects,gsparams=gsparams) + + return gsobject, safe
+ +
[docs]def _BuildConvolve(config, base, ignore, gsparams, logger): + """Build a Convolution object. + """ + req = { 'items' : list } + opt = { 'flux' : float } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt, ignore=ignore) + + gsobjects = [] + items = config['items'] + if not isinstance(items,list): + raise GalSimConfigError("items entry for type=Convolve is not a list.") + safe = True + for i in range(len(items)): + gsobject, safe1 = BuildGSObject(items, i, base, gsparams, logger) + safe = safe and safe1 + gsobjects.append(gsobject) + + if len(gsobjects) == 0: + raise GalSimConfigError("No valid items for type=Convolve") + elif len(gsobjects) == 1: + gsobject = gsobjects[0] + else: + if gsparams: gsparams = GSParams(**gsparams) + else: gsparams = None + gsobject = Convolve(gsobjects,gsparams=gsparams) + + return gsobject, safe
+ +
[docs]def _BuildList(config, base, ignore, gsparams, logger): + """Build a GSObject selected from a List. + """ + req = { 'items' : list } + opt = { 'index' : float , 'flux' : float } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt, ignore=ignore) + + items = config['items'] + if not isinstance(items,list): + raise GalSimConfigError("items entry for type=List is not a list.") + + # Setup the indexing sequence if it hasn't been specified using the length of items. + SetDefaultIndex(config, len(items)) + index, safe = ParseValue(config, 'index', base, int) + if index < 0 or index >= len(items): + raise GalSimConfigError("index %d out of bounds for List"%index) + + gsobject, safe1 = BuildGSObject(items, index, base, gsparams, logger) + safe = safe and safe1 + + return gsobject, safe
+ +def _BuildEval(config, base, ignore, gsparams, logger): + """Build a GSObject from an Eval string + """ + from .value_eval import _GenerateFromEval + req = { 'str': str } + params, _ = GetAllParams(config, base, req=req, ignore=ignore) + gsobject, safe = _GenerateFromEval(params, base, None) + if gsparams: + gsobject = gsobject.withGSParams(**gsparams) + + return gsobject, safe + + +def ParseAberrations(key, config, base, name): + """Parse a possible aberrations list in config dict. + + Parameters: + key: The key name with the aberrations list. + config: A dict with the tranformation information for this object. + base: The base dict of the configuration. + name: The name of the source object being parsed (only used for error reporting). + + Returns: + aberrations list or None + """ + if key in config: + aber_list = [0.0] * 4 # Initial 4 values are ignored. + aberrations = config[key] + if not isinstance(aberrations,list): + raise GalSimConfigError( + "aberrations entry for config.%s entry is not a list."%(name)) + safe = True + for i in range(len(aberrations)): + value, safe1 = ParseValue(aberrations, i, base, float) + aber_list.append(value) + safe = safe and safe1 + return aber_list + else: + return None + +def _BuildJointOpticalPSF(cls, config, base, ignore, gsparams, logger): + req, opt, single, _ = get_cls_params(cls) + + kwargs, safe = GetAllParams(config, base, req, opt, single, ignore = ['aberrations'] + ignore) + if gsparams: kwargs['gsparams'] = GSParams(**gsparams) + kwargs['aberrations'] = ParseAberrations('aberrations', config, base, cls.__name__) + + return cls(**kwargs), safe + +
[docs]def _BuildOpticalPSF(config, base, ignore, gsparams, logger): + """Build an OpticalPSF. + """ + return _BuildJointOpticalPSF(OpticalPSF, config, base, ignore, gsparams, logger)
+ +def _BuildChromaticOpticalPSF(config, base, ignore, gsparams, logger): + """Build a ChromaticOpticalPSF. + """ + # All the code for this is the same as for OpticalPSF, so use a shared implementation above. + return _BuildJointOpticalPSF(ChromaticOpticalPSF, config, base, ignore, gsparams, logger) + +def _BuildChromaticAtmosphere(config, base, ignore, gsparams, logger): + """Build a ChromaticAtmosphere. + """ + req = {'base_wavelength' : float} + opt = { + 'alpha' : float, + 'zenith_angle' : Angle, + 'parallactic_angle' : Angle, + 'zenith_coord' : CelestialCoord, + 'HA' : Angle, + 'latitude' : Angle, + 'pressure' : float, + 'temperature' : float, + 'H2O_pressure' : float, + } + ignore = ['base_profile'] + ignore + kwargs, safe = GetAllParams(config, base, req=req, opt=opt, ignore=ignore) + + if 'base_profile' not in config: + raise GalSimConfigError("Attribute base_profile is required for type=ChromaticAtmosphere") + base_profile, safe1 = BuildGSObject(config, 'base_profile', base, gsparams, logger) + safe = safe and safe1 + + if 'zenith_angle' not in kwargs: + sky_pos = base.get('sky_pos', None) + if sky_pos is None: + raise GalSimConfigError("Using zenith_angle with type=ChromaticAtmosphere requires " + "that sky_pos be available to use as the object coord.") + kwargs['obj_coord'] = sky_pos + safe = False + + psf = ChromaticAtmosphere(base_profile, **kwargs) + return psf, safe + + +# +# Now the functions for performing transformations +# + +
[docs]def TransformObject(gsobject, config, base, logger): + """Applies ellipticity, rotation, gravitational shearing and centroid shifting to a + supplied GSObject, in that order. + + Parameters: + gsobject: The GSObject to be transformed. + config: A dict with the tranformation information for this object. + base: The base dict of the configuration. + logger: A logger for logging debug statements. + + Returns: + transformed GSObject. + """ + logger = LoggerWrapper(logger) + # The transformations are applied in the following order: + _transformation_list = [ + ('dilate', _Dilate), + ('dilation', _Dilate), + ('ellip', _Shear), + ('rotate', _Rotate), + ('rotation', _Rotate), + ('scale_flux', _ScaleFlux), + ('lens', _Lens), + ('shear', _Shear), + ('magnify', _Magnify), + ('magnification', _Magnify), + ('shift', _Shift), + ] + + safe = True + for key, func in _transformation_list: + if key in config: + gsobject, safe1 = func(gsobject, config, key, base, logger) + safe = safe and safe1 + return gsobject, safe
+ +def _Shear(gsobject, config, key, base, logger): + shear, safe = ParseValue(config, key, base, Shear) + logger.debug('obj %d: shear = %f,%f',base.get('obj_num',0),shear.g1,shear.g2) + gsobject = gsobject._shear(shear) + return gsobject, safe + +def _Rotate(gsobject, config, key, base, logger): + theta, safe = ParseValue(config, key, base, Angle) + logger.debug('obj %d: theta = %f rad',base.get('obj_num',0),theta.rad) + gsobject = gsobject.rotate(theta) + return gsobject, safe + +def _ScaleFlux(gsobject, config, key, base, logger): + flux_ratio, safe = ParseValue(config, key, base, float) + logger.debug('obj %d: flux_ratio = %f',base.get('obj_num',0),flux_ratio) + gsobject = gsobject * flux_ratio + return gsobject, safe + +def _Dilate(gsobject, config, key, base, logger): + scale, safe = ParseValue(config, key, base, float) + logger.debug('obj %d: scale = %f',base.get('obj_num',0),scale) + gsobject = gsobject.dilate(scale) + return gsobject, safe + +def _Lens(gsobject, config, key, base, logger): + shear, safe = ParseValue(config[key], 'shear', base, Shear) + mu, safe1 = ParseValue(config[key], 'mu', base, float) + safe = safe and safe1 + logger.debug('obj %d: shear = %f,%f',base.get('obj_num',0),shear.g1,shear.g2) + logger.debug('obj %d: mu = %f',base.get('obj_num',0),mu) + gsobject = gsobject._lens(shear.g1, shear.g2, mu) + return gsobject, safe + +def _Magnify(gsobject, config, key, base, logger): + mu, safe = ParseValue(config, key, base, float) + logger.debug('obj %d: mu = %f',base.get('obj_num',0),mu) + gsobject = gsobject.magnify(mu) + return gsobject, safe + +def _Shift(gsobject, config, key, base, logger): + shift, safe = ParseValue(config, key, base, PositionD) + logger.debug('obj %d: shift = %f,%f',base.get('obj_num',0),shift.x,shift.y) + gsobject = gsobject._shift(shift.x, shift.y) + return gsobject, safe + +
[docs]def RegisterObjectType(type_name, build_func, input_type=None): + """Register an object type for use by the config apparatus. + + A few notes about the signature of the build functions: + + 1. The config parameter is the dict for the current object to be generated. So it should + be the case that config['type'] == type_name. + 2. The base parameter is the original config dict being processed. + 3. The ignore parameter is a list of items that should be ignored in the config dict if they + are present and not valid for the object being built. + 4. The gsparams parameter is a dict of kwargs that should be used to build a GSParams object + to use when building this object. + 5. The logger parameter is a logging.Logger object to use for logging progress if desired. + 6. The return value of build_func should be a tuple consisting of the object and a boolean, + safe, which indicates whether the generated object is safe to use again rather than + regenerate for subsequent postage stamps. e.g. if a PSF has all constant values, then it + can be used for all the galaxies in a simulation, which lets it keep any FFTs that it has + performed internally. OpticalPSF is a good example of where this can have a significant + speed up. + + Parameters: + type_name: The name of the 'type' specification in the config dict. + build_func: A function to build a GSObject from the config information. + The call signature is:: + + obj, safe = Build(config, base, ignore, gsparams, logger) + + input_type: If the type requires an input object, give the key name of the input + type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_gsobject_types[type_name] = build_func + RegisterInputConnectedType(input_type, type_name)
+ +RegisterObjectType('None', _BuildNone) +RegisterObjectType('Add', _BuildAdd) +RegisterObjectType('Sum', _BuildAdd) +RegisterObjectType('Convolve', _BuildConvolve) +RegisterObjectType('Convolution', _BuildConvolve) +RegisterObjectType('List', _BuildList) +RegisterObjectType('OpticalPSF', _BuildOpticalPSF) +RegisterObjectType('ChromaticOpticalPSF', _BuildChromaticOpticalPSF) +RegisterObjectType('ChromaticAtmosphere', _BuildChromaticAtmosphere) +RegisterObjectType('Eval', _BuildEval) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/image.html b/docs/_build/html/_modules/galsim/config/image.html new file mode 100644 index 00000000000..9c5bcc3ff5e --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/image.html @@ -0,0 +1,736 @@ + + + + + + galsim.config.image — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.image

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+
+from .util import LoggerWrapper, UpdateNProc, MultiProcess, SetupConfigRNG
+from .input import SetupInput, SetupInputsForImage
+from .value import ParseValue, GetAllParams
+from .wcs import BuildWCS
+from .sensor import BuildSensor
+from .bandpass import BuildBandpass
+from .stamp import BuildStamp, MakeStampTasks, ParseDType
+from .stamp import stamp_image_keys
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..position import PositionI, PositionD
+from ..bounds import BoundsI
+from ..celestial import CelestialCoord
+from ..image import Image
+from ..noise import VariableGaussianNoise
+
+# This file handles the building of an image by parsing config['image'].
+# This file includes the basic functionality, but it calls out to helper functions
+# for parts of the process that are different for different image types.  It includes
+# those helper functions for the simplest image type, Single.  See image_tiled.py and
+# image_scattered.py for the implementation of the Tiled and Scattered image types.
+
+# This module-level dict will store all the registered image types.
+# See the RegisterImageType function at the end of this file.
+# The keys are the (string) names of the image types, and the values will be builder objects
+# that will perform the different stages of processing to build each full image.
+valid_image_types = {}
+
+
+
[docs]def BuildImages(nimages, config, image_num=0, obj_num=0, logger=None): + """ + Build a number of postage stamp images as specified by the config dict. + + Parameters: + nimages: How many images to build. + config: The configuration dict. + image_num: If given, the current image number. [default: 0] + obj_num: If given, the first object number in the image. [default: 0] + logger: If given, a logger object to log progress. [default: None] + + Returns: + a list of images + """ + logger = LoggerWrapper(logger) + logger.debug('file %d: BuildImages nimages = %d: image, obj = %d,%d', + config.get('file_num',0),nimages,image_num,obj_num) + + if nimages == 0: + logger.warning('No images were built, since nimages == 0.') + return [] + + # Figure out how many processes we will use for building the images. + if 'image' not in config: config['image'] = {} + image = config['image'] + if nimages > 1 and 'nproc' in image: + nproc = ParseValue(image, 'nproc', config, int)[0] + # Update this in case the config value is -1 + nproc = UpdateNProc(nproc, nimages, config, logger) + else: + nproc = 1 + + if 'timeout' in image: + timeout = ParseValue(image, 'timeout', config, float)[0] + else: + timeout = 900 + + jobs = [] + for k in range(nimages): + kwargs = { 'image_num' : image_num, 'obj_num' : obj_num } + jobs.append(kwargs) + obj_num += GetNObjForImage(config, image_num, logger=logger) + image_num += 1 + + def done_func(logger, proc, k, image, t): + if image is not None: + # Note: numpy shape is y,x + ys, xs = image.array.shape + if proc is None: s0 = '' + else: s0 = '%s: '%proc + image_num = jobs[k]['image_num'] + logger.info(s0 + 'Image %d: size = %d x %d, time = %f sec', image_num, xs, ys, t) + + def except_func(logger, proc, k, e, tr): + if proc is None: s0 = '' + else: s0 = '%s: '%proc + image_num = jobs[k]['image_num'] + logger.error(s0 + 'Exception caught when building image %d', image_num) + logger.debug('%s',tr) + logger.error('Aborting the rest of this file') + + # Convert to the tasks structure we need for MultiProcess + tasks = MakeImageTasks(config, jobs, logger) + + images = MultiProcess(nproc, config, BuildImage, tasks, 'image', + logger=logger, timeout=timeout, + done_func=done_func, except_func=except_func) + + logger.debug('file %d: Done making images',config.get('file_num',0)) + if len(images) == 0: + logger.warning('No images were built. All were either skipped or had errors.') + + return images
+ +
[docs]def SetupConfigImageNum(config, image_num, obj_num, logger=None): + """Do the basic setup of the config dict at the image processing level. + + Includes: + - Set config['image_num'] = image_num + - Set config['obj_num'] = obj_num + - Set config['index_key'] = 'image_num' + - Make sure config['image'] exists + - Set default config['image']['type'] to 'Single' if not specified + - Check that the specified image type is valid. + + Parameters: + config: The configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + config['image_num'] = image_num + config['obj_num'] = obj_num + config['index_key'] = 'image_num' + + # Make config['image'] exist if it doesn't yet. + if 'image' not in config: + config['image'] = {} + image = config['image'] + if not isinstance(image, dict): + raise GalSimConfigError("config.image is not a dict.") + + if 'file_num' not in config: + config['file_num'] = 0 + + if 'type' not in image: + image['type'] = 'Single' + image_type = image['type'] + if image_type not in valid_image_types: + raise GalSimConfigValueError("Invalid image.type.", image_type, + list(valid_image_types.keys())) + + # In case this hasn't been done yet. + SetupInput(config, logger) + + # Build the rng to use at the image level. + seed = SetupConfigRNG(config, logger=logger) + logger.debug('image %d: seed = %d',image_num,seed)
+ + + +
[docs]def SetupConfigImageSize(config, xsize, ysize, logger=None): + """Do some further setup of the config dict at the image processing level based on + the provided image size. + + - Set config['image_xsize'], config['image_ysize'] to the size of the image + - Set config['image_origin'] to the origin of the image + - Set config['image_center'] to the center of the image + - Set config['image_bounds'] to the bounds of the image + - Build the WCS based on either config['image']['wcs'] or config['image']['pixel_scale'] + - Set config['wcs'] to be the built wcs + - If wcs.isPixelScale(), also set config['pixel_scale'] for convenience. + - Set config['world_center'] to either a given value or based on wcs and image_center + - Create a blank image if possible and store as config['current_image'] + + Parameters: + config: The configuration dict. + xsize: The size of the image in the x-dimension. + ysize: The size of the image in the y-dimension. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + config['image_xsize'] = xsize + config['image_ysize'] = ysize + image = config['image'] + + origin = 1 # default + if 'index_convention' in image: + convention = ParseValue(image,'index_convention',config,str)[0] + if convention.lower() in ('0', 'c', 'python'): + origin = 0 + elif convention.lower() in ('1', 'fortran', 'fits'): + origin = 1 + else: + raise GalSimConfigValueError("Unknown index_convention", convention, + ('0', 'c', 'python', '1', 'fortran', 'fits')) + + config['image_origin'] = PositionI(origin,origin) + config['image_center'] = PositionD( origin + (xsize-1.)/2., origin + (ysize-1.)/2. ) + bounds = BoundsI(origin, origin+xsize-1, origin, origin+ysize-1) + config['image_bounds'] = bounds + + # Build the wcs + wcs = BuildWCS(image, 'wcs', config, logger) + config['wcs'] = wcs + + # If the WCS is a PixelScale or OffsetWCS, then store the pixel_scale in base. The + # config apparatus does not use it -- we always use the wcs -- but we keep it in case + # the user wants to use it for an Eval item. It's one of the variables they are allowed + # to assume will be present for them. + if wcs._isPixelScale: + config['pixel_scale'] = wcs.scale + + # Set world_center + if 'world_center' in image: + config['world_center'] = ParseValue(image, 'world_center', config, CelestialCoord)[0] + else: + config['world_center'] = wcs.toWorld(config['image_center']) + + if bounds.isDefined(): + dtype = ParseDType(image, config) + config['current_image'] = Image(bounds=bounds, dtype=dtype, wcs=wcs, init_value=0) + else: + config['current_image'] = None
+ + +# Ignore these when parsing the parameters for specific Image types: +image_ignore = [ 'random_seed', 'noise', 'pixel_scale', 'wcs', 'sky_level', 'sky_level_pixel', + 'world_center', 'index_convention', 'nproc', 'timeout', 'bandpass', 'sensor', + 'use_flux_sky_areas' + ] + stamp_image_keys + +
[docs]def BuildImage(config, image_num=0, obj_num=0, logger=None): + """ + Build an Image according to the information in config. + + Parameters: + config: The configuration dict. + image_num: If given, the current image number. [default: 0] + obj_num: If given, the first object number in the image. [default: 0] + logger: If given, a logger object to log progress. [default: None] + + Returns: + the final image + """ + from .extra import SetupExtraOutputsForImage, ProcessExtraOutputsForImage + + logger = LoggerWrapper(logger) + logger.debug('image %d: BuildImage: image, obj = %d,%d',image_num,image_num,obj_num) + + # Setup basic things in the top-level config dict that we will need. + SetupConfigImageNum(config, image_num, obj_num, logger) + + cfg_image = config['image'] # Use cfg_image to avoid name confusion with the actual image + # we will build later. + image_type = cfg_image['type'] + + # Do the necessary initial setup for this image type. + builder = valid_image_types[image_type] + xsize, ysize = builder.setup(cfg_image, config, image_num, obj_num, image_ignore, logger) + + # Given this image size (which may be 0,0, in which case it will be set automatically later), + # do some basic calculations + SetupConfigImageSize(config, xsize, ysize, logger) + logger.debug('image %d: image_size = %d, %d',image_num,xsize,ysize) + logger.debug('image %d: image_origin = %s',image_num,config['image_origin']) + logger.debug('image %d: image_center = %s',image_num,config['image_center']) + + # Sometimes an input field needs to do something special at the start of an image. + SetupInputsForImage(config, logger) + + # Likewise for the extra output items. + SetupExtraOutputsForImage(config, logger) + + # If there is a bandpass field, load it into config['bandpass'] + bp = builder.buildBandpass(cfg_image, config, image_num, obj_num, logger) + if bp is not None: + config['bandpass'] = bp + + # If there is a sensor, build it now. + sensor = builder.buildSensor(cfg_image, config, image_num, obj_num, logger) + if sensor is not None: + config['sensor'] = sensor + + # Actually build the image now. This is the main working part of this function. + # It calls out to the appropriate build function for this image type. + image, current_var = builder.buildImage(cfg_image, config, image_num, obj_num, logger) + + # Store the current image in the base-level config for reference + config['current_image'] = image + + # Just in case these changed from their initial values, make sure they are correct now: + if image is not None: + config['image_origin'] = image.origin + config['image_center'] = image.true_center + config['image_bounds'] = image.bounds + logger.debug('image %d: image_origin => %s',image_num,config['image_origin']) + logger.debug('image %d: image_center => %s',image_num,config['image_center']) + logger.debug('image %d: image_bounds => %s',image_num,config['image_bounds']) + + # Mark that we are no longer doing a single galaxy by deleting image_pos from config top + # level, so it cannot be used for things like wcs.pixelArea(image_pos). + config.pop('image_pos', None) + + # Go back to using image_num for any indexing. + config['index_key'] = 'image_num' + + # Do whatever processing is required for the extra output items. + ProcessExtraOutputsForImage(config,logger) + + builder.addNoise(image, cfg_image, config, image_num, obj_num, current_var, logger) + + return image
+ + +
[docs]def GetNObjForImage(config, image_num, logger=None, approx=False): + """ + Get the number of objects that will be made for the image number image_num based on + the information in the config dict. + + Parameters: + config: The configuration dict. + image_num: The current image number. + logger: If given, a logger object to log progress. + approx: Whether an approximate/overestimate is ok [default: False] + + Returns: + the number of objects + """ + image = config.get('image',{}) + image_type = image.get('type','Single') + if image_type not in valid_image_types: + raise GalSimConfigValueError("Invalid image.type.", image_type, + list(valid_image_types.keys())) + return valid_image_types[image_type].getNObj(image, config, image_num, + logger=logger, approx=approx)
+ + +
[docs]def FlattenNoiseVariance(config, full_image, stamps, current_vars, logger): + """This is a helper function to bring the noise level up to a constant value + across the image. If some of the galaxies are RealGalaxy objects and noise whitening + (or symmetrizing) is turned on, then there will already be some noise in the + stamps that get built. This function goes through and figures out what the maximum + current variance is anywhere in the full image and adds noise to the other pixels + to bring everything up to that level. + + Parameters: + config: The configuration dict. + full_image: The full image onto which the noise should be added. + stamps: A list of the individual postage stamps. + current_vars: A list of the current variance in each postage stamps. + logger: If given, a logger object to log progress. + + Returns: + the final variance in the image + """ + logger = LoggerWrapper(logger) + rng = config['image_num_rng'] + nobjects = len(stamps) + max_current_var = max(tuple(current_vars) + (0,)) # Include 0 in case current_vars is empty. + if max_current_var > 0: + logger.debug('image %d: maximum noise varance in any stamp is %f', + config['image_num'], max_current_var) + # Then there was whitening applied in the individual stamps. + # But there could be a different variance in each postage stamp, so the first + # thing we need to do is bring everything up to a common level. + noise_image = Image(bounds=full_image.bounds, dtype=full_image.dtype) + for k in range(nobjects): + if stamps[k] is None: continue + b = stamps[k].bounds & full_image.bounds + if b.isDefined(): noise_image[b] += current_vars[k] + # Update this, since overlapping postage stamps may have led to a larger + # value in some pixels. + max_current_var = np.max(noise_image.array) + logger.debug('image %d: maximum noise varance in any pixel is %f', + config['image_num'], max_current_var) + # Figure out how much noise we need to add to each pixel. + noise_image = max_current_var - noise_image + # Add it. + full_image.addNoise(VariableGaussianNoise(rng,noise_image)) + # Now max_current_var is how much noise is in each pixel. + return max_current_var
+ + +
[docs]def MakeImageTasks(config, jobs, logger): + """Turn a list of jobs into a list of tasks. + + See the doc string for galsim.config.MultiProcess for the meaning of this distinction. + + For most image types, there is just one job per task, so the tasks list is just: + + tasks = [ [ (job, k) ] for k, job in enumerate(jobs) ] + + But some image types may need groups of jobs to be done sequentially by the same process. + The image type=Single for instance uses whatever grouping is needed for the stamp type. + + Parameters: + config: The configuration dict + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'image_num' and 'obj_num'. + logger: If given, a logger object to log progress. + + Returns: + a list of tasks + """ + image = config.get('image', {}) + image_type = image.get('type', 'Single') + return valid_image_types[image_type].makeTasks(image, config, jobs, logger)
+ + +
[docs]class ImageBuilder: + """A base class for building full images. + + The base class defines the call signatures of the methods that any derived class should follow. + It also includes the implementation of the default image type: Single. + """ + +
[docs] def setup(self, config, base, image_num, obj_num, ignore, logger): + """Do the initialization and setup for building the image. + + This figures out the size that the image will be, but doesn't actually build it yet. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if these parameters are present. + logger: If given, a logger object to log progress. + + Returns: + xsize, ysize + """ + logger.debug('image %d: Build Single Image: image, obj = %d,%d', + image_num,image_num,obj_num) + + extra_ignore = [ 'image_pos', 'world_pos' ] + opt = { 'size' : int , 'xsize' : int , 'ysize' : int } + params = GetAllParams(config, base, opt=opt, ignore=ignore+extra_ignore)[0] + + # If image_force_xsize and image_force_ysize were set in base, this overrides the + # read-in params. + if 'image_force_xsize' in base and 'image_force_ysize' in base: + xsize = base['image_force_xsize'] + ysize = base['image_force_ysize'] + else: + size = params.get('size',0) + xsize = params.get('xsize',size) + ysize = params.get('ysize',size) + if (xsize == 0) != (ysize == 0): + raise GalSimConfigError( + "Both (or neither) of image.xsize and image.ysize need to be defined and != 0.") + + return xsize, ysize
+ +
[docs] def buildBandpass(self, config, base, image_num, obj_num, logger): + """If thre is a 'bandpass' field in config['image'], load it. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + logger: If given, a logger object to log progress. + + Returns: + a gasim.Bandpass or None + """ + if 'bandpass' in config: + return BuildBandpass(config, 'bandpass', base, logger)[0] + else: + return None
+ +
[docs] def buildSensor(self, config, base, image_num, obj_num, logger): + """Build the sensor if given in the config dict. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + logger: If given, a logger object to log progress. + + Returns: + a galsim.Sensor or None + """ + if 'sensor' in config: + return BuildSensor(config, 'sensor', base, logger) + else: + return None
+ +
[docs] def buildImage(self, config, base, image_num, obj_num, logger): + """Build an Image based on the parameters in the config dict. + + For Single, this is just an image consisting of a single postage stamp. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + logger: If given, a logger object to log progress. + + Returns: + the final image and the current noise variance in the image as a tuple + """ + xsize = base['image_xsize'] + ysize = base['image_ysize'] + logger.debug('image %d: Single Image: size = %s, %s',image_num,xsize,ysize) + + image, current_var = BuildStamp( + base, obj_num=obj_num, xsize=xsize, ysize=ysize, do_noise=True, logger=logger) + if image is not None: + image.wcs = base['wcs'] # in case stamp has a local jacobian. + + current_image = base['current_image'] + if current_image is not None and image is not None: + b = current_image.bounds & image.bounds + if b.isDefined(): + current_image[b] += image[b] + image = current_image + + return image, current_var
+ +
[docs] def makeTasks(self, config, base, jobs, logger): + """Turn a list of jobs into a list of tasks. + + Each task is performed separately in multi-processing runs, so this provides a mechanism + to have multiple jobs depend on each other without being messed up by multi-processing. + E.g. you could have blends where each task consists of building several overlapping + galaxies (each of which would be a single job). Perhaps the first job would include + a calculation to determine where all the overlapping galaxies should go, and the later + jobs would use the results of this calculation and just place the later galaxies in the + appropriate place. + + Normally, though, each task is just a single job, in which case, this function is very + simple. + + For Single, this passes the job onto the MakeStampTasks function (which in turn is + normally quite simple). Most other types though probably want one job per task, for which + the appropriate code would be: + + return [ [ (job, k) ] for k, job in enumerate(jobs) ] + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'image_num' and 'obj_num'. + logger: If given, a logger object to log progress. + + Returns: + a list of tasks + """ + return MakeStampTasks(base, jobs, logger)
+ +
[docs] def addNoise(self, image, config, base, image_num, obj_num, current_var, logger): + """Add the final noise to the image. + + In the base class, this is a no op, since it directs the BuildStamp function to build + the noise at that level. But some image types need to do extra work at the end to + add the noise properly. + + Parameters: + image: The image onto which to add the noise. + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + current_var: The current noise variance in each postage stamps. + logger: If given, a logger object to log progress. + """ + pass
+ +
[docs] def getNObj(self, config, base, image_num, logger=None, approx=False): + """Get the number of objects that will be built for this image. + + For Single, this is just 1, but other image types would figure this out from the + configuration parameters. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + logger: If given, a logger object to log progress. + approx: Whether an approximate/overestimate is ok [default: False] + + Returns: + the number of objects + """ + return 1
+ +
[docs]def RegisterImageType(image_type, builder): + """Register an image type for use by the config apparatus. + + Parameters: + image_type: The name of the type in config['image'] + builder: A builder object to use for building the images. It should be an + instance of ImageBuilder or a subclass thereof. + """ + valid_image_types[image_type] = builder
+ +RegisterImageType('Single', ImageBuilder()) + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/image_scattered.html b/docs/_build/html/_modules/galsim/config/image_scattered.html new file mode 100644 index 00000000000..1b0ec82a06c --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/image_scattered.html @@ -0,0 +1,332 @@ + + + + + + galsim.config.image_scattered — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for galsim.config.image_scattered

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+import numpy as np
+
+from .image import ImageBuilder, FlattenNoiseVariance, RegisterImageType
+from .value import ParseValue, GetAllParams
+from .stamp import BuildStamps
+from .noise import AddSky, AddNoise
+from .input import ProcessInputNObjects
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..image import Image
+
+# This file adds image type Scattered, which places individual stamps at arbitrary
+# locations on a larger image.
+
+
[docs]class ScatteredImageBuilder(ImageBuilder): + + def setup(self, config, base, image_num, obj_num, ignore, logger): + """Do the initialization and setup for building the image. + + This figures out the size that the image will be, but doesn't actually build it yet. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if these parameters are present. + logger: If given, a logger object to log progress. + + Returns: + xsize, ysize + """ + logger.debug('image %d: Building Scattered: image, obj = %d,%d', + image_num,image_num,obj_num) + + self.nobjects = self.getNObj(config, base, image_num, logger=logger) + logger.debug('image %d: nobj = %d',image_num,self.nobjects) + + # These are allowed for Scattered, but we don't use them here. + extra_ignore = [ 'image_pos', 'world_pos', 'stamp_size', 'stamp_xsize', 'stamp_ysize', + 'nobjects' ] + opt = { 'size' : int , 'xsize' : int , 'ysize' : int, 'dtype': None } + params = GetAllParams(config, base, opt=opt, ignore=ignore+extra_ignore)[0] + + size = params.get('size',0) + full_xsize = params.get('xsize',size) + full_ysize = params.get('ysize',size) + + if (full_xsize <= 0) or (full_ysize <= 0): + raise GalSimConfigError( + "Both image.xsize and image.ysize need to be defined and > 0.") + + # If image_force_xsize and image_force_ysize were set in config, make sure it matches. + if ( ('image_force_xsize' in base and full_xsize != base['image_force_xsize']) or + ('image_force_ysize' in base and full_ysize != base['image_force_ysize']) ): + raise GalSimConfigError( + "Unable to reconcile required image xsize and ysize with provided " + "xsize=%d, ysize=%d, "%(full_xsize,full_ysize)) + + return full_xsize, full_ysize + + + def buildImage(self, config, base, image_num, obj_num, logger): + """Build an Image containing multiple objects placed at arbitrary locations. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + logger: If given, a logger object to log progress. + + Returns: + the final image and the current noise variance in the image as a tuple + """ + full_image = base['current_image'] + + if 'image_pos' in config and 'world_pos' in config: + raise GalSimConfigValueError( + "Both image_pos and world_pos specified for Scattered image.", + (config['image_pos'], config['world_pos'])) + + if ('image_pos' not in config and 'world_pos' not in config and + not ('stamp' in base and + ('image_pos' in base['stamp'] or 'world_pos' in base['stamp']))): + full_xsize = base['image_xsize'] + full_ysize = base['image_ysize'] + xmin = base['image_origin'].x + xmax = xmin + full_xsize-1 + ymin = base['image_origin'].y + ymax = ymin + full_ysize-1 + config['image_pos'] = { + 'type' : 'XY' , + 'x' : { 'type' : 'Random' , 'min' : xmin , 'max' : xmax }, + 'y' : { 'type' : 'Random' , 'min' : ymin , 'max' : ymax } + } + + stamps, current_vars = BuildStamps( + self.nobjects, base, logger=logger, obj_num=obj_num, do_noise=False) + + base['index_key'] = 'image_num' + + for k in range(self.nobjects): + # This is our signal that the object was skipped. + if stamps[k] is None: continue + bounds = stamps[k].bounds & full_image.bounds + logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds)) + logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(stamps[k].bounds)) + logger.debug('image %d: Overlap = %s',image_num,str(bounds)) + if bounds.isDefined(): + full_image[bounds] += stamps[k][bounds] + else: + logger.info( + "Object centered at (%d,%d) is entirely off the main image, " + "whose bounds are (%d,%d,%d,%d)."%( + stamps[k].center.x, stamps[k].center.y, + full_image.bounds.xmin, full_image.bounds.xmax, + full_image.bounds.ymin, full_image.bounds.ymax)) + + # Bring the image so far up to a flat noise variance + current_var = FlattenNoiseVariance( + base, full_image, stamps, current_vars, logger) + + return full_image, current_var + + def makeTasks(self, config, base, jobs, logger): + """Turn a list of jobs into a list of tasks. + + Here we just have one job per task. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'image_num' and 'obj_num'. + logger: If given, a logger object to log progress. + + Returns: + a list of tasks + """ + return [ [ (job, k) ] for k, job in enumerate(jobs) ] + + def addNoise(self, image, config, base, image_num, obj_num, current_var, logger): + """Add the final noise to a Scattered image + + Parameters: + image: The image onto which to add the noise. + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + current_var: The current noise variance in each postage stamps. + logger: If given, a logger object to log progress. + """ + base['current_noise_image'] = base['current_image'] + AddSky(base,image) + AddNoise(base,image,current_var,logger) + + + def getNObj(self, config, base, image_num, logger=None, approx=False): + """Get the number of objects that will be built for this image. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + logger: If given, a logger object to log progress. + approx: Whether an approximate/overestimate is ok [default: False] + + Returns: + the number of objects + """ + orig_index_key = base.get('index_key',None) + base['index_key'] = 'image_num' + base['image_num'] = image_num + + # Allow nobjects to be automatic based on input catalog + if 'nobjects' not in config: + nobj = ProcessInputNObjects(base, logger=logger, approx=approx) + if nobj is None: + raise GalSimConfigError( + "Attribute nobjects is required for image.type = Scattered") + else: + nobj = ParseValue(config,'nobjects',base,int)[0] + base['index_key'] = orig_index_key + return nobj
+ +# Register this as a valid image type +RegisterImageType('Scattered', ScatteredImageBuilder()) + + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/image_tiled.html b/docs/_build/html/_modules/galsim/config/image_tiled.html new file mode 100644 index 00000000000..6e1fcbaff38 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/image_tiled.html @@ -0,0 +1,362 @@ + + + + + + galsim.config.image_tiled — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.image_tiled

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+import numpy as np
+
+from .image import ImageBuilder, FlattenNoiseVariance, RegisterImageType
+from .util import GetRNG
+from .value import ParseValue, GetAllParams
+from .stamp import BuildStamps
+from .noise import AddSky, AddNoise
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..image import Image
+from .. import random
+
+# This file adds image type Tiled, which builds a larger image by tiling nx x ny individual
+# postage stamps.
+
+
[docs]class TiledImageBuilder(ImageBuilder): + + def setup(self, config, base, image_num, obj_num, ignore, logger): + """Do the initialization and setup for building the image. + + This figures out the size that the image will be, but doesn't actually build it yet. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if these parameters are present. + logger: If given, a logger object to log progress. + + Returns: + xsize, ysize + """ + logger.debug('image %d: Building Tiled: image, obj = %d,%d',image_num,image_num,obj_num) + + extra_ignore = [ 'image_pos' ] # We create this below, so on subequent passes, we ignore it. + req = { 'nx_tiles' : int , 'ny_tiles' : int } + opt = { 'stamp_size' : int , 'stamp_xsize' : int , 'stamp_ysize' : int , + 'border' : int , 'xborder' : int , 'yborder' : int , 'order' : str } + params = GetAllParams(config, base, req=req, opt=opt, ignore=ignore+extra_ignore)[0] + + self.nx_tiles = params['nx_tiles'] # We'll need this again later, so save them in self. + self.ny_tiles = params['ny_tiles'] + logger.debug('image %d: n_tiles = %d, %d',image_num,self.nx_tiles,self.ny_tiles) + + stamp_size = params.get('stamp_size',0) + self.stamp_xsize = params.get('stamp_xsize',stamp_size) + self.stamp_ysize = params.get('stamp_ysize',stamp_size) + + if (self.stamp_xsize <= 0) or (self.stamp_ysize <= 0): + raise GalSimConfigError( + "Both image.stamp_xsize and image.stamp_ysize need to be defined and > 0.") + + border = params.get("border",0) + self.xborder = params.get("xborder",border) + self.yborder = params.get("yborder",border) + + # Store the net grid spacing in the config dict as grid_xsize, grid_ysize for things like + # PowerSpectrum that might want to know the grid spacing. + base['grid_xsize'] = self.stamp_xsize + self.xborder + base['grid_ysize'] = self.stamp_ysize + self.yborder + + self.do_noise_in_stamps = self.xborder >= 0 and self.yborder >= 0 + # TODO: Note: if one of these is < 0 and the other is > 0, then + # this will add noise to the border region. Not exactly the + # design, but I didn't bother to do the bookkeeping right to + # make the borders pure 0 in that case. + + full_xsize = (self.stamp_xsize + self.xborder) * self.nx_tiles - self.xborder + full_ysize = (self.stamp_ysize + self.yborder) * self.ny_tiles - self.yborder + + # If image_force_xsize and image_force_ysize were set in config, make sure it matches. + if ( ('image_force_xsize' in base and full_xsize != base['image_force_xsize']) or + ('image_force_ysize' in base and full_ysize != base['image_force_ysize']) ): + raise GalSimConfigError( + "Unable to reconcile required image xsize and ysize with provided " + "nx_tiles=%d, ny_tiles=%d, xborder=%d, yborder=%d\n" + "Calculated full_size = (%d,%d) != required (%d,%d)."%( + self.nx_tiles, self.ny_tiles, self.xborder, self.yborder, + full_xsize, full_ysize, base['image_force_xsize'],base['image_force_ysize'])) + + return full_xsize, full_ysize + + + def buildImage(self, config, base, image_num, obj_num, logger): + """ + Build an Image consisting of a tiled array of postage stamps. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + logger: If given, a logger object to log progress. + + Returns: + the final image and the current noise variance in the image as a tuple + """ + full_image = base['current_image'] + + nobjects = self.nx_tiles * self.ny_tiles + + # Make a list of ix,iy values according to the specified order: + if 'order' in config: + order = ParseValue(config,'order',base,str)[0].lower() + else: + order = 'row' + if order.startswith('row'): + ix_list = [ ix for iy in range(self.ny_tiles) for ix in range(self.nx_tiles) ] + iy_list = [ iy for iy in range(self.ny_tiles) for ix in range(self.nx_tiles) ] + elif order.startswith('col'): + ix_list = [ ix for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] + iy_list = [ iy for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] + elif order.startswith('rand'): + ix_list = [ ix for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] + iy_list = [ iy for ix in range(self.nx_tiles) for iy in range(self.ny_tiles) ] + rng = GetRNG(config, base, logger, 'TiledImage, order = '+order) + random.permute(rng, ix_list, iy_list) + else: + raise GalSimConfigValueError("Invalid order.", order, ('row', 'col', 'random')) + + # Define a 'image_pos' field so the stamps can set their position appropriately in case + # we need it for PowerSpectum or NFWHalo. + x0 = (self.stamp_xsize-1)/2. + base['image_origin'].x + y0 = (self.stamp_ysize-1)/2. + base['image_origin'].y + dx = self.stamp_xsize + self.xborder + dy = self.stamp_ysize + self.yborder + config['image_pos'] = { + 'type' : 'XY', + 'x' : { 'type' : 'List', + 'items' : [ x0 + ix*dx for ix in ix_list ] + }, + 'y' : { 'type' : 'List', + 'items' : [ y0 + iy*dy for iy in iy_list ] + } + } + + stamps, current_vars = BuildStamps( + nobjects, base, logger=logger, obj_num=obj_num, + xsize=self.stamp_xsize, ysize=self.stamp_ysize, do_noise=self.do_noise_in_stamps) + + base['index_key'] = 'image_num' + + for k in range(nobjects): + logger.debug('image %d: full bounds = %s',image_num,str(full_image.bounds)) + logger.debug('image %d: stamp %d bounds = %s',image_num,k,str(stamps[k].bounds)) + assert full_image.bounds.includes(stamps[k].bounds) + b = stamps[k].bounds + full_image[b] += stamps[k] + + # Bring the noise in the image so far up to a flat noise variance + # Save the resulting noise variance as self.current_var. + current_var = 0 + if not self.do_noise_in_stamps: + current_var = FlattenNoiseVariance( + base, full_image, stamps, current_vars, logger) + return full_image, current_var + + def makeTasks(self, config, base, jobs, logger): + """Turn a list of jobs into a list of tasks. + + Here we just have one job per task. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'image_num' and 'obj_num'. + logger: If given, a logger object to log progress. + + Returns: + a list of tasks + """ + return [ [ (job, k) ] for k, job in enumerate(jobs) ] + + def addNoise(self, image, config, base, image_num, obj_num, current_var, logger): + """Add the final noise to a Tiled image + + Parameters: + image: The image onto which to add the noise. + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + obj_num: The first object number in the image. + current_var: The current noise variance in each postage stamps. + logger: If given, a logger object to log progress. + """ + # If didn't do noise above in the stamps, then need to do it here. + if not self.do_noise_in_stamps: + # Apply the sky and noise to the full image + base['current_noise_image'] = base['current_image'] + AddSky(base,image) + AddNoise(base,image,current_var,logger) + + def getNObj(self, config, base, image_num, logger=None, approx=False): + """Get the number of objects that will be built for this image. + + Parameters: + config: The configuration dict for the image field. + base: The base configuration dict. + image_num: The current image number. + logger: If given, a logger object to log progress. + approx: Whether an approximate/overestimate is ok [default: False] + + Returns: + the number of objects + """ + orig_index_key = base.get('index_key',None) + base['index_key'] = 'image_num' + base['image_num'] = image_num + + if 'nx_tiles' not in config or 'ny_tiles' not in config: + raise GalSimConfigError( + "Attributes nx_tiles and ny_tiles are required for image.type = Tiled") + nx = ParseValue(config,'nx_tiles',base,int)[0] + ny = ParseValue(config,'ny_tiles',base,int)[0] + base['index_key'] = orig_index_key + return nx*ny
+ +# Register this as a valid image type +RegisterImageType('Tiled', TiledImageBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/input.html b/docs/_build/html/_modules/galsim/config/input.html new file mode 100644 index 00000000000..20910a7f181 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/input.html @@ -0,0 +1,806 @@ + + + + + + galsim.config.input — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.input

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from multiprocessing.managers import NamespaceProxy
+
+from .util import LoggerWrapper, RemoveCurrent, GetRNG, GetLoggerProxy, get_cls_params
+from .util import SafeManager, GetIndex, PropagateIndexKeyRNGNum, single_threaded
+from .value import ParseValue, CheckAllParams, GetAllParams, SetDefaultIndex, _GetBoolValue
+from .value import RegisterValueType
+from ..errors import GalSimConfigError, GalSimConfigValueError, GalSimError
+from ..catalog import Catalog, Dict
+
+# This file handles processing the input items according to the specifications in config['input'].
+# This file includes the basic functionality, which is often sufficient for simple input types,
+# but it has hooks to allow more customized behavior where necessary. See input_*.py for examples.
+
+# This module-level dict will store all the registered input types.
+# See the RegisterInputType function near the end of this file.
+# The keys will be the (string) names of the extra output types, and the values will be
+# builder classes that will perform the different processing functions.
+# The keys will be the (string) names of the image types, and the values will be loaders
+# that load the input object's class as well some other information we need to know to how to
+# process the input object correctly.
+valid_input_types = {}
+
+# We also keep track of the connected value or gsobject types.
+# These are registered by the value or gsobject types that use each input object.
+connected_types = {}
+
+# Input objects can choose not to use an input manager, and instead use the shared memory model
+# described in the AtmosphericScreen doc string.  To make that work, they would set
+# use_proxy=False and provide worker_init and worker_initargs functions.
+# These are collected here to be run buy the MultiProcess function in config.util.
+worker_init_fns = []
+worker_initargs_fns = []
+
+# Ref: https://stackoverflow.com/questions/26499548/
+# This custom proxy lets us access attributes and properties of input objects, not just methods,
+# which are normally the only thing exposed in proxies.
+# I started out with the answers from martineau and shts38, but it didn't always work.
+# This implementation is my own (MJ) combining their ideas with some things that the AutoProxy
+# function in the multiprocessing package does.
+
+def InputProxy(target):
+    """ Create a derived NamespaceProxy class for `target`. """
+
+    # This bit follows what multiprocessing.managers.MakeProxy normally does.
+    dic = {}
+    public_methods = [m for m in dir(target) if m[0] != '_']
+    for meth in public_methods:
+        exec('''def %s(self, *args, **kwds):
+                    return self._callmethod(%r, args, kwds)
+             '''%(meth,meth), dic)
+
+    # NamespaceProxy starts with __getattribute__ defined, so subclass from that rather than
+    # BaseProxy, as MakeProxy normally does.
+    proxy_name = target.__name__ + "_Proxy"
+    ProxyType = type(proxy_name, (NamespaceProxy,), dic)
+
+    # Expose all the public methods and also __getattribute__ and __setattr__.
+    ProxyType._exposed_ = tuple(public_methods + ['__getattribute__', '__setattr__'])
+
+    return ProxyType
+
+
[docs]def ProcessInput(config, logger=None, file_scope_only=False, safe_only=False): + """ + Process the input field, reading in any specified input files or setting up + any objects that need to be initialized. + + Each item registered as a valid input type will be built and available at the top level + of config in config['_input_objs']. Since there is allowed to be more than one of each type + of input object (e.g. multilpe catalogs or multiple dicts), these are actually lists. + If there is only one e.g. catalog entry in config['input'], then this list will have one + element. + + e.g. config['_input_objs']['catalog'][0] holds the first catalog item defined in + config['input']['catalog'] (if any). + + Parameters: + config: The configuration dict to process + logger: If given, a logger object to log progress. [default: None] + file_scope_only: If True, only process the input items that are marked as being + possibly relevant for file- and image-level items. [default: False] + safe_only: If True, only process the input items whose construction parameters + are not going to change every file, so it can be made once and + used by multiple processes if appropriate. [default: False] + """ + if 'input' in config: + logger = LoggerWrapper(logger) + file_num = config.get('file_num',0) + logger.debug('file %d: Start ProcessInput',file_num) + + # We'll iterate through this list of keys a few times + all_keys = [str(k) for k in config['input'].keys() if k in valid_input_types] + + # The input items can be rather large. Especially RealGalaxyCatalog. So it is + # unwieldy to copy them in the config file for each process. Instead we use proxy + # objects which are implemented using multiprocessing.BaseManager. See + # + # http://docs.python.org/2/library/multiprocessing.html + # + # The input manager keeps track of all the real objects for us. We use it to put + # a proxy object in the config dict, which is copyable to other processes. + # The input manager itself should not be copied, so the function CopyConfig makes + # sure to only keep that in the original config dict, not the one that gets passed + # to other processed. + # The proxy objects are able to call public functions in the real object via + # multiprocessing communication channels. (A Pipe, I believe.) The BaseManager + # base class handles all the details. We just need to register each class we need + # with a name (called tag below) and then construct it by calling that tag function. + + # We don't need the manager stuff if we (a) are already in a multiprocessing Process, or + # (b) we are only loading for file scope, or (c) both config.image.nproc and + # config.output.nproc == 1. + use_manager = ( + 'current_nproc' not in config and + not file_scope_only and + ( ('image' in config and 'nproc' in config['image'] and + ParseValue(config['image'], 'nproc', config, int)[0] != 1) or + ('output' in config and 'nproc' in config['output'] and + ParseValue(config['output'], 'nproc', config, int)[0] != 1) ) ) + + if use_manager and '_input_manager' not in config: + class InputManager(SafeManager): pass + + # Register each input field with the InputManager class + for key in all_keys: + fields = config['input'][key] + nfields = len(fields) if isinstance(fields, list) else 1 + for num in range(nfields): + tag = key + str(num) + init_func = valid_input_types[key].init_func + proxy = InputProxy(init_func) + InputManager.register(tag, init_func, proxy) + # Start up the input_manager + config['_input_manager'] = InputManager() + with single_threaded(): + # Starting in python 3.12, there is a deprecation warning about using fork when + # a process is multithreaded. This can get triggered here by the start() + # function, even though I'm pretty sure this is completely safe. + # So at least until it is shown that this is a problem, just suppress + # this warning here by wrapping in single_threaded() + config['_input_manager'].start() + + # Read all input fields provided and create the corresponding object + # with the parameters given in the config file. + for key in all_keys: + loader = valid_input_types[key] + + # Skip this key if not relevant for file_scope_only run. + if file_scope_only and not loader.file_scope: continue + + logger.debug('file %d: Process input key %s',file_num,key) + LoadAllInputObj(config, key, safe_only, logger) + + # Check that there are no other attributes specified. + valid_keys = valid_input_types.keys() + CheckAllParams(config['input'], ignore=valid_keys)
+ + +
[docs]def SetupInput(config, logger=None): + """Process the input field if it hasn't been processed yet. + + This is mostly useful if the user isn't running through the full processing and just starting + at BuildImage say. This will make sure the input objects are set up in the way that they + normally would have been by the first level of processing in a ``galsim config_file`` run. + + Parameters: + config: The configuration dict in which to setup the input items. + logger: If given, a logger object to log progress. [default: None] + """ + if '_input_objs' not in config: + # Make sure any user-set index keys are propagated properly. + if 'input' in config: + PropagateIndexKeyRNGNum(config['input']) + ProcessInput(config, logger=logger)
+ + +def LoadAllInputObj(config, key, safe_only=False, logger=None): + """Load all items of a single input type, named key, with definition given by the dict field. + + This function just detects if the dict item for this key is a list and calls LoadInputObj + for every num. + + .. note:: + + This is designed as an internal implementation detail, not meant to be used by end users. + So it doesn't have some of the safeguards we normally put on public facing functions. + However, we expect the API to persist, and we'll try to use deprecations if we change + anything, so if users find it useful, it is fine to go ahead and use it in your own + custom input module implementations. + + Parameters: + config: The configuration dict to process + key: The key name of this input type + safe_only: Only load "safe" input objects. + logger: If given, a logger object to log progress. [default: None] + """ + fields = config['input'][key] + nfields = len(fields) if isinstance(fields, list) else 1 + for num in range(nfields): + input_obj = LoadInputObj(config, key, num, safe_only, logger) + return input_obj + + +def LoadInputObj(config, key, num=0, safe_only=False, logger=None): + """Load a single input object, named key, with definition given by the dict field. + + .. note:: + + This is designed as an internal implementation detail, not meant to be used by end users. + So it doesn't have some of the safeguards we normally put on public facing functions. + However, we expect the API to persist, and we'll try to use deprecations if we change + anything, so if users find it useful, it is fine to go ahead and use it in your own + custom input module implementations. + + Parameters: + config: The configuration dict to process + key: The key name of this input type + num: Which number in the list of this key, if needed. [default: 0] + safe_only: Only load "safe" input objects. + logger: If given, a logger object to log progress. [default: None] + + Returns: + The constructed input object, which is also saved in config['_input_objs'][key] + """ + logger = LoggerWrapper(logger) + if '_input_objs' not in config: + config['_input_objs'] = {} + all_input_objs = config['_input_objs'] + fields = config['input'][key] + nfields = len(fields) if isinstance(fields, list) else 1 + + if key not in all_input_objs: + all_input_objs[key] = [None] * nfields + + loader = valid_input_types[key] + field = fields[num] if isinstance(fields, list) else fields + input_objs = all_input_objs[key] + file_num = config.get('file_num',0) + + # Check if we already have it loaded. If so, early exit. + index, index_key = GetIndex(field, config) + if 'current' in field: + input_obj = input_objs[num] + _, csafe, _, cindex, _ = field['current'] + logger.debug('file %d: Current values for %s are %s, safe = %s, current index = %s', + file_num, key, str(input_obj), csafe, cindex) + if input_obj is not None and (csafe or cindex == index): + logger.debug('file %d: Using %s already read in',file_num,key) + return input_obj + + # Not loaded or not current. + logger.debug('file %d: Build input type %s',file_num,key) + try: + kwargs, safe = loader.getKwargs(field, config, logger) + except Exception as e: + # If an exception was raised here, and we are doing the safe_only run, + # then it probably needed an rng that we don't have yet. So really, that + # just implies that this input object isn't safe to keep around anyway. + # So in this case, we just continue on. If it was not a safe_only run, + # the exception is reraised. + # The one exception is if the exception indicates that another input type was needed. + # In that case, we load the dependent input type and try again. + if str(e).startswith("No input"): + dep_input = str(e).split()[2] + logger.info("%s input seems to depend on %s. Try loading that.", key, dep_input) + input_obj = LoadAllInputObj(config, dep_input, safe_only=safe_only, logger=logger) + # Now recurse to try this key again. + return LoadInputObj(config, key, num=num, safe_only=safe_only, logger=logger) + if safe_only: + logger.debug('file %d: caught exception: %s', file_num,e) + safe = False + else: + raise + + if safe_only and not safe: + logger.debug('file %d: Skip %s %d, since not safe',file_num,key,num) + input_objs[num] = None + return None + + logger.debug('file %d: %s kwargs = %s',file_num,key,kwargs) + use_proxy = ('_input_manager' in config + and 'current_nproc' not in config + and valid_input_types[key].useProxy(config, logger)) + if use_proxy: + tag = key + str(num) + if 'logger' in kwargs: + # Loggers can't be pickled. (At least prior to py3.7. Maybe they fixed this?) + # So if we have a logger, switch it for a proxy instead. + kwargs['logger'] = GetLoggerProxy(kwargs['logger']) + input_obj = getattr(config['_input_manager'],tag)(**kwargs) + else: + input_obj = loader.init_func(**kwargs) + + logger.debug('file %d: Built input object %s %d',file_num,key,num) + if 'file_name' in kwargs: + logger.debug('file %d: file_name = %s',file_num,kwargs['file_name']) + if loader.has_nobj: + if hasattr(input_obj, 'getApproxNObjects'): + logger.info('Input %s has approximately %d objects', key, input_obj.getApproxNObjects()) + else: + logger.info('Input %s has %d objects', key, input_obj.getNObjects()) + + input_objs[num] = input_obj + loader.initialize(input_objs, num, config, logger) + + # Invalidate any currently cached values that use this kind of input object: + # TODO: This isn't quite correct if there are multiple versions of this input + # item. e.g. you might want to invalidate dict0, but not dict1. + # So ideally, we would check for the num parameter in each config before + # invalidating it. Right now, we just invalidate everything with this type. + for value_type in connected_types[key]: + RemoveCurrent(config, type=value_type) + logger.debug('file %d: Cleared current vals for items with type %s', + file_num,value_type) + # Save the current status of this item in the normal way so we can check for it + # here and also so it can be used e.g. as @input.catalog in an Eval statement. + field['current'] = (input_obj, safe, None, index, index_key) + + return input_obj + + +
[docs]def ProcessInputNObjects(config, logger=None, approx=False): + """Process the input field, just enough to determine the number of objects. + + Some input items are relevant for determining the number of objects in a file or image. + This means we need to have them processed before splitting up jobs over multiple processes + (since the seed increments based on the number of objects). So this function builds + the input items that have a getNObjects() method and returns the number of objects. + + Caveat: This function tries each input type in galsim.config.valid_input_types in + order and returns the nobjects for the first one that works. If multiple input + items have nobjects and they are inconsistent, this function may return a + number of objects that isn't what you wanted. In this case, you should explicitly + set nobjects or nimages in the configuration dict, rather than relying on this + galsim.config "magic". + + Parameters: + config: The configuration dict to process + logger: If given, a logger object to log progress. [default: None] + approx: Whether an approximate count is ok. [default: False] + + Returns: + the number of objects to use. + """ + logger = LoggerWrapper(logger) + if 'input' in config: + SetupInput(config, logger=logger) + for key in valid_input_types: + loader = valid_input_types[key] + if key in config['input'] and loader.has_nobj: + # If it's a list, just use the first one. + input_obj = LoadInputObj(config, key, num=0, logger=logger) + if approx and hasattr(input_obj, 'getApproxNObjects'): + nobj = input_obj.getApproxNObjects() + else: + nobj = input_obj.getNObjects() + logger.debug('file %d: Found nobjects = %d for %s', + config.get('file_num',0), nobj, key) + return nobj + # If didn't find anything, return None. + return None
+ + +
[docs]def SetupInputsForImage(config, logger=None): + """Do any necessary setup of the input items at the start of an image. + + Parameters: + config: The configuration dict to process + logger: If given, a logger object to log progress. [default: None] + """ + if 'input' in config: + SetupInput(config, logger=logger) + for key in valid_input_types: + loader = valid_input_types[key] + if key in config['input']: + fields = config['input'][key] + input_objs = config['_input_objs'][key] + # Make fields a list if necessary. + if not isinstance(fields, list): fields = [ fields ] + + for num in range(len(fields)): + field = fields[num] + input_obj = input_objs[num] + loader.setupImage(input_obj, field, config, logger)
+ +def GetNumInputObj(input_type, base): + """Get the number of input objects of the given type + + Parameters: + input_type: The type of input object to count + base: The base config dict + """ + return len(base['_input_objs'][input_type]) + +# A helper function for getting the input object needed for generating a value or building +# a gsobject. +
[docs]def GetInputObj(input_type, config, base, param_name, num=0): + """Get the input object needed for generating a particular value + + Parameters: + input_type: The type of input object to get + config: The config dict for this input item + base: The base config dict + param_name: The type of value that we are trying to construct (only used for + error messages). + num: Which number in the list of this key, if needed. [default: 0] + """ + if '_input_objs' not in base or input_type not in base['_input_objs']: + raise GalSimConfigError( + "No input %s available for type = %s"%(input_type,param_name)) + + if num == 0 and 'num' in config: + num = ParseValue(config, 'num', base, int)[0] + + if num < 0: + raise GalSimConfigValueError("Invalid num < 0 supplied for %s."%param_name, num) + if num >= GetNumInputObj(input_type, base): + raise GalSimConfigValueError("Invalid num supplied for %s (too large)"%param_name, num) + + return base['_input_objs'][input_type][num]
+ + +
[docs]class InputLoader: + """Define how to load a particular input type. + + The base class is often sufficient for simple types, but you may derive from it and + override some of the functions to deal with special handling requirements. + + The loader object defines a few attributes that will be used by the processing framework, + so any derived class should make sure to define them as well. + + init_func + The class or function that will be used to build the input object. + + has_nobj + Whether the object can be used to automatically determine the number of + objects to build for a given file or image. For example, a galsim.Catalog has + a specific number of rows in it. In many cases, you will just want to run + through the whole catalog for each output file. So the number of objects to + build will just be the number of objects in the input catalog. [default: False] + + If this is True, the constructed input object must have a ``getNObjects()`` + method. The constructor may (if practical) only load enough to figure out + how many objects there are. Other attributes may use lazy properties to delay + finishing the read if that is efficient. + + It may optionally also have a ``getApproxNObjects()`` method, which may + return an approximate estimate of the number of objects. This is used for the + initial calculation of what object numbers to use for each file/image, and it + is generally acceptable if this is an over-estimate, even a pretty egregious + over-estimate if necessary. + + file_scope + Whether the input object might be relevant at file scope when the file and + image is initially being set up. [default: False] + + If this is False, then the input object won't be loaded until after the + initial file setup. For example, you might store the file names you want + to use for the output files in a YAML file, which you plan to read in as a + dict input object. Thus, dict is our canonical example of an input type for + which this parameter should be True. + + takes_logger + Whether the input object has a logger attribute. If so, and a proxy is being + used, then the logger will be replaced with a logger proxy. [default: False] + + use_proxy + Whether to use a proxy for commicating between processes. This is normally + necessary whenever multiprocessing is being used, but there are cases where it + is not necessary and just slows things down. [default: True] + + worker_init + One way to avoid using a proxy is to follow the shared memory model decsribed + in the doc string of `AtmosphericScreen`. This mode uses a global dict, which + is initialized for each worker at the start of multi-processing. To use this + method, one needs to provide an initialization function that gets run when + each worker starts. [default: None] + + worker_initargs + A function to provide the necessary arguments to worker_init. [default: None] + This is required whenever worker_init is not None. + """ + def __init__(self, init_func, has_nobj=False, file_scope=False, takes_logger=False, + use_proxy=True, worker_init=None, worker_initargs=None): + self.init_func = init_func + self.has_nobj = has_nobj + self.file_scope = file_scope + self.takes_logger = takes_logger + self.use_proxy = use_proxy + if (worker_init is None) != (worker_initargs is None): + raise GalSimError("Must provide both worker_init and worker_initargs") + if worker_init is not None: + worker_init_fns.append(worker_init) + worker_initargs_fns.append(worker_initargs) + +
[docs] def getKwargs(self, config, base, logger): + """Parse the config dict and return the kwargs needed to build the input object. + + The default implementation looks for special class attributes called: + + _req_params + A dict of required parameters and their types. + _opt_params + A dict of optional parameters and their types. + _single_params + A list of dicts of parameters such that one and only one of + parameter in each dict is required. + _takes_rng + A bool value saying whether an rng object is required. + + See galsim.Catalog for an example of a class that sets these attributes. + + In addition to the kwargs, we also return a bool value, safe, that indicates whether + the constructed object will be safe to keep around for multiple files (True) of if + it will need to be rebuilt for each output file (False). + + Parameters: + config: The config dict for this input item + base: The base config dict + logger: If given, a logger object to log progress. [default: None] + + Returns: + kwargs, safe + """ + req, opt, single, takes_rng = get_cls_params(self.init_func) + kwargs, safe = GetAllParams(config, base, req=req, opt=opt, single=single) + if takes_rng: + rng = GetRNG(config, base, logger, 'input '+self.init_func.__name__) + kwargs['rng'] = rng + safe = False + if self.takes_logger: + kwargs['logger'] = logger + return kwargs, safe
+ +
[docs] def useProxy(self, config, logger=None): + """Return whether to use a proxy for the input object. + + The default behavior is to return self.use_proxy, which is set by the constructor + parameters. But if you want to decide whether to use a proxy based on something + in the config dict, you can override this and decide at run time. + + Parameters: + config: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + return self.use_proxy
+ +
[docs] def setupImage(self, input_obj, config, base, logger): + """Do any necessary setup at the start of each image. + + In the base class, this function does not do anything. But see PowerSpectrumLoader + for an example that does require some setup at the start of each image. + + Parameters: + input_obj: The input object to use + config: The configuration dict for the input type + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + pass
+ +
[docs] def initialize(self, input_objs, num, base, logger): + """Do any global setup for input objects right after they are loaded. + + In the base class, this function does not do anything. It can be used to do + things like load an input object and then assign it to an eval variable in the + base configuration dictionary. + + When writing this method, remember that the entries in input objs are set to + ``None`` initially. This can cause problems for some operations on them and so + you may want to test for this condition with code like + ``all(iobj is not None for iobj in input_objs)``. + + Parameters: + input_objs: The (current) list of input objects. + num: The entry in the list that was loaded. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + pass
+ +
[docs]def RegisterInputType(input_type, loader): + """Register an input type for use by the config apparatus. + + Parameters: + input_type: The name of the type in config['input'] + loader: A loader object to use for loading in the input object. + It should be an instance of InputLoader or a subclass thereof. + + """ + valid_input_types[input_type] = loader + if input_type not in connected_types: + connected_types[input_type] = set()
+ +
[docs]def RegisterInputConnectedType(input_type, type_name): + """Register that some gsobject or value type is connected to a given input type. + + Parameters: + input_type: The name of the type in config['input'] + type_name: The name of the type that uses this input object. + """ + if isinstance(input_type, list): + for key in input_type: + RegisterInputConnectedType(key, type_name) + elif input_type is not None: + if input_type not in connected_types: + connected_types[input_type] = set() + connected_types[input_type].add(type_name)
+ +# We define in this file two simple input types: catalog and dict, which read in a Catalog +# or Dict from a file and then can use that to generate values. + +# Now define the value generators connected to the catalog and dict input types. +def _GenerateFromCatalog(config, base, value_type): + """Return a value read from an input catalog + """ + input_cat = GetInputObj('catalog', config, base, 'Catalog') + + # Setup the indexing sequence if it hasn't been specified. + # The normal thing with a Catalog is to just use each object in order, + # so we don't require the user to specify that by hand. We can do it for them. + SetDefaultIndex(config, input_cat.getNObjects()) + + # Coding note: the and/or bit is equivalent to a C ternary operator: + # input_cat.isFits() ? str : int + # which of course doesn't exist in python. This does the same thing (so long as the + # middle item evaluates to true). + req = { 'col' : input_cat.isFits() and str or int , 'index' : int } + opt = { 'num' : int } + kwargs, safe = GetAllParams(config, base, req=req, opt=opt) + col = kwargs['col'] + index = kwargs['index'] + + if value_type is str: + val = input_cat.get(index, col) + elif value_type is float: + val = input_cat.getFloat(index, col) + elif value_type is int: + val = input_cat.getInt(index, col) + else: # value_type is bool + val = _GetBoolValue(input_cat.get(index, col)) + + #print(base['file_num'],'Catalog: col = %s, index = %s, val = %s'%(col, index, val)) + return val, safe + +def _GenerateFromDict(config, base, value_type): + """Return a value read from an input dict. + """ + input_dict = GetInputObj('dict', config, base, 'Dict') + + req = { 'key' : str } + opt = { 'num' : int } + kwargs, safe = GetAllParams(config, base, req=req, opt=opt) + key = kwargs['key'] + + val = input_dict.get(key) + + #print(base['file_num'],'Dict: key = %s, val = %s'%(key,val)) + return val, safe + +# Register these as valid value types +RegisterValueType('Catalog', _GenerateFromCatalog, [ float, int, bool, str ], input_type='catalog') +RegisterInputType('catalog', InputLoader(Catalog, has_nobj=True)) +RegisterInputType('dict', InputLoader(Dict, file_scope=True)) +RegisterValueType('Dict', _GenerateFromDict, [ float, int, bool, str ], input_type='dict') +# Note: Doing the above in different orders for catalog and dict is intentional. It makes sure +# we test that this works for users no matter which order they do their registering. +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/input_cosmos.html b/docs/_build/html/_modules/galsim/config/input_cosmos.html new file mode 100644 index 00000000000..cbebdd58f63 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/input_cosmos.html @@ -0,0 +1,285 @@ + + + + + + galsim.config.input_cosmos — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.input_cosmos

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+
+from .input import InputLoader, GetInputObj, RegisterInputType
+from .gsobject import RegisterObjectType
+from .util import GetRNG
+from .value import GetAllParams, SetDefaultIndex, RegisterValueType
+from ..errors import GalSimConfigError
+from ..gsparams import GSParams
+from ..galaxy_sample import GalaxySample, COSMOSCatalog
+
+# This file adds input types cosmos_catalog and sample_galaxy and gsobject types
+# COSMOSGalaxy and SampleGalaxy.
+
+# The SampleGalaxy doesn't need anything special other than registration as a valid input type.
+# However, we do make a custom Loader so that we can add a logger line with some information about
+# the number of objects in the catalog that passed the initial cuts and other basic catalog info.
+
[docs]class SampleLoader(InputLoader): + def __init__(self, cls, input_field): + self.cls_name = cls.__name__ + self.input_field = input_field + super().__init__(cls) + + def setupImage(self, cosmos_cat, config, base, logger): + if logger: + # Only report as a warning the first time. After that, use info. + first = not base.get('_SampleLoader_reported_as_warning',False) + base['_SampleLoader_reported_as_warning'] = True + if first: + log_level = logging.WARNING + else: + log_level = logging.INFO + # It should be required that base['input']['cosmos_catalog'] exists, but + # just in case someone calls this in a weird way, use get() with defaults. + cc = base.get('input',{}).get(self.input_field,{}) + if isinstance(cc,list): cc = cc[0] + out_str = '' + if 'sample' in cc: + out_str += '\n sample = %s'%cc['sample'] + if 'dir' in cc: + out_str += '\n dir = %s'%cc['dir'] + if 'file_name' in cc: + out_str += '\n file_name = %s'%cc['file_name'] + if out_str != '': + logger.log(log_level, 'Using user-specified %s: %s',self.cls_name, out_str) + logger.info("file %d: Sample catalog has %d total objects; %d passed initial cuts.", + base['file_num'], cosmos_cat.getNTot(), cosmos_cat.nobjects) + if base.get('gal',{}).get('gal_type',None) == 'parametric': + logger.log(log_level,"Using parametric galaxies.") + else: + logger.log(log_level,"Using real galaxies.")
+ +RegisterInputType('cosmos_catalog', SampleLoader(COSMOSCatalog, 'cosmos_catalog')) +RegisterInputType('galaxy_sample', SampleLoader(GalaxySample, 'galaxy_sample')) + +# The gsobject types coupled to these are COSMOSGalaxy and SampleGalaxy respectively. + +
[docs]def _BuildCOSMOSGalaxy(config, base, ignore, gsparams, logger): + """Build a COSMOS galaxy using the cosmos_catalog input item. + """ + sample_cat = GetInputObj('cosmos_catalog', config, base, 'COSMOSGalaxy') + return _FinishBuildSampleGalaxy(config, base, ignore, gsparams, logger, + sample_cat, 'COSMOSGalaxy')
+ +def _BuildSampleGalaxy(config, base, ignore, gsparams, logger): + """Build a sample galaxy using the galaxy_sample input item. + """ + sample_cat = GetInputObj('galaxy_sample', config, base, 'SampleGalaxy') + return _FinishBuildSampleGalaxy(config, base, ignore, gsparams, logger, + sample_cat, 'SampleGalaxy') + +def _FinishBuildSampleGalaxy(config, base, ignore, gsparams, logger, sample_cat, cls_name): + ignore = ignore + ['num'] + + # Special: if galaxies are selected based on index, and index is Sequence or Random, and max + # isn't set, set it to nobjects-1. + if 'index' in config: + SetDefaultIndex(config, sample_cat.nobjects) + + opt = { "index" : int, + "gal_type" : str, + "noise_pad_size" : float, + "deep" : bool, + "sersic_prec": float, + "chromatic": bool, + "area": float, + "exptime": float, + } + + kwargs, safe = GetAllParams(config, base, opt=opt, ignore=ignore) + if gsparams: kwargs['gsparams'] = GSParams(**gsparams) + + rng = GetRNG(config, base, logger, cls_name) + + if 'index' not in kwargs: + kwargs['index'], n_rng_calls = sample_cat.selectRandomIndex(1, rng=rng, _n_rng_calls=True) + safe = False + + # Make sure this process gives consistent results regardless of the number of processes + # being used. + if not isinstance(sample_cat, GalaxySample) and rng is not None: + # Then sample_cat is really a proxy, which means the rng was pickled, so we need to + # discard the same number of random calls from the one in the config dict. + rng.discard(int(n_rng_calls)) + + kwargs['rng'] = rng + + # NB. Even though index is officially optional, it will always be present, either because it was + # set by a call to selectRandomIndex, explicitly by the user, or due to the call to + # SetDefaultIndex. + index = kwargs['index'] + if index >= sample_cat.nobjects: + raise GalSimConfigError( + "index=%s has gone past the number of entries in the %s"%(index, cls_name)) + + logger.debug('obj %d: %s kwargs = %s',base.get('obj_num',0), cls_name, kwargs) + + kwargs['self'] = sample_cat + + # Use a staticmethod of GalaxySample to avoid pickling the result of makeGalaxy() + # The RealGalaxy in particular has a large serialization, so it is more efficient to + # make it in this process, which is what happens here. + gal = GalaxySample._makeGalaxy(**kwargs) + + return gal, safe + +# Register this as a valid gsobject type +RegisterObjectType('COSMOSGalaxy', _BuildCOSMOSGalaxy, input_type='cosmos_catalog') +RegisterObjectType('SampleGalaxy', _BuildSampleGalaxy, input_type='galaxy_sample') + +# Finally, also provide accessor to values in the parametric catalog + +def _GetCOSMOSValue(config, base, value_type): + cosmos_cat = GetInputObj('cosmos_catalog', config, base, 'COSMOSValue') + req = { 'key': str, 'index': int } + kwargs, safe = GetAllParams(config, base, req=req) + return cosmos_cat.getValue(**kwargs) + +def _GetSampleValue(config, base, value_type): + sample_cat = GetInputObj('galaxy_sample', config, base, 'SampleValue') + req = { 'key': str, 'index': int } + kwargs, safe = GetAllParams(config, base, req=req) + return sample_cat.getValue(**kwargs) + +RegisterValueType('COSMOSValue', _GetCOSMOSValue, [float], input_type='cosmos_catalog') +RegisterValueType('SampleValue', _GetSampleValue, [float], input_type='galaxy_sample') +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/input_nfw.html b/docs/_build/html/_modules/galsim/config/input_nfw.html new file mode 100644 index 00000000000..f799b9022c5 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/input_nfw.html @@ -0,0 +1,225 @@ + + + + + + galsim.config.input_nfw — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.input_nfw

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from .input import InputLoader, GetInputObj, RegisterInputType
+from .value import GetCurrentValue, CheckAllParams, GetAllParams, RegisterValueType
+from .util import LoggerWrapper
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..shear import Shear
+from ..nfw_halo import NFWHalo
+
+# This file adds input type nfw_halo and value types NFWHaloShear and NFWHaloMagnification.
+
+
[docs]class NFWLoader(InputLoader): + def setupImage(self, input_obj, config, base, logger=None): + # Just attach the logger to the input_obj so we can use it when evaluating values. + input_obj.logger = LoggerWrapper(logger)
+ +# Register this as a valid input type +RegisterInputType('nfw_halo', NFWLoader(NFWHalo)) + +
[docs]def _GenerateFromNFWHaloShear(config, base, value_type): + """Return a shear calculated from an NFWHalo object. + """ + nfw_halo = GetInputObj('nfw_halo', config, base, 'NFWHaloShear') + logger = nfw_halo.logger + + if 'uv_pos' not in base: + raise GalSimConfigError("NFWHaloShear requested, but no position defined.") + pos = base['uv_pos'] + + if 'gal' not in base or 'redshift' not in base['gal']: + raise GalSimConfigError("NFWHaloShear requested, but no gal.redshift defined.") + redshift = GetCurrentValue('redshift', base['gal'], float, base) + + # There aren't any parameters for this, so just make sure num is the only (optional) + # one present. + CheckAllParams(config, opt={ 'num' : int }) + + g1,g2 = nfw_halo.getShear(pos,redshift) + + try: + shear = Shear(g1=g1,g2=g2) + except Exception as e: + logger.warning('obj %d: Warning: NFWHalo shear (g1=%f, g2=%f) is invalid. '%( + base['obj_num'],g1,g2) + 'Using shear = 0.') + shear = Shear(g1=0,g2=0) + + logger.debug('obj %d: NFWHalo shear = %s',base['obj_num'],shear) + return shear, False
+ + +
[docs]def _GenerateFromNFWHaloMagnification(config, base, value_type): + """Return a magnification calculated from an NFWHalo object. + """ + nfw_halo = GetInputObj('nfw_halo', config, base, 'NFWHaloMagnification') + logger = nfw_halo.logger + + if 'uv_pos' not in base: + raise GalSimConfigError("NFWHaloMagnification requested, but no position defined.") + pos = base['uv_pos'] + + if 'gal' not in base or 'redshift' not in base['gal']: + raise GalSimConfigError("NFWHaloMagnification requested, but no gal.redshift defined.") + redshift = GetCurrentValue('redshift', base['gal'], float, base) + + opt = { 'max_mu' : float, 'num' : int } + kwargs = GetAllParams(config, base, opt=opt)[0] + + max_mu = kwargs.get('max_mu', 25.) + if not max_mu > 0.: + raise GalSimConfigValueError( + "Invalid max_mu for type = NFWHaloMagnification (must be > 0)", max_mu) + + mu = nfw_halo.getMagnification(pos,redshift) + if mu < 0 or mu > max_mu: + logger.warning('obj %d: Warning: NFWHalo mu = %f means strong lensing. '%( + base['obj_num'],mu) + 'Using mu = %f'%max_mu) + mu = max_mu + + logger.debug('obj %d: NFWHalo mu = %s',base['obj_num'],mu) + return mu, False
+ + +# Register these as valid value types +RegisterValueType('NFWHaloShear', _GenerateFromNFWHaloShear, [ Shear ], + input_type='nfw_halo') +RegisterValueType('NFWHaloMagnification', _GenerateFromNFWHaloMagnification, [ float ], + input_type='nfw_halo') +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/input_powerspectrum.html b/docs/_build/html/_modules/galsim/config/input_powerspectrum.html new file mode 100644 index 00000000000..84e3345d96d --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/input_powerspectrum.html @@ -0,0 +1,389 @@ + + + + + + galsim.config.input_powerspectrum — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for galsim.config.input_powerspectrum

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import math
+import warnings
+import logging
+
+from .input import InputLoader, GetInputObj, RegisterInputType
+from .util import LoggerWrapper, SetupConfigRNG, GetRNG
+from .value import ParseValue, GetAllParams, CheckAllParams, RegisterValueType
+from .stamp import ParseWorldPos
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..position import PositionD
+from ..shear import Shear
+from ..random import BaseDeviate
+from ..lensing_ps import PowerSpectrum
+
+# This file adds input type nfw_halo and value types PowerSpectrumShear and
+# PowerSpectrumMagnification.
+
+# A PowerSpectrum input type requires a special initialization at the start of each image
+# to build the shear grid.  This is done in SetupPowerSpecrum.  There are also a couple of
+# parameters that are specific to that step, which we want to ignore when getting the
+# initialization kwargs, so we define a special GetPowerSpectrumKwargs function here.
+
+
[docs]class PowerSpectrumLoader(InputLoader): + + def getKwargs(self, config, base, logger): + """Parse the config dict and return the kwargs needed to build the PowerSpectrum object. + + Parameters: + config: The configuration dict for 'power_spectrum' + base: The base configuration dict + logger: If given, a logger object to log progress. + + Returns: + kwargs, safe + """ + logger = LoggerWrapper(logger) + + # If we are going to use a different rebuilding cadence than the normal once per image, + # then in order for this feature to work properly in a multiprocessing context, + # we need to have it use an rng that also updates at the same cadence as this + # index value. So if we don't have an rng_num yet, make a new random_seed value + # that tracks this index. + if ('index' in config and 'rng_num' not in config and 'image' in base and + 'random_seed' in base['image']): + obj_num = base.get('obj_num',None) + image_num = base.get('image_num',None) + file_num = base.get('file_num',None) + base['obj_num'] = base['image_num'] = base['file_num'] = 0 + if isinstance(base['image']['random_seed'],list): + first_seed = ParseValue(base['image']['random_seed'], 0, base, int)[0] + else: + first_seed = ParseValue(base['image'], 'random_seed', base, int)[0] + rs = base['image']['random_seed'] + if not isinstance(rs, list): rs = [rs] + first_seed += 31415 # An arbitrary offset to avoid the chance of unwanted correlations + first_seed = BaseDeviate(first_seed).raw() + rs.append({ 'type' : 'Eval', + 'str' : 'first + ps_index', + 'ifirst' : first_seed, + 'ips_index' : config['index'] }) + config['rng_num'] = len(rs) - 1 + base['image']['random_seed'] = rs + orig_index_key = base.get('index_key', 'file_num') + base['index_key'] = 'file_num' + SetupConfigRNG(base, logger=logger) + if image_num is not None: + base['index_key'] = 'image_num' + SetupConfigRNG(base, logger=logger) + base['index_key'] = orig_index_key + base['obj_num'] = obj_num + base['image_num'] = image_num + base['file_num'] = file_num + + # Ignore these parameters here, since they are for the buildGrid step, not the + # initialization of the PowerSpectrum object. + ignore = ['grid_spacing', 'ngrid', 'interpolant', 'variance', 'center', 'index'] + opt = PowerSpectrum._opt_params + return GetAllParams(config, base, opt=opt, ignore=ignore) + + def setupImage(self, input_obj, config, base, logger=None): + """Set up the PowerSpectrum input object's gridded values based on the + size of the image and the grid spacing. + + Parameters: + input_obj: The PowerSpectrum object to use + config: The configuration dict for 'power_spectrum' + base: The base configuration dict. + logger: If given, a logger object to log progress. + """ + logger = LoggerWrapper(logger) + # Attach the logger to the input_obj so we can use it when evaluating values. + input_obj.logger = logger + + if 'grid_spacing' in config: + grid_spacing = ParseValue(config, 'grid_spacing', base, float)[0] + elif 'grid_xsize' in base and 'grid_ysize' in base: + # Then we have a tiled image. Can use the tile spacing as the grid spacing. + grid_size = min(base['grid_xsize'], base['grid_ysize']) + # This size is in pixels, so we need to convert to arcsec using the pixel scale. + # Note: we use the (max) pixel scale at the image center. This isn't + # necessarily optimal, but it seems like the best choice for a non-trivial WCS. + scale = base['wcs'].maxLinearScale(base['image_center']) + grid_spacing = grid_size * scale + else: + raise GalSimConfigError( + "power_spectrum.grid_spacing required for non-tiled images") + + if 'ngrid' in config: + ngrid = ParseValue(config, 'ngrid', base, float)[0] + elif 'grid_xsize' in base and base['grid_xsize'] == base['grid_ysize']: + # PowerSpectrum can only do a square FFT, so make it the larger of the two n's. + nx_grid = int(math.ceil(base['image_xsize']/base['grid_xsize'])) + ny_grid = int(math.ceil(base['image_ysize']/base['grid_ysize'])) + ngrid = max(nx_grid, ny_grid) + 1 + # Normally that's good, but if tiles aren't square, need to drop through to the + # second option. + else: + image_size = max(base['image_xsize'], base['image_ysize']) + scale = base['wcs'].maxLinearScale(base['image_center']) + ngrid = int(math.ceil(image_size * scale / grid_spacing)) + 1 + + if 'interpolant' in config: + interpolant = ParseValue(config, 'interpolant', base, str)[0] + else: + interpolant = None + + if 'variance' in config: + variance = ParseValue(config, 'variance', base, float)[0] + else: + variance = None + + if 'center' in config: + center = ParseWorldPos(config, 'center', base, logger) + elif base['wcs']._isCelestial: + center = PositionD(0,0) + else: + center = base['wcs'].toWorld(base['image_center']) + + if 'index' in config: + index = ParseValue(config, 'index', base, int)[0] + current_index = config.get('current_setup_index',None) + if index == current_index: + logger.info('image %d: power spectrum grid is already current', + base.get('image_num',0)) + return + config['current_setup_index'] = index + + rng = GetRNG(config, base, logger, 'PowerSpectrum') + + # We don't care about the output here. This just builds the grid, which we'll + # access for each object using its position. + logger.debug('image %d: PowerSpectrum buildGrid(grid_spacing=%s, ngrid=%s, center=%s, ' + 'interpolant=%s, variance=%s)', + base.get('image_num',0), grid_spacing, ngrid, center, interpolant, variance) + input_obj.buildGrid(grid_spacing=grid_spacing, ngrid=ngrid, center=center, + rng=rng, interpolant=interpolant, variance=variance) + + # Make sure this process gives consistent results regardless of the number of processes + # being used. + if not isinstance(input_obj, PowerSpectrum) and rng is not None: + # Then input_obj is really a proxy, which means the rng was pickled, so we need to + # discard the same number of random calls from the one in the config dict. + rng.discard(input_obj.nRandCallsForBuildGrid())
+ +# Register this as a valid input type +RegisterInputType('power_spectrum', PowerSpectrumLoader(PowerSpectrum)) + + +# There are two value types associated with this: PowerSpectrumShear and +# PowerSpectrumMagnification. + +
[docs]def _GenerateFromPowerSpectrumShear(config, base, value_type): + """Return a shear calculated from a PowerSpectrum object. + """ + power_spectrum = GetInputObj('power_spectrum', config, base, 'PowerSpectrumShear') + logger = power_spectrum.logger + + if 'uv_pos' not in base: + raise GalSimConfigError("PowerSpectrumShear requested, but no position defined.") + pos = base['uv_pos'] + + # There aren't any parameters for this, so just make sure num is the only (optional) + # one present. + CheckAllParams(config, opt={ 'num' : int }) + + with warnings.catch_warnings(record=True) as w: + g1,g2 = power_spectrum.getShear(pos) + if len(w) > 0: + # Send the warning to the logger, rather than raising a normal warning. + # The warning here would typically be that the position is out of range of where the + # power spectrum is defined. So if we do get this and the position is not on the image, + # we probably don't care. In that case, just log it as debug, not warn. + log_level = (logging.WARNING if 'current_image' in base and + base['current_image'].outer_bounds.includes(pos) + else logging.DEBUG) + for ww in w: + logger.log(log_level, 'obj %d: %s',base['obj_num'], ww.message) + + try: + shear = Shear(g1=g1,g2=g2) + except Exception as e: + logger.warning('obj %d: Warning: PowerSpectrum shear (g1=%f, g2=%f) is invalid. '%( + base['obj_num'],g1,g2) + 'Using shear = 0.') + shear = Shear(g1=0,g2=0) + + logger.debug('obj %d: PowerSpectrum shear = %s',base['obj_num'],shear) + return shear, False
+ +
[docs]def _GenerateFromPowerSpectrumMagnification(config, base, value_type): + """Return a magnification calculated from a PowerSpectrum object. + """ + power_spectrum = GetInputObj('power_spectrum', config, base, 'PowerSpectrumMagnification') + logger = power_spectrum.logger + + if 'uv_pos' not in base: + raise GalSimConfigError( + "PowerSpectrumMagnification requested, but no position defined.") + pos = base['uv_pos'] + + opt = { 'max_mu' : float, 'num' : int } + kwargs = GetAllParams(config, base, opt=opt)[0] + + with warnings.catch_warnings(record=True) as w: + mu = power_spectrum.getMagnification(pos) + if len(w) > 0: + log_level = (logging.WARNING if 'current_image' in base and + base['current_image'].outer_bounds.includes(pos) + else logging.DEBUG) + for ww in w: + logger.log(log_level, 'obj %d: %s',base['obj_num'], ww.message) + + max_mu = kwargs.get('max_mu', 25.) + if not max_mu > 0.: + raise GalSimConfigValueError( + "Invalid max_mu for type = PowerSpectrumMagnification (must be > 0)", max_mu) + + if mu < 0 or mu > max_mu: + logger.warning('obj %d: Warning: PowerSpectrum mu = %f means strong lensing. '%( + base['obj_num'],mu) + 'Using mu=%f'%max_mu) + mu = max_mu + + logger.debug('obj %d: PowerSpectrum mu = %s',base['obj_num'],mu) + return mu, False
+ +# Register these as valid value types +RegisterValueType('PowerSpectrumShear', _GenerateFromPowerSpectrumShear, [Shear], + input_type='power_spectrum') +RegisterValueType('PowerSpectrumMagnification', _GenerateFromPowerSpectrumMagnification, [float], + input_type='power_spectrum') +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/input_real.html b/docs/_build/html/_modules/galsim/config/input_real.html new file mode 100644 index 00000000000..dfd36f78c2c --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/input_real.html @@ -0,0 +1,245 @@ + + + + + + galsim.config.input_real — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.input_real

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from .input import InputLoader, GetInputObj, RegisterInputType, GetNumInputObj
+from .util import GetRNG, get_cls_params
+from .value import GetAllParams, SetDefaultIndex
+from .gsobject import RegisterObjectType
+from ..gsparams import GSParams
+from ..errors import GalSimConfigError
+from ..real import RealGalaxyCatalog, RealGalaxy, ChromaticRealGalaxy
+
+# This file adds input type real_catalog and gsobject types RealGalaxy and RealGalaxyOriginal.
+
+# The RealGalaxyCatalog doesn't need anything special other than registration as a valid
+# input type.
+RegisterInputType('real_catalog', InputLoader(RealGalaxyCatalog, takes_logger=True))
+
+# There are two gsobject types that are coupled to this: RealGalaxy and RealGalaxyOriginal.
+
+
[docs]def _BuildRealGalaxy(config, base, ignore, gsparams, logger, param_name='RealGalaxy'): + """Build a RealGalaxy from the real_catalog input item. + """ + real_cat = GetInputObj('real_catalog', config, base, param_name) + + # Special: if index is Sequence or Random, and max isn't set, set it to nobjects-1. + # But not if they specify 'id' or have 'random=True', which overrides that. + if 'id' not in config: + if 'random' not in config: + SetDefaultIndex(config, real_cat.nobjects) + else: + if not config['random']: + SetDefaultIndex(config, real_cat.nobjects) + # Need to do this to avoid being caught by the GetAllParams() call, which will flag + # it if it has 'index' and 'random' set (but 'random' is False, so really it's OK). + del config['random'] + + req, opt, single, _ = get_cls_params(RealGalaxy) + kwargs, safe = GetAllParams(config, base, req, opt, single, ignore = ignore + ['num']) + if gsparams: kwargs['gsparams'] = GSParams(**gsparams) + + kwargs['rng'] = GetRNG(config, base, logger, param_name) + + if 'index' in kwargs: + index = kwargs['index'] + if index >= real_cat.nobjects or index < 0: + raise GalSimConfigError( + "index=%s has gone past the number of entries in the RealGalaxyCatalog"%index) + + kwargs['real_galaxy_catalog'] = real_cat + logger.debug('obj %d: %s kwargs = %s',base.get('obj_num',0),param_name,kwargs) + + gal = RealGalaxy(**kwargs) + + return gal, safe
+ + +
[docs]def _BuildRealGalaxyOriginal(config, base, ignore, gsparams, logger): + """Return the original image from a RealGalaxy using the real_catalog input item. + """ + gal, safe = _BuildRealGalaxy(config, base, ignore, gsparams, logger, + param_name='RealGalaxyOriginal') + return gal.original_gal, safe
+ +def _BuildChromaticRealGalaxy(config, base, ignore, gsparams, logger): + """Build a ChromaticRealGalaxy from several real_catalog input items. + """ + param_name = 'ChromaticRealGalaxy' + ncats = GetNumInputObj('real_catalog', base) + real_cats = [ GetInputObj('real_catalog', config, base, param_name, n) + for n in range(ncats)] + + # Special: if index is Sequence or Random, and max isn't set, set it to nobjects-1. + # But not if they specify 'id' or have 'random=True', which overrides that. + if 'id' not in config: + if 'random' not in config: + SetDefaultIndex(config, real_cats[0].nobjects) + else: + if not config['random']: + SetDefaultIndex(config, real_cats[0].nobjects) + # Need to do this to avoid being caught by the GetAllParams() call, which will flag + # it if it has 'index' and 'random' set (but 'random' is False, so really it's OK). + del config['random'] + + req, opt, single, _ = get_cls_params(ChromaticRealGalaxy) + + kwargs, safe = GetAllParams(config, base, req, opt, single, ignore) + if gsparams: kwargs['gsparams'] = GSParams(**gsparams) + + kwargs['rng'] = GetRNG(config, base, logger, param_name) + + if 'index' in kwargs: + index = kwargs['index'] + if index >= real_cats[0].nobjects or index < 0: + raise GalSimConfigError( + "index=%s has gone past the number of entries in the RealGalaxyCatalog"%index) + + kwargs['real_galaxy_catalogs'] = real_cats + logger.debug('obj %d: %s kwargs = %s',base.get('obj_num',0),param_name,kwargs) + + gal = ChromaticRealGalaxy(**kwargs) + + return gal, safe + + +# Register these as valid gsobject types +RegisterObjectType('RealGalaxy', _BuildRealGalaxy, input_type='real_catalog') +RegisterObjectType('RealGalaxyOriginal', _BuildRealGalaxyOriginal, input_type='real_catalog') +RegisterObjectType('ChromaticRealGalaxy', _BuildChromaticRealGalaxy, input_type='real_catalog') +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/noise.html b/docs/_build/html/_modules/galsim/config/noise.html new file mode 100644 index 00000000000..815310d6a8b --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/noise.html @@ -0,0 +1,770 @@ + + + + + + galsim.config.noise — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.noise

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+import numpy as np
+import math
+
+from .util import LoggerWrapper, GetIndex, GetRNG
+from .value import ParseValue, GetCurrentValue, GetAllParams
+from .input import RegisterInputConnectedType
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..image import Image
+from ..position import PositionI
+from ..random import PoissonDeviate
+from ..noise import GaussianNoise, PoissonNoise, DeviateNoise, CCDNoise
+from ..correlatednoise import getCOSMOSNoise, BaseCorrelatedNoise, UncorrelatedNoise
+
+# This file handles the functionality for adding noise and the sky to an image after
+# drawing the objects.
+
+# This module-level dict will store all the registered noise types.
+# See the RegisterNoiseType function at the end of this file.
+# The keys are the (string) names of the noise types, and the values will be builder objects
+# that will perform the different functions related to adding noise to images.
+valid_noise_types = {}
+
+#
+# First the driver functions:
+#
+
+
[docs]def AddSky(config, im): + """Add the sky level to the image + + Parameters: + config: The (base) configuration dict + im: The image onto which to add the sky + """ + index, orig_index_key = GetIndex(config['image'], config) + config['index_key'] = 'image_num' + sky = GetSky(config['image'], config, full=True) + if sky: + im += sky + config['index_key'] = orig_index_key
+ + +
[docs]def AddNoise(config, im, current_var=0., logger=None): + """ + Add noise to an image according to the noise specifications in the noise dict. + + Parameters: + config: The (base) configuration dict + im: The image onto which to add the noise + current_var: The current noise variance present in the image already [default: 0] + logger: If given, a logger object to log progress. [default: None] + + Returns: + Variance added to the image (units are ADU if gain != 1) + """ + from .stamp import SetupConfigObjNum + + logger = LoggerWrapper(logger) + if 'noise' in config['image']: + noise = config['image']['noise'] + else: # No noise. + return 0 + if not isinstance(noise, dict): + raise GalSimConfigError("image.noise is not a dict.") + + # Default is Poisson + noise_type = noise.get('type', 'Poisson') + if noise_type not in valid_noise_types: + raise GalSimConfigValueError("Invalid noise.type.", noise_type, + list(valid_noise_types.keys())) + + # We need to use image_num for the index_key, but if we are running this from the stamp + # building phase, then we want to use obj_num_rng for the noise rng. So get the rng now + # before we change config['index_key']. + index, orig_index_key = GetIndex(noise, config) + rng = GetRNG(noise, config) + + # This makes sure draw_method is properly copied over and given a default value. + SetupConfigObjNum(config, config.get('obj_num',0), logger) + draw_method = GetCurrentValue('draw_method', config['stamp'], str, config) + + builder = valid_noise_types[noise_type] + config['index_key'] = 'image_num' + var = builder.addNoise(noise, config, im, rng, current_var, draw_method, logger) + config['index_key'] = orig_index_key + + return var
+ +
[docs]def CalculateNoiseVariance(config, full=False): + """ + Calculate the noise variance from the noise specified in the noise dict. + + Parameters: + config: The (base) configuration dict + full: If the noise is variable across the image, return the full image with the + noise variance at every pixel. Otherwise, just return the value at the center. + + Returns: + the noise variance (units are ADU if gain != 1) + """ + if 'noise' in config['image']: + noise = config['image']['noise'] + else: # No noise. + return 0 + if not isinstance(noise, dict): + raise GalSimConfigError("image.noise is not a dict.") + + noise_type = noise.get('type', 'Poisson') + if noise_type not in valid_noise_types: + raise GalSimConfigValueError("Invalid noise.type.", noise_type, + list(valid_noise_types.keys())) + + index, orig_index_key = GetIndex(noise, config) + config['index_key'] = 'image_num' + + builder = valid_noise_types[noise_type] + var = builder.getNoiseVariance(noise, config, full=full) + config['index_key'] = orig_index_key + + return var
+ +
[docs]def AddNoiseVariance(config, im, include_obj_var=False, logger=None): + """ + Add the noise variance to an image according to the noise specifications in the noise dict. + Typically, this is used for building a weight map, which is typically the inverse variance. + + Parameters: + config: The (base) configuration dict + im: The image onto which to add the variance values + include_obj_var: Whether to add the variance from the object photons for noise + models that have a component based on the number of photons. + Note: if this is True, the returned variance will not include this + contribution to the noise variance. [default: False] + logger: If given, a logger object to log progress. [default: None] + + Returns: + the variance in the image (units are ADU if gain != 1) + """ + logger = LoggerWrapper(logger) + if 'noise' in config['image']: + noise = config['image']['noise'] + else: # No noise. + return + if not isinstance(noise, dict): + raise GalSimConfigError("image.noise is not a dict.") + + noise_type = noise.get('type', 'Poisson') + if noise_type not in valid_noise_types: + raise GalSimConfigValueError("Invalid noise.type.", noise_type, + list(valid_noise_types.keys())) + + index, orig_index_key = GetIndex(noise, config) + config['index_key'] = 'image_num' + + builder = valid_noise_types[noise_type] + builder.addNoiseVariance(noise, config, im, include_obj_var, logger) + config['index_key'] = orig_index_key
+ +
[docs]def GetSky(config, base, logger=None, full=False): + """Parse the sky information and return either a float value for the sky level per pixel + or an image, as needed. + + If an image is required (because wcs is not uniform) then it will use the presence of + base['image_pos'] to determine what size image to return (stamp or full). If there is + a current image_pos, then we are doing a stamp. Otherwise a full image. + + Parameters: + config: The configuration field with the sky specification, which can be either + base['image'] or base['image']['noise'] + base: The base configuration dict + logger: If given, a logger object to log progress. [default: None] + full: If the sky level is variable across the image, return the full + image with the sky at every pixel. Otherwise, just return the + sky at the image center. + + Returns: + sky, either a float value or an Image. (The latter only if full=True) + """ + logger = LoggerWrapper(logger) + if 'sky_level' in config: + if 'sky_level_pixel' in config: + raise GalSimConfigValueError( + "Cannot specify both sky_level and sky_level_pixel", + (config['sky_level'], config['sky_level_pixel'])) + sky_level = ParseValue(config,'sky_level',base,float)[0] + logger.debug('image %d, obj %d: sky_level = %f', + base.get('image_num',0),base.get('obj_num',0), sky_level) + wcs = base['wcs'] + if wcs._isUniform: + sky_level_pixel = sky_level * wcs.pixelArea() + logger.debug('image %d, obj %d: Uniform: sky_level_pixel = %f', + base.get('image_num',0),base.get('obj_num',0), sky_level_pixel) + return sky_level_pixel + elif full: + # This calculation is non-trivial, so store this in case we need it again. + tag = (id(base), base.get('file_num',0), base.get('image_num',0), base.get('obj_num',0)) + if config.get('_current_sky_tag',None) == tag: + logger.debug('image %d, obj %d: Using saved sky image', + base.get('image_num',0),base.get('obj_num',0)) + return config['_current_sky'] + else: + logger.debug('image %d, obj %d: Non-uniform wcs. Making sky image', + base.get('image_num',0),base.get('obj_num',0)) + bounds = base['current_noise_image'].bounds + sky = Image(bounds, wcs=wcs) + wcs.makeSkyImage(sky, sky_level) + sensor = base.get('sensor', None) + if sensor is not None: + center = base.get('image_origin', PositionI(1,1)) - sky.origin + use_flux = config.get('use_flux_sky_areas', False) + # TODO: If use_flux_sky_areas = True, then we should really build this up + # in steps. E.g. for a flat field. + # This one step calcualtion isn't right. + area = sensor.calculate_pixel_areas(sky, orig_center=center, use_flux=use_flux) + sky *= area + config['_current_sky_tag'] = tag + config['_current_sky'] = sky + return sky + else: + center = base['current_noise_image'].bounds.true_center + return wcs.local(image_pos=center).pixelArea() * sky_level + elif 'sky_level_pixel' in config: + sky_level_pixel = ParseValue(config,'sky_level_pixel',base,float)[0] + logger.debug('image %d, obj %d: sky_level_pixel = %f', + base.get('image_num',0),base.get('obj_num',0), sky_level_pixel) + return sky_level_pixel + else: + return 0.
+ + +# items that are parsed separately from the normal noise function +noise_ignore = [ 'whiten', 'symmetrize', 'use_flux_sky_areas' ] + +
[docs]class NoiseBuilder: + """A base class for building noise objects and applying the noise to images. + + The base class doesn't do anything, but it defines the call signatures of the methods + that derived classes should use for the different specific noise types. + """ +
[docs] def addNoise(self, config, base, im, rng, current_var, draw_method, logger): + """Read the noise parameters from the config dict and add the appropriate noise to the + given image. + + Parameters: + config: The configuration dict for the noise field. + base: The base configuration dict. + im: The image onto which to add the noise + rng: The random number generator to use for adding the noise. + current_var: The current noise variance present in the image already. + draw_method: The method that was used to draw the objects on the image. + logger: If given, a logger object to log progress. + + Returns: + the variance of the noise model (units are ADU if gain != 1) + """ + raise NotImplementedError("The %s class has not overridden addNoise"%self.__class__)
+ +
[docs] def getNoiseVariance(self, config, base, full=False): + """Read the noise parameters from the config dict and return the variance. + + Parameters: + config: The configuration dict for the noise field. + base: The base configuration dict. + full: If the noise is variable across the image, return the full image with the + noise variance at every pixel. Otherwise, just return the value at the + center. + + Returns: + the variance of the noise model (units are ADU if gain != 1) + """ + raise NotImplementedError("The %s class has not overridden addNoise"%self.__class__)
+ +
[docs] def addNoiseVariance(self, config, base, im, include_obj_var, logger): + """Read the noise parameters from the config dict and add the appropriate noise variance + to the given image. + + This is used for constructing the weight map iamge. It doesn't add a random value to + each pixel. Rather, it adds the variance of the noise that was used in the main image to + each pixel in this image. + + This method has a default implemenation that is appropriate for noise models that have + a constant noise variance. It just gets the variance from getNoiseVariance and adds + that constant value to every pixel. + + Parameters: + config: The configuration dict for the noise field. + base: The base configuration dict. + im: The image onto which to add the noise variance + include_obj_var: Whether the noise variance values should the photon noise from + object flux in addition to the sky flux. Only relevant for + noise models that are based on the image flux values such as + Poisson and CCDNoise. + logger: If given, a logger object to log progress. + """ + im += self.getNoiseVariance(config, base, full=True)
+ +# +# Gaussian +# + +
[docs]class GaussianNoiseBuilder(NoiseBuilder): + + def addNoise(self, config, base, im, rng, current_var, draw_method, logger): + + # Read the noise variance + var = self.getNoiseVariance(config, base) + ret = var # save for the return value + + # If we already have some variance in the image (from whitening), then we subtract this much + # from sigma**2. + if current_var: + logger.debug('image %d, obj %d: Target variance is %f, current variance is %f', + base.get('image_num',0),base.get('obj_num',0),var,current_var) + if var < current_var: + raise GalSimConfigError( + "Whitening already added more noise than the requested Gaussian noise.") + var -= current_var + + # Now apply the noise. + sigma = math.sqrt(var) + im.addNoise(GaussianNoise(rng,sigma=sigma)) + + logger.debug('image %d, obj %d: Added Gaussian noise with var = %f', + base.get('image_num',0),base.get('obj_num',0),var) + + return ret + + def getNoiseVariance(self, config, base, full=False): + + # The noise level can be specified either as a sigma or a variance. Here we just calculate + # the value of the variance from either one. + single = [ { 'sigma' : float , 'variance' : float } ] + params = GetAllParams(config, base, single=single, ignore=noise_ignore)[0] + if 'sigma' in params: + sigma = params['sigma'] + return sigma * sigma + else: + return params['variance']
+ + +# +# Poisson +# + +
[docs]class PoissonNoiseBuilder(NoiseBuilder): + + def addNoise(self, config, base, im, rng, current_var, draw_method, logger): + + # Get how much extra sky to assume from the image.noise attribute. + sky = GetSky(base['image'], base, logger, full=True) + extra_sky = GetSky(config, base, logger, full=True) + total_sky = sky + extra_sky # for the return value + if isinstance(total_sky, Image): + var = np.mean(total_sky.array) + else: + var = total_sky + # (This could be zero, in which case we only add poisson noise for the object photons) + + # If we already have some variance in the image (from whitening), then we subtract this + # much off of the sky level. It's not precisely accurate, since the existing variance is + # Gaussian, rather than Poisson, but it's the best we can do. + if current_var: + logger.debug('image %d, obj %d: Target variance is %f, current variance is %f', + base.get('image_num',0),base.get('obj_num',0), var, current_var) + if isinstance(total_sky, Image): + test = np.any(total_sky.array < current_var) + else: + test = (total_sky < current_var) + if test: + raise GalSimConfigError( + "Whitening already added more noise than the requested Poisson noise.") + total_sky -= current_var + extra_sky -= current_var + + # At this point, there is a slight difference between fft and phot. For photon shooting, + # the galaxy already has Poisson noise, so we want to make sure not to add that again! + if draw_method == 'phot': + # Only add in the noise from the sky. + if isinstance(total_sky, Image): + noise_im = total_sky.copy() + noise_im.addNoise(PoissonNoise(rng)) + noise_im -= total_sky + # total_sky should now have zero mean, but with the noise of the total sky level. + im += noise_im + else: + im.addNoise(DeviateNoise(PoissonDeviate(rng, mean=total_sky))) + # This deviate adds a noisy version of the sky, so need to subtract the mean + # back off. + im -= total_sky + else: + # Do the normal PoissonNoise calculation. + if isinstance(total_sky, Image): + im += extra_sky + im.addNoise(PoissonNoise(rng)) + im -= extra_sky + else: + im.addNoise(PoissonNoise(rng, sky_level=extra_sky)) + + logger.debug('image %d, obj %d: Added Poisson noise', + base.get('image_num',0),base.get('obj_num',0)) + return var + + def getNoiseVariance(self, config, base, full=False): + # The noise variance is the net sky level per pixel + sky = GetSky(base['image'], base, full=full) + sky += GetSky(config, base, full=full) + return sky + + def addNoiseVariance(self, config, base, im, include_obj_var, logger): + if include_obj_var: + # The current image at this point should be the noise-free, sky-free image, + # which is the object variance in each pixel. + im += base['current_noise_image'] + + # Note: For the phot case, we don't actually have an exact value for the variance in + # each pixel, but the drawn image before adding the Poisson noise is our best guess for + # the variance from the object's flux, so if we want the object variance included, this + # is still the best we can do. + + # Add the total sky level + im += self.getNoiseVariance(config, base, full=True)
+ + +# +# CCD +# + +
[docs]class CCDNoiseBuilder(NoiseBuilder): + + def getCCDNoiseParams(self, config, base): + opt = { 'gain' : float , 'read_noise' : float } + ignore = ['sky_level', 'sky_level_pixel'] + params = GetAllParams(config, base, opt=opt, ignore=noise_ignore + ignore)[0] + gain = params.get('gain',1.0) + read_noise = params.get('read_noise',0.0) + read_noise_var = read_noise**2 + + return gain, read_noise, read_noise_var + + def addNoise(self, config, base, im, rng, current_var, draw_method, logger): + + # This process goes a lot like the Poisson routine. There are just two differences. + # First, the Poisson noise is in electrons, not ADU, and now we allow for a gain = e-/ADU, + # so we need to account for that properly. Second, we also allow for an additional Gaussian + # read noise. + gain, read_noise, read_noise_var = self.getCCDNoiseParams(config, base) + + # Get how much extra sky to assume from the image.noise attribute. + sky = GetSky(base['image'], base, logger, full=True) + extra_sky = GetSky(config, base, logger, full=True) + total_sky = sky + extra_sky + if isinstance(total_sky, Image): + var_adu = np.mean(total_sky.array) / gain + read_noise_var / gain**2 + else: + var_adu = total_sky / gain + read_noise_var / gain**2 + + + # If we already have some variance in the image (from whitening), then we try to subtract + # it from the read noise if possible. If not, we subtract the rest off of the sky level. + # It's not precisely accurate, since the existing variance is Gaussian, rather than + # Poisson, but it's the best we can do. + if current_var: + logger.debug('image %d, obj %d: Target variance is %f, current variance is %f', + base.get('image_num',0),base.get('obj_num',0), var_adu, current_var) + read_noise_var_adu = read_noise_var / gain**2 + if isinstance(total_sky, Image): + test = np.any(total_sky.array/gain + read_noise_var_adu < current_var) + else: + logger.debug('image %d, obj %d: Target variance is %f, current variance is %f', + base.get('image_num',0),base.get('obj_num',0), + var_adu, current_var) + test = var_adu < current_var + if test: + raise GalSimConfigError( + "Whitening already added more noise than the requested CCD noise.") + if read_noise_var_adu >= current_var: + # First try to take away from the read_noise, since this one is actually Gaussian. + read_noise_var -= current_var * gain**2 + read_noise = math.sqrt(read_noise_var) + else: + # Take read_noise down to zero, since already have at least that much already. + current_var -= read_noise_var_adu + read_noise = 0 + read_noise_var = 0 + # Take the rest away from the sky level + total_sky -= current_var * gain + extra_sky -= current_var * gain + + # At this point, there is a slight difference between fft and phot. For photon shooting, + # the galaxy already has Poisson noise, so we want to make sure not to add that again! + if draw_method == 'phot': + # Add in the noise from the sky. + if isinstance(total_sky, Image): + noise_im = total_sky.copy() + if gain != 1.0: noise_im *= gain + noise_im.addNoise(PoissonNoise(rng)) + if gain != 1.0: noise_im /= gain + noise_im -= total_sky + # total_sky should now have zero mean, but with the noise of the total sky level. + im += noise_im + else: + if gain != 1.0: im *= gain + pd = PoissonDeviate(rng, mean=total_sky*gain) + im.addNoise(DeviateNoise(pd)) + if gain != 1.0: im /= gain + im -= total_sky + # And add the read noise + if read_noise != 0.: + im.addNoise(GaussianNoise(rng, sigma=read_noise/gain)) + else: + # Do the normal CCDNoise calculation. + if isinstance(total_sky, Image): + im += extra_sky + im.addNoise(CCDNoise(rng, gain=gain, read_noise=read_noise)) + im -= extra_sky + else: + im.addNoise(CCDNoise(rng, gain=gain, read_noise=read_noise, sky_level=extra_sky)) + + logger.debug('image %d, obj %d: Added CCD noise with gain = %f, read_noise = %f', + base.get('image_num',0),base.get('obj_num',0), gain, read_noise) + + return var_adu + + def getNoiseVariance(self, config, base, full=False): + # The noise variance is sky / gain + (read_noise/gain)**2 + gain, read_noise, read_noise_var = self.getCCDNoiseParams(config, base) + + # Start with the background sky level for the image + sky = GetSky(base['image'], base, full=full) + sky += GetSky(config, base, full=full) + + # Account for the gain and read_noise + read_noise_var_adu = read_noise_var / gain**2 + return sky / gain + read_noise_var_adu + + def addNoiseVariance(self, config, base, im, include_obj_var, logger): + gain, read_noise, read_noise_var = self.getCCDNoiseParams(config, base) + if include_obj_var: + # The current image at this point should be the noise-free, sky-free image, + # which is the object variance in each pixel. + if gain != 1.0: + im += base['current_noise_image']/gain + else: + im += base['current_noise_image'] + + # Now add on the regular CCDNoise from the sky and read noise. + im += self.getNoiseVariance(config, base, full=True)
+ + +# +# Correlated +# + +class CorrelatedNoiseBuilder(NoiseBuilder): + + def getNoise(self, config, base, rng=None): + # Save the constructed CorrelatedNoise object, since we might need it again. + tag = (id(base), base['file_num'], base['image_num']) + if base.get('_current_cn_tag',None) == tag: + return base['_current_cn'] + else: + cn = self._readNoiseFile(config, base, rng) + base['_current_cn'] = cn + base['_current_cn_tag'] = tag + return cn + + def _readNoiseFile(self, config, base, rng=None): + req = { 'file_name': str, 'pixel_scale': float } + opt = { 'variance' : float } + kwargs = GetAllParams(config, base, req=req, opt=opt, ignore=noise_ignore)[0] + if rng is None: + rng = GetRNG(config, base) + return BaseCorrelatedNoise.from_file(rng=rng, **kwargs) + + def addNoise(self, config, base, im, rng, current_var, draw_method, logger): + + # Build the correlated noise + cn = self.getNoise(config,base,rng) + var = cn.getVariance() + + # Subtract off the current variance if any + if current_var: + logger.debug('image %d, obj %d: Target variance is %f, current variance is %f', + base.get('image_num',0),base.get('obj_num',0), var, current_var) + if var < current_var: + raise GalSimConfigError( + "Whitening already added more noise than the requested COSMOS noise.") + cn -= UncorrelatedNoise(current_var, rng=rng, wcs=cn.wcs) + + # Add the noise to the image + im.addNoise(cn) + + logger.debug('image %d, obj %d: Added COSMOS correlated noise with variance = %f', + base.get('image_num',0),base.get('obj_num',0), var) + return var + + def getNoiseVariance(self, config, base, full=False): + cn = self.getNoise(config,base) + return cn.getVariance() + +# Specialization for COSMOS noise in particular (the OG version of this noise type). +
[docs]class COSMOSNoiseBuilder(CorrelatedNoiseBuilder): + def _readNoiseFile(self, config, base, rng=None): + opt = { 'file_name' : str, 'cosmos_scale' : float, 'variance' : float } + kwargs = GetAllParams(config, base, opt=opt, ignore=noise_ignore)[0] + if rng is None: + rng = GetRNG(config, base) + return getCOSMOSNoise(rng=rng, **kwargs)
+ + +
[docs]def RegisterNoiseType(noise_type, builder, input_type=None): + """Register a noise type for use by the config apparatus. + + Parameters: + noise_type: The name of the type in config['image']['noise'] + builder: A builder object to use for building the noise. It should be an + instance of a subclass of NoiseBuilder. + input_type: If the builder utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_noise_types[noise_type] = builder + RegisterInputConnectedType(input_type, noise_type)
+ + +RegisterNoiseType('Gaussian', GaussianNoiseBuilder()) +RegisterNoiseType('Poisson', PoissonNoiseBuilder()) +RegisterNoiseType('CCD', CCDNoiseBuilder()) +RegisterNoiseType('Correlated', CorrelatedNoiseBuilder()) +RegisterNoiseType('COSMOS', COSMOSNoiseBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/output.html b/docs/_build/html/_modules/galsim/config/output.html new file mode 100644 index 00000000000..192e065525a --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/output.html @@ -0,0 +1,710 @@ + + + + + + galsim.config.output — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.output

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import os
+import logging
+import time
+
+from .util import LoggerWrapper, UpdateNProc, CopyConfig, MultiProcess, SetupConfigRNG
+from .util import RetryIO, SetDefaultExt
+from .value import ParseValue, CheckAllParams
+from .input import ProcessInput
+from .extra import valid_extra_outputs, SetupExtraOutput, WriteExtraOutputs
+from .extra import AddExtraOutputHDUs, CheckNoExtraOutputHDUs
+from .image import BuildImage, GetNObjForImage
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..utilities import ensure_dir
+from ..fits import writeMulti
+
+# This file handles building the output files according to the specifications in config['output'].
+# This file includes the basic functionality, but it calls out to helper functions for the
+# different types of output files.  It includes the implementation of the default output type,
+# 'Fits'.  See output_multifits.py for 'MultiFits' and output_datacube.py for 'DataCube'.
+
+# This module-level dict will store all the registered output types.
+# See the RegisterOutputType function at the end of this file.
+# The keys are the (string) names of the output types, and the values will be builder objects
+# that will perform the different stages of processing to construct and write the output file(s).
+valid_output_types = {}
+
+
[docs]def BuildFiles(nfiles, config, file_num=0, logger=None, except_abort=False): + """ + Build a number of output files as specified in config. + + Parameters: + nfiles: The number of files to build. + config: A configuration dict. + file_num: If given, the first file_num. [default: 0] + logger: If given, a logger object to log progress. [default: None] + except_abort: Whether to abort processing when a file raises an exception (True) + or just report errors and continue on (False). [default: False] + + Returns: + the final config dict that was used. + """ + logger = LoggerWrapper(logger) + t1 = time.time() + + # The next line relies on getting errors when the rng is undefined. However, the default + # rng is None, which is a valid thing to construct a Deviate object from. So for now, + # set the rng to object() to make sure we get errors where we are expecting to. + config['rng'] = object() + + # Process the input field for the first file. Often there are "safe" input items + # that won't need to be reprocessed each time. So do them here once and keep them + # in the config for all file_nums. This is more important if nproc != 1. + ProcessInput(config, logger=logger, safe_only=True) + + jobs = [] # Will be a list of the kwargs to use for each job + info = [] # Will be a list of (file_num, file_name) correspongind to each jobs. + + # Count from 0 to make sure image_num, etc. get counted right. We'll start actually + # building the files at first_file_num. + first_file_num = file_num + file_num = 0 + image_num = 0 + obj_num = 0 + + # Figure out how many processes we will use for building the files. + if 'output' not in config: config['output'] = {} + output = config['output'] + if nfiles > 1 and 'nproc' in output: + nproc = ParseValue(output, 'nproc', config, int)[0] + # Update this in case the config value is -1 + nproc = UpdateNProc(nproc, nfiles, config, logger) + # We'll want a pristine version later to give to the workers. + else: + nproc = 1 + orig_config = CopyConfig(config) + + if 'timeout' in output: + timeout = ParseValue(output, 'timeout', config, float)[0] + else: + timeout = 3600 + + if nfiles == 0: + logger.warning("No files were made, since nfiles == 0.") + return orig_config + + for k in range(nfiles + first_file_num): + SetupConfigFileNum(config, file_num, image_num, obj_num, logger) + + builder = valid_output_types[output['type']] + builder.setup(output, config, file_num, logger) + + # Process the input fields that might be relevant at file scope: + ProcessInput(config, logger=logger, file_scope_only=True) + + # Get the number of objects in each image for this file. + nobj = GetNObjForFile(config, file_num, image_num, logger=logger, approx=True) + + # The kwargs to pass to BuildFile + kwargs = { + 'file_num' : file_num, + 'image_num' : image_num, + 'obj_num' : obj_num + } + + if file_num >= first_file_num: + # Get the file_name here, in case it needs to create directories, which is not + # safe to do with multiple processes. (At least not without extra code in the + # getFilename function...) + file_name = builder.getFilename(output, config, logger) + jobs.append(kwargs) + info.append( (file_num, file_name) ) + + # nobj is a list of nobj for each image in that file. + # So len(nobj) = nimages and sum(nobj) is the total number of objects + # This gets the values of image_num and obj_num ready for the next loop. + file_num += 1 + image_num += len(nobj) + obj_num += sum(nobj) + + def done_func(logger, proc, k, result, t2): + file_num, file_name = info[k] + file_name2, t = result # This is the t for which 0 means the file was skipped. + if file_name2 != file_name: # pragma: no cover (I think this should never happen.) + raise GalSimConfigError("Files seem to be out of sync. %s != %s", file_name, file_name2) + if t != 0 and logger: + if proc is None: s0 = '' + else: s0 = '%s: '%proc + logger.warning(s0 + 'File %d = %s: time = %f sec', file_num, file_name, t) + + def except_func(logger, proc, k, e, tr): + file_num, file_name = info[k] + if proc is None: s0 = '' + else: s0 = '%s: '%proc + logger.error(s0 + 'Exception caught for file %d = %s', file_num, file_name) + if except_abort: + logger.debug('%s',tr) + logger.error('File %s not written.',file_name) + else: + logger.warning('%s',tr) + logger.error('File %s not written! Continuing on...',file_name) + + # Convert to the tasks structure we need for MultiProcess + # Each task is a list of (job, k) tuples. In this case, we only have one job per task. + tasks = [ [ (job, k) ] for (k, job) in enumerate(jobs) ] + + results = MultiProcess(nproc, orig_config, BuildFile, tasks, 'file', + logger=logger, timeout=timeout, + done_func=done_func, except_func=except_func, + except_abort=except_abort) + t2 = time.time() + + if len(results) == 0: + nfiles_written = 0 + else: + fnames, times = zip(*results) + nfiles_written = sum([ t!=0 for t in times]) + + if nfiles_written == 0: + logger.warning('No files were written. All were either skipped or had errors.') + else: + if nfiles_written > 1 and nproc != 1: + logger.warning('Total time for %d files with %d processes = %f sec', + nfiles_written,nproc,t2-t1) + logger.warning('Done building files') + + #Return the config used for the run - this may be useful since one can + #save information here in e.g. custom output types + return orig_config
+ +output_ignore = [ 'nproc', 'timeout', 'skip', 'noclobber', 'retry_io' ] + +
[docs]def BuildFile(config, file_num=0, image_num=0, obj_num=0, logger=None): + """ + Build an output file as specified in config. + + Parameters: + config: A configuration dict. + file_num: If given, the current file_num. [default: 0] + image_num: If given, the current image_num. [default: 0] + obj_num: If given, the current obj_num. [default: 0] + logger: If given, a logger object to log progress. [default: None] + + Returns: + (file_name, t), a tuple of the file name and the time taken to build file + Note: t==0 indicates that this file was skipped. + """ + logger = LoggerWrapper(logger) + t1 = time.time() + + SetupConfigFileNum(config, file_num, image_num, obj_num, logger) + output = config['output'] + output_type = output['type'] + builder = valid_output_types[output_type] + builder.setup(output, config, file_num, logger) + + # Put these values in the config dict so we won't have to run them again later if + # we need them. e.g. ExtraOuput processing uses these. + nobj = GetNObjForFile(config, file_num, image_num, logger=logger) + nimages = len(nobj) + config['nimages'] = nimages + config['nobj'] = nobj + logger.debug('file %d: BuildFile with type=%s to build %d images, starting with %d', + file_num,output_type,nimages,image_num) + + # Make sure the inputs and extra outputs are set up properly. + ProcessInput(config, logger=logger) + SetupExtraOutput(config, logger=logger) + + # Get the file name + file_name = builder.getFilename(output, config, logger) + + # Check if we ought to skip this file + if 'skip' in output and ParseValue(output, 'skip', config, bool)[0]: + logger.warning('Skipping file %d = %s because output.skip = True',file_num,file_name) + return file_name, 0 # Note: time=0 is the indicator that a file was skipped. + if ('noclobber' in output + and ParseValue(output, 'noclobber', config, bool)[0] + and os.path.isfile(file_name)): + logger.warning('Skipping file %d = %s because output.noclobber = True' + ' and file exists',file_num,file_name) + return file_name, 0 + + if logger.isEnabledFor(logging.DEBUG): + logger.debug('file %d: file_name = %s',file_num,file_name) + else: + logger.warning('Start file %d = %s', file_num, file_name) + + ignore = output_ignore + list(valid_extra_outputs) + data = builder.buildImages(output, config, file_num, image_num, obj_num, ignore, logger) + + # If any images came back as None, then remove them, since they cannot be written. + data = [ im for im in data if im is not None ] + + if len(data) == 0: + logger.warning('Skipping file %d = %s because all images were None',file_num,file_name) + return file_name, 0 + + # Go back to file_num as the default index_key. + config['index_key'] = 'file_num' + + data = builder.addExtraOutputHDUs(config, data, logger) + + if 'retry_io' in output: + ntries = ParseValue(output,'retry_io',config,int)[0] + # This is how many _re_-tries. Do at least 1, so ntries is 1 more than this. + ntries = ntries + 1 + else: + ntries = 1 + + args = (data, file_name, output, config, logger) + RetryIO(builder.writeFile, args, ntries, file_name, logger) + logger.debug('file %d: Wrote %s to file %r',file_num,output_type,file_name) + + builder.writeExtraOutputs(config, data, logger) + + t2 = time.time() + + return file_name, t2-t1
+ +
[docs]def GetNFiles(config, logger=None): + """ + Get the number of files that will be made, based on the information in the config dict. + + Parameters: + config: The configuration dict. + logger: If given, a logger object to log progress. [default: None] + + Returns: + the number of files + """ + output = config.get('output',{}) + output_type = output.get('type','Fits') + if output_type not in valid_output_types: + raise GalSimConfigValueError("Invalid output.type.", output_type, + list(valid_output_types.keys())) + return valid_output_types[output_type].getNFiles(output, config, logger=logger)
+ + +
[docs]def GetNImagesForFile(config, file_num, logger=None): + """ + Get the number of images that will be made for the file number file_num, based on the + information in the config dict. + + Parameters: + config: The configuration dict. + file_num: The current file number. + logger: If given, a logger object to log progress. [default: None] + + Returns: + the number of images + """ + output = config.get('output',{}) + output_type = output.get('type','Fits') + if output_type not in valid_output_types: + raise GalSimConfigValueError("Invalid output.type.", output_type, + list(valid_output_types.keys())) + return valid_output_types[output_type].getNImages(output, config, file_num, logger=logger)
+ + +
[docs]def GetNObjForFile(config, file_num, image_num, logger=None, approx=False): + """ + Get the number of objects that will be made for each image built as part of the file file_num, + which starts at image number image_num, based on the information in the config dict. + + Parameters: + config: The configuration dict. + file_num: The current file number. + image_num: The current image number. + logger: If given, a logger object to log progress. [default: None] + approx: Whether an approximate/overestimate is ok [default: False] + + Returns: + a list of the number of objects in each image [ nobj0, nobj1, nobj2, ... ] + """ + output = config.get('output',{}) + output_type = output.get('type','Fits') + if output_type not in valid_output_types: + raise GalSimConfigValueError("Invalid output.type.", output_type, + list(valid_output_types.keys())) + return valid_output_types[output_type].getNObjPerImage(output, config, file_num, image_num, + logger=logger, approx=approx)
+ + +
[docs]def SetupConfigFileNum(config, file_num, image_num, obj_num, logger=None): + """Do the basic setup of the config dict at the file processing level. + + Includes: + - Set config['file_num'] = file_num + - Set config['image_num'] = image_num + - Set config['obj_num'] = obj_num + - Set config['index_key'] = 'file_num' + - Set config['start_image_num'] = image_num + - Set config['start_obj_num'] = obj_num + - Make sure config['output'] exists + - Set default config['output']['type'] to 'Fits' if not specified + - Check that the specified output type is valid. + + Parameters: + config: A configuration dict. + file_num: The current file_num. (If file_num=None, then don't set file_num or + start_obj_num items in the config dict.) + image_num: The current image_num. + obj_num: The current obj_num. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + config['file_num'] = file_num + config['start_obj_num'] = obj_num + config['start_image_num'] = image_num + config['image_num'] = image_num + config['obj_num'] = obj_num + config['index_key'] = 'file_num' + + if 'output' not in config: + config['output'] = {} + if 'type' not in config['output']: + config['output']['type'] = 'Fits' + + # Check that the type is valid + output_type = config['output']['type'] + if output_type not in valid_output_types: + raise GalSimConfigValueError("Invalid output.type.", output_type, + list(valid_output_types.keys()))
+ + +
[docs]class OutputBuilder: + """A base class for building and writing the output objects. + + The base class defines the call signatures of the methods that any derived class should follow. + It also includes the implementation of the default output type: Fits. + """ + + # A class attribute that sub-classes may override. + default_ext = '.fits' + +
[docs] def setup(self, config, base, file_num, logger): + """Do any necessary setup at the start of processing a file. + + The base class just calls SetupConfigRNG, but this provides a hook for sub-classes to + do more things before any processing gets started on this file. + + Parameters: + config: The configuration dict for the output type. + base: The base configuration dict. + file_num: The current file_num. + logger: If given, a logger object to log progress. + """ + seed = SetupConfigRNG(base, logger=logger) + logger.debug('file %d: seed = %d',file_num,seed)
+ +
[docs] def getFilename(self, config, base, logger): + """Get the file_name for the current file being worked on. + + Note that the base class defines a default extension = '.fits'. + This can be overridden by subclasses by changing the default_ext property. + + Parameters: + config: The configuration dict for the output type. + base: The base configuration dict. + logger: If given, a logger object to log progress. + + Returns: + the filename to build. + """ + if 'file_name' in config: + SetDefaultExt(config['file_name'], self.default_ext) + file_name = ParseValue(config, 'file_name', base, str)[0] + elif 'root' in base and self.default_ext is not None: + # If a file_name isn't specified, we use the name of the config file + '.fits' + file_name = base['root'] + self.default_ext + else: + raise GalSimConfigError( + "No file_name specified and unable to generate it automatically.") + + # Prepend a dir to the beginning of the filename if requested. + if 'dir' in config: + dir = ParseValue(config, 'dir', base, str)[0] + file_name = os.path.join(dir,file_name) + + ensure_dir(file_name) + + return file_name
+ +
[docs] def buildImages(self, config, base, file_num, image_num, obj_num, ignore, logger): + """Build the images for output. + + In the base class, this function just calls BuildImage to build the single image to + put in the output file. So the returned list only has one item. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file_num. + image_num: The current image_num. + obj_num: The current obj_num. + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if they are present. + logger: If given, a logger object to log progress. + + Returns: + a list of the images built + """ + # There are no extra parameters to get, so just check that there are no invalid parameters + # in the config dict. + ignore += [ 'file_name', 'dir', 'nfiles' ] + CheckAllParams(config, ignore=ignore) + + image = BuildImage(base, image_num, obj_num, logger=logger) + return [ image ]
+ +
[docs] def getNFiles(self, config, base, logger=None): + """Returns the number of files to be built. + + In the base class, this is just output.nfiles. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + logger: If given, a logger object to log progress. + + Returns: + the number of files to build. + """ + if 'nfiles' in config: + return ParseValue(config, 'nfiles', base, int)[0] + else: + return 1
+ +
[docs] def getNImages(self, config, base, file_num, logger=None): + """Returns the number of images to be built for a given ``file_num``. + + In the base class, we only build a single image, so it returns 1. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file number. + logger: If given, a logger object to log progress. + + Returns: + the number of images to build. + """ + return 1
+ +
[docs] def getNObjPerImage(self, config, base, file_num, image_num, logger=None, approx=False): + """ + Get the number of objects that will be made for each image built as part of the file + file_num, which starts at image number image_num, based on the information in the config + dict. + + Parameters: + config: The configuration dict. + base: The base configuration dict. + file_num: The current file number. + image_num: The current image number (the first one for this file). + logger: If given, a logger object to log progress. + approx: Whether an approximate/overestimate is ok [default: False] + + Returns: + a list of the number of objects in each image [ nobj0, nobj1, nobj2, ... ] + """ + nimages = self.getNImages(config, base, file_num, logger=logger) + nobj = [ GetNObjForImage(base, image_num+j, logger=logger, approx=approx) + for j in range(nimages) ] + base['image_num'] = image_num # Make sure this is set back to current image num. + return nobj
+ +
[docs] def canAddHdus(self): + """Returns whether it is permissible to add extra HDUs to the end of the data list. + + In the base class, this returns True. + """ + return True
+ +
[docs] def addExtraOutputHDUs(self, config, data, logger): + """If appropriate, add any extra output items that go into HDUs to the data list. + + Parameters: + config: The configuration dict for the output field. + data: The data to write. Usually a list of images. + logger: If given, a logger object to log progress. + + Returns: + data (possibly updated with additional items) + """ + if self.canAddHdus(): + data = AddExtraOutputHDUs(config, data, logger) + else: + CheckNoExtraOutputHDUs(config, config['output']['type'], logger) + return data
+ +
[docs] def writeFile(self, data, file_name, config, base, logger): + """Write the data to a file. + + Parameters: + data: The data to write. Usually a list of images returned by + buildImages, but possibly with extra HDUs tacked onto the end + from the extra output items. + file_name: The file_name to write to. + config: The configuration dict for the output field. + base: The base configuration dict. + logger: If given, a logger object to log progress. + """ + writeMulti(data,file_name)
+ +
[docs] def writeExtraOutputs(self, config, data, logger): + """If appropriate, write any extra output items that write their own files. + + Parameters: + config: The configuration dict for the output field. + data: The data to write. Usually a list of images. + logger: If given, a logger object to log progress. + """ + WriteExtraOutputs(config, data, logger)
+ + +
[docs]def RegisterOutputType(output_type, builder): + """Register an output type for use by the config apparatus. + + Parameters: + output_type: The name of the type in config['output'] + builder: A builder object to use for building and writing the output file. + It should be an instance of OutputBuilder or a subclass thereof. + """ + # Make a concrete instance of the builder. + valid_output_types[output_type] = builder
+ +# The base class is also the builder for type = Fits. +RegisterOutputType('Fits', OutputBuilder()) + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/output_datacube.html b/docs/_build/html/_modules/galsim/config/output_datacube.html new file mode 100644 index 00000000000..41466ffdfaa --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/output_datacube.html @@ -0,0 +1,256 @@ + + + + + + galsim.config.output_datacube — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for galsim.config.output_datacube

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import time
+
+from .output import OutputBuilder, RegisterOutputType
+from .util import CopyConfig
+from .image import BuildImages, BuildImage, GetNObjForImage
+from .input import ProcessInputNObjects
+from .value import ParseValue, CheckAllParams
+from ..errors import GalSimConfigError
+from ..fits import writeCube
+
+
[docs]class DataCubeBuilder(OutputBuilder): + """Builder class for constructing and writing DataCube output types. + """ + + def buildImages(self, config, base, file_num, image_num, obj_num, ignore, logger): + """Build the images + + A point of attention for DataCubes is that they must all be the same size. + This function builds the first image alone, finds out its size and then forces + all subsequent images to be the same size. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file_num. + image_num: The current image_num. + obj_num: The current obj_num. + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if they are present. + logger: If given, a logger object to log progress. + + Returns: + a list of the images built + """ + nimages = self.getNImages(config, base, file_num, logger=logger) + + # The above call sets up a default nimages if appropriate. Now, check that there are no + # invalid parameters in the config dict. + req = { 'nimages' : int } + ignore += [ 'file_name', 'dir', 'nfiles' ] + CheckAllParams(config, ignore=ignore, req=req) + + # All images need to be the same size for a data cube. + # Enforce this by building the first image outside the below loop and setting + # config['image_force_xsize'] and config['image_force_ysize'] to be the size of the first + # image. + t1 = time.time() + base1 = CopyConfig(base) + image0 = BuildImage(base1, image_num, obj_num, logger=logger) + t2 = time.time() + # Note: numpy shape is y,x + ys, xs = image0.array.shape + logger.info('Image %d: size = %d x %d, time = %f sec', image_num, xs, ys, t2-t1) + + # Note: numpy shape is y,x + image_ysize, image_xsize = image0.array.shape + base['image_force_xsize'] = image_xsize + base['image_force_ysize'] = image_ysize + + images = [ image0 ] + + if nimages > 1: + obj_num += GetNObjForImage(base, image_num, logger=logger) + images += BuildImages(nimages-1, base, logger=logger, + image_num=image_num+1, obj_num=obj_num) + + return images + + def getNImages(self, config, base, file_num, logger=None): + """Returns the number of images to be built. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file number. + logger: If given, a logger object to log progress. + + Returns: + the number of images to build. + """ + # Allow nimages to be automatic based on input catalog if image type is Single + if ( 'nimages' not in config and + ( 'image' not in base or 'type' not in base['image'] or + base['image']['type'] == 'Single' ) ): + nimages = ProcessInputNObjects(base) + if nimages: + config['nimages'] = nimages + if 'nimages' not in config: + raise GalSimConfigError( + "Attribute output.nimages is required for output.type = MultiFits") + return ParseValue(config,'nimages',base,int)[0] + + def writeFile(self, data, file_name, config, base, logger): + """Write the data to a file. + + Parameters: + data: The data to write. Usually a list of images returned by + buildImages, but possibly with extra HDUs tacked onto the end + from the extra output items. + file_name: The file_name to write to. + config: The configuration dict for the output field. + base: The base configuration dict. + logger: If given, a logger object to log progress. + """ + writeCube(data,file_name) + + def canAddHdus(self): + """Returns whether it is permissible to add extra HDUs to the end of the data list. + + False for DataCube. + """ + return False
+ + +# Register this as a valid output type +RegisterOutputType('DataCube', DataCubeBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/output_multifits.html b/docs/_build/html/_modules/galsim/config/output_multifits.html new file mode 100644 index 00000000000..0153f41135a --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/output_multifits.html @@ -0,0 +1,207 @@ + + + + + + galsim.config.output_multifits — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for galsim.config.output_multifits

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import os
+import logging
+
+from .output import OutputBuilder, RegisterOutputType
+from .image import BuildImages
+from .input import ProcessInputNObjects
+from .value import ParseValue, CheckAllParams
+from ..errors import GalSimConfigError
+
+
[docs]class MultiFitsBuilder(OutputBuilder): + """Builder class for constructing and writing MultiFits output types. + """ + + def buildImages(self, config, base, file_num, image_num, obj_num, ignore, logger): + """Build the images + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file_num. + image_num: The current image_num. + obj_num: The current obj_num. + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if they are present. + logger: If given, a logger object to log progress. + + Returns: + a list of the images built + """ + nimages = self.getNImages(config, base, file_num, logger=logger) + + # The above call sets up a default nimages if appropriate. Now, check that there are no + # invalid parameters in the config dict. + req = { 'nimages' : int } + ignore += [ 'file_name', 'dir', 'nfiles' ] + CheckAllParams(config, ignore=ignore, req=req) + + return BuildImages(nimages, base, image_num, obj_num, logger=logger) + + def getNImages(self, config, base, file_num, logger=None): + """ + Get the number of images for a MultiFits file type. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file number. + logger: If given, a logger object to log progress. + + Returns: + the number of images + """ + # Allow nimages to be automatic based on input catalog if image type is Single + if ( 'nimages' not in config and + ( 'image' not in base or 'type' not in base['image'] or + base['image']['type'] == 'Single' ) ): + nimages = ProcessInputNObjects(base) + if nimages: + config['nimages'] = nimages + if 'nimages' not in config: + raise GalSimConfigError( + "Attribute output.nimages is required for output.type = MultiFits") + return ParseValue(config,'nimages',base,int)[0]
+ + +# Register this as a valid output type +RegisterOutputType('MultiFits', MultiFitsBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/photon_ops.html b/docs/_build/html/_modules/galsim/config/photon_ops.html new file mode 100644 index 00000000000..95bd522c233 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/photon_ops.html @@ -0,0 +1,388 @@ + + + + + + galsim.config.photon_ops — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.photon_ops

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from .util import LoggerWrapper, GetIndex, GetRNG, get_cls_params
+from .value import ParseValue, GetAllParams, CheckAllParams, SetDefaultIndex
+from .input import RegisterInputConnectedType
+from .sed import BuildSED
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..utilities import basestring
+from ..photon_array import PhotonOp
+from ..photon_array import WavelengthSampler, FRatioAngles, PhotonDCR, Refraction, FocusDepth
+from ..photon_array import TimeSampler, PupilImageSampler, PupilAnnulusSampler
+
+# This file handles the construction of photon_ops in config['stamp']['photon_ops'].
+
+# This module-level dict will store all the registered photon_op types.
+# See the RegisterPhotonOpType function at the end of this file.
+# The keys are the (string) names of the photon_ops types, and the values will be builders that
+# know how to build the photon operator object.
+valid_photon_op_types = {}
+
+
+def BuildPhotonOps(config, key, base, logger=None):
+    """Read the parameters from config[key] (which should be a list) and return a constructed
+    photon_ops as a list.
+
+    Parameters:
+        config:     A dict with the configuration information for the photon ops.
+                    (usually base['stamp'])
+        key:        The key in the dict for the photon_ops list.
+        base:       The base dict of the configuration.
+        logger:     Optionally, provide a logger for logging debug statements. [default: None]
+
+    Returns:
+        the photon_ops list
+    """
+    logger = LoggerWrapper(logger)
+    if not isinstance(config[key], list):
+        raise GalSimConfigError("photon_ops must be a list")
+    photon_ops = config[key]  # The list in the config dict
+    ops = [] # List of the actual operators
+    for i in range(len(photon_ops)):
+        op = BuildPhotonOp(photon_ops, i, base, logger)
+        ops.append(op)
+    return ops
+
+def BuildPhotonOp(config, key, base, logger=None):
+    """Read the parameters from config[key] and return a single constructed photon_op object.
+
+    Parameters:
+        config:     A list with the configuration information for the photon ops.
+                    (usually base['stamp']['photon_ops'])
+        key:        The index in the list for this photon_op.  It's called key, since for most
+                    things, this is a key into a dict, but here it's normally an integer index
+                    into the photon_ops list.
+        base:       The base dict of the configuration.
+        logger:     Optionally, provide a logger for logging debug statements. [default: None]
+
+    Returns:
+        a object that would be valid in a photon_ops list
+    """
+    logger = LoggerWrapper(logger)
+    logger.debug('obj %d: Start BuildPhotonOp key = %s',base.get('obj_num',0),key)
+
+    param = config[key]
+
+    # Check for direct value, else get the type
+    if isinstance(param, PhotonOp):
+        return param
+    elif isinstance(param, basestring) and (param[0] == '$' or param[0] == '@'):
+        return ParseValue(config, key, base, None)[0]
+    elif isinstance(param, dict) and 'type' in param:
+        op_type = param['type']
+    else:
+        raise GalSimConfigError("photon_op must be either a PhotonOp or a dict")
+
+    # For these two, just do the usual ParseValue function.
+    if op_type in ('Eval', 'Current'):
+        return ParseValue(config, key, base, None)[0]
+
+    if op_type not in valid_photon_op_types:
+        raise GalSimConfigValueError("Invalid photon_op type.", op_type,
+                                     list(valid_photon_op_types.keys()))
+
+    # Check if we can use the current cached object
+    index, index_key = GetIndex(param, base)
+    if 'current' in param:
+        cop, csafe, cvalue_type, cindex, cindex_key = param['current']
+        if cindex == index:
+            logger.debug('obj %d: The photon_op is already current', base.get('obj_num',0))
+            logger.debug('obj %d: index_key = %s, index = %d',base.get('obj_num',0),
+                         cindex_key, cindex)
+            return cop
+
+    # Need to use a builder.
+    logger.debug('obj %d: Building photon_op type %s', base.get('obj_num',0), op_type)
+    builder = valid_photon_op_types[op_type]
+    op = builder.buildPhotonOp(param, base, logger)
+    logger.debug('obj %d: photon_op = %s', base.get('obj_num',0), str(op))
+
+    param['current'] = op, False, None, index, index_key
+
+    return op
+
+
+
[docs]class PhotonOpBuilder: + """A base class for building PhotonOp objects. + + The base class defines the call signatures of the methods that any derived class should follow. + """ +
[docs] def buildPhotonOp(self, config, base, logger): + """Build the PhotonOp based on the specifications in the config dict. + + Note: Sub-classes must override this function with a real implementation. + + Parameters: + config: The configuration dict for the PhotonOp + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed PhotonOp object. + """ + raise NotImplementedError("The %s class has not overridden buildPhotonOp"%self.__class__)
+ + +class SimplePhotonOpBuilder(PhotonOpBuilder): + """A class for building simple PhotonOp objects. + + The initializer takes an init_func, which is the class or function to call to build the + PhotonOp. For the kwargs, it calls getKwargs, which does the normal parsing of the req_params + and related class attributes. + """ + def __init__(self, init_func): + self.init_func = init_func + + def getKwargs(self, config, base, logger): + """Get the kwargs to pass to the build function based on the following attributes of + init_func: + + _req_params + A dict of required parameters and their types. + _opt_params + A dict of optional parameters and their types. + _single_params + A list of dicts of parameters such that one and only one of + parameter in each dict is required. + _takes_rng + A bool value saying whether an rng object is required. + + See the classes in photon_array.py for examples of classes that set these attributes. + + Parameters: + config: The configuration dict for the photon_op type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + kwargs + """ + req, opt, single, takes_rng = get_cls_params(self.init_func) + kwargs, safe = GetAllParams(config, base, req, opt, single) + if takes_rng: # pragma: no cover None of ours have this anymore. But it's still allowed. + kwargs['rng'] = GetRNG(config, base, logger, self.init_func.__name__) + return kwargs + + def buildPhotonOp(self, config, base, logger): + """Build the PhotonOp based on the specifications in the config dict. + + Parameters: + config: The configuration dict for the photon_op type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed PhotonOp object. + """ + kwargs = self.getKwargs(config,base,logger) + return self.init_func(**kwargs) + +class WavelengthSamplerBuilder(PhotonOpBuilder): + """Build a WavelengthSampler + """ + # This one needs special handling for sed and bandpass + def buildPhotonOp(self, config, base, logger): + req, opt, single, takes_rng = get_cls_params(WavelengthSampler) + kwargs, safe = GetAllParams(config, base, req, opt, single, ignore=['sed']) + if 'sed' not in config: + raise GalSimConfigError("sed is required for WavelengthSampler") + sed = BuildSED(config, 'sed', base, logger)[0] + kwargs['sed'] = sed + if 'bandpass' not in base: + raise GalSimConfigError("bandpass is required for WavelengthSampler") + kwargs['bandpass'] = base['bandpass'] + return WavelengthSampler(**kwargs) + +class PhotonDCRBuilder(PhotonOpBuilder): + """Build a PhotonDCR + """ + # This one needs special handling for obj_coord + def buildPhotonOp(self, config, base, logger): + req, opt, single, takes_rng = get_cls_params(PhotonDCR) + kwargs, safe = GetAllParams(config, base, req, opt, single) + if 'sky_pos' in base: + kwargs['obj_coord'] = base['sky_pos'] + return PhotonDCR(**kwargs) + +class ListPhotonOpBuilder(PhotonOpBuilder): + """Select a photon_op from a list + """ + def buildPhotonOp(self, config, base, logger): + req = { 'items' : list } + opt = { 'index' : int } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt) + items = config['items'] + if not isinstance(items,list): + raise GalSimConfigError("items entry for type=List is not a list.") + + # Setup the indexing sequence if it hasn't been specified using the length of items. + SetDefaultIndex(config, len(items)) + index, safe = ParseValue(config, 'index', base, int) + + if index < 0 or index >= len(items): + raise GalSimConfigError("index %d out of bounds for photon_op type=List"%index) + return BuildPhotonOp(items, index, base) + +
[docs]def RegisterPhotonOpType(photon_op_type, builder, input_type=None): + """Register a photon_op type for use by the config apparatus. + + Parameters: + photon_op_type: The name of the config type to register + builder: A builder object to use for building the PhotonOp object. It should + be an instance of a subclass of PhotonOpBuilder. + input_type: If the PhotonOp builder utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_photon_op_types[photon_op_type] = builder + RegisterInputConnectedType(input_type, photon_op_type)
+ + +RegisterPhotonOpType('WavelengthSampler', WavelengthSamplerBuilder()) +RegisterPhotonOpType('FRatioAngles', SimplePhotonOpBuilder(FRatioAngles)) +RegisterPhotonOpType('PhotonDCR', PhotonDCRBuilder()) +RegisterPhotonOpType('Refraction', SimplePhotonOpBuilder(Refraction)) +RegisterPhotonOpType('FocusDepth', SimplePhotonOpBuilder(FocusDepth)) +RegisterPhotonOpType('List', ListPhotonOpBuilder()) +RegisterPhotonOpType('PupilImageSampler', SimplePhotonOpBuilder(PupilImageSampler)) +RegisterPhotonOpType('PupilAnnulusSampler', SimplePhotonOpBuilder(PupilAnnulusSampler)) +RegisterPhotonOpType('TimeSampler', SimplePhotonOpBuilder(TimeSampler)) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/process.html b/docs/_build/html/_modules/galsim/config/process.html new file mode 100644 index 00000000000..8ce286b38af --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/process.html @@ -0,0 +1,428 @@ + + + + + + galsim.config.process — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.process

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import sys
+import os
+import json
+
+# Lots of function that used to be here are now in util, so import them back here in case users
+# were using them as galsim.config.process.*.  But having them in util means that we can safely
+# import from .util in other files without triggering a circular import cycle.
+from .util import *
+
+from .output import GetNFiles, BuildFiles
+from ..errors import GalSimValueError
+
+top_level_fields = ['psf', 'gal', 'stamp', 'image', 'input', 'output',
+                    'eval_variables', 'root', 'modules', 'profile']
+
+rng_fields = ['rng', 'obj_num_rng', 'image_num_rng', 'file_num_rng',
+              'obj_num_rngs', 'image_num_rngs', 'file_num_rngs']
+
+valid_index_keys = [ 'obj_num_in_file', 'obj_num', 'image_num', 'file_num' ]
+
+# This module-level dict will store all the registered template names.
+# See the RegisterTemplate function at the end of this file.
+# The keys are the (string) names of the templates, and the values are the corresponding
+# real file location on disk.
+valid_templates = {}
+
+
[docs]def ReadConfig(config_file, file_type=None, logger=None): + """Read in a configuration file and return the corresponding dicts. + + A YAML file is allowed to define several dicts using multiple documents. The GalSim parser + treats this as a set of multiple jobs to be done. The first document is taken to be a "base" + dict that has common definitions for all the jobs. Then each subsequent document has the + (usually small) modifications to the base dict for each job. See demo6.yaml, demo8.yaml and + demo9.yaml in the GalSim/examples directory for example usage. + + On output, the returned list will have an entry for each job to be done. If there are + multiple documents, then the first dict is a merge of the first two documents, the + second a merge of the first and third, and so on. Each job includes the first document + merged with each subseqent document in turn. If there is only one document defined, + the returned list will have one element, which is this dict. + + A JSON file does not have this feature, but to be consistent, we always return a list, + which would only have one element in this case. + + Parameters: + config_file: The name of the configuration file to read. + file_type: If given, the type of file to read. [default: None, which mean + infer the file type from the extension.] + logger: If given, a logger object to log progress. [default: None] + + Returns: + list of config dicts + """ + logger = LoggerWrapper(logger) + logger.warning('Reading config file %s', config_file) + # Determine the file type from the extension if necessary: + if file_type is None: + name, ext = os.path.splitext(config_file) + if ext.lower().startswith('.j'): + file_type = 'json' + else: + # Let YAML be the default if the extension is not .y* or .j*. + file_type = 'yaml' + logger.debug('File type determined to be %s', file_type) + else: + logger.debug('File type specified to be %s', file_type) + + if file_type == 'yaml': + logger.info('Reading YAML config file %s', config_file) + config = ReadYaml(config_file) + else: + logger.info('Reading JSON config file %s', config_file) + config = ReadJson(config_file) + + ConvertNones(config) + logger.debug('Successfully read in config file.') + + return config
+ +
[docs]def ImportModules(config, gdict=None): + """Import any modules listed in config['modules']. + + These won't be brought into the running scope of the config processing, but any side + effects of the import statements will persist. In particular, these are allowed to + register additional custom types that can then be used in the current config dict. + + Parameters: + config: The configuration dict. + """ + if gdict is None: + gdict = globals() + if 'modules' in config: + for module in config['modules']: + try: + exec('import ' + module, gdict) + except ImportError: + # Try adding '.' to path, in case loading a local module and '.' not present. + if '.' not in sys.path: + sys.path.append('.') + exec('import ' + module, gdict) + else: + raise
+ +
[docs]def ProcessTemplate(config, base, logger=None): + """If the config dict has a 'template' item, read in the appropriate file and + make any requested updates. + + Parameters: + config: The configuration dict. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + """ + from .value_eval import _GenerateFromEval + + logger = LoggerWrapper(logger) + if 'template' in config: + template_string = config.pop('template') + logger.debug("Processing template specified as %s",template_string) + + # Allow it to be an Eval. We don't have much set up yet in the config dict, + # but really simple Eval strings should still be workable. + if template_string[0] == '$': + temp_config = { 'type': 'Eval', 'str': template_string[1:] } + template_string = _GenerateFromEval(temp_config, base, str)[0] + + # Parse the template string + if ':' in template_string: + config_file, field = template_string.split(':') + else: + config_file, field = template_string, None + + # If it is a registered name, get the real file name + if config_file in valid_templates: + logger.info("Template %s is registered as %s", + config_file, valid_templates[config_file]) + config_file = valid_templates[config_file] + + # Read the config file if appropriate + if config_file != '': + template = ReadConfig(config_file, logger=logger)[0] + ImportModules(template) + else: + template = base + + # Pull out the specified field, if any + if field is not None: + template = GetFromConfig(template, field) + + # In case template has further templates to process, do that now. + ProcessTemplate(template, base=base, logger=logger) + + # Copy over the template config into this one. + new_params = config.copy() # N.B. Already popped config['template']. + config.clear() + + config.update(template) + + if 'modules' in config: + # We want to keep all the modules from either place in the config dict + # so things like Eval can import everything that might be needed. + config['modules'].extend(new_params.pop('modules', [])) + + # Update the config with the requested changes + UpdateConfig(config, new_params, logger)
+ + +
[docs]def ProcessAllTemplates(config, logger=None, base=None): + """Check through the full config dict and process any fields that have a 'template' item. + + Parameters: + config: The configuration dict. + logger: If given, a logger object to log progress. [default: None] + base: The base configuration dict. [default: None] + """ + if base is None: base = config + ProcessTemplate(config, base, logger) + for (key, field) in list(config.items()): + if isinstance(field, dict): + ProcessAllTemplates(field, logger, base) + elif isinstance(field, list): + for item in field: + if isinstance(item, dict): + ProcessAllTemplates(item, logger, base)
+ +# This is the main script to process everything in the configuration dict. +
[docs]def Process(config, logger=None, njobs=1, job=1, new_params=None, except_abort=False): + """ + Do all processing of the provided configuration dict. In particular, this + function handles processing the output field, calling other functions to + build and write the specified files. The input field is processed before + building each file. + + Sometimes, it can be helpful to split up a processing jobs over multiple machines + (i.e. not just multiple processes, which can be handled natively with the output.nproc + or image.nproc options). In this case, you can ask the Process command to split up + the total amount of work into njobs and only do one of those jobs here. To do this, + set njobs to be the number of jobs total and job to be which job should be done here. + + Parameters: + config: The configuration dict. + logger: If given, a logger object to log progress. [default: None] + njobs: The total number of jobs to split the work into. [default: 1] + job: Which job should be worked on here (1..njobs). [default: 1] + new_params: A dict of new parameter values that should be used to update the config + dict after any template loading (if any). [default: None] + except_abort: Whether to abort processing when a file raises an exception (True) + or just report errors and continue on (False). [default: False] + + Returns: + the final config dict that was used. + """ + logger = LoggerWrapper(logger) + if njobs < 1: + raise GalSimValueError("Invalid number of jobs",njobs) + if job < 1: + raise GalSimValueError("Invalid job number. Must be >= 1.",job) + if job > njobs: + raise GalSimValueError("Invalid job number. Must be <= njobs (%d)"%(njobs),job) + + # First thing to do is deep copy the input config to make sure we don't modify the original. + config = CopyConfig(config) + + # Import any modules if requested + ImportModules(config) + + # Process any template specifications in the dict. + ProcessAllTemplates(config, logger) + + # Update using any new_params that are given: + if new_params is not None: + UpdateConfig(config, new_params, logger) + + # Do this again in case any new modules were added by the templates or command line params. + ImportModules(config) + + logger.debug("Final config dict to be processed: \n%s", + json.dumps(config, default=lambda o: repr(o), indent=4)) + + # Warn about any unexpected fields. + unexpected = [ k for k in config if k not in top_level_fields and k[0] != '_' ] + if len(unexpected) > 0 and logger: + logger.warning("Warning: config dict contains the following unexpected fields: %s.", + unexpected) + logger.warning("These fields are not (directly) processed by the config processing.") + + # Determine how many files we will be processing in total. + # Usually, this is just output.nfiles, but different output types may define this differently. + nfiles = GetNFiles(config, logger=logger) + logger.debug('nfiles = %d',nfiles) + + if njobs > 1: + # Start each job at file_num = nfiles * job / njobs + start = nfiles * (job-1) // njobs + end = nfiles * job // njobs + logger.warning('Splitting work into %d jobs. Doing job %d',njobs,job) + logger.warning('Building %d out of %d total files: file_num = %d .. %d', + end-start,nfiles,start,end-1) + nfiles = end-start + else: + start = 0 + + if nfiles == 1: + except_abort = True # Mostly just so the message reads better. + + #BuildFiles returns the config dictionary, which can includes stuff added + #by custom output types during the run. + config_out = BuildFiles(nfiles, config, file_num=start, logger=logger, + except_abort=except_abort) + #Return config_out in case useful + return config_out
+ +
[docs]def RegisterTemplate(template_name, file_name): + """Register a template config file with the given named alias. + + There are currently no named templates shipped with GalSim, but this function + provides a mechanism for modules to register a config file with a more user-friendly + name for a module-provided configuration that may be stored in an awkward location + on disk. + + E.g. LSSTDESC.imSim has a few configurations that include most of the default recommended + modules for producing various simulations of LSST images. They are stored in the imSim + data directory, but users can just use the named value as a more convenient alias. + + Parameters: + template_name: The name to allow in a 'template' field in lieu of the file name. + file_name: The actual file name on disk with the configuration file. + """ + valid_templates[template_name] = file_name
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/sed.html b/docs/_build/html/_modules/galsim/config/sed.html new file mode 100644 index 00000000000..d54f54e82ec --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/sed.html @@ -0,0 +1,308 @@ + + + + + + galsim.config.sed — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.sed

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from astropy.units import Quantity, Unit
+
+from .util import LoggerWrapper
+from .value import ParseValue, GetAllParams, GetIndex
+from .input import RegisterInputConnectedType
+from .bandpass import BuildBandpass
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..sed import SED
+from ..utilities import basestring, LRU_Cache
+
+# This module-level dict will store all the registered SED types.
+# See the RegisterSEDType function at the end of this file.
+# The keys are the (string) names of the SED types, and the values will be builders that know
+# how to build the SED object.
+valid_sed_types = {}
+
+def BuildSED(config, key, base, logger=None):
+    """Read the SED parameters from config[key] and return a constructed SED object.
+
+    Parameters:
+        config:     A dict with the configuration information.
+        key:        The key name in config indicating which object to build.
+        base:       The base dict of the configuration.
+        logger:     Optionally, provide a logger for logging debug statements. [default: None]
+
+    Returns:
+        (sed, safe) where sed is an SED instance, and safe is whether it is safe to reuse.
+    """
+    logger = LoggerWrapper(logger)
+    logger.debug('obj %d: Start BuildSED key = %s',base.get('obj_num',0),key)
+
+    param = config[key]
+
+    # Check for direct value, else get the SED type
+    if isinstance(param, SED):
+        return param, True
+    elif isinstance(param, basestring) and (param[0] == '$' or param[0] == '@'):
+        return ParseValue(config, key, base, None)
+    elif isinstance(param, dict):
+        sed_type = param.get('type','FileSED')
+    else:
+        raise GalSimConfigError("%s must be either an SED or a dict"%key)
+
+    # For these two, just do the usual ParseValue function.
+    if sed_type in ('Eval', 'Current'):
+        return ParseValue(config, key, base, None)
+
+    # Check if we can use the current cached object
+    index, index_key = GetIndex(param, base)
+    if 'current' in param:
+        csed, csafe, cvalue_type, cindex, cindex_key = param['current']
+        if cindex == index:
+            logger.debug('obj %d: The SED object is already current', base.get('obj_num',0))
+            logger.debug('obj %d: index_key = %s, index = %d',base.get('obj_num',0),
+                         cindex_key, cindex)
+            return csed, csafe
+
+    if sed_type not in valid_sed_types:
+        raise GalSimConfigValueError("Invalid sed.type.", sed_type, list(valid_sed_types.keys()))
+    logger.debug('obj %d: Building sed type %s', base.get('obj_num',0), sed_type)
+    builder = valid_sed_types[sed_type]
+    sed, safe = builder.buildSED(param, base, logger)
+    logger.debug('obj %d: sed = %s', base.get('obj_num',0), sed)
+
+    param['current'] = sed, safe, SED, index, index_key
+
+    return sed, safe
+
+
+
[docs]class SEDBuilder: + """A base class for building SED objects. + + The base class defines the call signatures of the methods that any derived class should follow. + """ +
[docs] def buildSED(self, config, base, logger): + """Build the SED based on the specifications in the config dict. + + Note: Sub-classes must override this function with a real implementation. + + Parameters: + config: The configuration dict for the SED type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed SED object. + """ + raise NotImplementedError("The %s class has not overridden buildSED"%self.__class__)
+ + +def _read_sed_file(file_name, wave_type, flux_type): + return SED(file_name, wave_type, flux_type) +read_sed_file = LRU_Cache(_read_sed_file) + +class FileSEDBuilder(SEDBuilder): + """A class for loading an SED from a file + + FileSED expected the following parameters: + + file_name (required) The file to load + wave_type(required) The units (nm or Ang) of the wavelengths in the file + flux_type (required) Which kind of flux values are in the file + Allowed values: flambda, fnu, fphotons, 1 + """ + def buildSED(self, config, base, logger): + """Build the SED based on the specifications in the config dict. + + Parameters: + config: The configuration dict for the SED type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed SED object. + """ + logger = LoggerWrapper(logger) + + req = { + 'file_name': str, + 'wave_type': (Unit, str), + 'flux_type': (Unit, str), + } + opt = { + 'norm_flux_density': (float, Quantity), + 'norm_wavelength': (float, Quantity), + 'norm_flux': float, + 'redshift': float + } + ignore = ['norm_bandpass'] + + kwargs, safe = GetAllParams(config, base, req=req, opt=opt, ignore=ignore) + + file_name = kwargs.pop('file_name') + + norm_flux_density = kwargs.pop('norm_flux_density', None) + norm_wavelength = kwargs.pop('norm_wavelength', None) + norm_flux = kwargs.pop('norm_flux', None) + redshift = kwargs.pop('redshift', 0.) + wave_type = kwargs.pop('wave_type') + flux_type = kwargs.pop('flux_type') + + logger.info("Using SED file: %s",file_name) + sed = read_sed_file(file_name, wave_type, flux_type) + if norm_flux_density is not None: + sed = sed.withFluxDensity(norm_flux_density, wavelength=norm_wavelength) + elif norm_flux: + bandpass, safe1 = BuildBandpass(config, 'norm_bandpass', base, logger) + sed = sed.withFlux(norm_flux, bandpass=bandpass) + safe = safe and safe1 + sed = sed.atRedshift(redshift) + + return sed, safe + +
[docs]def RegisterSEDType(sed_type, builder, input_type=None): + """Register a SED type for use by the config apparatus. + + Parameters: + sed_type: The name of the type in the config dict. + builder: A builder object to use for building the SED object. It should + be an instance of a subclass of SEDBuilder. + input_type: If the SED builder utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_sed_types[sed_type] = builder + RegisterInputConnectedType(input_type, sed_type)
+ +RegisterSEDType('FileSED', FileSEDBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/sensor.html b/docs/_build/html/_modules/galsim/config/sensor.html new file mode 100644 index 00000000000..87070093b98 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/sensor.html @@ -0,0 +1,328 @@ + + + + + + galsim.config.sensor — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.sensor

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+
+from .util import LoggerWrapper, GetIndex, GetRNG, get_cls_params
+from .value import ParseValue, GetAllParams, CheckAllParams, SetDefaultIndex
+from .input import RegisterInputConnectedType
+from ..sensor import Sensor, SiliconSensor
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..utilities import basestring
+
+# This file handles the construction of a Sensor in config['image']['sensor'].
+
+# This module-level dict will store all the registered sensor types.
+# See the RegisterSensorType function at the end of this file.
+# The keys are the (string) names of the sensor types, and the values will be builders that
+# know how to build the Sensor objects.
+valid_sensor_types = {}
+
+
+def BuildSensor(config, key, base, logger=None):
+    """Read the parameters from config[key] and return a constructed Sensor.
+
+    Parameters:
+        config:     A dict with the configuration information for the sensor.
+                    (usually base['image'])
+        key:        The key in the dict for the sensor configuration.
+        base:       The base dict of the configuration.
+        logger:     Optionally, provide a logger for logging debug statements. [default: None]
+
+    Returns:
+        a Sensor
+    """
+    logger = LoggerWrapper(logger)
+    logger.debug('obj %d: Start BuildSensor key = %s',base.get('obj_num',0),key)
+
+    param = config[key]
+
+    # Check for direct value, else get the type
+    if isinstance(param, Sensor):
+        return param
+    elif isinstance(param, basestring) and (param[0] == '$' or param[0] == '@'):
+        return ParseValue(config, key, base, None)[0]
+    elif isinstance(param, dict):
+        sensor_type = param.get('type', 'Simple')
+    else:
+        raise GalSimConfigError("sensor must be either a Sensor or a dict")
+
+    # For these two, just do the usual ParseValue function.
+    if sensor_type in ('Eval', 'Current'):
+        return ParseValue(config, key, base, None)[0]
+
+    if sensor_type not in valid_sensor_types:
+        raise GalSimConfigValueError("Invalid sensor type.", sensor_type,
+                                     list(valid_sensor_types.keys()))
+
+    # Check if we can use the current cached object
+    index, index_key = GetIndex(param, base)
+    if 'current' in param:
+        csensor, csafe, cvalue_type, cindex, cindex_key = param['current']
+        if cindex == index:
+            logger.debug('obj %d: The sensor is already current', base.get('obj_num',0))
+            logger.debug('obj %d: index_key = %s, index = %d',base.get('obj_num',0),
+                         cindex_key, cindex)
+            return csensor
+
+    # Need to use a builder.
+    logger.debug('obj %d: Building sensor type %s', base.get('obj_num',0), sensor_type)
+    builder = valid_sensor_types[sensor_type]
+    sensor = builder.buildSensor(param, base, logger)
+    logger.debug('obj %d: sensor = %s', base.get('obj_num',0), sensor)
+
+    param['current'] = sensor, False, None, index, index_key
+
+    return sensor
+
+
+
[docs]class SensorBuilder: + """A base class for building Sensor objects. + + The base class defines the call signatures of the methods that any derived class should follow. + """ +
[docs] def buildSensor(self, config, base, logger): + """Build the Sensor based on the specifications in the config dict. + + Note: Sub-classes must override this function with a real implementation. + + Parameters: + config: The configuration dict for the Sensor + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed Sensor object. + """ + raise NotImplementedError("The %s class has not overridden buildSensor"%self.__class__)
+ + +class SimpleSensorBuilder(SensorBuilder): + """A class for building simple Sensor objects. + + The initializer takes an init_func, which is the class or function to call to build the + Sensor. For the kwargs, it calls getKwargs, which does the normal parsing of the req_params + and related class attributes. + """ + def __init__(self, init_func): + self.init_func = init_func + + def getKwargs(self, config, base, logger): + """Get the kwargs to pass to the build function based on the following attributes of + init_func: + + _req_params + A dict of required parameters and their types. + _opt_params + A dict of optional parameters and their types. + _single_params + A list of dicts of parameters such that one and only one of + parameter in each dict is required. + _takes_rng + A bool value saying whether an rng object is required. + + See the classes in sensor.py for examples of classes that set these attributes. + + Parameters: + config: The configuration dict for the sensor type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + kwargs + """ + req, opt, single, takes_rng = get_cls_params(self.init_func) + kwargs, safe = GetAllParams(config, base, req, opt, single) + if takes_rng: + kwargs['rng'] = GetRNG(config, base, logger, self.init_func.__name__) + return kwargs + + def buildSensor(self, config, base, logger): + """Build the Sensor based on the specifications in the config dict. + + Parameters: + config: The configuration dict for the sensor type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed Sensor object. + """ + kwargs = self.getKwargs(config,base,logger) + return self.init_func(**kwargs) + +class ListSensorBuilder(SensorBuilder): + """Select a sensor from a list + """ + def buildSensor(self, config, base, logger): + req = { 'items' : list } + opt = { 'index' : int } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt) + items = config['items'] + if not isinstance(items,list): + raise GalSimConfigError("items entry for type=List is not a list.") + + # Setup the indexing sequence if it hasn't been specified using the length of items. + SetDefaultIndex(config, len(items)) + index, safe = ParseValue(config, 'index', base, int) + + if index < 0 or index >= len(items): + raise GalSimConfigError("index %d out of bounds for sensor type=List"%index) + return BuildSensor(items, index, base) + +
[docs]def RegisterSensorType(sensor_type, builder, input_type=None): + """Register a sensor type for use by the config apparatus. + + Parameters: + sensor_type: The name of the config type to register + builder: A builder object to use for building the Sensor object. It should + be an instance of a subclass of SensorBuilder. + input_type: If the Sensor builder utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_sensor_types[sensor_type] = builder + RegisterInputConnectedType(input_type, sensor_type)
+ + +RegisterSensorType('Simple', SimpleSensorBuilder(Sensor)) +RegisterSensorType('Silicon', SimpleSensorBuilder(SiliconSensor)) +RegisterSensorType('List', ListSensorBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/stamp.html b/docs/_build/html/_modules/galsim/config/stamp.html new file mode 100644 index 00000000000..b67a2e86f8c --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/stamp.html @@ -0,0 +1,1399 @@ + + + + + + galsim.config.stamp — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.stamp

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+import numpy as np
+import math
+import traceback
+
+from .util import LoggerWrapper, GetRNG, UpdateNProc, MultiProcess, SetupConfigRNG, RemoveCurrent
+from .input import SetupInput
+from .gsobject import UpdateGSParams, SkipThisObject
+from .gsobject import BuildGSObject
+from .value import ParseValue, CheckAllParams
+from .noise import CalculateNoiseVariance, AddSky, AddNoise
+from .wcs import BuildWCS
+from .photon_ops import BuildPhotonOps
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..image import Image
+from ..wcs import PixelScale
+from ..position import PositionD, PositionI
+from ..bounds import _BoundsI
+from ..celestial import CelestialCoord
+from ..angle import arcsec
+from ..gsobject import GSObject
+from ..convolve import Convolve, Convolution
+from ..box import Pixel
+from ..chromatic import ChromaticObject
+from ..bandpass import Bandpass
+
+# This file handles the building of postage stamps to place onto a larger image.
+# There is only one type of stamp currently, called Basic, which builds a galaxy from
+# config['gal'] and a PSF from config['psf'] (either but not both of which may be absent),
+# colvolves them together, and draws onto a postage stamp.  This is the default functionality
+# and is typically not specified explicitly.  But there are hooks in place to allow for other
+# options, either in future versions of GalSim or through user modules.
+
+# This module-level dict will store all the registered stamp types.
+# See the RegisterStampType function at the end of this file.
+# The keys are the (string) names of the output types, and the values will be builder objects
+# that will perform the different stages of processing to build each stamp image.
+valid_stamp_types = {}
+
+
+
[docs]def BuildStamps(nobjects, config, obj_num=0, + xsize=0, ysize=0, do_noise=True, logger=None): + """ + Build a number of postage stamp images as specified by the config dict. + + Parameters: + nobjects: How many postage stamps to build. + config: A configuration dict. + obj_num: If given, the current obj_num. [default: 0] + xsize: The size of a single stamp in the x direction. [default: 0, + which means to look first for config.stamp.xsize, then for + config.image.stamp_xsize, and if neither are given, then use + automatic sizing.] + ysize: The size of a single stamp in the y direction. [default: 0, + which means to look first for config.stamp.ysize, then for + config.image.stamp_ysize, and if neither are given, then use + automatic sizing.] + do_noise: Whether to add noise to the image (according to config['noise']). + [default: True] + logger: If given, a logger object to log progress. [default: None] + + Returns: + the tuple (images, current_vars). Both are themselves tuples. + """ + logger = LoggerWrapper(logger) + logger.debug('image %d: BuildStamps nobjects = %d: obj = %d', + config.get('image_num',0),nobjects,obj_num) + + if nobjects == 0: + logger.warning("No stamps were built, since nstamps == 0.") + return (), () + + # Figure out how many processes we will use for building the stamps: + if nobjects > 1 and 'image' in config and 'nproc' in config['image']: + nproc = ParseValue(config['image'], 'nproc', config, int)[0] + # Update this in case the config value is -1 + nproc = UpdateNProc(nproc, nobjects, config, logger) + else: + nproc = 1 + + if 'image' in config and 'timeout' in config['image']: + timeout = ParseValue(config['image'], 'timeout', config, float)[0] + else: + timeout = 900 + + jobs = [] + for k in range(nobjects): + kwargs = { + 'obj_num' : obj_num + k, + 'xsize' : xsize, + 'ysize' : ysize, + 'do_noise' : do_noise, + } + jobs.append(kwargs) + + def done_func(logger, proc, k, result, t): + if result[0] is not None: + # Note: numpy shape is y,x + image = result[0] + ys, xs = image.array.shape + if proc is None: s0 = '' + else: s0 = '%s: '%proc + obj_num = jobs[k]['obj_num'] + logger.info(s0 + 'Stamp %d: size = %d x %d, time = %f sec', obj_num, xs, ys, t) + + def except_func(logger, proc, k, e, tr): + if proc is None: s0 = '' + else: s0 = '%s: '%proc + obj_num = jobs[k]['obj_num'] + logger.error(s0 + 'Exception caught when building stamp %d', obj_num) + logger.debug('%s',tr) + logger.error('Aborting the rest of this image') + + # Convert to the tasks structure we need for MultiProcess. + # Each task is a list of (job, k) tuples. + tasks = MakeStampTasks(config, jobs, logger) + + results = MultiProcess(nproc, config, BuildStamp, tasks, 'stamp', + logger=logger, timeout=timeout, + done_func=done_func, except_func=except_func) + + images, current_vars = zip(*results) + + logger.debug('image %d: Done making stamps',config.get('image_num',0)) + if all(im is None for im in images): + logger.warning('No stamps were built. All objects were skipped.') + + return images, current_vars
+ +# A list of keys that really belong in stamp, but are allowed in image both for convenience +# and backwards-compatibility reasons. Any of these present will be copied over to +# config['stamp'] if they exist in config['image']. +stamp_image_keys = ['offset', 'retry_failures', 'gsparams', 'draw_method', 'dtype', + 'n_photons', 'max_extra_noise', 'poisson_flux', 'obj_rng'] + +
[docs]def SetupConfigObjNum(config, obj_num, logger=None): + """Do the basic setup of the config dict at the stamp (or object) processing level. + + Includes: + + - Set config['obj_num'] = obj_num + - Set config['index_key'] = 'obj_num' + - Make sure config['stamp'] exists + - Set default config['stamp']['type'] to 'Basic' + - Copy over values from config['image'] that are allowed there, but really belong + in config['stamp']. + - Set config['stamp']['draw_method'] to 'auto' if not given. + + Parameters: + config: A configuration dict. + obj_num: The current obj_num. + logger: If given, a logger object to log progress. [default: None] + """ + logger = LoggerWrapper(logger) + config['obj_num'] = obj_num + config['index_key'] = 'obj_num' + + # Make config['stamp'] exist if it doesn't yet. + if 'stamp' not in config: + config['stamp'] = {} + stamp = config['stamp'] + if '_done' in stamp: return + + # Everything after here only needs to be done once. + + if not isinstance(stamp, dict): + raise GalSimConfigError("config.stamp is not a dict.") + if 'type' not in stamp: + stamp['type'] = 'Basic' + + if 'file_num' not in config: + config['file_num'] = 0 + if 'image_num' not in config: + config['image_num'] = 0 + + # Copy over some things from config['image'] if they are given there. + # These are things that we used to advertise as being in the image field, but now that + # we have a stamp field, they really make more sense here. But for backwards compatibility, + # or just because they can make sense in either place, we allow them to be in 'image' still. + if 'image' in config: + image = config['image'] + for key in stamp_image_keys: + if key in image and key not in stamp: + stamp[key] = image[key] + else: + config['image'] = {} + + if 'draw_method' not in stamp: + stamp['draw_method'] = 'auto' + + # In case this hasn't been done yet. + SetupInput(config, logger) + + # Mark this as done, so later passes can skip a lot of this. + stamp['_done'] = True
+ +
[docs]def SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos, logger=None): + """Do further setup of the config dict at the stamp (or object) processing level reflecting + the stamp size and position in either image or world coordinates. + + Note: This is now a StampBuilder method. So this function just calls StampBuilder.locateStamp. + + Parameters: + config: A configuration dict. + xsize: The size of the stamp in the x-dimension. [may be 0 if unknown] + ysize: The size of the stamp in the y-dimension. [may be 0 if unknown] + image_pos: The position of the stamp in image coordinates. [may be None] + world_pos: The position of the stamp in world coordinates. [may be None] + logger: If given, a logger object to log progress. [default: None] + """ + stamp = config.get('stamp',{}) + stamp_type = stamp.get('type','Basic') + builder = valid_stamp_types[stamp_type] + builder.locateStamp(stamp, config, xsize, ysize, image_pos, world_pos, logger)
+ + +# Ignore these when parsing the parameters for specific stamp types: +stamp_ignore = ['xsize', 'ysize', 'size', 'image_pos', 'world_pos', 'sky_pos', + 'offset', 'retry_failures', 'gsparams', 'draw_method', 'dtype', + 'n_photons', 'max_extra_noise', 'poisson_flux', 'photon_ops', + 'skip', 'reject', 'min_flux_frac', 'min_snr', 'max_snr', + 'quick_skip', 'obj_rng', 'index_key', 'rng_index_key', 'rng_num'] + +valid_draw_methods = ('auto', 'fft', 'phot', 'real_space', 'no_pixel', 'sb') + +
[docs]def BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None): + """ + Build a single stamp image using the given config file + + Parameters: + config: A configuration dict. + obj_num: If given, the current obj_num [default: 0] + xsize: The xsize of the stamp to build (if known). [default: 0] + ysize: The ysize of the stamp to build (if known). [default: 0] + do_noise: Whether to add noise to the image (according to config['noise']). + [default: True] + logger: If given, a logger object to log progress. [default: None] + + Returns: + the tuple (image, current_var) + """ + from .extra import ProcessExtraOutputsForStamp + + logger = LoggerWrapper(logger) + SetupConfigObjNum(config, obj_num, logger) + + stamp = config['stamp'] + stamp_type = stamp['type'] + if stamp_type not in valid_stamp_types: + raise GalSimConfigValueError("Invalid stamp.type.", stamp_type, + list(valid_stamp_types.keys())) + builder = valid_stamp_types[stamp_type] + + if builder.quickSkip(stamp, config): + ProcessExtraOutputsForStamp(config, True, logger) + return None, 0 + + builder.setupRNG(stamp, config, logger) + + if 'skip_failures' in stamp: + skip_failures = ParseValue(stamp, 'skip_failures', config, bool)[0] + else: + skip_failures = False + + if 'retry_failures' in stamp: + ntries = ParseValue(stamp,'retry_failures',config,int)[0] + if skip_failures and ntries: # pragma: no cover + # This is definitely covered by the tests. + # I can't figure out why codecov doesn't think so. + raise GalSimConfigValueError( + "Cannot use retry_failures when skip_failures=True", ntries) + # This is how many _re_-tries. Do at least 1, so ntries is 1 more than this. + ntries = ntries + 1 + elif ('reject' in stamp or 'min_flux_frac' in stamp or + 'min_snr' in stamp or 'max_snr' in stamp): + # Still impose a maximum number of tries to prevent infinite loops. + ntries = 20 + else: + ntries = 1 + + itry = 0 + while True: + itry += 1 # itry increases from 1..ntries at which point we just reraise the exception. + + # The rest of the stamp generation stage is wrapped in a try/except block. + # If we catch an exception, we continue the for loop to try again. + # On the last time through, we reraise any exception caught. + # If no exception is thrown, we simply break the loop and return. + try: + + # Do the necessary initial setup for this stamp type. + xsize, ysize, image_pos, world_pos = builder.setup( + stamp, config, xsize, ysize, stamp_ignore, logger) + + # Determine the stamp size and location + builder.locateStamp(stamp, config, xsize, ysize, image_pos, world_pos, logger) + + # Get the global gsparams kwargs. Individual objects can add to this. + gsparams = {} + if 'gsparams' in stamp: + gsparams = UpdateGSParams(gsparams, stamp['gsparams'], config) + + # Note: Skip is different from Reject. + # Skip means we return None for this stamp image and continue on. + # Reject means we retry this object using the same obj_num. + # This has implications for the total number of objects as well as + # things like ring tests that rely on objects being made in pairs. + # + # Skip is also different from prof = None. + # If prof is None, then the user indicated that no object should be + # drawn on this stamp, but that a noise image is still desired. + if builder.getSkip(stamp, config, logger): + raise SkipThisObject('') + + # Build the object to draw + psf = builder.buildPSF(stamp, config, gsparams, logger) + prof = builder.buildProfile(stamp, config, psf, gsparams, logger) + + # Make an empty image + im = builder.makeStamp(stamp, config, xsize, ysize, logger) + + # Determine which draw method to use + method = builder.getDrawMethod(stamp, config, logger) + + # Determine the net offset (starting from config['stamp_offset'] typically. + offset = builder.getOffset(stamp, config, logger) + + # At this point, we may want to update whether the object should be skipped + # based on other information about the profile and location. + if builder.updateSkip(prof, im, method, offset, stamp, config, logger): + raise SkipThisObject('') + + # Draw the object on the postage stamp + im = builder.draw(prof, im, method, offset, stamp, config, logger) + # Store the final version of the current profile for reference. + config['current_prof'] = prof + + # Update the drawn image according to the SNR if desired. + scale_factor = builder.getSNRScale(im, stamp, config, logger) + im, prof = builder.applySNRScale(im, prof, scale_factor, method, logger) + + # Set the origin appropriately + builder.updateOrigin(stamp, config, im) + + # Store the current stamp in the base-level config for reference + config['current_stamp'] = im + # This is also information that the weight image calculation needs + config['do_noise_in_stamps'] = do_noise + + # Check if this object should be rejected. + reject = builder.reject(stamp, config, prof, psf, im, logger) + if reject: + if itry < ntries: + logger.warning('Object %d: Rejecting this object and rebuilding', obj_num) + builder.reset(config, logger) + continue + else: + raise GalSimConfigError( + "Rejected an object %d times. If this is expected, " + "you should specify a larger stamp.retry_failures."%(ntries)) + + ProcessExtraOutputsForStamp(config, False, logger) + + # We always need to do the whiten step here in the stamp processing + current_var = builder.whiten(prof, im, stamp, config, logger) + if current_var != 0.: + logger.debug('obj %d: whitening noise brought current var to %f', + config.get('obj_num',0),current_var) + + # Sometimes, depending on the image type, we go on to do the rest of the noise as well. + if do_noise: + im, current_var = builder.addNoise(stamp,config,im,current_var,logger) + + except SkipThisObject as e: + if e.msg != '': + logger.debug('obj %d: Caught SkipThisObject: e = %s',obj_num,e.msg) + logger.info('Skipping object %d %s', obj_num, e.msg) + # If xsize, ysize != 0, then this makes a blank stamp for this object. + # Otherwise, it's just None here. + im = builder.makeStamp(stamp, config, xsize, ysize, logger) + ProcessExtraOutputsForStamp(config, True, logger) + return im, 0. + + except Exception as e: + if skip_failures: + logger.info('Object %d: Caught exception %s',obj_num,str(e)) + tr = traceback.format_exc() + logger.debug('obj %d: Traceback = %s',obj_num,tr) + logger.info('Skipping this object') + # Now same as SkipThisObject case above. + im = builder.makeStamp(stamp, config, xsize, ysize, logger) + ProcessExtraOutputsForStamp(config, True, logger) + return im, 0. + elif itry >= ntries: + # Then this was the last try. Just re-raise the exception. + logger.info('Object %d: Caught exception %s',obj_num,str(e)) + if ntries > 1: + logger.error( + 'Object %d: Too many exceptions/rejections for this object. Aborting.', + obj_num) + raise + else: + logger.info('Object %d: Caught exception %s',obj_num,str(e)) + logger.info('This is try %d/%d, so trying again.',itry,ntries) + tr = traceback.format_exc() + logger.debug('obj %d: Traceback = %s',obj_num,tr) + # Need to remove the "current"s from the config dict. Otherwise, + # the value generators will do a quick return with the cached value. + builder.reset(config, logger) + continue + + else: + # No exception. + return im, current_var
+ + +
[docs]def MakeStampTasks(config, jobs, logger): + """Turn a list of jobs into a list of tasks. + + See the doc string for galsim.config.MultiProcess for the meaning of this distinction. + + For the Basic stamp type, there is just one job per task, so the tasks list is just:: + + tasks = [ [ (job, k) ] for k, job in enumerate(jobs) ] + + But other stamp types may need groups of jobs to be done sequentially by the same process. + cf. stamp type=Ring. + + Parameters: + config: The configuration dict + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'obj_num'. + logger: A logger object to log progress. + + Returns: + a list of tasks + """ + stamp = config.get('stamp', {}) + stamp_type = stamp.get('type', 'Basic') + return valid_stamp_types[stamp_type].makeTasks(stamp, config, jobs, logger)
+ + +
[docs]def DrawBasic(prof, image, method, offset, config, base, logger, **kwargs): + """The basic implementation of the draw command + + This function is provided as a free function, rather than just the base class implementation + in StampBuilder to make it easier for classes derived from StampBuilder to use to help + implement their draw functions. The base class, StampBuilder, just calls this function + for its draw method. + + This version also allows for additional kwargs, which are passed on to the drawImage function. + e.g. you can add add_to_image=True or setup_only=True if these are helpful. + + Parameters: + prof: The profile to draw. + image: The image onto which to draw the profile (which may be None). + method: The method to use in drawImage. + offset: The offset to apply when drawing. + config: The configuration dict for the stamp field. + base: The base configuration dict. + logger: A logger object to log progress. + **kwargs: Any additional kwargs are passed along to the drawImage function. + + Returns: + the resulting image + """ + logger = LoggerWrapper(logger) + # Setup the kwargs to pass to drawImage + # (Start with any additional kwargs given as extra kwargs to DrawBasic and add to it.) + kwargs['image'] = image + kwargs['offset'] = offset + kwargs['method'] = method + if 'wcs' not in kwargs and 'scale' not in kwargs: + kwargs['wcs'] = base['wcs'].local(image_pos = base['image_pos']) + sensor = base.get('sensor',None) + if (method == 'phot' or sensor is not None) and 'rng' not in kwargs: + # Note: use the image.noise rng_num, in case stamp is doing a non-standard cadence. + # The cadence specified in the noise field is what to use for photon shooting. + noise = base.get('image',{}).get('noise',{}) + noise = noise if isinstance(noise, dict) else {} + rng = GetRNG(noise, base, logger, "method='phot'") + kwargs['rng'] = rng + + # Check validity of extra phot options: + max_extra_noise = None + if 'n_photons' in config and 'n_photons' not in kwargs: + if method != 'phot': + raise GalSimConfigError('n_photons is invalid with method != phot') + if 'max_extra_noise' in config: + logger.warning( + "Both 'max_extra_noise' and 'n_photons' are set in config dict, " + "ignoring 'max_extra_noise'.") + kwargs['n_photons'] = ParseValue(config, 'n_photons', base, int)[0] + elif 'max_extra_noise' in config: + max_extra_noise = ParseValue(config, 'max_extra_noise', base, float)[0] + if method != 'phot' and max_extra_noise is not None: + raise GalSimConfigError('max_extra_noise is invalid with method != phot') + + if 'poisson_flux' in config and 'poisson_flux' not in kwargs: + if method != 'phot': + raise GalSimConfigError('poisson_flux is invalid with method != phot') + kwargs['poisson_flux'] = ParseValue(config, 'poisson_flux', base, bool)[0] + + if max_extra_noise is not None and 'max_extra_noise' not in kwargs: + if max_extra_noise < 0.: + raise GalSimConfigError("image.max_extra_noise cannot be negative") + if 'image' in base and 'noise' in base['image']: + noise_var = CalculateNoiseVariance(base) + else: + raise GalSimConfigError("Need to specify noise level when using max_extra_noise") + if noise_var < 0.: + raise GalSimConfigError("noise_var calculated to be < 0.") + max_extra_noise *= noise_var + kwargs['max_extra_noise'] = max_extra_noise + + bandpass = base.get('bandpass', None) + if bandpass is not None: + kwargs['bandpass'] = bandpass + elif isinstance(prof, ChromaticObject): + raise GalSimConfigError("Drawing chromatic object requires specifying bandpass") + + if 'photon_ops' in config: + kwargs['photon_ops'] = BuildPhotonOps(config, 'photon_ops', base, logger) + + if sensor is not None: + sensor.updateRNG(rng) + kwargs['sensor'] = sensor + + if image is None: + kwargs['dtype'] = ParseDType(config, base) + + if logger.isEnabledFor(logging.DEBUG): + # Don't output the full image array. Use str(image) for that kwarg. And Bandpass. + alt_kwargs = dict([(k, str(kwargs[k]) if isinstance(kwargs[k],(Image,Bandpass)) + else kwargs[k]) + for k in kwargs]) + logger.debug('obj %d: drawImage kwargs = %s',base.get('obj_num',0), alt_kwargs) + logger.debug('obj %d: prof = %s',base.get('obj_num',0),prof) + try: + image = prof.drawImage(**kwargs) + except Exception as e: + logger.debug('obj %d: prof = %r', base.get('obj_num',0), prof) + raise + logger.debug('obj %d: added_flux = %s',base.get('obj_num',0), image.added_flux) + return image
+ +
[docs]def ParseWorldPos(config, param_name, base, logger): + """A helper function to parse the 'world_pos' value. + + The world_pos can be specified either as a regular RA, Dec (which in GalSim is known as a + CelestialCoord) or as Euclidean coordinates in the local tangent plane relative to the + image center (a PositionD). + + 1. For the RA/Dec option, the world_pos field should use the type RADec, which includes two + values named ra and dec, each of which should be an Angle type. e.g.:: + + world_pos: + type : RADec + ra : 37 hours + dec: -23 degrees + + Technically, any other type that results in a CelestialCoord is valid, but RADec is the + only one that is defined natively in GalSim. + + 2. For the relative position in the local tangent plane (where 0,0 is the position of the + image center), you can use any PositionD type. e.g.:: + + world_pos: + type : RandomCircle + radius : 12 # arcsec + inner_radius : 3 # arcsec + + Parameters: + config: The configuration dict for the stamp field. + param_name: The name of the field in the config dict to parse as a world_pos. + Normally, this is just 'world_pos'. + base: The base configuration dict. + + Returns: + either a CelestialCoord or a PositionD instance. + """ + param = config[param_name] + wcs = base.get('wcs', PixelScale(1.0)) # should be here, but just in case... + if wcs._isCelestial: + return ParseValue(config, param_name, base, CelestialCoord)[0] + else: + return ParseValue(config, param_name, base, PositionD)[0]
+ +def ParseDType(config, base): + dtype = config.get('dtype', None) + if isinstance(dtype, str): + try: + try: + dtype = np.dtype(dtype).type + except Exception: + gdict = globals().copy() + exec('import numpy', gdict) + exec('import numpy as np', gdict) + dtype = eval(dtype, gdict) + except Exception: + raise GalSimConfigValueError( + "dtype = %s is invalid."%dtype, Image.valid_dtypes) from None + if dtype is None and base.get('current_image', None) is not None: + dtype = base['current_image'].dtype + return dtype + +
[docs]class StampBuilder: + """A base class for building stamp images of individual objects. + + The base class defines the call signatures of the methods that any derived class should follow. + It also includes the implementation of the default stamp type: Basic. + """ + +
[docs] def setup(self, config, base, xsize, ysize, ignore, logger): + """ + Do the initialization and setup for building a postage stamp. + + In the base class, we check for and parse the appropriate size and position values in + config (aka base['stamp'] or base['image']. + + Values given in base['stamp'] take precedence if these are given in both places (which + would be confusing, so probably shouldn't do that, but there might be a use case where it + would make sense). + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + xsize: The xsize of the image to build (if known). + ysize: The ysize of the image to build (if known). + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if these parameters are present. + logger: A logger object to log progress. + + Returns: + xsize, ysize, image_pos, world_pos + """ + # Check for spurious parameters + CheckAllParams(config, ignore=ignore) + + # Update the size if necessary + image = base['image'] + if 'xsize' in config: + xsize = ParseValue(config,'xsize',base,int)[0] + elif 'size' in config: + xsize = ParseValue(config,'size',base,int)[0] + elif 'stamp_xsize' in image: + xsize = ParseValue(image,'stamp_xsize',base,int)[0] + elif 'stamp_size' in image: + xsize = ParseValue(image,'stamp_size',base,int)[0] + # else use the input xsize + + if 'ysize' in config: + ysize = ParseValue(config,'ysize',base,int)[0] + elif 'size' in config: + ysize = ParseValue(config,'size',base,int)[0] + elif 'stamp_ysize' in image: + ysize = ParseValue(image,'stamp_ysize',base,int)[0] + elif 'stamp_size' in image: + ysize = ParseValue(image,'stamp_size',base,int)[0] + # else use the input ysize + + # Determine where this object is going to go: + if 'image_pos' in config: + image_pos = ParseValue(config, 'image_pos', base, PositionD)[0] + elif 'image_pos' in image: + image_pos = ParseValue(image, 'image_pos', base, PositionD)[0] + else: + image_pos = None + + if 'world_pos' in config: + world_pos = ParseWorldPos(config, 'world_pos', base, logger) + elif 'world_pos' in image: + world_pos = ParseWorldPos(image, 'world_pos', base, logger) + else: + world_pos = None + + return xsize, ysize, image_pos, world_pos
+ +
[docs] def quickSkip(self, config, base): + """Check whether this object should be skipped before doing any work. + + The base class looks for stamp.quick_skip and returns True if it is preset and + evaluates to True. + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + + @returns skip + """ + return ('quick_skip' in config and ParseValue(config, 'quick_skip', base, bool)[0])
+ +
[docs] def setupRNG(self, config, base, logger): + """Setup the RNG for this object. + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + """ + if 'obj_rng' in config: + if not ParseValue(config,'obj_rng',base,bool)[0]: + # Just use the image_num rng(s). + base['obj_num_rngs'] = base.get('image_num_rngs',None) + base['obj_num_rng'] = base.get('image_num_rng',None) + return + + # Add 1 to the seed here so the first object has a different rng than the file or image. + seed = SetupConfigRNG(base, seed_offset=1, logger=logger) + logger.debug('obj %d: seed = %d',base.get('obj_num',0),seed)
+ +
[docs] def locateStamp(self, config, base, xsize, ysize, image_pos, world_pos, logger): + """Determine where and how large the stamp should be. + + The base class version does the followin: + + - If given, set base['stamp_xsize'] = xsize + - If given, set base['stamp_ysize'] = ysize + - If only image_pos or world_pos is given, compute the other from base['wcs'] + - Set base['index_pos'] = image_pos + - Set base['world_pos'] = world_pos + - Calculate the appropriate value of the center of the stamp, to be used with the + command: stamp_image.setCenter(stamp_center). Save this as base['stamp_center'] + - Calculate the appropriate offset for the position of the object from the center of + the stamp due to just the fractional part of the image position, not including + any base['stamp']['offset'] item that may be present in the base dict. + Save this as base['stamp_offset'] + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + @param xsize The size of the stamp in the x-dimension. [may be 0 if unknown] + @param ysize The size of the stamp in the y-dimension. [may be 0 if unknown] + @param image_pos The position of the stamp in image coordinates. [may be None] + @param world_pos The position of the stamp in world coordinates. [may be None] + @param logger A logger object to log progress. + """ + logger = LoggerWrapper(logger) + + # Make sure we have a valid wcs in case image-level processing was skipped. + if 'wcs' not in base: + base['wcs'] = BuildWCS(base['image'], 'wcs', config, logger) + wcs = base['wcs'] + + if xsize: base['stamp_xsize'] = xsize + if ysize: base['stamp_ysize'] = ysize + + # If we have either image_pos or world_pos, calculate the other. + if image_pos is not None and world_pos is None: + world_pos = wcs.toWorld(image_pos) + elif world_pos is not None and image_pos is None: + image_pos = wcs.toImage(world_pos) + + # If the world_pos is a CelestialCoord, then we also call it sky_pos. + # If the world_pos is not celestial, or the user wants to override it for some reason, + # then the user may optionally define a sky_pos value, which gets saved as base['sky_pos']. + # This may be useful for things that need to know where in the sky the pointing is, + # even if the WCS is not a CelestialWCS. + if 'sky_pos' in config: + base['sky_pos'] = ParseValue(config, 'sky_pos', base, CelestialCoord)[0] + elif isinstance(world_pos, CelestialCoord): + base['sky_pos'] = world_pos + + # Sometimes we need a "world" position as flat position, rather than a CelestialCoord. + # This is called uv_pos. If the WCS is a EuclideanWCS, then uv_pos = world_pos. + # If the WCS is a CelestialWCS, then sky_pos = world_pos, and we calculate uv_pos as + # the tangent-plane projection. + if isinstance(world_pos, CelestialCoord): + # Then project this position relative to the image center. + world_center = base.get('world_center', wcs.toWorld(base['image_center'])) + u, v = world_center.project(world_pos, projection='gnomonic') + base['uv_pos'] = PositionD(u/arcsec, v/arcsec) + else: + base['uv_pos'] = world_pos + + if image_pos is not None: + # The image_pos refers to the location of the true center of the image, which is + # not necessarily the nominal center we need for adding to the final image. In + # particular, even-sized images have their nominal center offset by 1/2 pixel up + # and to the right. + # N.B. This works even if xsize,ysize == 0, since the auto-sizing always produces + # even sized images. + nominal_x = image_pos.x # Make sure we don't change image_pos, which is + nominal_y = image_pos.y # stored in base['image_pos']. + if xsize % 2 == 0: nominal_x += 0.5 + if ysize % 2 == 0: nominal_y += 0.5 + + stamp_center = PositionI(int(math.floor(nominal_x+0.5)), + int(math.floor(nominal_y+0.5))) + stamp_offset = PositionD(nominal_x-stamp_center.x, + nominal_y-stamp_center.y) + else: + stamp_center = None + stamp_offset = PositionD(0.,0.) + # Set the image_pos to the image center in case the wcs needs it. Probably, if + # there is no image_pos or world_pos defined, then it is unlikely a + # non-trivial wcs will have been set. So anything would actually be fine. + image_pos = PositionD( (xsize+1.)/2, (ysize+1.)/2 ) + + base['stamp_center'] = stamp_center + base['stamp_offset'] = stamp_offset + base['image_pos'] = image_pos + base['world_pos'] = world_pos + + if xsize: + logger.debug('obj %d: xsize,ysize = %s,%s',base.get('obj_num',0),xsize,ysize) + logger.debug('obj %d: image_pos = %s',base.get('obj_num',0),image_pos) + if world_pos: + logger.debug('obj %d: world_pos = %s',base.get('obj_num',0),world_pos) + if stamp_center: + logger.debug('obj %d: stamp_center = %s',base.get('obj_num',0),stamp_center)
+ +
[docs] def getSkip(self, config, base, logger): + """Initial check of whether to skip this object based on the stamp.skip field. + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + @param logger A logger object to log progress. + + @returns skip + """ + if 'skip' in config: + skip = ParseValue(config, 'skip', base, bool)[0] + if skip: + logger.debug("obj %d: stamp.skip = True",base.get('obj_num',0)) + else: + skip = False + return skip
+ +
[docs] def buildPSF(self, config, base, gsparams, logger): + """Build the PSF object. + + For the Basic stamp type, this builds a PSF from the base['psf'] dict, if present, + else returns None. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + gsparams: A dict of kwargs to use for a GSParams. More may be added to this + list by the galaxy object. + logger: A logger object to log progress. + + Returns: + the PSF + """ + return BuildGSObject(base, 'psf', gsparams=gsparams, logger=logger)[0]
+ +
[docs] def buildProfile(self, config, base, psf, gsparams, logger): + """Build the surface brightness profile (a GSObject) to be drawn. + + For the Basic stamp type, this builds a galaxy from the base['gal'] dict and convolves + it with the psf (if given). If either the psf or the galaxy is None, then the other one + is returned as is. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + psf: The PSF, if any. This may be None, in which case, no PSF is convolved. + gsparams: A dict of kwargs to use for a GSParams. More may be added to this + list by the galaxy object. + logger: A logger object to log progress. + + Returns: + the final profile + """ + gal = BuildGSObject(base, 'gal', gsparams=gsparams, logger=logger)[0] + + if psf: + if gal: + return Convolve(gal,psf) + else: + return psf + else: + if gal: + return gal + elif 'gal' in base or 'psf' in base: + return None + else: + raise GalSimConfigError( + "At least one of gal or psf must be specified in config. " + "If you really don't want any object, use gal type = None.")
+ +
[docs] def makeStamp(self, config, base, xsize, ysize, logger): + """Make the initial empty postage stamp image, if possible. + + If we don't know xsize, ysize, return None, in which case the stamp will be created + automatically by the drawImage command based on the natural size of the profile. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + xsize: The xsize of the image to build (if known). + ysize: The ysize of the image to build (if known). + logger: A logger object to log progress. + + Returns: + the image + """ + if xsize and ysize: + dtype = ParseDType(config, base) + bounds = _BoundsI(1,xsize,1,ysize) + + # Set the origin appropriately + stamp_center = base['stamp_center'] + if stamp_center: + bounds = bounds.shift(stamp_center - bounds.center) + else: + bounds = bounds.shift(base.get('image_origin',PositionI(1,1)) - PositionI(1,1)) + + im = Image(bounds=bounds, dtype=dtype, init_value=0) + + return im + else: + return None
+ +
[docs] def getDrawMethod(self, config, base, logger): + """Determine the draw method to use. + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + @param logger A logger object to log progress. + + @returns method + """ + method = ParseValue(config,'draw_method',base,str)[0] + if method not in valid_draw_methods: + raise GalSimConfigValueError("Invalid draw_method.", method, valid_draw_methods) + return method
+ +
[docs] def getOffset(self, config, base, logger): + """Determine the offset to use. + + The base class version adds the stamp_offset, which comes from calculations related to + world_pos and image_pos, to the field stamp.offset if any. + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + @param logger A logger object to log progress. + + @returns offset + """ + offset = base['stamp_offset'] + if 'offset' in config: + offset += ParseValue(config, 'offset', base, PositionD)[0] + logger.debug('obj %d: stamp_offset = %s, offset = %s',base.get('obj_num',0), + base['stamp_offset'], offset) + return offset
+ +
[docs] def updateSkip(self, prof, image, method, offset, config, base, logger): + """Before drawing the profile, see whether this object can be trivially skipped. + + The base method checks if the object is completely off the main image, so the + intersection bounds will be undefined. In this case, don't bother drawing the + postage stamp for this object. + + Parameters: + prof: The profile to draw. + image: The image onto which to draw the profile (which may be None). + method: The method to use in drawImage. + offset: The offset to apply when drawing. + config: The configuration dict for the stamp field. + base: The base configuration dict. + logger: A logger object to log progress. + + Returns: + whether to skip drawing this object. + """ + if isinstance(prof,GSObject) and base.get('current_image',None) is not None: + if image is None: + prof = base['wcs'].toImage(prof, image_pos=base['image_pos']) + prof = prof._shift(-0.5+offset.x, -0.5+offset.y) + if method in ('auto', 'fft', 'real_space'): + prof = Convolution(prof, Pixel(1.)) + N = prof.getGoodImageSize(1.) + bounds = _BoundsI(1,N,1,N) + + # Set the origin appropriately + stamp_center = base['stamp_center'] + if stamp_center: + bounds = bounds.shift(stamp_center - bounds.center) + else: + bounds = bounds.shift(base.get('image_origin',PositionI(1,1)) - PositionI(1,1)) + else: + bounds = image.bounds + + overlap = bounds & base['current_image'].bounds + if not overlap.isDefined(): + logger.info('obj %d: skip drawing object because its image will be entirely off ' + 'the main image.', base.get('obj_num',0)) + return True + + return False
+ +
[docs] def draw(self, prof, image, method, offset, config, base, logger): + """Draw the profile on the postage stamp image. + + Parameters: + prof: The profile to draw. + image: The image onto which to draw the profile (which may be None). + method: The method to use in drawImage. + offset: The offset to apply when drawing. + config: The configuration dict for the stamp field. + base: The base configuration dict. + logger: A logger object to log progress. + + Returns: + the resulting image + """ + if prof is None: + return image + else: + return DrawBasic(prof,image,method,offset,config,base,logger)
+ +
[docs] def whiten(self, prof, image, config, base, logger): + """If appropriate, whiten the resulting image according to the requested noise profile + and the amount of noise originally present in the profile. + + Parameters: + prof: The profile to draw. + image: The image onto which to draw the profile. + config: The configuration dict for the stamp field. + base: The base configuration dict. + logger: A logger object to log progress. + + Returns: + the variance of the resulting whitened (or symmetrized) image. + """ + # If the object has a noise attribute, then check if we need to do anything with it. + current_var = 0. # Default if not overwritten + if isinstance(prof,GSObject) and prof.noise is not None: + if 'image' in base and 'noise' in base['image']: + noise = base['image']['noise'] + whiten = symmetrize = False + if 'whiten' in noise: + whiten = ParseValue(noise, 'whiten', base, bool)[0] + if 'symmetrize' in noise: + symmetrize = ParseValue(noise, 'symmetrize', base, int)[0] + if whiten and symmetrize: + raise GalSimConfigError('Only one of whiten or symmetrize is allowed') + if whiten or symmetrize: + # In case the galaxy was cached, update the rng + rng = GetRNG(noise, base, logger, "whiten") + prof.noise.rng.reset(rng) + if whiten: + current_var = prof.noise.whitenImage(image) + if symmetrize: + current_var = prof.noise.symmetrizeImage(image, symmetrize) + return current_var
+ +
[docs] def getSNRScale(self, image, config, base, logger): + """Calculate the factor by which to rescale the image based on a desired S/N level. + + Note: The default implementation does this for the gal or psf field, so if a custom + stamp builder uses some other way to get the profiles, this method should + probably be overridden. + + Parameters: + image: The current image. + config: The configuration dict for the stamp field. + base: The base configuration dict. + logger: A logger object to log progress. + + Returns: + scale_factor + """ + if 'gal' in base and 'signal_to_noise' in base['gal']: + key = 'gal' + elif 'gal' not in base and 'psf' in base and 'signal_to_noise' in base['psf']: + key = 'psf' + else: + return 1. + + if 'flux' in base[key]: + raise GalSimConfigError( + 'Only one of signal_to_noise or flux may be specified for %s'%key) + + if 'image' in base and 'noise' in base['image']: + noise_var = CalculateNoiseVariance(base) + else: + raise GalSimConfigError( + "Need to specify noise level when using %s.signal_to_noise"%key) + sn_target = ParseValue(base[key], 'signal_to_noise', base, float)[0] + + # Now determine what flux we need to get our desired S/N + # There are lots of definitions of S/N, but here is the one used by Great08 + # We use a weighted integral of the flux: + # S = sum W(x,y) I(x,y) / sum W(x,y) + # N^2 = Var(S) = sum W(x,y)^2 Var(I(x,y)) / (sum W(x,y))^2 + # Now we assume that Var(I(x,y)) is dominated by the sky noise, so + # Var(I(x,y)) = var + # We also assume that we are using a matched filter for W, so W(x,y) = I(x,y). + # Then a few things cancel and we find that + # S/N = sqrt( sum I(x,y)^2 / var ) + + sn_meas = np.sqrt( np.sum(image.array**2, dtype=float) / noise_var ) + # Now we rescale the flux to get our desired S/N + scale_factor = sn_target / sn_meas + return scale_factor
+ +
[docs] def applySNRScale(self, image, prof, scale_factor, method, logger): + """Apply the scale_factor from getSNRScale to the image and profile. + + The default implementaion just multiplies each of them, but if prof is not a regular + GSObject, then you might need to do something different. + + Parameters: + image: The current image. + prof: The profile that was drawn. + scale_factor: The factor by which to scale both image and prof. + method: The method used by drawImage. + logger: A logger object to log progress. + + Returns: + image, prof (after being properly scaled) + """ + if scale_factor != 1.0: + if method == 'phot': + logger.warning( + "signal_to_noise calculation is not accurate for draw_method = phot") + image *= scale_factor + prof *= scale_factor + return image, prof
+ +
[docs] def updateOrigin(self, config, base, image): + """Update the image origin to be centered at the right place + + @param config The configuration dict for the stamp field. + @param base The base configuration dict. + @param image The postage stamp image. + """ + stamp_center = base['stamp_center'] + if stamp_center: + image.setCenter(stamp_center) + else: + image.setOrigin(base.get('image_origin',PositionI(1,1)))
+ + +
[docs] def reject(self, config, base, prof, psf, image, logger): + """Check to see if this object should be rejected. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + prof: The profile that was drawn. + psf: The psf that was used to build the profile. + image: The postage stamp image. No noise is on it yet at this point. + logger: A logger object to log progress. + + Returns: + whether to reject this object + """ + # Early exit if no profile + if prof is None: + return False + + if 'reject' in config: + if ParseValue(config, 'reject', base, bool)[0]: + logger.info('obj %d: reject evaluated to True',base.get('obj_num',0)) + return True + if 'min_flux_frac' in config: + if not isinstance(prof, GSObject): + raise GalSimConfigError( + "Cannot apply min_flux_frac for stamp types that do not use " + "a single GSObject profile.") + expected_flux = prof.flux + measured_flux = np.sum(image.array, dtype=float) + min_flux_frac = ParseValue(config, 'min_flux_frac', base, float)[0] + logger.debug('obj %d: flux_frac = %f', base.get('obj_num',0), + measured_flux / expected_flux) + if measured_flux < min_flux_frac * expected_flux: + logger.warning('Object %d: Measured flux = %f < %s * %f.', + base.get('obj_num',0), measured_flux, min_flux_frac, expected_flux) + return True + if 'min_snr' in config or 'max_snr' in config: + if not isinstance(prof, GSObject): + raise GalSimConfigError( + "Cannot apply min_snr for stamp types that do not use " + "a single GSObject profile.") + var = CalculateNoiseVariance(base) + sumsq = np.sum(image.array**2, dtype=float) + snr = np.sqrt(sumsq / var) + logger.debug('obj %d: snr = %f', base.get('obj_num',0), snr) + if 'min_snr' in config: + min_snr = ParseValue(config, 'min_snr', base, float)[0] + if snr < min_snr: + logger.warning('Object %d: Measured snr = %f < %s.', + base.get('obj_num',0), snr, min_snr) + return True + if 'max_snr' in config: + max_snr = ParseValue(config, 'max_snr', base, float)[0] + if snr > max_snr: + logger.warning('Object %d: Measured snr = %f > %s.', + base.get('obj_num',0), snr, max_snr) + return True + return False
+ +
[docs] def reset(self, base, logger): + """Reset some aspects of the config dict so the object can be rebuilt after rejecting the + current object. + + Parameters: + base: The base configuration dict. + logger: A logger object to log progress. + """ + # Clear current values out of psf, gal, and stamp if they are not safe to reuse. + # This means they are either marked as safe or indexed by something other than obj_num. + for field in ('psf', 'gal', 'stamp'): + if field in base: + RemoveCurrent(base[field], keep_safe=True, index_key='obj_num')
+ +
[docs] def addNoise(self, config, base, image, current_var, logger): + """ + Add the sky level and the noise to the stamp. + + Note: This only gets called if the image type requests that the noise be added to each + stamp individually, rather than to the full image and the end. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + image: The current image. + current_var: The current noise variance present in the image already. + logger: A logger object to log progress. + + Returns: + the new values of image, current_var + """ + base['current_noise_image'] = base['current_stamp'] + AddSky(base,image) + current_var = AddNoise(base,image,current_var,logger) + return image, current_var
+ +
[docs] def makeTasks(self, config, base, jobs, logger): + """Turn a list of jobs into a list of tasks. + + For the Basic stamp type, there is just one job per task, so the tasks list is just: + + tasks = [ [ (job, k) ] for k, job in enumerate(jobs) ] + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'obj_num'. + logger: A logger object to log progress. + + Returns: + a list of tasks + """ + return [ [(job, k)] for k, job in enumerate(jobs) ]
+ + +def RegisterStampType(stamp_type, builder): + """Register an image type for use by the config apparatus. + + Parameters: + stamp_type: The name of the type in config['stamp'] + builder: A builder object to use for building the stamp images. It should be + an instance of StampBuilder or a subclass thereof. + """ + valid_stamp_types[stamp_type] = builder + +RegisterStampType('Basic', StampBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/stamp_ring.html b/docs/_build/html/_modules/galsim/config/stamp_ring.html new file mode 100644 index 00000000000..901241be5eb --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/stamp_ring.html @@ -0,0 +1,302 @@ + + + + + + galsim.config.stamp_ring — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.stamp_ring

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import math
+
+from .stamp import StampBuilder, RegisterStampType
+from .value import ParseValue, GetAllParams, SetDefaultIndex
+from .gsobject import BuildGSObject, TransformObject
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..convolve import Convolve
+from ..angle import Angle, radians
+
+# This file adds stamp type Ring which builds an object once every n times, and then
+# rotates it in a ring for the other n-1 times per per group.
+
+
[docs]class RingBuilder(StampBuilder): + """This performs the tasks necessary for building a Ring stamp type. + + It uses the regular Basic functions for most things. + It specializes the setup, buildProfile, reject, and makeTasks functions. + """ + + def setup(self, config, base, xsize, ysize, ignore, logger): + """Do the initialization and setup for the Ring type. + + Most of the work is done by SetupBasic, but we do need to check that the required parameters + are present, and also that no additional parameters are present. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + xsize: The xsize of the image to build (if known). + ysize: The ysize of the image to build (if known). + ignore: A list of parameters that are allowed to be in config that we can + ignore here. i.e. it won't be an error if these parameters are present. + logger: If given, a logger object to log progress. + + Returns: + xsize, ysize, image_pos, world_pos + """ + req = { 'num' : int } + opt = { 'full_rotation' : Angle , 'index' : int } + # Ignore the transformation specifications that are allowed in stamp for Ring types. + ignore = ignore + [ + 'dilate', 'dilation', 'ellip', 'rotate', 'rotation', 'scale_flux', + 'magnify', 'magnification', 'shear', 'shift' ] + + params = GetAllParams(config, base, req=req, opt=opt, ignore=ignore)[0] + + num = params['num'] + if num <= 0: + raise GalSimConfigValueError("Attribute num for gal.type == Ring must be > 0", num) + + # Setup the indexing sequence if it hasn't been specified using the number of items. + SetDefaultIndex(config, num) + + # Set the default full_rotation to pi radians + if 'full_rotation' not in params: + config['full_rotation'] = math.pi * radians + + # Now go on and do the base class setup. + ignore = ignore + list(req) + list(opt) + return super(RingBuilder, self).setup(config, base, xsize, ysize, ignore, logger) + + def buildProfile(self, config, base, psf, gsparams, logger): + """ + Build the object to be drawn. + + For the first item in the ring, this is the same as Basic. It stores the galaxy object + created on the first time. Then for later stamps in the ring, it retrieves the stored + first galaxy and just rotates it before convolving by the psf. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + psf: The PSF, if any. This may be None, in which case, no PSF is convolved. + gsparams: A dict of kwargs to use for a GSParams. More may be added to this + list by the galaxy object. + logger: If given, a logger object to log progress. + + Returns: + the final profile + """ + # These have all already been checked to exist in SetupRing. + num = ParseValue(config, 'num', base, int)[0] + index = ParseValue(config, 'index', base, int)[0] + if index < 0 or index >= num: + raise GalSimConfigError("index %d out of bounds for Ring"%index) + + if index % num == 0: + # Then we are on the first item in the ring, so make it normally. + gal = BuildGSObject(base, 'gal', gsparams=gsparams, logger=logger)[0] + if gal is None: + raise GalSimConfigError( + "The gal field must define a valid galaxy for stamp type=Ring.") + # Save the galaxy profile for next time. + self.first = gal + else: + # Grab the saved first galaxy. + if not hasattr(self, 'first'): + raise GalSimConfigError( + "Building Ring after the first item, but no first gal stored.") + gal = self.first + full_rot = ParseValue(config, 'full_rotation', base, Angle)[0] + dtheta = full_rot / num + gal = gal.rotate(index*dtheta) + + # Apply any transformations that are given in the stamp field. + gal = TransformObject(gal, config, base, logger)[0] + + if psf is not None: + return Convolve(gal,psf) + else: + return gal + + def reject(self, config, base, prof, psf, image, logger): + """Check to see if this object should be rejected. + + This is the same as base class reject for the first item in the ring. Later items are not + checked though, since rejecting them would mess up the ring. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + prof: The profile that was drawn. + psf: The psf that was used to build the profile. + image: The postage stamp image. No noise is on it yet at this point. + logger: If given, a logger object to log progress. + + Returns: + whether the galaxy was rejected. + """ + index = ParseValue(config, 'index', base, int)[0] + if index == 0: + return super(RingBuilder,self).reject(config, base, prof, psf, image, logger) + else: + return False + + def makeTasks(self, config, base, jobs, logger): + """Turn a list of jobs into a list of tasks. + + For the Ring stamp type, we group jobs into sets of num. If there are extra jobs that + don't fit into a full ring, the last task will be a partial ring. + + Parameters: + config: The configuration dict for the stamp field. + base: The base configuration dict. + jobs: A list of jobs to split up into tasks. Each job in the list is a + dict of parameters that includes 'obj_num'. + logger: If given, a logger object to log progress. + + Returns: + a list of tasks + """ + if 'num' not in config: + raise GalSimConfigError("Attribute num is required for type = Ring") + num = ParseValue(config, 'num', base, int)[0] + ntot = len(jobs) + tasks = [ [ (jobs[j], j) for j in range(k,min(k+num,ntot)) ] for k in range(0, ntot, num) ] + return tasks
+ + +# Register this as a valid stamp type +RegisterStampType('Ring', RingBuilder()) + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/util.html b/docs/_build/html/_modules/galsim/config/util.html new file mode 100644 index 00000000000..59b64c7c475 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/util.html @@ -0,0 +1,1211 @@ + + + + + + galsim.config.util — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.util

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+import copy
+import sys
+import time
+import json
+from io import StringIO
+import cProfile, pstats
+from collections import OrderedDict
+from multiprocessing.managers import BaseManager
+from multiprocessing import current_process, get_context, cpu_count
+from queue import Empty
+import traceback
+
+from ..utilities import SimpleGenerator, single_threaded
+from ..random import BaseDeviate
+from ..errors import GalSimConfigError, GalSimConfigValueError, GalSimError, galsim_warn
+
+max_queue_size = 32767  # This is where multiprocessing.Queue starts to have trouble.
+                        # We make it a settable parameter here really for unit testing.
+                        # I don't think there is any reason for end users to want to set this.
+
+
[docs]def MergeConfig(config1, config2, logger=None): + """ + Merge config2 into config1 such that it has all the information from either config1 or + config2 including places where both input dicts have some of a field defined. + + e.g. If config1 has image.pixel_scale, and config2 has image.noise, then the returned dict + will have both. + + For real conflicts (the same value in both cases), config1's value takes precedence + """ + logger = LoggerWrapper(logger) + for (key, value) in config2.items(): + if not key in config1: + # If this key isn't in config1 yet, just add it + config1[key] = copy.deepcopy(value) + elif isinstance(value,dict) and isinstance(config1[key],dict): + # If they both have a key, first check if the values are dicts + # If they are, just recurse this process and merge those dicts. + MergeConfig(config1[key],value,logger) + else: + # Otherwise config1 takes precedence + logger.info("Not merging key %s from the base config, since the later " + "one takes precedence",key) + pass
+ +
[docs]def ReadYaml(config_file): + """Read in a YAML configuration file and return the corresponding dicts. + + A YAML file is allowed to define several dicts using multiple documents. The GalSim parser + treats this as a set of multiple jobs to be done. The first document is taken to be a "base" + dict that has common definitions for all the jobs. Then each subsequent document has the + (usually small) modifications to the base dict for each job. See demo6.yaml, demo8.yaml and + demo9.yaml in the GalSim/examples directory for example usage. + + The return value will be a list of dicts, one dict for each job to be done. + + Parameters: + config_file: The name of the configuration file to read. + + Returns: + list of config dicts + """ + import yaml + + with open(config_file) as f: + all_config = [ c for c in yaml.load_all(f.read(), yaml.SafeLoader) ] + + # If there is only 1 yaml document, then it is of course used for the configuration. + # If there are multiple yaml documents, then the first one defines a common starting + # point for the later documents. + # So the configurations are taken to be: + # all_config[0] + all_config[1] + # all_config[0] + all_config[2] + # all_config[0] + all_config[3] + # ... + # See demo6.yaml and demo8.yaml in the examples directory for examples of this feature. + + if len(all_config) > 1: + # Break off the first one if more than one: + base_config = all_config[0] + all_config = all_config[1:] + # And merge it into each of the other dicts + for c in all_config: + MergeConfig(c, base_config) + + return all_config
+ + +
[docs]def ReadJson(config_file): + """Read in a JSON configuration file and return the corresponding dicts. + + A JSON file only defines a single dict. However to be parallel to the functionality of + ReadYaml, the output is a list with a single item, which is the dict defined by the JSON file. + + Parameters: + config_file: The name of the configuration file to read. + + Returns: + [config_dict] + """ + + with open(config_file) as f: + # cf. http://stackoverflow.com/questions/6921699/can-i-get-json-to-load-into-an-ordereddict-in-python + config = json.load(f, object_pairs_hook=OrderedDict) + + # JSON files only ever define a single job, but we need to return a list with this one item. + return [config]
+ + +
[docs]def ConvertNones(config): + """Convert any items whose value is 'None' to None. + + To allow some parameters to be set to None in the config dict (e.g. in a list, where only + some values need to be None), we convert all values == 'None' to None. + + Parameters: + config: The config dict to process + """ + if isinstance(config, dict): + keys = config.keys() + else: + keys = range(len(config)) + + for key in keys: + # Recurse to lower levels, if any + if isinstance(config[key],(list,dict)): + ConvertNones(config[key]) + + # Convert any Nones at this level + elif config[key] == 'None': + config[key] = None
+ + +
[docs]def RemoveCurrent(config, keep_safe=False, type=None, index_key=None): + """ + Remove any "current" values stored in the config dict at any level. + + Parameters: + config: The configuration dict. + keep_safe: Should current values that are marked as safe be preserved? + [default: False] + type: If provided, only clear the current value of objects that use this + particular type. [default: None, which means to remove current values + of all types.] + index_key: If provided, only clear the current value of objects that use this + index_key (or start with this index_key, so obj_num also does + obj_num_in_file). [default: None] + """ + # End recursion if this is not a dict. + if not isinstance(config,dict): return + + # Recurse to lower levels, if any + force = False # If lower levels removed anything, then force removal at this level as well. + for key in config: + if isinstance(key, str) and key[0] == '_': + continue # These are our own implementation details, not the normal dict. + if isinstance(config[key],list): + for item in config[key]: + force = RemoveCurrent(item, keep_safe, type, index_key) or force + else: + force = RemoveCurrent(config[key], keep_safe, type, index_key) or force + if force: + keep_safe = False + type = None + index_key = None + + # Delete the current_val at this level, if any + if 'current' in config: + cval, csafe, ctype, cindex, cindex_key = config['current'] + if (not (keep_safe and csafe) + and (type is None or ('type' in config and config['type'] == type)) + and (index_key is None or cindex_key.startswith(index_key))): + del config['current'] + return True + return force
+ +
[docs]def CopyConfig(config): + """ + If you want to use a config dict for multiprocessing, you need to deep copy + the gal, psf, and pix fields, since they cache values that are not picklable. + If you don't do the deep copy, then python balks when trying to send the updated + config dict back to the root process. We do this a few different times, so encapsulate + the copy semantics once here. + + Parameters: + config: The configuration dict to copy. + + Returns: + a deep copy of the config dict. + """ + from .process import top_level_fields, rng_fields + + # Start with a shallow copy of the regular things that aren't one of our special fields. + # Make sure not to copy the input_manager if there is one, since using it isn't thread safe. + galsim_fields = top_level_fields + rng_fields + config1 = { k:v for k,v in config.items() if k not in galsim_fields and k != '_input_manager' } + + def recursive_copy(d): + # copy the given dict, skipping any current leading underscore fields. + if isinstance(d, dict): + # Use type(d) rather than dict comprehension, since in python 2.7, 3.5, this may + # be an OrderedDict, and we want to preserve that. + return type(d)([(k,recursive_copy(v)) for k,v in d.items() if not k.startswith('_')]) + elif isinstance(d, list): + return [recursive_copy(v) for v in d] + else: + return d + + # Now copy all the regular config fields to make sure things like current don't + # get clobbered by two processes writing to the same dict. Keep any current items + # that are already here, since they should be safe, but remove leading underscore items. + for field in top_level_fields: + if field in config: + config1[field] = recursive_copy(config[field]) + + # And deep copy any rng fields: + for field in rng_fields: + if field in config: + config1[field] = copy.deepcopy(config[field]) + + return config1
+ +class SafeManager(BaseManager): + """There are a few places we need a Manager object. This one uses the 'fork' context, + rather than whatever the default is on your system (which may be fork or may be spawn). + + Starting in python 3.8, the spawn context started becoming more popular. It's supposed to + be safer, but it wants to pickle a lot of things that aren't picklable, so it doesn't work. + Using this as the base class instead, should work. And doing it it one place means that we + only have one place to change this is there is a different strategy that works better. + """ + def __init__(self): + super(SafeManager, self).__init__(ctx=get_context('fork')) + + +
[docs]def GetLoggerProxy(logger): + """Make a proxy for the given logger that can be passed into multiprocessing Processes + and used safely. + + Parameters: + logger: The logger to make a copy of + + Returns: + a proxy for the given logger + """ + if logger: + class LoggerManager(SafeManager): pass + logger_generator = SimpleGenerator(logger) + LoggerManager.register('logger', callable = logger_generator) + logger_manager = LoggerManager() + with single_threaded(): + logger_manager.start() + logger_proxy = logger_manager.logger() + else: + logger_proxy = None + return logger_proxy
+ +
[docs]class LoggerWrapper: + """A wrap around a Logger object that checks whether a debug or info or warn call will + actually produce any output before calling the functions. + + This seems like a gratuitous wrapper, and it is if the object being wrapped is a real + Logger object. However, we use it to wrap proxy objects (returned from GetLoggerProxy) + that would otherwise send the arguments of logger.debug(...) calls through a multiprocessing + pipe before (typically) being ignored. Here, we check whether the call will actually + produce any output before calling the functions. + + Parameters: + logger: The logger object to wrap. + """ + def __init__(self, logger): + if isinstance(logger,LoggerWrapper): + self.logger = logger.logger + self.level = logger.level + else: + self.logger = logger + # When multiprocessing, it is faster to check if the level is enabled locally, rather + # than communicating over the pipe to ask the base logger if it isEnabledFor a given + # level. + # If the logger is None, use more than the top logging level (CRITICAL). + self.level = self.logger.getEffectiveLevel() if self.logger else logging.CRITICAL+1 + + def getEffectiveLevel(self): + return self.level + + def __bool__(self): + return self.logger is not None + __nonzero__ = __bool__ + + def debug(self, *args, **kwargs): + if self.logger and self.isEnabledFor(logging.DEBUG): + self.logger.debug(*args, **kwargs) + + def info(self, *args, **kwargs): + if self.logger and self.isEnabledFor(logging.INFO): + self.logger.info(*args, **kwargs) + + def warning(self, *args, **kwargs): + if self.logger and self.isEnabledFor(logging.WARNING): + self.logger.warning(*args, **kwargs) + + def error(self, *args, **kwargs): + if self.logger and self.isEnabledFor(logging.ERROR): + self.logger.error(*args, **kwargs) + + def critical(self, *args, **kwargs): + if self.logger and self.isEnabledFor(logging.CRITICAL): + self.logger.critical(*args, **kwargs) + + def log(self, level, *args, **kwargs): + if self.logger and self.isEnabledFor(level): + self.logger.log(level, *args, **kwargs) + + def isEnabledFor(self, level): + return level >= self.level
+ + +
[docs]def UpdateNProc(nproc, ntot, config, logger=None): + """Update nproc + + - If nproc < 0, set nproc to ncpu + - Make sure nproc <= ntot + + Parameters: + nproc: The nominal number of processes from the config dict + ntot: The total number of files/images/stamps to do, so the maximum number of + processes that would make sense. + config: The configuration dict to copy. + logger: If given, a logger object to log progress. [default: None] + + Returns: + the number of processes to use. + """ + logger = LoggerWrapper(logger) + # First if nproc < 0, update based on ncpu + if nproc <= 0: + # Try to figure out a good number of processes to use + try: + nproc = cpu_count() + logger.debug("ncpu = %d.",nproc) + except Exception as e: + logger.warning("nproc <= 0, but unable to determine number of cpus.") + logger.warning("Caught error: %s",e) + logger.warning("Using single process") + nproc = 1 + + # Second, make sure we aren't already in a multiprocessing mode + if nproc > 1 and 'current_nproc' in config: + logger.debug("Already multiprocessing. Ignoring image.nproc") + nproc = 1 + + # Third, pypy multiprocessing in Python 3.6 seems to be flaky with multiprocessing, + # so don't use multiple processes on that sysem. + if nproc != 1 and 'PyPy' in sys.version and '3.6' in sys.version: # pragma: no cover + logger.warning("PyPy 3.6 multiprocessing is unreliable. Ignoring nproc.") + nproc = 1 + + # Finally, don't try to use more processes than jobs. It wouldn't fail or anything. + # It just looks bad to have 3 images processed with 8 processes or something like that. + if nproc > ntot: + logger.debug("There are only %d jobs to do. Reducing nproc to %d."%(ntot,ntot)) + nproc = ntot + return nproc
+ + +
[docs]def ParseRandomSeed(config, param_name, base, seed_offset): + """Parse the random_seed field, converting an integer (initial) seed value into a + Sequence. + """ + from .value import ParseValue + + # Normally, random_seed parameter is just a number, which really means to use that seed + # start a sequential sequence of seeds for each object. + # However, we allow for random_seed to be a gettable parameter, so for the + # normal case, we just convert it into a Sequence. + # We only do this for the first item in the list though. Any items after the first + # are left as they are, so the user can do something more complicated if they want. + if param_name in ['random_seed', 0] and not ( + isinstance(config[param_name], dict) and config[param_name].get('default', False)): + if (isinstance(config[param_name], dict) + and config[param_name].get('type',None) == 'Sequence'): + # Not really a deprecation, but similar. + # Warn users of the change in behavior. + galsim_warn('You have a custom Sequence as the (first) random_seed. ' + 'As of GalSim version 2.6, this will be parsed as an integer ' + 'and converted to a new sequence indexed by obj_num, ' + 'rather than whatever sequence you have specified. ' + 'You probably want to put your custom random_seed sequence ' + 'as the second item in the random_seed list and use rng_num=1.') + + first = ParseValue(config, param_name, base, int)[0] + seed_rng = BaseDeviate(first) + new_first = seed_rng.raw() + # Note: This new "first" seed is actually the seed value to use for anything at file or + # image scope using the obj_num of the first object in the file or image. + # Seeds for objects will start at 1 more than this. + config[param_name] = { + 'type' : 'Sequence', + 'index_key' : 'obj_num', + 'first' : new_first, + # This is out indicator that the dict has been converted from an initial + # integer value to generate the sequence. After the first time, we don't + # want to redo this step. + 'default' : True + } + + index_key = base['index_key'] + if index_key == 'obj_num': + # The normal case + seed = ParseValue(config, param_name, base, int)[0] + else: + # If we are setting either the file_num or image_num rng, we need to be careful. + base['index_key'] = 'obj_num' + seed = ParseValue(config, param_name, base, int)[0] + base['index_key'] = index_key + seed += seed_offset + + # Normally, the file_num rng and the image_num rng can be the same. So if seed + # comes out the same as what we already built, then just use the existing file_num_rng. + if index_key == 'image_num' and seed == base.get('file_num_seed',None): + rng = base['file_num_rng'] + else: + rng = BaseDeviate(seed) + + return seed, rng
+ +
[docs]def PropagateIndexKeyRNGNum(config, index_key=None, rng_num=None, rng_index_key=None, key=None): + """Propagate any index_key or rng_num specification in a dict to all sub-fields + """ + if isinstance(config, list): + for item in config: + PropagateIndexKeyRNGNum(item, index_key, rng_num) + return + + if not isinstance(config, dict): return + + if 'index_key' in config: + index_key = config['index_key'] + elif index_key is not None: + config['index_key'] = index_key + + if 'rng_num' in config: + rng_num = config['rng_num'] + elif rng_num is not None: + config['rng_num'] = rng_num + + if 'rng_index_key' in config: + rng_index_key = config['rng_index_key'] + elif rng_index_key is not None: + config['rng_index_key'] = rng_index_key + + if key is None: + for key, field in config.items(): + if isinstance(key, str) and key[0] == '_': continue + PropagateIndexKeyRNGNum(field, index_key, rng_num, rng_index_key) + else: + PropagateIndexKeyRNGNum(config[key], index_key, rng_num, rng_index_key)
+ + +
[docs]def SetupConfigRNG(config, seed_offset=0, logger=None): + """Set up the RNG in the config dict. + + - Setup config['image']['random_seed'] if necessary + - Set config['rng'] and other related values based on appropriate random_seed + + Parameters: + config: The configuration dict. + seed_offset: An offset to use relative to what config['image']['random_seed'] gives. + [default: 0] + logger: If given, a logger object to log progress. [default: None] + + Returns: + the seed used to initialize the RNG. + """ + from .process import rng_fields + + logger = LoggerWrapper(logger) + + # If we are starting a new file, clear out the existing rngs. + index_key = config['index_key'] + if index_key == 'file_num': + for key in rng_fields + ['seed', 'obj_num_seed', 'image_num_seed', 'file_num_seed']: + config.pop(key, None) + + # This can be present for efficiency, since GaussianDeviates produce two values at a time, + # so it is more efficient to not create a new GaussianDeviate object each time. + # But if so, we need to remove it now. + config.pop('gd', None) + + # All fields have a default index_key that they would normally use as well as a default + # rng_num = 0 if the random_seed is a list. But we allow users to specify alternate values + # of index_key and/or rng_num at any level in the dict. We want that specification to + # propagate down to lower levels from that point forward. The easiest way to do so is to + # explicitly run through the dict once and propagate any index_key and/or rng_num fields + # to all sub-fields. + if not config.get('_propagated_index_key_rng_num',False): + logger.debug('Propagating any index_key or rng_num specifications') + for field in config.values(): + PropagateIndexKeyRNGNum(field) + config['_propagated_index_key_rng_num'] = True + + if 'image' not in config or 'random_seed' not in config['image']: + logger.debug('obj %d: No random_seed specified. Using /dev/urandom', + config.get('obj_num',0)) + config['seed'] = 0 + rng = BaseDeviate() + config['rng'] = rng + config[index_key + '_rng'] = rng + return 0 + + image = config['image'] + # Normally, there is just one random_seed and it is just an integer. However, sometimes + # it is useful to have 2 (or more) rngs going with different seed sequences. To enable this, + # image.random_seed is allowed to be a list, each item of which may be either an integer or + # a dict defining an integer sequence. If it is a list, we parse each item separately + # and then put the combined results into config['rng'] as a list. + if isinstance(image['random_seed'], list): + lst = image['random_seed'] + logger.debug('random_seed = %s',CleanConfig(lst)) + logger.debug('seed_offset = %s',seed_offset) + seeds = [] + rngs = [] + for i in range(len(lst)): + seed, rng = ParseRandomSeed(lst, i, config, seed_offset) + logger.debug('seed %d = %s',i,seed) + seeds.append(seed) + rngs.append(rng) + if i == 0: + # Helpful to get this done right away, because later seeds might be based + # on a random number that uses the first rng. + # cf. test_eval_full_word in test_config_output.py. + config['seed'] = seed + config['rng'] = rng + config[index_key + '_seed'] = seed + config[index_key + '_rng'] = rng + config[index_key + '_rngs'] = rngs + logger.debug('obj %d: random_seed is a list. Initializing rngs with seeds %s', + config.get('obj_num',0), seeds) + return seeds[0] + else: + seed, rng = ParseRandomSeed(image, 'random_seed', config, seed_offset) + config['seed'] = seed + config['rng'] = rng + config[index_key + '_seed'] = seed + config[index_key + '_rng'] = rng + logger.debug('obj %d: Initializing rng with seed %s', config.get('obj_num',0), seed) + return seed
+ + +
[docs]def ParseExtendedKey(config, key): + """Traverse all but the last item in an extended key and return the resulting config, key. + + If key is an extended key like gal.items.0.ellip.e, then this will return the tuple. + (config['gal']['items'][0]['ellip'], 'e'). + + If key is a regular string, then is just returns the original (config, key). + + Parameters: + config: The configuration dict. + key: The possibly extended key. + + Returns: + the equivalent (config, key) where key is now a regular non-extended key. + """ + # This is basically identical to the code for Dict.get(key) in catalog.py. + chain = key.split('.') + while True: + d = config + k = chain.pop(0) + try: k = int(k) + except ValueError: pass + if len(chain) == 0: break + try: + config = d[k] + except (TypeError, KeyError): + # TypeError for the case where d is a float or Position2D, so d[k] is invalid. + # KeyError for the case where d is a dict, but k is not a valid key. + raise GalSimConfigError( + "Unable to parse extended key %s. Field %s is invalid."%(key,k)) from None + return d, k
+ +
[docs]def GetFromConfig(config, key): + """Get the value for the (possibly extended) key from a config dict. + + If key is a simple string, then this is equivalent to config[key]. + However, key is allowed to be a chain of keys such as 'gal.items.0.ellip.e', in which + case this function will return config['gal']['items'][0]['ellip']['e']. + + Parameters: + config: The configuration dict. + key: The possibly extended key. + + Returns: + the value of that key from the config. + """ + d, k = ParseExtendedKey(config, key) + try: + value = d[k] + except Exception as e: + raise GalSimConfigError( + "Unable to parse extended key %s. Field %s is invalid."%(key,k)) from None + return value
+ +
[docs]def SetInConfig(config, key, value, logger=None): + """Set the value of a (possibly extended) key in a config dict. + + If key is a simple string, then this is equivalent to config[key] = value. + However, key is allowed to be a chain of keys such as 'gal.items.0.ellip.e', in which + case this function will set config['gal']['items'][0]['ellip']['e'] = value. + + Parameters: + config: The configuration dict. + key: The possibly extended key. + + Returns: + the value of that key from the config. + """ + d, k = ParseExtendedKey(config, key) + if value == '': + # This means remove it, if it is there. + try: + d.pop(k,None) + except TypeError: + # Then d was really a list. + # This has some potentially counter-intuitive consequences, so let the user + # know about them. + if logger: + logger.warning("Warning: Removing item %d from %s. "%(k,key[:key.rfind('.')]) + + "Any further adjustments to this list must use the new "+ + "list index values, not the original indices.") + del d[k] + else: + try: + d[k] = value + except Exception as e: + raise GalSimConfigError( + "Unable to parse extended key %s. Field %s is invalid."%(key,k)) from None
+ + +
[docs]def UpdateConfig(config, new_params, logger=None): + """Update the given config dict with additional parameters/values. + + Parameters: + config: The configuration dict to update. + new_params: A dict of parameters to update. The keys of this dict may be + chained field names, such as gal.first.dilate, which will be + parsed to update config['gal']['first']['dilate']. + """ + for key, value in new_params.items(): + SetInConfig(config, key, value, logger)
+ + +
[docs]def MultiProcess(nproc, config, job_func, tasks, item, logger=None, timeout=900, + done_func=None, except_func=None, except_abort=True): + """A helper function for performing a task using multiprocessing. + + A note about the nomenclature here. We use the term "job" to mean the job of building a single + file or image or stamp. The output of each job is gathered into the list of results that + is returned. A task is a collection of one or more jobs that are all done by the same + processor. For simple cases, each task is just a single job, but for things like a Ring + test, the task needs to have the jobs for a full ring. + + The tasks argument is a list of tasks. + Each task in that list is a list of jobs. + Each job is a tuple consisting of (kwargs, k), where kwargs is the dict of kwargs to pass to + the job_func and k is the index of this job in the full list of jobs. + + Parameters: + nproc: How many processes to use. + config: The configuration dict. + job_func: The function to run for each job. It will be called as:: + + result = job_func(**kwargs) + + where kwargs is from one of the jobs in the task list. + tasks: A list of tasks to run. Each task is a list of jobs, each of which is + a tuple (kwargs, k). + item: A string indicating what is being worked on. + logger: If given, a logger object to log progress. [default: None] + timeout: How many seconds to allow for each task before timing out. [default: 900] + done_func: A function to run upon completion of each job. It will be called as:: + + done_func(logger, proc, k, result, t) + + where proc is the process name, k is the index of the job, result is + the return value of that job, and t is the time taken. [default: None] + except_func: A function to run if an exception is encountered. It will be called as:: + + except_func(logger, proc, k, ex, tr) + + where proc is the process name, k is the index of the job that failed, + ex is the exception caught, and tr is the traceback. [default: None] + except_abort: Whether an exception should abort the rest of the processing. + If False, then the returned results list will not include anything + for the jobs that failed. [default: True] + + Returns: + a list of the outputs from job_func for each job + """ + from .input import worker_init_fns, worker_initargs_fns + + # The worker function will be run once in each process. + # It pulls tasks off the task_queue, runs them, and puts the results onto the results_queue + # to send them back to the main process. + # The *tasks* can be made up of more than one *job*. Each job involves calling job_func + # with the kwargs from the list of jobs. + # Each job also carries with it its index in the original list of all jobs. + def worker(task_queue, results_queue, config, logger, initializers, initargs): + proc = current_process().name + + for init, args in zip(initializers, initargs): + init(*args) + + # The logger object passed in here is a proxy object. This means that all the arguments + # to any logging commands are passed through the pipe to the real Logger object on the + # other end of the pipe. This tends to produce a lot of unnecessary communication, since + # most of those commands don't actually produce any output (e.g. logger.debug(..) commands + # when the logging level is not DEBUG). So it is helpful to wrap this object in a + # LoggerWrapper that checks whether it is worth sending the arguments back to the original + # Logger before calling the functions. + logger = LoggerWrapper(logger) + + if 'profile' in config and config['profile']: + pr = cProfile.Profile() + pr.enable() + else: + pr = None + + for task in iter(task_queue.get, 'STOP'): + try : + logger.debug('%s: Received job to do %d %ss, starting with %s', + proc,len(task),item,task[0][1]) + for kwargs, k in task: + t1 = time.time() + kwargs['config'] = config + kwargs['logger'] = logger + result = job_func(**kwargs) + t2 = time.time() + results_queue.put( (result, k, t2-t1, proc) ) + except Exception as e: + tr = traceback.format_exc() + logger.debug('%s: Caught exception: %s\n%s',proc,str(e),tr) + results_queue.put( (e, k, tr, proc) ) + logger.debug('%s: Received STOP', proc) + if pr is not None: + pr.disable() + pr.dump_stats(config.get('root', 'galsim') + '-' + str(proc) + '.pstats') + s = StringIO() + sortby = 'time' # Note: This is now called tottime, but time seems to be a valid + # alias for this that is backwards compatible to older versions + # of pstats. + ps = pstats.Stats(pr, stream=s).sort_stats(sortby).reverse_order() + ps.print_stats() + logger.error("*** Start profile for %s ***\n%s\n*** End profile for %s ***", + proc,s.getvalue(),proc) + + njobs = sum([len(task) for task in tasks]) + + if nproc > 1: + logger.warning("Using %d processes for %s processing",nproc,item) + + ctx = get_context('fork') + Process = ctx.Process + Queue = ctx.Queue + + # Temporarily mark that we are multiprocessing, so we know not to start another + # round of multiprocessing later. + config['current_nproc'] = nproc + + if 'profile' in config and config['profile']: + logger.info("Starting separate profiling for each of the %d processes.",nproc) + + # The logger is not picklable, so we need to make a proxy for it so all the + # processes can emit logging information safely. + logger_proxy = GetLoggerProxy(logger) + + # We need to set the number of OpenMP threads to 1 during multiprocessing, otherwise + # it may spawn e.g. 64 threads in each of 64 processes, which tends to be bad. + with single_threaded(): + + # Send the tasks to the task_queue. + ntasks = len(tasks) + if ntasks > max_queue_size: + logger.info("len(tasks) = %d is more than max_queue_size = %d", + len(tasks),max_queue_size) + njoin = (ntasks-1) // max_queue_size + 1 + new_size = (ntasks-1) // njoin + 1 + tasks = [ + [job for task in tasks[njoin*k : min(njoin*(k+1),ntasks)] for job in task] + for k in range(new_size) + ] + ntasks = len(tasks) + logger.info("Joined in groups of %d: new len(tasks) = %d",njoin,ntasks) + task_queue = Queue(ntasks) + for task in tasks: + task_queue.put(task) + + # Run the tasks. + # Each Process command starts up a parallel process that will keep checking the queue + # for a new task. If there is one there, it grabs it and does it. If not, it waits + # until there is one to grab. When it finds a 'STOP', it shuts down. + results_queue = Queue(ntasks) + p_list = [] + for j in range(nproc): + # The process name is actually the default name that Process would generate on its + # own for the first time we do this. But after that, if we start another round of + # multiprocessing, then it just keeps incrementing the numbers, rather than starting + # over at Process-1. As far as I can tell, it's not actually spawning more + # processes, so for the sake of the logging output, we name the processes explicitly. + initializers = worker_init_fns + initargs = [initargs_fn() for initargs_fn in worker_initargs_fns] + p = Process(target=worker, args=(task_queue, results_queue, config, logger_proxy, + initializers, initargs), + name='Process-%d'%(j+1)) + p.start() + p_list.append(p) + + raise_error = None + + try: + # In the meanwhile, the main process keeps going. We pull each set of images off + # of the results_queue and put them in the appropriate place in the lists. + # This loop is happening while the other processes are still working on their tasks. + results = [ None for k in range(njobs) ] + for kk in range(njobs): + try: + res, k, t, proc = results_queue.get(timeout=timeout) + except Empty: + # If timeout, then a queue.Empty is raised, which is pretty uninformative. + # So reraise with a better description of what happened. + logger.error("Multiprocessing timed out waiting for a task to finish.") + logger.error("If you expect very long jobs, consider raising the value" + "of timeout. Currently set to %s",timeout) + raise GalSimError("Multiprocessing timed out") from None + if isinstance(res, Exception): + # res is really the exception, e + # t is really the traceback + # k is the index for the job that failed + if except_func is not None: # pragma: no branch + except_func(logger, proc, k, res, t) + if except_abort: + for j in range(nproc): + p_list[j].terminate() + raise_error = res + break + else: + # The normal case + if done_func is not None: # pragma: no branch + done_func(logger, proc, k, res, t) + results[k] = res + + except BaseException as e: + # I'm actually not sure how to make this happen. We do a good job of catching + # all the normal kinds of exceptions that might occur and dealing with them cleanly. + # However, if something happens that we didn't anticipate, this should make an + # attempt to clean up the task queue and worker before raising the error further. + logger.error("Caught a fatal exception during multiprocessing:\n%r",e) + logger.error("%s",traceback.format_exc()) + # Clear any unclaimed jobs that are still in the queue + while not task_queue.empty(): + try: + # If something else gets between the empty() check and this get, + # it will raise an Empty exception. Ignore it. + task_queue.get_nowait() + except Exception: # pragma: no cover + pass + # And terminate any jobs that might still be running. + for j in range(nproc): + p_list[j].terminate() + raise_error = e + + finally: + # Stop the processes + # Once you are done with the processes, putting nproc 'STOP's will stop them all. + # This is important, because the program will keep running as long as there are + # running processes, even if the main process gets to the end. So you do want to + # make sure to add those 'STOP's at some point! + for j in range(nproc): + try: + # Sometimes this one will hang if we timed out above. Not sure why, but + # the nowait seems to fix it. And if there was a reason for put() to + # hang, then put_nowait might raise an exception. Ignore it. + task_queue.put_nowait('STOP') + except Exception: # pragma: no cover + pass + for j in range(nproc): + p_list[j].join() + task_queue.close() + + del config['current_nproc'] + + if raise_error is not None: + raise raise_error + + else : # nproc == 1 + results = [ None ] * njobs + for task in tasks: + for kwargs, k in task: + try: + t1 = time.time() + kwargs['config'] = config + kwargs['logger'] = logger + result = job_func(**kwargs) + t2 = time.time() + if done_func is not None: # pragma: no branch + done_func(logger, None, k, result, t2-t1) + results[k] = result + except Exception as e: + tr = traceback.format_exc() + if except_func is not None: # pragma: no branch + except_func(logger, None, k, e, tr) + if except_abort: + raise + + # If there are any failures, then there will still be some Nones in the results list. + # Remove them. + results = [ r for r in results if r is not None ] + + return results
+ + +valid_index_keys = [ 'obj_num_in_file', 'obj_num', 'image_num', 'file_num' ] + +
[docs]def GetIndex(config, base, is_sequence=False): + """Return the index to use for the current object or parameter and the index_key. + + First check for an explicit index_key value given by the user. + Then if base[index_key] is other than obj_num, use that. + Finally, if this is a sequence, default to 'obj_num_in_file', otherwise 'obj_num'. + + Returns: + index, index_key + """ + if 'index_key' in config: + index_key = config['index_key'] + if index_key not in valid_index_keys: + raise GalSimConfigValueError("Invalid index_key.", index_key, valid_index_keys) + else: + index_key = base.get('index_key','obj_num') + if index_key == 'obj_num' and is_sequence: + index_key = 'obj_num_in_file' + + if index_key == 'obj_num_in_file': + index = base.get('obj_num',0) - base.get('start_obj_num',0) + index_key = 'obj_num' + else: + index = base.get(index_key,0) + + return index, index_key
+ + +
[docs]def GetRNG(config, base, logger=None, tag=''): + """Get the appropriate current rng according to whatever the current index_key is. + + If a logger is provided, then it will emit a warning if there is no current rng setup. + + Parameters: + config: The configuration dict for the current item being worked on. + base: The base configuration dict. + logger: If given, a logger object to log progress. [default: None] + tag: If given, an appropriate name for the current item to use in the + warning message. [default: ''] + + Returns: + either the appropriate rng for the current index_key or None + """ + logger = LoggerWrapper(logger) + if 'rng_index_key' in config: + index_key = config['rng_index_key'] + if index_key not in valid_index_keys: + raise GalSimConfigValueError("Invalid rng_index_key.", index_key, valid_index_keys) + else: + index, index_key = GetIndex(config, base) + logger.debug("GetRNG for %s",index_key) + + rng_num = config.get('rng_num', 0) + if rng_num != 0: + if int(rng_num) != rng_num: + raise GalSimConfigValueError("rng_num must be an integer", rng_num) + rngs = base.get(index_key + '_rngs', None) + if rngs is None: + raise GalSimConfigError("rng_num is only allowed when image.random_seed is a list") + if rng_num < 0 or rng_num > len(rngs): + raise GalSimConfigError("rng_num is invalid. Must be in [0,%d]"%(len(rngs))) + rng = rngs[int(rng_num)] + else: + rng = base.get(index_key + '_rng', None) + + if rng is None: + logger.debug("No index_key_rng. Use base[rng]") + rng = base.get('rng',None) + + if rng is None and logger: + # Only report the warning the first time. + rng_tag = tag + '_reported_no_rng' + if rng_tag not in base: + base[rng_tag] = True + logger.warning("No base['rng'] available for %s. Using /dev/urandom.",tag) + + return rng
+ +
[docs]def CleanConfig(config, keep_current=False): + """Return a "clean" config dict without any leading-underscore values + + GalSim config dicts store a lot of ancillary information internally to help improve + efficiency. However, some of these are actually pointers to other places in the dict, so + printing a config dict, or even what should be a small portion of one, can have infinite loops. + + This helper function is useful when debugging config processing to strip out all of these + leading-underscore values, so that printing the dict is reasonable. + + >>> print(galsim.config.CleanConfig(config_dict)) + """ + if isinstance(config, dict): + return { k : CleanConfig(config[k], keep_current) for k in config + if not (isinstance(k, str) and k[0] == '_') + and (keep_current or k != 'current') } + elif isinstance(config, list): + return [ CleanConfig(item, keep_current) for item in config ] + else: + return config
+ +
[docs]def SetDefaultExt(config, default_ext): + """Set a default ext in a config 'file_name' field if appropriate. + + Parameters: + config: The configuration dict for the item that might need to be given + a default 'ext' value. + default_ext: The default extension to set in the config dict if one is not set. + """ + if ( isinstance(config,dict) and 'type' in config and + config['type'] == 'NumberedFile' and 'ext' not in config ): + config['ext'] = default_ext
+ + +_sleep_mult = 1 # 1 second normally, but make it a variable, so I can change it when unit testing. +
[docs]def RetryIO(func, args, ntries, file_name, logger): + """A helper function to retry I/O commands + """ + itry = 0 + while True: + itry += 1 + try: + ret = func(*args) + except OSError as e: + if itry == ntries: + # Then this was the last try. Just re-raise the exception. + raise + else: + logger.warning('File %s: Caught OSError: %s',file_name,str(e)) + logger.warning('This is try %d/%d, so sleep for %d sec and try again.', + itry,ntries,itry) + time.sleep(itry * _sleep_mult) + continue + else: + break + return ret
+ +def get_cls_params(cls): + """Get the _req_params, _opt_params, _single_params, and _takes_rng definitions + from the given class (or function, but usually class). + + This handles the defaults of empty dict or list or False as appropriate. + """ + req = getattr(cls,'_req_params', {}) + opt = getattr(cls,'_opt_params', {}) + single = getattr(cls,'_single_params', []) + takes_rng = getattr(cls,'_takes_rng', False) + + return req, opt, single, takes_rng +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/value.html b/docs/_build/html/_modules/galsim/config/value.html new file mode 100644 index 00000000000..1e594754150 --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/value.html @@ -0,0 +1,997 @@ + + + + + + galsim.config.value — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.value

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from astropy.units import Quantity, Unit
+from .util import PropagateIndexKeyRNGNum, GetIndex, ParseExtendedKey
+
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..gsobject import GSObject
+from ..angle import Angle, AngleUnit, radians, degrees, hours
+from ..position import PositionD
+from ..celestial import CelestialCoord
+from ..shear import Shear
+from ..table import LookupTable
+from ..utilities import basestring
+
+# This file handles the parsing of values given in the config dict.  It includes the basic
+# parsing functionality along with generators for most of the simple value types.
+# Additional value types are defined in value_random.py, value_eval.py, input.py,
+# input_powerspectrum.py, input_nfw.py, and input_fitsheader.
+
+# This module-level dict will store all the registered value types.
+# See the RegisterValueType function at the end of this file.
+# The keys are the (string) names of the value types, and the values are a tuple of the
+# function to call to generate the value and a list of the types (float, int, str, etc.)
+# that the value type is able to generate.
+valid_value_types = {}
+
+
+# Standard keys to ignore while parsing values:
+standard_ignore = [
+    'type', 'current', 'index_key', 'repeat', 'rng_num', '_gen_fn', '_get', 'rng_index_key',
+    '#' # When we read in json files, there represent comments
+]
+
+
[docs]def ParseValue(config, key, base, value_type): + """Read or generate a parameter value from config. + + Parameters: + config: The config dict from which to parse the value. + key: The key value in the dict to parse. + base: The base config dict. [default: None, which means use base=config] + value_type: The value_type expected. [default: None, which means it won't check + that the value is the right type.] + + Returns: + the tuple (value, safe). + """ + from .gsobject import BuildGSObject + + if isinstance(value_type, tuple): + for vt in value_type: + try: + return ParseValue(config, key, base, vt) + except (GalSimConfigError, TypeError): + pass + else: + raise GalSimConfigError( + "Could not parse %s as any of types %s."%(key, value_type)) + + # Special: if the "value_type" is GSObject, then switch over to that builder instead. + if value_type is GSObject: + return BuildGSObject(config, key, base) + + param = config[key] + #print('ParseValue for key = ',key,', value_type = ',str(value_type)) + #print('param = ',param) + #print('nums = ',base.get('file_num',0), base.get('image_num',0), base.get('obj_num',0)) + + if isinstance(param, dict): + + type_name = param.get('type',None) + #print('type = ',type_name, value_type) + + # Check what index key we want to use for this value. + index, index_key = GetIndex(param, base, is_sequence=(type_name=='Sequence')) + #print('index, index_key = ',index,index_key) + + if '_gen_fn' in param: + generate_func = param['_gen_fn'] + + if 'current' in param: + cval, csafe, cvalue_type, cindex, cindex_key = param['current'] + if 'repeat' in param: + repeat = ParseValue(param, 'repeat', base, int)[0] + use_current = (cindex//repeat == index//repeat) + else: + use_current = (cindex == index) + if use_current: + if (value_type is not None and cvalue_type is not None and + cvalue_type != value_type): + raise GalSimConfigError( + "Attempt to parse %s multiple times with different value types: " + "%s and %s"%(key, value_type, cvalue_type)) + #print(index,'Using current value of ',key,' = ',param['current'][0]) + return cval, csafe + elif value_type is dict and type_name is None: + return param, True + else: + # Only need to check this the first time. + if type_name is None: + raise GalSimConfigError( + "%s.type attribute required when providing a dict."%(key)) + + # Check if the value_type is valid. + # (See valid_value_types defined at the top of the file.) + if type_name not in valid_value_types: + raise GalSimConfigValueError("Unrecognized %s.type"%(key), type_name, + valid_value_types.keys()) + + # Get the generating function and the list of valid types for it. + generate_func, valid_types = valid_value_types[type_name] + + if value_type not in valid_types: + if value_type is None and len(valid_types) == 1: + value_type = valid_types[0] + else: + if value_type is None: + err_str = ( + "Could not determine the appropriate value_type to use for " + + "parameter %s with type=%s.\n"%(key, type_name) + + "Probably it's being used in a Current context before being " + + "evaluated.\n" + + "Consider using an explicit value-typed type name like %s_%s "%( + type_name, valid_types[0].__name__) + + "to help GalSim know which value_type to expect.\n") + else: + err_str = ( + "Invalid value_type specified for parameter %s with type=%s.\n"%( + key, type_name) + + "In this context, GalSim was expecting something that can be "+ + "evaluated as a %s.\n"%(value_type)) + raise GalSimConfigValueError(err_str, value_type, valid_types) + param['_gen_fn'] = generate_func + + #print('generate_func = ',generate_func) + val_safe = generate_func(param, base, value_type) + #print('returned val, safe = ',val_safe) + if isinstance(val_safe, tuple): + val, safe = val_safe + else: + # If a user-defined type forgot to return safe, just assume safe = False + # It's an easy mistake to make and the TypeError that gets emitted isn't + # terribly informative about what the error is. + val = val_safe + safe = False + #print('generate_func = ',generate_func) + #print('param = ',param) + #print('val_safe = ',val_safe) + + # Make sure we really got the right type back. (Just in case...) + if value_type is not None and not isinstance(val,value_type) and val is not None: + val = value_type(val) + + # Save the current value for possible use by the Current type + index, index_key = GetIndex(param, base, is_sequence=(type_name=='Sequence')) + param['current'] = (val, safe, value_type, index, index_key) + #print(key,' = ',val) + + return val, safe + + else: # Not a dict + + # Check for some special markup on string items and convert them to normal dicts. + if isinstance(param, basestring): + if param[0] == '$': + config[key] = { 'type': 'Eval', 'str': str(param[1:]) } + PropagateIndexKeyRNGNum(config, key=key) + return ParseValue(config, key, base, value_type) + if param[0] == '@': + config[key] = { 'type': 'Current', 'key': str(param[1:]) } + PropagateIndexKeyRNGNum(config, key=key) + return ParseValue(config, key, base, value_type) + + # See if it's already the right kind of object, in which case we can just return it. + if value_type is None or param is None or isinstance(param, value_type): + #print(key,' = ',param) + return param, True + + # Convert lists to dicts with type=List + if isinstance(param, list) and value_type is not list: + config[key] = { 'type': 'List', 'items': param } + PropagateIndexKeyRNGNum(config, key=key) + return ParseValue(config, key, base, value_type) + + # The rest of these are special processing options for specific value_types: + if value_type is list: + # Just don't turn a string into a list of characters. + if isinstance(param, basestring): + raise GalSimConfigError("Could not parse %s as a list."%param) + if value_type is Angle: + # Angle is a special case. Angles are specified with a final string to + # declare what unit to use. + val = _GetAngleValue(param) + elif value_type is bool: + # For bool, we allow a few special string conversions + val = _GetBoolValue(param) + elif value_type is PositionD: + # For PositionD, we allow a string of x,y + val = _GetPositionValue(param) + else: + # If none of the above worked, just try a normal value_type initialization. + # This makes sure strings are converted to float (or other type) if necessary. + # In particular things like 1.e6 aren't converted to float automatically + # by the yaml reader. (Although I think this is a bug.) + try: + val = value_type(param) + except (ValueError, TypeError): + raise GalSimConfigError("Could not parse %s as a %s"%(param, value_type)) + #print(key,' = ',val) + + # Save the converted type for next time so it will hit the first if statement here + # instead of recalculating the value. + config[key] = val + return val, True
+ + +
[docs]def GetCurrentValue(key, config, value_type=None, base=None): + """Get the current value of another config item given the key name. + + Parameters: + key: The (extended) key value in the dict to get the current value of. + config: The config dict from which to get the key. + value_type: The value_type expected. [default: None, which means it won't check + that the value is the right type.] + base: The base config dict. [default: None, which means use base=config] + + Returns: + the current value + """ + #print('GetCurrent %s. value_type = %s'%(key,value_type)) + if base is None: + base = config + + if '.' in key: + config, key = ParseExtendedKey(config, key) + + val, safe = EvaluateCurrentValue(key, config, base, value_type) + return val
+ +
[docs]def EvaluateCurrentValue(key, config, base, value_type=None): + """Helper function to evaluate the current value at config[key] where key is no longer + an extended key, and config is the local dict where it is relevant. + + Parameters: + key: The key value in the dict to get the current value of. + config: The config dict from which to get the key. + base: The base config dict. + value_type: The value_type expected. [default: None, which means it won't check + that the value is the right type.] + """ + from .input import GetInputObj + + if config is base.get('input',None): + try: + # If we are trying to access a current input, then use GetInputObj instead. + return GetInputObj(key, base, base, 'Current'), True + except GalSimConfigError: + # If ProcessInput hasn't been called, then the above won't work. + # Try the normal Current processing in that case. + pass + + if not isinstance(config[key], dict): + if value_type is not None or (isinstance(config[key],str) and config[key][0] in ('@','$')): + # This will work fine to evaluate the current value, but will also + # compute it if necessary + #print('Not dict. Parse value normally') + return ParseValue(config, key, base, value_type) + else: + # If we are not given the value_type, and it's not a dict, then the + # item is probably just some value already. + # (Unless it is a base item, in which case, it is not safe.) + #print('Not dict, no value_type. Assume %s is ok.'%d[k]) + return config[key], (config != base) + else: + if value_type is None and 'current' in config[key]: + # If there is already a current val, use it. + #print('Dict with current. Use it: ',d[k]['current'][0]) + return config[key]['current'][:2] + else: + # Otherwise, parse the value for this key + #print('Parse value normally') + return ParseValue(config, key, base, value_type)
+ +
[docs]def SetDefaultIndex(config, num): + """ + When the number of items in a list is known, we allow the user to omit some of + the parameters of a Sequence or Random and set them automatically based on the + size of the list, catalog, etc. + + Parameters: + config: The config dict with the field to be updated. + num: The number to use for the length of the index Sequence if appropriate. + """ + # We use a default item (set to True) to indicate that the value of nitems, last, or max + # has been set here, rather than by the user. This way if the number of items in the + # catalog changes from one file to the next, it will be update correctly to the new + # number of catalog entries. + + if 'index' not in config: + config['index'] = { + 'type' : 'Sequence', + 'nitems' : num, + 'default' : num + } + elif isinstance(config['index'],dict) and 'type' in config['index']: + index = config['index'] + + if index.get('default',-1) == num: return + if '_get' in index: del index['_get'] + + type_name = index['type'] + if type_name == 'Sequence' and 'nitems' in index and 'default' in index: + index['nitems'] = num + index['default'] = num + elif (type_name == 'Sequence' + and 'nitems' not in index + and index.get('step',1) > 0 + and ('last' not in index or 'default' in index) ): + index['last'] = num-1 + index['default'] = num + elif ( type_name == 'Sequence' + and 'nitems' not in index + and index.get('step',1) < 0 + and ('last' not in index or 'default' in index) ): + index['last'] = 0 + index['default'] = num + elif ( type_name == 'Random' + and ('min' not in index or 'default' in index) + and ('max' not in index or 'default' in index) ): + index['min'] = 0 + index['max'] = num-1 + index['default'] = num + if 'index_key' in config: + config['index']['index_key'] = config['index_key']
+ + +
[docs]def CheckAllParams(config, req={}, opt={}, single=[], ignore=[]): + """Check that the parameters for a particular item are all valid + + Parameters: + config: The config dict to check + req: The required items [default: {}] + opt: The optional items [default: {}] + single: List of items where exactly one is required [default: []] + ignore: Items to ignore [default: []] + + Returns: + a dict, get, with get[key] = value_type for all keys to get. + """ + if '_get' in config: return config['_get'] + + get = {} + valid_keys = list(req) + list(opt) + # Check required items: + for (key, value_type) in req.items(): + if key in config: + get[key] = value_type + else: + raise GalSimConfigError( + "Attribute %s is required for type = %s"%(key,config.get('type',None))) + + # Check optional items: + for (key, value_type) in opt.items(): + if key in config: + get[key] = value_type + + # Check items for which exacly 1 should be defined: + for s in single: + valid_keys += list(s) + count = 0 + for (key, value_type) in s.items(): + if key in config: + count += 1 + if count > 1: + raise GalSimConfigError( + "Only one of the attributes %s is allowed for type = %s"%( + s.keys(),config.get('type',None))) + get[key] = value_type + if count == 0: + raise GalSimConfigError( + "One of the attributes %s is required for type = %s"%( + s.keys(),config.get('type',None))) + + # Check that there aren't any extra keys in config aside from a few we expect: + valid_keys += ignore + valid_keys += standard_ignore + for key in config: + # Generators are allowed to use item names that start with _, which we ignore here. + if key not in valid_keys and not key.startswith('_'): + raise GalSimConfigError("Unexpected attribute %s found"%(key)) + + config['_get'] = get + return get
+ + +
[docs]def GetAllParams(config, base, req={}, opt={}, single=[], ignore=[]): + """Check and get all the parameters for a particular item + + Parameters: + config: The config dict from which to get items. + req: The required items [default: {}] + opt: The optional items [default: {}] + single: List of items where exactly one is required [default: []] + ignore: Items to ignore [default: []] + + Returns: + the tuple (kwargs, safe). + """ + get = CheckAllParams(config,req,opt,single,ignore) + kwargs = {} + safe = True + for (key, value_type) in sorted(get.items()): + val, safe1 = ParseValue(config, key, base, value_type) + safe = safe and safe1 + kwargs[key] = val + return kwargs, safe
+ + + + +# +# Now the functions for directly converting an item in the config dict into a value. +# The ones that need a special function are: Angle, PositionD, and bool. +# + +def _GetAngleValue(param): + """Convert a string consisting of a value and an angle unit into an Angle. + """ + try : + value, unit = param.rsplit(None,1) + unit = AngleUnit.from_name(unit) + if unit is hours and ':' in value: + return Angle.from_hms(value) + elif unit is degrees and ':' in value: + return Angle.from_dms(value) + else: + value = float(value) + return Angle(value, unit) + except (ValueError, TypeError, AttributeError) as e: + raise GalSimConfigError("Unable to parse %s as an Angle. Caught %s"%(param,e)) + + +def _GetPositionValue(param): + """Convert a tuple or a string that looks like "a,b" into a galsim.PositionD. + """ + try: + x, y = param + x = float(x) + y = float(y) + except (ValueError, TypeError): + try: + x, y = param.split(',') + x = float(x.strip()) + y = float(y.strip()) + except (ValueError, TypeError, AttributeError) as e: + raise GalSimConfigError("Unable to parse %s as a PositionD. Caught %s"%(param,e)) + return PositionD(x,y) + + +def _GetBoolValue(param): + """Convert a string to a bool + """ + if isinstance(param, basestring): + if param.strip().upper() in ('TRUE', 'YES'): + return True + elif param.strip().upper() in ('FALSE', 'NO'): + return False + else: + try: + val = bool(int(param)) + return val + except (ValueError, TypeError, AttributeError) as e: + raise GalSimConfigError("Unable to parse %s as a bool. Caught %s"%(param,e)) + else: + # This always works. + # Everything in Python is convertible to bool. + return bool(param) + + +# +# Now all the GenerateFrom functions: +# + +def _GenerateFromG1G2(config, base, value_type): + """Return a Shear constructed from given (g1, g2) + """ + req = { 'g1' : float, 'g2' : float } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from G1G2: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromE1E2(config, base, value_type): + """Return a Shear constructed from given (e1, e2) + """ + req = { 'e1' : float, 'e2' : float } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from E1E2: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromEta1Eta2(config, base, value_type): + """Return a Shear constructed from given (eta1, eta2) + """ + req = { 'eta1' : float, 'eta2' : float } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from Eta1Eta2: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromGBeta(config, base, value_type): + """Return a Shear constructed from given (g, beta) + """ + req = { 'g' : float, 'beta' : Angle } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from GBeta: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromEBeta(config, base, value_type): + """Return a Shear constructed from given (e, beta) + """ + req = { 'e' : float, 'beta' : Angle } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from EBeta: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromEtaBeta(config, base, value_type): + """Return a Shear constructed from given (eta, beta) + """ + req = { 'eta' : float, 'beta' : Angle } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from EtaBeta: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromQBeta(config, base, value_type): + """Return a Shear constructed from given (q, beta) + """ + req = { 'q' : float, 'beta' : Angle } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from QBeta: kwargs = ',kwargs) + return Shear(**kwargs), safe + +def _GenerateFromXY(config, base, value_type): + """Return a PositionD constructed from given (x,y) + """ + req = { 'x' : float, 'y' : float } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from XY: kwargs = ',kwargs) + return PositionD(**kwargs), safe + +def _GenerateFromRTheta(config, base, value_type): + """Return a PositionD constructed from given (r,theta) + """ + req = { 'r' : float, 'theta' : Angle } + kwargs, safe = GetAllParams(config, base, req=req) + r = kwargs['r'] + theta = kwargs['theta'] + #print(base['obj_num'],'Generate from RTheta: kwargs = ',kwargs) + return PositionD(r*theta.cos(), r*theta.sin()), safe + +def _GenerateFromRADec(config, base, value_type): + """Return a CelestialCoord constructed from given (ra,dec) + """ + req = { 'ra' : Angle, 'dec' : Angle } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from RADec: kwargs = ',kwargs) + return CelestialCoord(**kwargs), safe + +def _GenerateFromRad(config, base, value_type): + """Return an Angle constructed from given theta in radians + """ + req = { 'theta' : float } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from Rad: kwargs = ',kwargs) + return kwargs['theta'] * radians, safe + +def _GenerateFromDeg(config, base, value_type): + """Return an Angle constructed from given theta in degrees + """ + req = { 'theta' : float } + kwargs, safe = GetAllParams(config, base, req=req) + #print(base['obj_num'],'Generate from Deg: kwargs = ',kwargs) + return kwargs['theta'] * degrees, safe + +def _GenerateFromSequence(config, base, value_type): + """Return next in a sequence of integers + """ + ignore = [ 'default' ] + opt = { 'first' : value_type, 'last' : value_type, 'step' : value_type, + 'repeat' : int, 'nitems' : int, 'index_key' : str } + kwargs, safe = GetAllParams(config, base, opt=opt, ignore=ignore) + + step = kwargs.get('step',1) + first = kwargs.get('first',0) + repeat = kwargs.get('repeat',1) + last = kwargs.get('last',None) + nitems = kwargs.get('nitems',None) + + if repeat <= 0: + raise GalSimConfigValueError( + "Invalid repeat for type = Sequence (must be > 0)", repeat) + if last is not None and nitems is not None: + raise GalSimConfigError( + "At most one of the attributes last and nitems is allowed for type = Sequence") + + index, index_key = GetIndex(kwargs, base, is_sequence=True) + #print('in GenFromSequence: index = ',index,index_key) + + if value_type is bool: + # Then there are only really two valid sequences: Either 010101... or 101010... + # Aside from the repeat value of course. + if first: + first = 1 + step = -1 + nitems = 2 + else: + first = 0 + step = 1 + nitems = 2 + + elif value_type is float: + if last is not None: + nitems = int( (last-first)/step + 0.5 ) + 1 + else: + if last is not None: + nitems = (last - first)//step + 1 + #print('nitems = ',nitems) + #print('repeat = ',repeat) + + index = index // repeat + #print('index => ',index) + + if nitems is not None and nitems > 0: + index = index % nitems + #print('index => ',index) + + value = first + index*step + #print(base[index_key],'Sequence index = %s + %d*%s = %s'%(first,index,step,value)) + return value, False + + +def _GenerateFromNumberedFile(config, base, value_type): + """Return a file_name using a root, a number, and an extension + """ + if 'num' not in config: + config['num'] = { 'type' : 'Sequence' } + req = { 'root' : str , 'num' : int } + opt = { 'ext' : str , 'digits' : int } + kwargs, safe = GetAllParams(config, base, req=req, opt=opt) + + template = kwargs['root'] + if 'digits' in kwargs: + template += '%%0%dd'%kwargs['digits'] + else: + template += '%d' + if 'ext' in kwargs: + template += kwargs['ext'] + s = eval("'%s'%%%d"%(template,kwargs['num'])) + #print(base['obj_num'],'NumberedFile = ',s) + return s, safe + +def _GenerateFromFormattedStr(config, base, value_type): + """Create a string from a format string + """ + req = { 'format' : str, 'items' : list } + # Ignore items for now, we'll deal with it differently. + params, safe = GetAllParams(config, base, req=req) + format = params['format'] + items = params['items'] + + # Figure out what types we are expecting for the list elements: + tokens = format.split('%') + val_types = [] + skip = False + for token in tokens[1:]: # skip first one. + # It we have set skip, then skip this one. + if skip: + skip = False + continue + # If token == '', then this is a %% in the original string. Skip this and the next token. + if len(token) == 0: + skip = True + continue + token = token.lstrip('0123456789lLh') # ignore field size, and long/short specification + if len(token) == 0: + raise GalSimConfigError("Unable to parse %r as a valid format string"%format) + if token[0].lower() in 'diouxX': + val_types.append(int) + elif token[0].lower() in 'eEfFgG': + val_types.append(float) + elif token[0].lower() in 'rs': + val_types.append(str) + else: + raise GalSimConfigError("Unable to parse %r as a valid format string"%format) + + if len(val_types) != len(items): + raise GalSimConfigError( + "Number of items for FormatStr (%d) does not match number expected from " + "format string (%d)"%(len(items), len(val_types))) + vals = [] + for index in range(len(items)): + val, safe1 = ParseValue(items, index, base, val_types[index]) + safe = safe and safe1 + vals.append(val) + + final_str = format%tuple(vals) + #print(base['obj_num'],'FormattedStr = ',final_str) + return final_str, safe + + +def _GenerateFromList(config, base, value_type): + """Return next item from a provided list + """ + req = { 'items' : list } + opt = { 'index' : int } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt) + items, safe = ParseValue(config,'items',base,list) + + # Setup the indexing sequence if it hasn't been specified using the length of items. + SetDefaultIndex(config, len(items)) + index, safe1 = ParseValue(config, 'index', base, int) + safe = safe and safe1 + + if index < 0 or index >= len(items): + raise GalSimConfigError("index %d out of bounds for type=List"%index) + val, safe1 = ParseValue(items, index, base, value_type) + safe = safe and safe1 + #print(base['obj_num'],'List index = %d, val = %s'%(index,val)) + return val, safe + +def _GenerateFromSum(config, base, value_type): + """Return next item from a provided list + """ + req = { 'items' : list } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req) + items, safe = ParseValue(config,'items',base,list) + + sum, safe1 = ParseValue(items, 0, base, value_type) + safe = safe and safe1 + + for k in range(1,len(items)): + val, safe1 = ParseValue(items, k, base, value_type) + sum += val + safe = safe and safe1 + + return sum, safe + +def _GenerateFromFile(config, base, value_type): + """Return a LookupTable from reading a file + """ + req = { 'file_name' : str } + opt = { 'interpolant' : str, 'x_log' : bool, 'f_log' : bool, 'amplitude' : float } + kwargs, safe = GetAllParams(config, base, req=req, opt=opt) + table = LookupTable.from_file(**kwargs) + return table, False + +def _GenerateFromCurrent(config, base, value_type): + """Get the current value of another config item. + """ + if '_kd' in config: + k, d = config['_kd'] + else: + req = { 'key' : str } + params, safe = GetAllParams(config, base, req=req, ignore=['_kd','num']) + key = params['key'] + #print('GetCurrent %s. value_type = %s'%(key,value_type)) + + d, k = ParseExtendedKey(base, key) + config['_kd'] = k,d + + try: + return EvaluateCurrentValue(k, d, base, value_type) + except (TypeError, ValueError) as e: + raise GalSimConfigError("%s\nError generating Current value with key = %s"%(e,k)) + + +def _GenerateFromQuantity(config, base, value_type): + """Return a Quantity from a value and a unit + """ + req = { 'value' : float, 'unit' : str } + kwargs, safe = GetAllParams(config, base, req=req) + return Quantity(kwargs['value'], kwargs['unit']), safe + + +def _GenerateFromUnit(config, base, value_type): + """Return a Unit from a string + """ + req = { 'unit' : str } + kwargs, safe = GetAllParams(config, base, req=req) + return Unit(kwargs['unit']), safe + +
[docs]def RegisterValueType(type_name, gen_func, valid_types, input_type=None): + """Register a value type for use by the config apparatus. + + A few notes about the signature of the generating function: + + 1. The config parameter is the dict for the current value to be generated. So it should + be the case that config['type'] == type_name. + 2. The base parameter is the original config dict being processed. + 3. The value_type parameter is the intended type of the generated value. It should + be one of the values that you specify as valid in valid_types. + 4. The return value of gen_func should be a tuple consisting of the value and a boolean, + safe, which indicates whether the generated value is safe to use again rather than + regenerate for subsequent objects. This will be used upstream to determine if + objects constructed using this value are safe to keep or if they have to be rebuilt. + + The allowed types to include in valid_types are: float, int, bool, str, galsim.Angle, + galsim.Shear, galsim.PositionD. In addition, including None in this list means that + it is valid to use this type if you don't necessarily know what type you are expecting. + This happens when building a truth catalog where each item should already be generated + and the current value and type stored, so currently the only two types that allow + None as a valid type are Current and Eval. + + Parameters: + type_name: The name of the 'type' specification in the config dict. + gen_func: A function to generate a value from the config information. + The call signature is:: + + value, safe = Generate(config, base, value_type) + + valid_types: A list of types for which this type name is valid. + input_type: If the generator utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + from .input import RegisterInputConnectedType + + valid_value_types[type_name] = (gen_func, tuple(valid_types)) + RegisterInputConnectedType(input_type, type_name) + if len(valid_types) > 1: + for t in valid_types: + if t is not None: + RegisterValueType(type_name + '_' + t.__name__, gen_func, [t])
+ + +RegisterValueType('List', _GenerateFromList, + [ float, int, bool, str, Angle, Shear, PositionD, CelestialCoord, LookupTable ]) +RegisterValueType('Current', _GenerateFromCurrent, + [ float, int, bool, str, Angle, Shear, PositionD, CelestialCoord, LookupTable, + dict, list, None, Quantity, Unit ]) +RegisterValueType('Sum', _GenerateFromSum, [ float, int, Angle, Shear, PositionD, Quantity ]) +RegisterValueType('Sequence', _GenerateFromSequence, [ float, int, bool ]) +RegisterValueType('NumberedFile', _GenerateFromNumberedFile, [ str ]) +RegisterValueType('FormattedStr', _GenerateFromFormattedStr, [ str ]) +RegisterValueType('Rad', _GenerateFromRad, [ Angle ]) +RegisterValueType('Radians', _GenerateFromRad, [ Angle ]) +RegisterValueType('Deg', _GenerateFromDeg, [ Angle ]) +RegisterValueType('Degrees', _GenerateFromDeg, [ Angle ]) +RegisterValueType('E1E2', _GenerateFromE1E2, [ Shear ]) +RegisterValueType('EBeta', _GenerateFromEBeta, [ Shear ]) +RegisterValueType('G1G2', _GenerateFromG1G2, [ Shear ]) +RegisterValueType('GBeta', _GenerateFromGBeta, [ Shear ]) +RegisterValueType('Eta1Eta2', _GenerateFromEta1Eta2, [ Shear ]) +RegisterValueType('EtaBeta', _GenerateFromEtaBeta, [ Shear ]) +RegisterValueType('QBeta', _GenerateFromQBeta, [ Shear ]) +RegisterValueType('XY', _GenerateFromXY, [ PositionD ]) +RegisterValueType('RTheta', _GenerateFromRTheta, [ PositionD ]) +RegisterValueType('RADec', _GenerateFromRADec, [ CelestialCoord ]) +RegisterValueType('File', _GenerateFromFile, [ LookupTable ]) +RegisterValueType('Quantity', _GenerateFromQuantity, [ Quantity ]) +RegisterValueType('Unit', _GenerateFromUnit, [ Unit ]) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/config/wcs.html b/docs/_build/html/_modules/galsim/config/wcs.html new file mode 100644 index 00000000000..82d8af7523c --- /dev/null +++ b/docs/_build/html/_modules/galsim/config/wcs.html @@ -0,0 +1,431 @@ + + + + + + galsim.config.wcs — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.config.wcs

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import logging
+
+from .util import LoggerWrapper, GetIndex, GetRNG, get_cls_params
+from .value import ParseValue, GetAllParams, CheckAllParams, SetDefaultIndex
+from .input import RegisterInputConnectedType
+from ..errors import GalSimConfigError, GalSimConfigValueError
+from ..wcs import BaseWCS, PixelScale, OffsetWCS, JacobianWCS, AffineTransform
+from ..wcs import ShearWCS, OffsetShearWCS, UVFunction, RaDecFunction
+from ..fitswcs import TanWCS, FitsWCS
+from ..angle import Angle, AngleUnit
+from ..position import PositionD
+from ..celestial import CelestialCoord
+from ..utilities import basestring
+
+# This file handles the construction of wcs types in config['image']['wcs'].
+
+# This module-level dict will store all the registered wcs types.
+# See the RegisterWCSType function at the end of this file.
+# The keys are the (string) names of the wcs types, and the values will be builders that know
+# how to build the WCS object.
+valid_wcs_types = {}
+
+
+
[docs]def BuildWCS(config, key, base, logger=None): + """Read the wcs parameters from config[key] and return a constructed wcs object. + + Parameters: + config: A dict with the configuration information. (usually base['image']) + key: The key name in config indicating which object to build. + base: The base dict of the configuration. + logger: Optionally, provide a logger for logging debug statements. [default: None] + + Returns: + a BaseWCS instance + """ + logger = LoggerWrapper(logger) + logger.debug('image %d: Start BuildWCS key = %s',base.get('image_num',0),key) + + try: + param = config[key] + except KeyError: + # Default if no wcs is to use PixelScale + if 'pixel_scale' in config: + scale = ParseValue(config, 'pixel_scale', base, float)[0] + else: + scale = 1.0 + return PixelScale(scale) + + # Check for direct value, else get the wcs type + if isinstance(param, BaseWCS): + return param + elif isinstance(param, basestring) and (param[0] == '$' or param[0] == '@'): + return ParseValue(config, key, base, None)[0] + elif isinstance(param, dict): + wcs_type = param.get('type', 'PixelScale') + else: + raise GalSimConfigError("wcs must be either a BaseWCS or a dict") + + # For these two, just do the usual ParseValue function. + if wcs_type in ('Eval', 'Current'): + return ParseValue(config, key, base, None)[0] + + # Check if we can use the current cached object + orig_index_key = base.get('index_key','image_num') + base['index_key'] = 'image_num' + index, index_key = GetIndex(param, base) + if 'current' in param: + cwcs, csafe, cvalue_type, cindex, cindex_key = param['current'] + if cindex == index: + logger.debug('image %d: The wcs object is already current', base.get('image_num',0)) + logger.debug('image %d: index_key = %s, index = %d',base.get('image_num',0), + cindex_key, cindex) + return cwcs + + # Special case: origin == center means to use image_center for the wcs origin + if 'origin' in param and param['origin'] == 'center': + origin = base['image_center'] + param['origin'] = origin + + if wcs_type not in valid_wcs_types: + raise GalSimConfigValueError("Invalid image.wcs.type.", wcs_type, + list(valid_wcs_types.keys())) + logger.debug('image %d: Building wcs type %s', base.get('image_num',0), wcs_type) + builder = valid_wcs_types[wcs_type] + wcs = builder.buildWCS(param, base, logger) + logger.debug('image %d: wcs = %s', base.get('image_num',0), wcs) + + param['current'] = wcs, False, None, index, index_key + base['index_key'] = orig_index_key + + return wcs
+ + +
[docs]class WCSBuilder: + """A base class for building WCS objects. + + The base class defines the call signatures of the methods that any derived class should follow. + """ +
[docs] def buildWCS(self, config, base, logger): + """Build the WCS based on the specifications in the config dict. + + Note: Sub-classes must override this function with a real implementation. + + Parameters: + config: The configuration dict for the wcs type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed WCS object. + """ + raise NotImplementedError("The %s class has not overridden buildWCS"%self.__class__)
+ + +
[docs]class SimpleWCSBuilder(WCSBuilder): + """A class for building simple WCS objects. + + The initializer takes an init_func, which is the class or function to call to build the WCS. + For the kwargs, it calls getKwargs, which does the normal parsing of the req_params and + related class attributes. + """ + def __init__(self, init_func): + self.init_func = init_func + + def getKwargs(self, build_func, config, base): + """Get the kwargs to pass to the build function based on the following attributes of + build_func: + + _req_params + A dict of required parameters and their types. + _opt_params + A dict of optional parameters and their types. + _single_params + A list of dicts of parameters such that one and only one of + parameter in each dict is required. + _takes_rng + A bool value saying whether an rng object is required. + (Which would be weird for this, but it's part of our standard set.) + + See any of the classes in wcs.py for examples of classes that set these attributes. + + Parameters: + build_func: The class or function from which to get the + config: The configuration dict for the wcs type. + base: The base configuration dict. + + Returns: + kwargs + """ + # Then use the standard trick of reading the required and optional parameters + # from the class or function attributes. + req, opt, single, takes_rng = get_cls_params(build_func) + + # Pull in the image layer pixel_scale as a scale item if necessary. + if ( ('scale' in req or 'scale' in opt) and 'scale' not in config and + 'pixel_scale' in base['image'] ): + config['scale'] = base['image']['pixel_scale'] + + kwargs, safe = GetAllParams(config, base, req, opt, single) + + # This would be weird, but might as well check... + if takes_rng: # pragma: no cover + kwargs['rng'] = GetRNG(config, base) + return kwargs + + def buildWCS(self, config, base, logger): + """Build the WCS based on the specifications in the config dict. + + Parameters: + config: The configuration dict for the wcs type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed WCS object. + """ + kwargs = self.getKwargs(self.init_func,config,base) + return self.init_func(**kwargs)
+ + +
[docs]class OriginWCSBuilder(SimpleWCSBuilder): + """A specialization for WCS classes that use a different type depending on whether there + is an origin or world_origin parameter in the config dict. + """ + def __init__(self, init_func, origin_init_func): + self.init_func = init_func + self.origin_init_func = origin_init_func + + def buildWCS(self, config, base, logger): + """Build the WCS based on the specifications in the config dict, using the appropriate + type depending on whether an origin is provided. + + Parameters: + config: The configuration dict for the wcs type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed WCS object. + """ + if 'origin' in config or 'world_origin' in config: + build_func = self.origin_init_func + else: + build_func = self.init_func + kwargs = self.getKwargs(build_func,config,base) + return build_func(**kwargs)
+ + +
[docs]class TanWCSBuilder(WCSBuilder): + """The TanWCS type needs special handling to get the kwargs, since the TanWCS function + takes an AffineTransform as one of the arguments, so we need to build that from + dudx, dudy, etc. We also need to construct a CelestialCoord object for the world_origin, + which we make from ra, dec paramters. + """ + def __init__(self): pass + + def buildWCS(self, config, base, logger): + """Build the TanWCS based on the specifications in the config dict. + + Parameters: + config: The configuration dict for the wcs type. + base: The base configuration dict. + logger: If provided, a logger for logging debug statements. + + Returns: + the constructed WCS object. + """ + req = { "dudx" : float, "dudy" : float, "dvdx" : float, "dvdy" : float, + "ra" : Angle, "dec" : Angle } + opt = { "units" : str, "origin" : PositionD } + params, safe = GetAllParams(config, base, req=req, opt=opt) + + dudx = params['dudx'] + dudy = params['dudy'] + dvdx = params['dvdx'] + dvdy = params['dvdy'] + ra = params['ra'] + dec = params['dec'] + units = params.get('units', 'arcsec') + origin = params.get('origin', None) + + affine = AffineTransform(dudx, dudy, dvdx, dvdy, origin) + world_origin = CelestialCoord(ra, dec) + units = AngleUnit.from_name(units) + + return TanWCS(affine=affine, world_origin=world_origin, units=units)
+ +
[docs]class ListWCSBuilder(WCSBuilder): + """Select a wcs from a list + """ + def buildWCS(self, config, base, logger): + req = { 'items' : list } + opt = { 'index' : int } + # Only Check, not Get. We need to handle items a bit differently, since it's a list. + CheckAllParams(config, req=req, opt=opt) + items = config['items'] + if not isinstance(items,list): + raise GalSimConfigError("items entry for type=List is not a list.") + + # Setup the indexing sequence if it hasn't been specified using the length of items. + SetDefaultIndex(config, len(items)) + index, safe = ParseValue(config, 'index', base, int) + + if index < 0 or index >= len(items): + raise GalSimConfigError("index %d out of bounds for wcs type=List"%index) + return BuildWCS(items, index, base)
+ +
[docs]def RegisterWCSType(wcs_type, builder, input_type=None): + """Register a wcs type for use by the config apparatus. + + Parameters: + wcs_type: The name of the type in config['image']['wcs'] + builder: A builder object to use for building the WCS object. It should + be an instance of a subclass of WCSBuilder. + input_type: If the WCS builder utilises an input object, give the key name of the + input type here. (If it uses more than one, this may be a list.) + [default: None] + """ + valid_wcs_types[wcs_type] = builder + RegisterInputConnectedType(input_type, wcs_type)
+ + +RegisterWCSType('PixelScale', OriginWCSBuilder(PixelScale, OffsetWCS)) +RegisterWCSType('Shear', OriginWCSBuilder(ShearWCS, OffsetShearWCS)) +RegisterWCSType('Jacobian', OriginWCSBuilder(JacobianWCS, AffineTransform)) +RegisterWCSType('Affine', OriginWCSBuilder(JacobianWCS, AffineTransform)) +RegisterWCSType('UVFunction', SimpleWCSBuilder(UVFunction)) +RegisterWCSType('RaDecFunction', SimpleWCSBuilder(RaDecFunction)) +RegisterWCSType('Fits', SimpleWCSBuilder(FitsWCS)) +RegisterWCSType('Tan', TanWCSBuilder()) +RegisterWCSType('List', ListWCSBuilder()) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/convolve.html b/docs/_build/html/_modules/galsim/convolve.html new file mode 100644 index 00000000000..252e46d7748 --- /dev/null +++ b/docs/_build/html/_modules/galsim/convolve.html @@ -0,0 +1,1070 @@ + + + + + + galsim.convolve — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.convolve

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Convolve', 'Convolution', 'Deconvolve', 'Deconvolution',
+            'AutoConvolve', 'AutoConvolution', 'AutoCorrelate', 'AutoCorrelation', ]
+
+import numpy as np
+import copy
+
+from . import _galsim
+from .gsparams import GSParams
+from .gsobject import GSObject
+from .utilities import lazy_property
+from .errors import GalSimError, galsim_warn
+from . import chromatic as chrom
+
+
[docs]def Convolve(*args, **kwargs): + """A function for convolving 2 or more `GSObject` or `ChromaticObject` instances. + + This function will inspect its input arguments to decide if a `Convolution` object or a + `ChromaticConvolution` object is required to represent the convolution of surface + brightness profiles. + + Parameters: + args: Unnamed args should be a list of objects to convolve. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the objects have hard + edges.] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + a `Convolution` or `ChromaticConvolution` instance as appropriate. + """ + # First check for number of arguments != 0 + if len(args) == 0: + raise TypeError("At least one ChromaticObject or GSObject must be provided.") + elif len(args) == 1: + if isinstance(args[0], (GSObject, chrom.ChromaticObject)): + args = [args[0]] + elif isinstance(args[0], list) or isinstance(args[0], tuple): + args = args[0] + else: + raise TypeError("Single input argument must be a GSObject, ChromaticObject, " + + "or a (possibly mixed) list of them.") + # else args is already the list of objects + + if any([isinstance(a, chrom.ChromaticObject) for a in args]): + return chrom.ChromaticConvolution(*args, **kwargs) + else: + return Convolution(*args, **kwargs)
+ + +
[docs]class Convolution(GSObject): + """A class for convolving 2 or more `GSObject` instances. + + The convolution will normally be done using discrete Fourier transforms of each of the component + profiles, multiplying them together, and then transforming back to real space. + + There is also an option to do the convolution as integrals in real space. To do this, use the + optional keyword argument ```real_space = True``. Currently, the real-space integration is only + enabled for convolving 2 profiles. (Aside from the trivial implementaion for 1 profile.) If + you try to use it for more than 2 profiles, an exception will be raised. + + The real-space convolution is normally slower than the DFT convolution. The exception is if + both component profiles have hard edges, e.g. a truncated `Moffat` or `Sersic` with a `Pixel`. + In that case, the highest frequency ``maxk`` for each component is quite large since the + ringing dies off fairly slowly. So it can be quicker to use real-space convolution instead. + Also, real-space convolution tends to be more accurate in this case as well. + + If you do not specify either ``real_space = True`` or ``False`` explicitly, then we check if + there are 2 profiles, both of which have hard edges. In this case, we automatically use + real-space convolution. In all other cases, the default is not to use real-space convolution. + + The normal way to use this class is to use the Convolve() factory function:: + + >>> gal = galsim.Sersic(n, half_light_radius) + >>> psf = galsim.Gaussian(sigma) + >>> final = galsim.Convolve([gal, psf]) + + The objects to be convolved may be provided either as multiple unnamed arguments (e.g. + ``Convolve(psf, gal)``) or as a list (e.g. ``Convolve([psf, gal])``). Any number of objects may + be provided using either syntax. (Well, the list has to include at least 1 item.) + + Parameters: + args: Unnamed args should be a list of objects to convolve. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the objects have hard + edges.] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Note: if ``gsparams`` is unspecified (or None), then the Convolution instance will use the most + restrictive combination of parameters from each of the component objects. Normally, this means + the smallest numerical value (e.g. folding_threshold, xvalue_accuracy, etc.), but for a few + parameters, the largest numerical value is used. See `GSParams.combine` for details. + + Furthermore, the gsparams used for the Convolution (either given explicitly or derived from the + components) will normally be applied to each of the components. It doesn't usually make much + sense to apply stricter-than-normal accuracy or threshold values to one component but not + another in a Convolution, so this ensures that they all have consistent rendering behavior. + However, if you want to keep the existing gsparams of the component objects, then you may + set ``propagate_gsparams=False``. + """ + def __init__(self, *args, **kwargs): + # First check for number of arguments != 0 + if len(args) == 0: + raise TypeError("At least one GSObject must be provided.") + elif len(args) == 1: + if isinstance(args[0], GSObject): + args = [args[0]] + elif isinstance(args[0], list) or isinstance(args[0], tuple): + args = args[0] + else: + raise TypeError("Single input argument must be a GSObject or list of them.") + # else args is already the list of objects + + # Check kwargs + # real_space can be True or False (default if omitted is None), which specifies whether to + # do the convolution as an integral in real space rather than as a product in fourier + # space. If the parameter is omitted (or explicitly given as None I guess), then + # we will usually do the fourier method. However, if there are 2 components _and_ both of + # them have hard edges, then we use real-space convolution. + real_space = kwargs.pop("real_space", None) + gsparams = kwargs.pop("gsparams", None) + self._propagate_gsparams = kwargs.pop('propagate_gsparams', True) + + # Make sure there is nothing left in the dict. + if kwargs: + raise TypeError( + "Convolution constructor got unexpected keyword argument(s): %s"%kwargs.keys()) + + # Check whether to perform real space convolution... + # Start by checking if all objects have a hard edge. + hard_edge = True + for obj in args: + if not isinstance(obj, GSObject): + raise TypeError("Arguments to Convolution must be GSObjects, not %s"%obj) + if not obj.has_hard_edges: + hard_edge = False + + if real_space is None: + # The automatic determination is to use real_space if 2 items, both with hard edges. + if len(args) <= 2: + real_space = hard_edge + else: + real_space = False + elif bool(real_space) != real_space: + raise TypeError("real_space must be a boolean") + + # Warn if doing DFT convolution for objects with hard edges + if not real_space and hard_edge: + + if len(args) == 2: + galsim_warn("Doing convolution of 2 objects, both with hard edges. " + "This might be more accurate and/or faster using real_space=True") + else: + galsim_warn("Doing convolution where all objects have hard edges. " + "There might be some inaccuracies due to ringing in k-space.") + + if real_space: + # Can't do real space if nobj > 2 + if len(args) > 2: + galsim_warn("Real-space convolution of more than 2 objects is not implemented. " + "Switching to DFT method.") + real_space = False + + # Also can't do real space if any object is not analytic, so check for that. + else: + for obj in args: + if not obj.is_analytic_x: + galsim_warn("A component to be convolved is not analytic in real space. " + "Cannot use real space convolution. Switching to DFT method.") + real_space = False + break + + # Save the construction parameters (as they are at this point) as attributes so they + # can be inspected later if necessary. + self._real_space = bool(real_space) + + # Figure out what gsparams to use + if gsparams is None: + # If none is given, take the most restrictive combination from the obj_list. + self._gsparams = GSParams.combine([obj.gsparams for obj in args]) + else: + # If something explicitly given, then use that. + self._gsparams = GSParams.check(gsparams) + + # Apply gsparams to all in obj_list. + if self._propagate_gsparams: + self._obj_list = [obj.withGSParams(self._gsparams) for obj in args] + else: + self._obj_list = args + + @property + def obj_list(self): + """The list of objects being convolved. + """ + return self._obj_list + + @property + def real_space(self): + """Whether this `Convolution` should be drawn using real-space convolution rather + than FFT convolution. + """ + return self._real_space + + @lazy_property + def _sbp(self): + SBList = [obj._sbp for obj in self.obj_list] + return _galsim.SBConvolve(SBList, self._real_space, self.gsparams._gsp) + + @lazy_property + def _noise(self): + # If one of the objects has a noise attribute, then we convolve it by the others. + # More than one is not allowed. + _noise = None + for i, obj in enumerate(self.obj_list): + if obj.noise is not None: + if _noise is not None: + galsim_warn("Unable to propagate noise in galsim.Convolution when " + "multiple objects have noise attribute") + break + _noise = obj.noise + others = [ obj2 for k, obj2 in enumerate(self.obj_list) if k != i ] + if len(others) == 1: + _noise = _noise.convolvedWith(others[0]) + elif len(others) > 1: + _noise = _noise.convolvedWith(Convolve(others)) + # else len == 0, so just use _noise directly. + return _noise + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of each object being convolved. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj_list = [ obj.withGSParams(ret._gsparams) for obj in self.obj_list ] + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Convolution) and + self.obj_list == other.obj_list and + self.real_space == other.real_space and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.Convolution", tuple(self.obj_list), self.real_space, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.Convolution(%r, real_space=%r, gsparams=%r, propagate_gsparams=%r)'%( + self.obj_list, self.real_space, self.gsparams, self._propagate_gsparams) + + def __str__(self): + str_list = [ str(obj) for obj in self.obj_list ] + s = 'galsim.Convolve(%s'%(', '.join(str_list)) + if self.real_space: + s += ', real_space=True' + s += ')' + return s + + def _prepareDraw(self): + for obj in self.obj_list: + obj._prepareDraw() + + @lazy_property + def _maxk(self): + maxk_list = [obj.maxk for obj in self.obj_list] + return np.min(maxk_list) + + @lazy_property + def _stepk(self): + # This is approximate. stepk ~ 2pi/R + # Assume R_final^2 = Sum(R_i^2) + # So 1/stepk^2 = 1/Sum(1/stepk_i^2) + inv_stepksq_list = [obj.stepk**(-2) for obj in self.obj_list] + return np.sum(inv_stepksq_list)**(-0.5) + + @lazy_property + def _has_hard_edges(self): + return len(self.obj_list) == 1 and self.obj_list[0].has_hard_edges + + @lazy_property + def _is_axisymmetric(self): + axi_list = [obj.is_axisymmetric for obj in self.obj_list] + return bool(np.all(axi_list)) + + @lazy_property + def _is_analytic_x(self): + if len(self.obj_list) == 1: + return self.obj_list[0].is_analytic_x + elif self.real_space and len(self.obj_list) == 2: + ax_list = [obj.is_analytic_x for obj in self.obj_list] + return bool(np.all(ax_list)) + else: + return False + + @lazy_property + def _is_analytic_k(self): + ak_list = [obj.is_analytic_k for obj in self.obj_list] + return bool(np.all(ak_list)) + + @lazy_property + def _centroid(self): + cen_list = [obj.centroid for obj in self.obj_list] + return sum(cen_list[1:], cen_list[0]) + + @lazy_property + def _flux(self): + flux_list = [obj.flux for obj in self.obj_list] + return np.prod(flux_list) + + def _calc_pn(self): + pos_list = [obj.positive_flux for obj in self.obj_list] + neg_list = [obj.negative_flux for obj in self.obj_list] + p_net = pos_list[0] + n_net = neg_list[0] + for p,n in zip(pos_list[1:], neg_list[1:]): + p_net, n_net = p_net*p + n_net*n, p_net*n + n_net*p + return p_net, n_net + + @lazy_property + def _positive_flux(self): + p,n = self._calc_pn() + return p + + @lazy_property + def _negative_flux(self): + p,n = self._calc_pn() + return n + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @lazy_property + def _max_sb(self): + # This one is probably the least accurate of all the estimates of maxSB. + # The calculation is based on the exact value for Gaussians. + # maxSB = flux / 2pi sigma^2 + # When convolving multiple Gaussians together, the sigma^2 values add: + # sigma_final^2 = Sum_i sigma_i^2 + # from which we can calculate + # maxSB = flux_final / 2pi sigma_final^2 + # or + # maxSB = flux_final / Sum_i (flux_i / maxSB_i) + # + # For non-Gaussians, this procedure will tend to produce an over-estimate of the + # true maximum SB. Non-Gaussian profiles tend to have peakier parts which get smoothed + # more than the Gaussian does. So this is likely to be too high, which is acceptable. + area_list = [obj.flux / obj.max_sb for obj in self.obj_list] + return self.flux / np.sum(area_list) + + def _xValue(self, pos): + if len(self.obj_list) == 1: + return self.obj_list[0]._xValue(pos) + elif len(self.obj_list) == 2: + try: + return self._sbp.xValue(pos._p) + except (AttributeError, RuntimeError): + raise GalSimError( + "At least one profile in %s does not implement real-space convolution"%self) from None + else: + raise GalSimError("Cannot use real_space convolution for >2 profiles") + + def _kValue(self, pos): + kv_list = [obj.kValue(pos) for obj in self.obj_list] + return np.prod(kv_list) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + if len(self.obj_list) == 1: + self.obj_list[0]._drawReal(image, jac, offset, flux_scaling) + elif len(self.obj_list) == 2: + try: + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + except (AttributeError, RuntimeError): + raise GalSimError( + "At least one profile in %s does not implement real-space convolution"%self) from None + else: + raise GalSimError("Cannot use real_space convolution for >2 profiles") + + def _shoot(self, photons, rng): + self.obj_list[0]._shoot(photons, rng) + # It may be necessary to shuffle when convolving because we do not have a + # gaurantee that the convolvee's photons are uncorrelated, e.g., they might + # both have their negative ones at the end. + # However, this decision is now made by the convolve method. + for obj in self.obj_list[1:]: + p1 = PhotonArray(len(photons)) + obj._shoot(p1, rng) + photons.convolve(p1, rng) + + def _drawKImage(self, image, jac=None): + self.obj_list[0]._drawKImage(image, jac) + if len(self.obj_list) > 1: + im1 = image.copy() + for obj in self.obj_list[1:]: + obj._drawKImage(im1, jac) + image *= im1 + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d
+ + + +
[docs]def Deconvolve(obj, gsparams=None, propagate_gsparams=True): + """A function for deconvolving by either a `GSObject` or `ChromaticObject`. + + This function will inspect its input argument to decide if a `Deconvolution` object or a + `ChromaticDeconvolution` object is required to represent the deconvolution by a surface + brightness profile. + + Parameters: + obj: The object to deconvolve. + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the deconvolved object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + a `Deconvolution` or `ChromaticDeconvolution` instance as appropriate. + """ + if isinstance(obj, chrom.ChromaticObject): + return chrom.ChromaticDeconvolution(obj, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + elif isinstance(obj, GSObject): + return Deconvolution(obj, gsparams=gsparams, propagate_gsparams=propagate_gsparams) + else: + raise TypeError("Argument to Deconvolve must be either a GSObject or a ChromaticObject.")
+ + +
[docs]class Deconvolution(GSObject): + """A class for deconvolving a `GSObject`. + + The Deconvolution class represents a deconvolution kernel. Note that the Deconvolution class, + or compound objects (Sum, Convolution) that include a Deconvolution as one of the components, + cannot be photon-shot using the 'phot' method of `GSObject.drawImage` method. + + You may also specify a ``gsparams`` argument. See the docstring for `GSParams` for more + information about this option. Note: if ``gsparams`` is unspecified (or None), then the + Deconvolution instance inherits the same `GSParams` as the object being deconvolved. + + The normal way to use this class is to use the Deconvolve() factory function:: + + >>> inv_psf = galsim.Deconvolve(psf) + >>> deconv_gal = galsim.Convolve(inv_psf, gal) + + Parameters: + obj: The object to deconvolve. + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the deconvolved object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + _has_hard_edges = False + _is_analytic_x = False + + def __init__(self, obj, gsparams=None, propagate_gsparams=True): + if not isinstance(obj, GSObject): + raise TypeError("Argument to Deconvolution must be a GSObject.") + + # Save the original object as an attribute, so it can be inspected later if necessary. + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._min_acc_kvalue = obj.flux * self.gsparams.kvalue_accuracy + self._inv_min_acc_kvalue = 1./self._min_acc_kvalue + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._orig_obj = obj.withGSParams(self._gsparams) + else: + self._orig_obj = obj + + @property + def orig_obj(self): + """The original object that is being deconvolved. + """ + return self._orig_obj + + @property + def _noise(self): + if self.orig_obj.noise is not None: + galsim_warn("Unable to propagate noise in galsim.Deconvolution") + return None + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of the wrapped component object. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._orig_obj = self._orig_obj.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Deconvolution) and + self.orig_obj == other.orig_obj and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.Deconvolution", self.orig_obj, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.Deconvolution(%r, gsparams=%r, propagate_gsparams=%r)'%( + self.orig_obj, self.gsparams, self._propagate_gsparams) + + def __str__(self): + return 'galsim.Deconvolve(%s)'%self.orig_obj + + def _prepareDraw(self): + self.orig_obj._prepareDraw() + + @property + def _maxk(self): + return self.orig_obj.maxk + + @property + def _stepk(self): + return self.orig_obj.stepk + + @property + def _is_axisymmetric(self): + return self.orig_obj.is_axisymmetric + + @property + def _is_analytic_k(self): + return self.orig_obj.is_analytic_k + + @property + def _centroid(self): + return -self.orig_obj.centroid + + @lazy_property + def _flux(self): + return 1./self.orig_obj.flux + + @lazy_property + def _max_sb(self): + # The only way to really give this any meaning is to consider it in the context + # of being part of a larger convolution with other components. The calculation + # of maxSB for Convolve is + # maxSB = flux_final / Sum_i (flux_i / maxSB_i) + # + # A deconvolution will contribute a -sigma^2 to the sum, so a logical choice for + # maxSB is to have flux / maxSB = -flux_adaptee / maxSB_adaptee, so its contribution + # to the Sum_i 2pi sigma^2 is to subtract its adaptee's value of sigma^2. + # + # maxSB = -flux * maxSB_adaptee / flux_adaptee + # = -maxSB_adaptee / flux_adaptee^2 + # + return -self.orig_obj.max_sb / self.orig_obj.flux**2 + + def _kValue(self, pos): + # Really, for very low original kvalues, this gets very high, which can be unstable + # in the presence of noise. So if the original value is less than min_acc_kvalue, + # we instead just return 1/min_acc_kvalue rather than the real inverse. + kval = self.orig_obj._kValue(pos) + if abs(kval) < self._min_acc_kvalue: + return self._inv_min_acc_kvalue + else: + return 1./kval + + def _drawKImage(self, image, jac=None): + self.orig_obj._drawKImage(image, jac) + do_inverse = np.abs(image.array) > self._min_acc_kvalue + image.array[do_inverse] = 1./image.array[do_inverse] + image.array[~do_inverse] = self._inv_min_acc_kvalue + kx,ky = image.get_pixel_centers() + if jac is not None: + # N.B. The jacobian is transposed in k space. This is not a typo. + kx,ky = (jac[0,0] * kx + jac[1,0] * ky), (jac[0,1] * kx + jac[1,1] * ky) + ksq = (kx**2 + ky**2) * image.scale**2 + # Set to zero outside of nominal maxk so as not to amplify high frequencies. + image.array[ksq > self.maxk**2] = 0.
+ + +
[docs]def AutoConvolve(obj, real_space=None, gsparams=None, propagate_gsparams=True): + """A function for autoconvolving either a `GSObject` or `ChromaticObject`. + + This function will inspect its input argument to decide if a `AutoConvolution` object or a + `ChromaticAutoConvolution` object is required to represent the convolution of a surface + brightness profile with itself. + + Parameters: + obj: The object to be convolved with itself. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the object has hard + edges.] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the auto-convolved object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + a `AutoConvolution` or `ChromaticAutoConvolution` instance as appropriate. + """ + if isinstance(obj, chrom.ChromaticObject): + return chrom.ChromaticAutoConvolution(obj, real_space=real_space, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + elif isinstance(obj, GSObject): + return AutoConvolution(obj, real_space=real_space, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + else: + raise TypeError("Argument to AutoConvolve must be either a GSObject or a ChromaticObject.")
+ + +
[docs]class AutoConvolution(Convolution): + """A special class for convolving a `GSObject` with itself. + + It is equivalent in functionality to ``Convolve([obj,obj])``, but takes advantage of + the fact that the two profiles are the same for some efficiency gains. + + The normal way to use this class is to use the AutoConvolve() factory function:: + + >>> psf_sq = galsim.AutoConvolve(psf) + + Parameters: + obj: The object to be convolved with itself. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the object has hard + edges.] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the auto-convolved object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, real_space=None, gsparams=None, propagate_gsparams=True): + if not isinstance(obj, GSObject): + raise TypeError("Argument to AutoConvolution must be a GSObject.") + + # Check whether to perform real space convolution... + # Start by checking if obj has a hard edge. + hard_edge = obj.has_hard_edges + + if real_space is None: + # The automatic determination is to use real_space if obj has hard edges. + real_space = hard_edge + elif bool(real_space) != real_space: + raise TypeError("real_space must be a boolean") + + # Warn if doing DFT convolution for objects with hard edges. + if not real_space and hard_edge: + galsim_warn("Doing auto-convolution of object with hard edges. " + "This might be more accurate and/or faster using real_space=True") + + # Can't do real space if object is not analytic, so check for that. + if real_space and not obj.is_analytic_x: + galsim_warn("Object to be auto-convolved is not analytic in real space. " + "Cannot use real space convolution. Switching to DFT method.") + real_space = False + + # Save the construction parameters (as they are at this point) as attributes so they + # can be inspected later if necessary. + self._real_space = bool(real_space) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._orig_obj = obj.withGSParams(self._gsparams) + else: + self._orig_obj = obj + + # So we can use Convolve methods when there is no advantage to overloading. + self._obj_list = [self._orig_obj, self._orig_obj] + + @lazy_property + def _sbp(self): + return _galsim.SBAutoConvolve(self.orig_obj._sbp, self._real_space, self.gsparams._gsp) + + @property + def orig_obj(self): + """The original object that is being auto-convolved. + """ + return self._orig_obj + @property + def real_space(self): + """Whether this `Convolution` should be drawn using real-space convolution rather + than FFT convolution. + """ + return self._real_space + + @property + def _noise(self): + if self.orig_obj.noise is not None: + galsim_warn("Unable to propagate noise in galsim.AutoConvolution") + return None + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of the wrapped component object. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._orig_obj = self._orig_obj.withGSParams(ret._gsparams) + ret._obj_list = [ret._orig_obj, ret._orig_obj] + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, AutoConvolution) and + self.orig_obj == other.orig_obj and + self.real_space == other.real_space and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.AutoConvolution", self.orig_obj, self.real_space, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.AutoConvolution(%r, real_space=%r, gsparams=%r, propagate_gsparams=%r)'%( + self.orig_obj, self.real_space, self.gsparams, self._propagate_gsparams) + + def __str__(self): + s = 'galsim.AutoConvolve(%s'%self.orig_obj + if self.real_space: + s += ', real_space=True' + s += ')' + return s + + def _prepareDraw(self): + self.orig_obj._prepareDraw() + + def _shoot(self, photons, rng): + self.orig_obj._shoot(photons, rng) + photons2 = PhotonArray(len(photons)) + self.orig_obj._shoot(photons2, rng) + photons.convolve(photons2, rng)
+ + +
[docs]def AutoCorrelate(obj, real_space=None, gsparams=None, propagate_gsparams=True): + """A function for autocorrelating either a `GSObject` or `ChromaticObject`. + + This function will inspect its input argument to decide if a `AutoCorrelation` object or a + `ChromaticAutoCorrelation` object is required to represent the correlation of a surface + brightness profile with itself. + + Parameters: + obj: The object to be convolved with itself. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the object has hard + edges.] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the auto-convorrelated object. + This is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + an `AutoCorrelation` or `ChromaticAutoCorrelation` instance as appropriate. + """ + if isinstance(obj, chrom.ChromaticObject): + return chrom.ChromaticAutoCorrelation(obj, real_space=real_space, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + elif isinstance(obj, GSObject): + return AutoCorrelation(obj, real_space=real_space, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + else: + raise TypeError("Argument to AutoCorrelate must be either a GSObject or a ChromaticObject.")
+ + +
[docs]class AutoCorrelation(Convolution): + """A special class for correlating a `GSObject` with itself. + + It is equivalent in functionality to:: + + galsim.Convolve([obj,obj.createRotated(180.*galsim.degrees)]) + + but takes advantage of the fact that the two profiles are the same for some efficiency gains. + + This class is primarily targeted for use by the `BaseCorrelatedNoise` models when convolving + with a `GSObject`. + + The normal way to use this class is to use the `AutoCorrelate` factory function:: + + >>> psf_sq = galsim.AutoCorrelate(psf) + + Parameters: + obj: The object to be convolved with itself. + real_space: Whether to use real space convolution. [default: None, which means + to automatically decide this according to whether the object has hard + edges.] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the auto-convorrelated object. + This is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + def __init__(self, obj, real_space=None, gsparams=None, propagate_gsparams=True): + if not isinstance(obj, GSObject): + raise TypeError("Argument to AutoCorrelation must be a GSObject.") + + # Check whether to perform real space convolution... + # Start by checking if obj has a hard edge. + hard_edge = obj.has_hard_edges + + if real_space is None: + # The automatic determination is to use real_space if obj has hard edges. + real_space = hard_edge + elif bool(real_space) != real_space: + raise TypeError("real_space must be a boolean") + + # Warn if doing DFT convolution for objects with hard edges. + if not real_space and hard_edge: + galsim_warn("Doing auto-correlation of object with hard edges. " + "This might be more accurate and/or faster using real_space=True") + + # Can't do real space if object is not analytic, so check for that. + if real_space and not obj.is_analytic_x: + galsim_warn("Object to be auto-correlated is not analytic in real space. " + "Cannot use real space convolution. Switching to DFT method.") + real_space = False + + # Save the construction parameters (as they are at this point) as attributes so they + # can be inspected later if necessary. + self._real_space = bool(real_space) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._orig_obj = obj.withGSParams(self._gsparams) + else: + self._orig_obj = obj + + # So we can use Convolve methods when there is no advantage to overloading. + self._obj_list = [self._orig_obj, self._orig_obj.transform(-1,0,0,-1)] + + @lazy_property + def _sbp(self): + return _galsim.SBAutoCorrelate(self.orig_obj._sbp, self._real_space, self.gsparams._gsp) + + @property + def orig_obj(self): + """The original object that is being auto-correlated. + """ + return self._orig_obj + @property + def real_space(self): + """Whether this `Convolution` should be drawn using real-space convolution rather + than FFT convolution. + """ + return self._real_space + + @property + def _noise(self): + if self.orig_obj.noise is not None: + galsim_warn("Unable to propagate noise in galsim.AutoCorrelation") + return None + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of the wrapped component object. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._orig_obj = self._orig_obj.withGSParams(ret._gsparams) + ret._obj_list = [ret._orig_obj, ret._orig_obj.transform(-1,0,0,-1)] + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, AutoCorrelation) and + self.orig_obj == other.orig_obj and + self.real_space == other.real_space and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.AutoCorrelation", self.orig_obj, self.real_space, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.AutoCorrelation(%r, real_space=%r, gsparams=%r, propagate_gsparams=%r)'%( + self.orig_obj, self.real_space, self.gsparams, self._propagate_gsparams) + + def __str__(self): + s = 'galsim.AutoCorrelate(%s'%self.orig_obj + if self.real_space: + s += ', real_space=True' + s += ')' + return s + + def _prepareDraw(self): + self._orig_obj._prepareDraw() + + def _shoot(self, photons, rng): + self.orig_obj._shoot(photons, rng) + photons2 = PhotonArray(len(photons)) + self.orig_obj._shoot(photons2, rng) + + # Flip sign of (x, y) in one of the results + photons2.scaleXY(-1) + + photons.convolve(photons2, rng)
+ +# Put this at the bottom to avoid circular import errors. +from .photon_array import PhotonArray +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/correlatednoise.html b/docs/_build/html/_modules/galsim/correlatednoise.html new file mode 100644 index 00000000000..7925c3c7a86 --- /dev/null +++ b/docs/_build/html/_modules/galsim/correlatednoise.html @@ -0,0 +1,1819 @@ + + + + + + galsim.correlatednoise — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.correlatednoise

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'BaseCorrelatedNoise', 'CorrelatedNoise', 'UncorrelatedNoise',
+            'getCOSMOSNoise', 'CovarianceSpectrum', ]
+
+import numpy as np
+import os
+import math
+
+from .image import Image
+from .random import BaseDeviate
+from .gsparams import GSParams
+from .wcs import PixelScale, BaseWCS, compatible
+from . import utilities
+from . import fits
+from . import meta_data
+from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimUndefinedBoundsError
+from .errors import GalSimIncompatibleValuesError, galsim_warn
+from .interpolant import Linear
+from .position import _PositionD
+from .angle import radians
+from .random import GaussianDeviate
+from .box import Pixel
+from .convolve import Convolve, AutoCorrelate, AutoConvolve
+from .interpolatedimage import InterpolatedImage, InterpolatedKImage
+
+
+def whitenNoise(self, noise):
+    # This will be inserted into the Image class as a method.  So self = image.
+    """Whiten the noise in the image assuming that the noise currently in the image can be described
+    by the `BaseCorrelatedNoise` object ``noise``.  See `BaseCorrelatedNoise.whitenImage` for more
+    details of how this method works.
+
+    Parameters:
+        noise:      The `BaseCorrelatedNoise` model to use when figuring out how much noise to add
+                    to make the final noise white.
+
+    Returns:
+        the theoretically calculated variance of the combined noise fields in the updated image.
+    """
+    return noise.whitenImage(self)
+
+def symmetrizeNoise(self, noise, order=4):
+    # This will be inserted into the Image class as a method.  So self = image.
+    """Impose N-fold symmetry (where N=``order`` is an even integer >=4) on the noise in a square
+    image assuming that the noise currently in the image can be described by the
+    `BaseCorrelatedNoise` object ``noise``.  See `BaseCorrelatedNoise.symmetrizeImage` for more
+    details of how this method works.
+
+    Parameters:
+        noise:      The `BaseCorrelatedNoise` model to use when figuring out how much noise to add
+                    to make the final noise have symmetry at the desired order.
+        order:      Desired symmetry order.  Must be an even integer larger than 2.
+                    [default: 4]
+
+    Returns:
+        the theoretically calculated variance of the combined noise fields in the updated image.
+    """
+    return noise.symmetrizeImage(self, order=order)
+
+# Now inject whitenNoise and symmetrizeNoise as methods of the Image class.
+Image.whitenNoise = whitenNoise
+Image.symmetrizeNoise = symmetrizeNoise
+
+
[docs]class BaseCorrelatedNoise: + """A Base Class describing 2D correlated Gaussian random noise fields. + + A BaseCorrelatedNoise will not generally be instantiated directly. This is recommended as the + current ``BaseCorrelatedNoise.__init__`` interface does not provide any guarantee that the input + `GSObject` represents a physical correlation function, e.g. a profile that is an even function + (two-fold rotationally symmetric in the plane) and peaked at the origin. The proposed pattern + is that users instead instantiate derived classes, such as the `CorrelatedNoise`, which are + able to guarantee the above. + + If you have the correlation function as an image on file, you can use the class method + `from_file`, which does confirm that the file has the appropriate symmetry. + + The BaseCorrelatedNoise is therefore here primarily to define the way in which derived classes + (currently only `CorrelatedNoise` and `UncorrelatedNoise`) store the random deviate, noise + correlation function profile and allow operations with it, generate images containing noise with + these correlation properties, and generate covariance matrices according to the correlation + function. + + Parameters: + rng: A `BaseDeviate` instance to use for generating the random numbers. + gsobject: The `GSObject` defining the correlation function. + + .. warning: + + The user is responsible for ensuring this profile is 2-fold rotationally + symmetric. This is **not** checked. + + wcs: The wcs for the image to define the phyical relationship between the pixels. + [default: PixelScale(1.0)] + """ + def __init__(self, rng, gsobject, wcs=PixelScale(1.0)): + if rng is not None and not isinstance(rng, BaseDeviate): + raise TypeError( + "Supplied rng argument not a galsim.BaseDeviate or derived class instance.") + + if rng is None: rng = BaseDeviate() + self._rng = rng + # Act as a container for the GSObject used to represent the correlation funcion. + self._profile = gsobject + self.wcs = wcs + + # When applying normal or whitening noise to an image, we normally do calculations. + # If _profile_for_cached is profile, then it means that we can use the stored values in + # _rootps_cache, _rootps_whitening_cache, and/or _rootps_symmetrizing_cache and avoid having + # to redo the calculations. + # So for now, we start out with _profile_for_cached = None, and _rootps_cache, + # _rootps_whitening_cache, _rootps_symmetrizing_cache empty. + self._profile_for_cache = None + self._rootps_cache = {} + self._rootps_whitening_cache = {} + self._rootps_symmetrizing_cache = {} + # Also set up the cache for a stored value of the variance, needed for efficiency once the + # noise field can get convolved with other GSObjects making is_analytic_x False + self._variance_cached = None + +
[docs] @classmethod + def from_file(cls, file_name, pixel_scale, rng=None, variance=0., x_interpolant=None, + gsparams=None): + """Read a correlated noise profile from a file. + + The file should contain an image of the correlation. + + * The image should be square with odd size in each direction. + * The central pixel corresponds to the zero-lag correlation, i.e. the variance. + Call this pixel element (0,0). + * The other pixels (i,j) give the cross-correlation of the noise for pixels separated + by i pixels in the x direction (columns) and j pixels in the y direction (rows). + * The image therefore must be 180 degree rotationally symmetric. i.e. the value at + (i,j) must be the same as at (-i,-j). + + The pixel_scale is also required and defines the pixel scale of the original image. + + The default ``x_interpolant`` is a ``galsim.Linear()``, which uses bilinear interpolation. + The use of this interpolant is an approximation that gives good empirical results without + requiring internal convolution of the correlation function profile by a `Pixel` object when + applying correlated noise to images: such an internal convolution has been found to be + computationally costly in practice, requiring the Fourier transform of very large arrays. + + The use of the bilinear interpolants means that the representation of correlated noise will + be noticeably inaccurate in at least the following two regimes: + + 1. If the pixel scale of the desired final output (e.g. the target image of + `BaseCorrelatedNoise.drawImage`, `BaseCorrelatedNoise.applyTo` or + `BaseCorrelatedNoise.whitenImage`) is small relative to the separation between pixels in + the ``image`` used to instantiate ``cn`` as shown above. + 2. If the `BaseCorrelatedNoise` instance ``cn`` was instantiated with an image of scale + comparable to that in the final output, and ``cn`` has been rotated or otherwise + transformed (e.g. via the `BaseCorrelatedNoise.rotate`, `BaseCorrelatedNoise.shear` + methods; see below). + + Conversely, the approximation will work best in the case where the correlated noise used to + instantiate the ``cn`` is taken from an input image for which ``image.scale`` is smaller + than that in the desired output. This is the most common use case in the practical + treatment of correlated noise when simulating galaxies from space as observed in + ground-based surveys. + + Changing from the default bilinear interpolant is made possible, but not recommended. + In our validation tests, we found that the Linear interpolant usually gave the most + accurate results. + + Parameters: + file_name: The name of the file to read. + pixel_scale: The pixel scale of the original image. + rng: If provided, a random number generator to use as the random number + generator of the resulting noise object. (may be any kind of + `BaseDeviate` object) [default: None, in which case, one will be + automatically created, using the time as a seed.] + variance: Scales the correlation function so that its point variance, equivalent + to its value at zero separation distance, matches this value. + [default: 0., which means to use the variance in the original file.] + x_interpolant: Forces use of a non-default interpolant for interpolation of the + internal lookup table in real space. See below for more details. + [default: galsim.Linear()] + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + a `BaseCorrelatedNoise` instance + """ + if not os.path.isfile(file_name): + raise OSError("The file %r does not exist."%(file_name)) + try: + cfimage = fits.read(file_name) + except (OSError, AttributeError, TypeError) as e: + raise OSError("Unable to read file %s.\n%r"%(file_name,e)) + + # Check for invalid images: + if cfimage.nrow != cfimage.ncol: + raise GalSimError("Input image is not square.") + if cfimage.nrow % 2 != 1: + raise GalSimError("Input image does not have odd size.") + if np.max(np.abs((cfimage - cfimage.rot_180()).array)) > 1.e-12: + raise GalSimError("Input image does not have 180 degree rotational symmetry.") + center_val = np.abs(cfimage[cfimage.center]) + if np.max(np.abs(cfimage.array/center_val)) > 1. + 1.e-12: + raise GalSimError("Input image central value is not the maximum") + + # Also check for invalid negative variance + if variance < 0: + raise GalSimRangeError("Specified variance must be zero or positive.", + variance, 0, None) + + # If x_interpolant not specified on input, use bilinear + if x_interpolant is None: + x_interpolant = Linear() + else: + x_interpolant = utilities.convert_interpolant(x_interpolant) + + # Build the cf profile. + ii = InterpolatedImage(cfimage, scale=pixel_scale, normalization="sb", + calculate_stepk=False, calculate_maxk=False, + x_interpolant=x_interpolant, gsparams=gsparams) + ret = BaseCorrelatedNoise(rng, ii, PixelScale(pixel_scale)) + # If the input keyword variance is non-zero, scale the correlation function to have this + # variance + if variance > 0.: + ret = ret.withVariance(variance) + return ret
+ + @property + def rng(self): + """The `BaseDeviate` for this object. + """ + return self._rng + + @property + def gsparams(self): + """The `GSParams` for this object. + """ + return self._profile.gsparams + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given `GSParams`. + """ + if gsparams == self.gsparams: return self + return BaseCorrelatedNoise(self.rng, self._profile.withGSParams(gsparams, **kwargs), + self.wcs)
+ + # Make "+" work in the intuitive sense (variances being additive, correlation functions add as + # you would expect) + def __add__(self, other): + if not compatible(self.wcs, other.wcs): + galsim_warn("Adding two CorrelatedNoise objects with incompatible WCS. " + "The result will have the WCS of the first object.") + return BaseCorrelatedNoise(self.rng, self._profile + other._profile, self.wcs) + + def __sub__(self, other): + if not compatible(self.wcs, other.wcs): + galsim_warn("Subtracting two CorrelatedNoise objects with incompatible WCS. " + "The result will have the WCS of the first object.") + return BaseCorrelatedNoise(self.rng, self._profile - other._profile, self.wcs) + + def __mul__(self, variance_ratio): + return self.withScaledVariance(variance_ratio) + def __div__(self, variance_ratio): + return self.withScaledVariance(1./variance_ratio) + __truediv__ = __div__ + +
[docs] def copy(self, rng=None): + """Returns a copy of the correlated noise model. + + By default, the copy will share the `BaseDeviate` random number generator with the parent + instance. However, you can provide a new rng to use in the copy if you want with:: + + >>> cn_copy = cn.copy(rng=new_rng) + """ + if rng is None: + rng = self.rng + return BaseCorrelatedNoise(rng, self._profile, self.wcs)
+ + def __repr__(self): + return "galsim.BaseCorrelatedNoise(%r,%r,%r)"%( + self.rng, self._profile, self.wcs) + + def __str__(self): + return "galsim.BaseCorrelatedNoise(%s,%s)"%(self._profile, self.wcs) + + # Quick and dirty. Just check reprs are equal. + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self)) + + def _clear_cache(self): + """Check if the profile has changed and clear caches if appropriate. + """ + if self._profile_for_cache is not self._profile: + self._rootps_cache.clear() + self._rootps_whitening_cache.clear() + self._rootps_symmetrizing_cache.clear() + self._variance_cached = None + # Set profile_for_cache for next time. + self._profile_for_cache = self._profile + +
[docs] def applyTo(self, image): + """Apply this correlated Gaussian random noise field to an input `Image`. + + To add deviates to every element of an image, the syntax:: + + >>> image.addNoise(correlated_noise) + + is preferred. However, this is equivalent to calling this instance's `applyTo` method as + follows:: + + >>> correlated_noise.applyTo(image) + + On output the `Image` instance ``image`` will have been given additional noise according to + the given `BaseCorrelatedNoise` instance ``correlated_noise``. Normally, ``image.scale`` + is used to determine the input pixel separation, and if ``image.scale <= 0`` a pixel scale + of 1 is assumed. If the image has a non-uniform WCS, the local uniform approximation at + the center of the image will be used. + + Note that the correlations defined in a correlated_noise object are defined in terms of + world coordinates (i.e. typically arcsec on the sky). Some care is thus required if you + apply correlated noise to an image with a non-trivial WCS. The correlations will have a + specific direction and scale in world coordinates, so if you apply them to an image with + a WCS that has a rotation or a different pixel scale than the original, the resulting + correlations will have the correct direction and scale in world coordinates, but a + different direction and/or scale in image coordinates. + + If you want to override this behavior, you can view your image with the WCS of the + correlation function and apply the noise to that. For example:: + + >>> image = galsim.Image(nx, ny, wcs=complicated_wcs) + >>> noise = galsim.getCOSMOSNoise(rng=rng) + >>> image.view(wcs=noise.wcs).addNoise(noise) + + This will create noise whose pixel-to-pixel correlations match those of the original + correlated noise image (in this case, the COSMOS images). If the input image has no WCS + set, then it will be treated as having the same WCS as the noise. + + Note that the correlated noise field in ``image`` will be periodic across its boundaries: + this is due to the fact that the internals of the `BaseCorrelatedNoise` currently use a + relatively simple implementation of noise generation using the Fast Fourier Transform. + If you wish to avoid this property being present in your final ``image`` you should add the + noise to an ``image`` of greater extent than you need, and take a subset. + + Parameters: + image: The input `Image` object. + """ + # Note that this uses the (fast) method of going via the power spectrum and FFTs to generate + # noise according to the correlation function represented by this instance. An alternative + # would be to use the covariance matrices and eigendecomposition. However, it is O(N^6) + # operations for an NxN image! FFT-based noise realization is O(2 N^2 log[N]) so we use it + # for noise generation applications. + + # Check that the input has defined bounds + if not isinstance(image, Image): + raise TypeError("Input image argument must be a galsim.Image.") + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Input image argument must have defined bounds.") + + # If the profile has changed since last time (or if we have never been here before), + # clear out the stored values. + self._clear_cache() + + if image.wcs is None: + wcs = self.wcs + else: + wcs = image.wcs.local(image.true_center) + + # Then retrieve or redraw the sqrt(power spectrum) needed for making the noise field + rootps = self._get_update_rootps(image.array.shape, wcs) + + # Finally generate a random field in Fourier space with the right PS + noise_array = _generate_noise_from_rootps(self.rng, image.array.shape, rootps) + + # Add it to the image + image.array[:,:] += noise_array + return image
+ +
[docs] def whitenImage(self, image): + """Apply noise designed to whiten correlated Gaussian random noise in an input `Image`. + + On input, The `Image`, ``image``, is assumed to have correlated noise described by this + `BaseCorrelatedNoise` instance. + + On output ``image`` will have been given additional (correlated) noise designed to whiten + the noise profile. + + Note: the syntax ``image.whitenNoise(noise)`` is normally preferred, but it is equivalent + to:: + + >>> correlated_noise.whitenImage(image) + + If the ``image`` originally contained noise with a correlation function described by the + ``correlated_noise`` instance, the combined noise after using the whitenImage() method + will be approximately uncorrelated. Tests using COSMOS noise fields suggest ~0.3% residual + off-diagonal covariances after whitening, relative to the variance, although results may + vary depending on the precise correlation function of the noise field. + (See ``devel/external/hst/compare_whitening_subtraction.py`` for the COSMOS tests.) + + Note that the code doesn't check that the "if" above is true: the user MUST make sure this + is the case for the final noise to be uncorrelated. + + Normally, ``image.scale`` is used to determine the input `Image` pixel separation, and if + ``image.wcs`` is None, it will use the wcs of the noise. If the image has a non-uniform + WCS, the local uniform approximation at the center of the image will be used. + + If you are interested in a theoretical calculation of the variance in the final noise field + after whitening, the whitenImage() method in fact returns this variance. For example:: + + >>> variance = correlated_noise.whitenImage(image) + + **Example**: + + To see noise whitening in action, let us use a model of the correlated noise in COSMOS + as returned by the `getCOSMOSNoise` function. Let's initialize and add noise to an image:: + + >>> cn = galsim.getCOSMOSNoise() + >>> image = galsim.ImageD(256, 256, scale=0.03) + >>> # The scale should match the COSMOS default since didn't specify another + >>> image.addNoise(cn) + + The ``image`` will then contain a realization of a random noise field with COSMOS-like + correlation. Using the whitenImage() method, we can now add more noise to ``image`` + with a power spectrum specifically designed to make the combined noise fields uncorrelated:: + + >>> cn.whitenImage(image) + + Of course, this whitening comes at the cost of adding further noise to the image, but + the algorithm is designed to make this additional noise (nearly) as small as possible. + + Parameters: + image: The input `Image` object. + + Returns: + the theoretically calculated variance of the combined noise fields in the updated image. + """ + # Note that this uses the (fast) method of going via the power spectrum and FFTs to generate + # noise according to the correlation function represented by this instance. An alternative + # would be to use the covariance matrices and eigendecomposition. However, it is O(N^6) + # operations for an NxN image! FFT-based noise realization is O(2 N^2 log[N]) so we use it + # for noise generation applications. + + # Check that the input has defined bounds + if not isinstance(image, Image): + raise TypeError("Input image not a galsim.Image object") + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Input image argument must have defined bounds.") + + # If the profile has changed since last time (or if we have never been here before), + # clear out the stored values. + self._clear_cache() + + if image.wcs is None: + wcs = self.wcs + else: + wcs = image.wcs.local(image.true_center) + + # Then retrieve or redraw the sqrt(power spectrum) needed for making the whitening noise, + # and the total variance of the combination + rootps_whitening, variance = self._get_update_rootps_whitening(image.array.shape, wcs) + + # Finally generate a random field in Fourier space with the right PS and add to image + noise_array = _generate_noise_from_rootps(self.rng, image.array.shape, rootps_whitening) + image += Image(noise_array) + + # Return the variance to the interested user + return variance
+ +
[docs] def symmetrizeImage(self, image, order=4): + """Apply noise designed to impose N-fold symmetry on the existing noise in a (square) input + `Image`. + + On input, The `Image`, ``image``, is assumed to have correlated noise described by this + `BaseCorrelatedNoise` instance. + + On output ``image`` will have been given additional (correlated) noise designed to + symmetrize the noise profile. + + When called for a non-square image, this method will raise an exception, unlike the noise + whitening routines. + + The ``order`` of the symmetry can be supplied as a keyword argument, with the default being + 4 because this is presumably the minimum required for the anisotropy of noise correlations + to not affect shear statistics. + + Note: the syntax ``image.symmetrizeNoise(noise, order)`` is preferred, but it is equivalent + to:: + + >>> correlated_noise.symmetrizeImage(image, order=order) + + If the ``image`` originally contained noise with a correlation function described by the + ``correlated_noise`` instance, the combined noise after using the symmetrizeImage() method + will have a noise correlation function with N-fold symmetry, where ``N=order``. + + Note that the code doesn't check that the "if" above is true: the user MUST make sure this + is the case for the final noise correlation function to be symmetric in the requested way. + + Normally, ``image.scale`` is used to determine the input pixel separation, and if + ``image.wcs`` is None, it will use the wcs of the noise. If the image has a non-uniform + WCS, the local uniform approximation at the center of the image will be used. + + If you are interested in a theoretical calculation of the variance in the final noise field + after imposing symmetry, the symmetrizeImage() method in fact returns this variance. + For example:: + + >>> variance = correlated_noise.symmetrizeImage(image, order=order) + + For context, in comparison with the `whitenImage` method for the case of noise + correlation functions that are roughly like those in the COSMOS HST data, the amount of + noise added to impose N-fold symmetry is usually much less than what is added to fully + whiten the noise. The usage of symmetrizeImage() is totally analogous to the usage of + `whitenImage`. + + Parameters: + image: The square input `Image` object. + order: The order at which to require the noise to be symmetric. All noise fields + are already 2-fold symmetric, so ``order`` should be an even integer >2. + [default: 4]. + + Returns: + the theoretically calculated variance of the combined noise fields in the updated image. + """ + # Check that the input has defined bounds + if not isinstance(image, Image): + raise TypeError("Input image not a galsim.Image object") + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Input image argument must have defined bounds.") + + # Check that the input is square in shape. + if image.array.shape[0] != image.array.shape[1]: + raise GalSimValueError("Input image must be square.", image.array.shape) + + # Check that the input order is an allowed value. + if order % 2 != 0 or order <= 2: + raise GalSimValueError("Order must be an even number >=4.", order) + + # If the profile has changed since last time (or if we have never been here before), + # clear out the stored values. Note that this cache is not the same as the one used for + # whitening. + self._clear_cache() + + if image.wcs is None: + wcs = self.wcs + else: + wcs = image.wcs.local(image.true_center) + + # Then retrieve or redraw the sqrt(power spectrum) needed for making the symmetrizing noise, + # and the total variance of the combination. + rootps_symmetrizing, variance = self._get_update_rootps_symmetrizing( + image.array.shape, wcs, order) + + # Finally generate a random field in Fourier space with the right PS and add to image. + noise_array = _generate_noise_from_rootps(self.rng, image.array.shape, rootps_symmetrizing) + image += Image(noise_array) + + # Return the variance to the interested user + return variance
+ +
[docs] def expand(self, scale): + """Scale the linear scale of correlations in this noise model by ``scale``. + + Scales the linear dimensions of the image by the factor ``scale``, e.g. + ``half_light_radius`` <-- ``half_light_radius * scale``. + + Parameters: + scale: The linear rescaling factor to apply. + + Returns: + a new `BaseCorrelatedNoise` object with the specified expansion. + """ + return BaseCorrelatedNoise(self.rng, self._profile.expand(scale), self.wcs)
+ +
[docs] def dilate(self, scale): + """Apply the appropriate changes to the scale and variance for when the object has + an applied dilation. + + Parameters: + scale: The linear dilation scale factor. + + Returns: + a new `BaseCorrelatedNoise` object with the specified dilation. + """ + # Expansion changes the flux by scale**2, dilate reverses that to conserve flux, + # so the variance needs to change by scale**-4. + return BaseCorrelatedNoise(self.rng, self._profile.expand(scale) / scale**4, self.wcs)
+ +
[docs] def magnify(self, mu): + """Apply the appropriate changes to the scale and variance for when the object has + an applied magnification ``mu``. + + Parameters: + mu: The lensing magnification + + Returns: + a new `BaseCorrelatedNoise` object with the specified magnification. + """ + return BaseCorrelatedNoise(self.rng, self._profile.magnify(mu), self.wcs)
+ +
[docs] def lens(self, g1, g2, mu): + """Apply the appropriate changes for when the object has an applied shear and magnification. + + Parameters: + g1: First component of lensing (reduced) shear to apply to the object. + g2: Second component of lensing (reduced) shear to apply to the object. + mu: Lensing magnification to apply to the object. + + Returns: + a new `BaseCorrelatedNoise` object with the specified shear and magnification. + """ + return BaseCorrelatedNoise(self.rng, self._profile.lens(g1,g2,mu), self.wcs)
+ +
[docs] def rotate(self, theta): + """Apply a rotation ``theta`` to this correlated noise model. + + Parameters: + theta: Rotation angle (`Angle` object, positive means anticlockwise). + + Returns: + a new `BaseCorrelatedNoise` object with the specified rotation. + """ + return BaseCorrelatedNoise(self.rng, self._profile.rotate(theta), self.wcs)
+ +
[docs] def shear(self, *args, **kwargs): + """Apply a shear to this correlated noise model, where arguments are either a `Shear`, + or arguments that will be used to initialize one. + + For more details about the allowed keyword arguments, see the `Shear` docstring. + + Parameters: + shear: The shear to be applied. Or, as described above, you may instead supply + parameters do construct a shear directly. eg. ``corr.shear(g1=g1,g2=g2)``. + + Returns: + a new `BaseCorrelatedNoise` object with the specified shear. + """ + return BaseCorrelatedNoise(self.rng, self._profile.shear(*args,**kwargs), self.wcs)
+ +
[docs] def transform(self, dudx, dudy, dvdx, dvdy): + """Apply an arbitrary jacobian transformation to this correlated noise model. + + Parameters: + dudx: du/dx, where (x,y) are the current coords, and (u,v) are the new coords. + dudy: du/dy, where (x,y) are the current coords, and (u,v) are the new coords. + dvdx: dv/dx, where (x,y) are the current coords, and (u,v) are the new coords. + dvdy: dv/dy, where (x,y) are the current coords, and (u,v) are the new coords. + + Returns: + a new `BaseCorrelatedNoise` object with the specified transformation. + """ + return BaseCorrelatedNoise(self.rng, self._profile.transform(dudx,dudy,dvdx,dvdy), + self.wcs)
+ +
[docs] def getVariance(self): + """Return the point variance of this noise field, equal to its correlation function value at + zero distance. + + This is the variance of values in an image filled with noise according to this model. + """ + # Test whether we can simply return the zero-lag correlation function value, which gives the + # variance of an image of noise generated according to this model + if self._profile.is_analytic_x: + variance = self._profile.xValue(_PositionD(0., 0.)) + else: + # If the profile has changed since last time (or if we have never been here before), + # clear out the stored values. + self._clear_cache() + + # Then use cached version or rebuild if necessary + if self._variance_cached is not None: + variance = self._variance_cached + else: + imtmp = Image(1, 1, dtype=float) + # GalSim internals handle this correctly w/out folding + self.drawImage(imtmp, scale=1.) + variance = imtmp(1, 1) + self._variance_cached = variance # Store variance for next time + return variance
+ +
[docs] def withVariance(self, variance): + """Set the point variance of the noise field, equal to its correlation function value at + zero distance, to an input ``variance``. The rest of the correlated noise field is scaled + proportionally. + + Parameters: + variance: The desired point variance in the noise. + + Returns: + a `BaseCorrelatedNoise` object with the new variance. + """ + if variance <= 0.: + raise GalSimValueError("variance must be > 0 in withVariance", variance) + variance_ratio = variance / self.getVariance() + return self * variance_ratio
+ +
[docs] def withScaledVariance(self, variance_ratio): + """Scale the entire correlated noise field by the given factor. + + This is equivalent to cn * variance_ratio. + + Parameters: + variance_ratio: The factor by which to scale the variance of the correlation + function profile. + + Returns: + a `BaseCorrelatedNoise` object whose variance and covariances have been scaled up by + the given factor. + """ + return BaseCorrelatedNoise(self.rng, self._profile * variance_ratio, self.wcs)
+ +
[docs] def convolvedWith(self, gsobject, gsparams=None): + """Convolve the correlated noise model with an input `GSObject`. + + The resulting correlated noise model will then give a statistical description of the noise + field that would result from convolving noise generated according to the initial correlated + noise with a kernel represented by ``gsobject`` (e.g. a PSF). + + The practical purpose of this method is that it allows us to model what is happening to + noise in the images from Hubble Space Telescope that we use for simulating PSF convolved + galaxies with the `RealGalaxy` class. + + This modifies the representation of the correlation function, but leaves the random number + generator unchanged. + + **Examples**: + + The following command simply applies a Moffat PSF with slope parameter beta=3. and + FWHM=0.7:: + + >>> cn = cn.convolvedWith(galsim.Moffat(beta=3., fwhm=0.7)) + + Often we will want to convolve with more than one function. For example, if we wanted to + simulate how a noise field would look if convolved with a ground-based PSF (such as the + Moffat above) and then rendered onto a new (typically larger) pixel grid, the following + example command demonstrates the syntax:: + + >>> cn = cn.convolvedWith( + ... galsim.Convolve([galsim.Deconvolve(galsim.Pixel(0.03)), + ... galsim.Pixel(0.2), galsim.Moffat(3., fwhm=0.7), + + Note, we also deconvolve by the original pixel, which should be the pixel size of the + image from which the ``correlated_noise`` was made. This command above is functionally + equivalent to:: + + >>> cn = cn.convolvedWith(galsim.Deconvolve(galsim.Pixel(0.03))) + >>> cn = cn.convolvedWith(galsim.Pixel(0.2)) + >>> cn = cn.convolvedWith(galsim.Moffat(beta=3., fwhm=0.7)) + + as is demanded for a linear operation such as convolution. + + Parameters: + gsobject: A `GSObject` or derived class instance representing the function + with which the user wants to convolve the correlated noise model. + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + the new `BaseCorrelatedNoise` of the convolved profile. + """ + conv = Convolve([self._profile, AutoCorrelate(gsobject,gsparams=gsparams)], + gsparams=gsparams) + return BaseCorrelatedNoise(self.rng, conv, self.wcs)
+ +
[docs] def drawImage(self, image=None, scale=None, wcs=None, dtype=None, add_to_image=False): + """A method for drawing profiles storing correlation functions. + + This is a mild reimplementation of the `GSObject.drawImage` method. The ``method`` is + automatically set to 'sb' and cannot be changed, and the ``gain`` is set to unity. + Also, not all the normal parameters of the `GSObject` method are available. + + If ``scale`` and ``wcs`` are not set, and the ``image`` has no ``wcs`` attribute, then this + will use the wcs of the `BaseCorrelatedNoise` object. + + Parameters: + image: If provided, this will be the image on which to draw the profile. + If ``image`` is None, then an automatically-sized `Image` will be + created. If ``image`` is given, but its bounds are undefined (e.g. if + it was constructed with ``image = galsim.Image()``), then it will be + resized appropriately based on the profile's size [default: None]. + scale: If provided, use this as the pixel scale for the image. [default: None] + wcs: If provided, use this as the wcs for the image (possibly overriding any + existing ``image.wcs``). At most one of ``scale`` or ``wcs`` may be + provided. [default: None] Note: If no WCS is provided either via + ``scale``, ``wcs`` or ``image.wcs``, then the noise object's wcs will + be used. + dtype: The data type to use for an automatically constructed image. Only + valid if ``image`` is None. [default: None, which means to use + numpy.float32] + add_to_image: Whether to add flux to the existing image rather than clear out + anything in the image before drawing. + Note: This requires that ``image`` be provided and that it have defined + bounds. [default: False] + + Returns: + an `Image` of the correlation function. + """ + wcs = self._profile._determine_wcs(scale, wcs, image, self.wcs) + + return self._profile.drawImage( + image=image, wcs=wcs, dtype=dtype, method='sb', gain=1., + add_to_image=add_to_image, use_true_center=False)
+ +
[docs] def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, + add_to_image=False): + """A method for drawing profiles storing correlation functions (i.e., power spectra) in + Fourier space. + + This is a mild reimplementation of the `GSObject.drawKImage` method. The ``gain`` is + automatically set to unity and cannot be changed. Also, not all the normal parameters of + the `GSObject` method are available. + + If ``scale`` is not set, and ``image`` has no ``wcs`` attribute, then this will use the + wcs of the `BaseCorrelatedNoise` object, which must be a `PixelScale`. + + Parameters: + image: If provided, this will be the `Image` onto which to draw the k-space + image. If ``image`` is None, then an automatically-sized image will be + created. If ``image`` is given, but its bounds are undefined, then it + will be resized appropriately based on the profile's size. + [default: None] + nx: If provided and ``image`` is None, use to set the x-direction size of + the image. Must be accompanied by ``ny``. + ny: If provided and ``image`` is None, use to set the y-direction size of + the image. Must be accompanied by ``nx``. + bounds: If provided and ``image`` is None, use to set the bounds of the image. + scale: If provided, use this as the pixel scale, dk, for the images. + If ``scale`` is None and ``image`` is given, then take the provided + images' pixel scale (which must be equal). + If ``scale`` is None and ``image`` is None, then use the Nyquist scale. + If ``scale <= 0`` (regardless of ``image``), then use the Nyquist scale. + [default: None] + add_to_image: Whether to add to the existing images rather than clear out + anything in the image before drawing. + Note: This requires that ``image`` be provided and that it has defined + bounds. [default: False] + + Returns: + the tuple of `Image` instances, ``(re, im)`` (created if necessary) + """ + return self._profile.drawKImage( + image=image, nx=nx, ny=ny, bounds=bounds, scale=scale, add_to_image=add_to_image)
+ + def _get_update_rootps(self, shape, wcs): + # Internal utility function for querying the rootps cache, used by applyTo(), + # whitenImage(), and symmetrizeImage() methods. + + # Query using the rfft2/irfft2 half-sized shape (shape[0], shape[1] // 2 + 1) + half_shape = (shape[0], shape[1] // 2 + 1) + key = (half_shape, wcs) + + # Use the cached value if possible. + rootps = self._rootps_cache.get(key, None) + + # If not, draw the correlation function to the desired size and resolution, then DFT to + # generate the required array of the square root of the power spectrum + if rootps is None: + # Draw this correlation function into an array. If this is not done at the same wcs as + # the original image from which the CF derives, even if the image is rotated, then this + # step requires interpolation and the newcf (used to generate the PS below) is thus + # approximate at some level + newcf = Image(shape[1], shape[0], wcs=wcs, dtype=float) + self.drawImage(newcf) + + # Since we just drew it, save the variance value for posterity. + var = newcf(newcf.bounds.center) + self._variance_cached = var + + if var <= 0.: # pragma: no cover This should be impossible... + raise GalSimError("CorrelatedNoise found to have negative variance.") + + # Then calculate the sqrt(PS) that will be used to generate the actual noise. First do + # the power spectrum (PS) + ps = np.fft.rfft2(newcf.array) + + # The PS we expect should be *purely* +ve, but there are reasons why this is not the + # case. One is that the PS is calculated from a correlation function CF that has not + # been rolled to be centred on the [0, 0] array element. Another reason is due to the + # approximate nature of the CF rendered above. Thus an abs(ps) will be necessary when + # calculating the sqrt(). + # This all means that the performance of correlated noise fields should always be tested + # for any given scientific application that requires high precision output. An example + # of such a test is the generation of noise whitened images of sheared RealGalaxies in + # Section 9.2 of the GalSim paper (Rowe, Jarvis, Mandelbaum et al. 2014) + + # Given all the above, it might make sense to warn the user if we do detect a PS that + # doesn't "look right" (i.e. has strongly negative values where these are not expected). + # This is the subject of Issue #587 on GalSim's GitHub repository page (see + # https://github.com/GalSim-developers/GalSim/issues/587) + + # For now we just take the sqrt(abs(PS)): + rootps = np.sqrt(np.abs(ps)) + + # Save this in the cache + self._rootps_cache[key] = rootps + + return rootps + + def _get_update_rootps_whitening(self, shape, wcs, headroom=1.05): + # Internal utility function for querying the rootps_whitening cache, used by the + # whitenImage() method, and calculate and update it if not present. + + # Returns: rootps_whitening, variance + + # Query using the rfft2/irfft2 half-sized shape (shape[0], shape[1] // 2 + 1) + half_shape = (shape[0], shape[1] // 2 + 1) + + key = (half_shape, wcs) + + # Use the cached values if possible. + rootps_whitening, variance = self._rootps_whitening_cache.get(key, (None,None)) + + # If not, calculate the whitening power spectrum as (almost) the smallest power spectrum + # that when added to rootps**2 gives a flat resultant power that is nowhere negative. + # Note that rootps = sqrt(power spectrum), and this procedure therefore works since power + # spectra add (rather like variances). The resulting power spectrum will be all positive + # (and thus physical). + if rootps_whitening is None: + + rootps = self._get_update_rootps(shape, wcs) + ps_whitening = -rootps * rootps + ps_whitening += np.abs(np.min(ps_whitening)) * headroom # Headroom adds a little extra + rootps_whitening = np.sqrt(ps_whitening) # variance, for "safety" + + # Finally calculate the theoretical combined variance to output alongside the image + # to be generated with the rootps_whitening. Note that although we use the [0, 0] + # element we could use any as the PS should be flat. + variance = rootps[0, 0]**2 + ps_whitening[0, 0] + + # Then add all this and the relevant wcs to the _rootps_whitening_cache + self._rootps_whitening_cache[key] = (rootps_whitening, variance) + + return rootps_whitening, variance + + def _get_update_rootps_symmetrizing(self, shape, wcs, order, headroom=1.02): + # Internal utility function for querying the ``rootps_symmetrizing`` cache, used by the + # symmetrizeImage() method, and calculate and update it if not present. + + # Returns: rootps_symmetrizing, variance + + # Query using the rfft2/irfft2 half-sized shape (shape[0], shape[1] // 2 + 1) + half_shape = (shape[0], shape[1] // 2 + 1) + + key = (half_shape, wcs, order) + + # Use the cached values if possible. + rootps_symmetrizing, variance = self._rootps_symmetrizing_cache.get(key, (None,None)) + + # If not, calculate the symmetrizing power spectrum as (almost) the smallest power spectrum + # that when added to rootps**2 gives a power that has N-fold symmetry, where `N=order`. + # Note that rootps = sqrt(power spectrum), and this procedure therefore works since power + # spectra add (rather like variances). The resulting power spectrum will be all positive + # (and thus physical). + if rootps_symmetrizing is None: + + rootps = self._get_update_rootps(shape, wcs) + ps_actual = rootps * rootps + # This routine will get a PS that is a symmetrized version of `ps_actual` at the desired + # order, that also satisfies the requirement of being >= ps_actual for all k values. + ps_symmetrized = self._get_symmetrized_ps(ps_actual, order) + ps_symmetrizing = ps_symmetrized * headroom - ps_actual # add a little extra variance + rootps_symmetrizing = np.sqrt(ps_symmetrizing) + + # Finally calculate the theoretical combined variance to output alongside the image to + # be generated with the rootps_symmetrizing. + # Here, unlike in _get_update_rootps_whitening, the final power spectrum is not flat, so + # we have to take the mean power instead of just using the [0, 0] element. + # Note that the mean of the power spectrum (fourier space) is the zero lag value in + # real space, which is the desired variance. + variance = np.mean(rootps**2 + ps_symmetrizing) + + # Then add all this and the relevant wcs to the _rootps_symmetrizing_cache + self._rootps_symmetrizing_cache[key] = (rootps_symmetrizing, variance) + + return rootps_symmetrizing, variance + + def _get_symmetrized_ps(self, ps, order): + # Internal utility function for taking an input power spectrum and generating a version of + # it with symmetry at a given order. + + # We make an image of the PS and turn it into an galsim.InterpolatedImage in order to carry + # out the necessary rotations using well-tested interpolation routines. We will also + # require the output to be strictly >= the input noise power spectrum, so that it should be + # possible to generate noise with power equal to the difference between the two power + # spectra. + + # Initialize a temporary copy of the original PS array, expanded to full size rather than + # the compact halfcomplex format that the PS is supplied in, which we will turn into an + # InterpolatedImage + # Check for an input ps which was even-sized along the y axis, which needs special treatment + do_expansion = False + if ps.shape[0] % 2 == 0: + do_expansion = True + # Then roll the PS by half its size in the leading dimension, centering it in that dimension + # (we will construct the expanded array to be centred in the other dimension) + ps_rolled = utilities.roll2d(ps, (ps.shape[0] // 2, 0)) + # Then create and fill an expanded-size tmp_arr with this PS + if not do_expansion: + tmp_arr = np.zeros((ps_rolled.shape[0], 2 * ps_rolled.shape[1] - 1)) # Both dims now odd + # Fill the first half, the RHS... + tmp_arr[:, ps.shape[1]-1:] = ps_rolled + # Then do the LHS of tmp_arr, straightforward enough, fill with the inverted RHS + tmp_arr[:, :ps.shape[1]-1] = ps_rolled[:, 1:][::-1, ::-1] + else: + # For the even-sized leading dimension ps, we have to do a tiny bit more work than + # the odd case... + tmp_arr = np.zeros((ps_rolled.shape[0] + 1, 2 * ps_rolled.shape[1] - 1)) + tmp_arr[:-1, ps_rolled.shape[1]-1:] = ps_rolled + tmp_arr[1:, :ps_rolled.shape[1]-1] = ps_rolled[:, 1:][::-1, ::-1] + # Then one tiny element breaks the symmetry of the above, so fix this + tmp_arr[-1, tmp_arr.shape[1] // 2] = tmp_arr[0, tmp_arr.shape[1] // 2] + + # Also initialize the array in which to build up the symmetrized PS. + final_arr = tmp_arr.copy() + tmp_im = Image(tmp_arr, scale=1) + tmp_obj = InterpolatedImage(tmp_im, calculate_maxk=False, calculate_stepk=False) + + # Now loop over the rotations by 2pi/order. + for i_rot in range(order): + # For the first one, we don't rotate at all. + if i_rot > 0: + # For later ones, rotate by 2pi/order, and draw it back into a new image. + tmp_obj = tmp_obj.rotate(2.*np.pi*radians/order) + tmp_im = Image(tmp_arr.shape[1], tmp_arr.shape[0], scale=1) + tmp_obj.drawImage(tmp_im, scale=1, method='sb') + final_arr[tmp_im.array > final_arr] = tmp_im.array[tmp_im.array > final_arr] + + # Now simply take the halfcomplex, compact stored part that we are interested in, + # remembering that the kx=ky=0 element is still in the centre + final_arr = final_arr[:, final_arr.shape[1]//2:] + # If we extended the array to be odd-sized along y, we have to go back to an even subarray + if do_expansion: final_arr = final_arr[:-1, :] + # Finally roll back the leading dimension + final_arr = utilities.roll2d(final_arr, (-(final_arr.shape[0] // 2), 0)) + # final_arr now contains the halfcomplex compact format PS of the maximum of the set of PS + # images rotated by 2pi/order, which (a) should be symmetric at the required order and + # (b) be the minimal array that is symmetric at that order and >= the original PS. So we do + # not have to add any more noise to ensure that the target symmetrized PS is always >= the + # original one. + return final_arr
+ +### +# Now a standalone utility function for generating noise according to an input (square rooted) +# Power Spectrum +# +def _generate_noise_from_rootps(rng, shape, rootps): + # Utility function for generating a NumPy array containing a Gaussian random noise field with + # a user-specified power spectrum also supplied as a NumPy array. + + # shape is the shape of the output array, needed because of the use of Hermitian symmetry to + # increase inverse FFT efficiency using the np.fft.irfft2 function (gets sent + # to the kwarg s of np.fft.irfft2) + # rootps is a NumPy array containing the square root of the discrete power spectrum ordered + # in two dimensions according to the usual DFT pattern for np.fft.rfft2 output + + # Returns a NumPy array (contiguous) of the requested shape, filled with the noise field. + + # Quickest to create Gaussian rng each time needed, so do that here... + # Note sigma scaling: 1/sqrt(2) needed so <|gaussvec|**2> = product(shape) + # shape needed because of the asymmetry in the 1/N^2 division in the NumPy FFT/iFFT + gd = GaussianDeviate(rng, sigma=np.sqrt(.5 * shape[0] * shape[1])) + + # Fill a couple of arrays with this noise + gvec_real = utilities.rand_arr((shape[0], shape[1]//2+1), gd) + gvec_imag = utilities.rand_arr((shape[0], shape[1]//2+1), gd) + # Prepare a complex vector upon which to impose Hermitian symmetry + gvec = gvec_real + 1J * gvec_imag + # Now impose requirements of Hermitian symmetry on random Gaussian halfcomplex array, and ensure + # self-conjugate elements (e.g. [0, 0]) are purely real and multiplied by sqrt(2) to compensate + # for lost variance, see https://github.com/GalSim-developers/GalSim/issues/563 + # First do the bits necessary for both odd and even shapes: + gvec[-1:shape[0]//2:-1, 0] = np.conj(gvec[1:(shape[0]+1)//2, 0]) + rt2 = np.sqrt(2.) + gvec[0, 0] = rt2 * gvec[0, 0].real + # Then make the changes necessary for even sized arrays + if shape[1] % 2 == 0: # x dimension even + gvec[-1:shape[0]//2:-1, shape[1]//2] = np.conj(gvec[1:(shape[0]+1)//2, shape[1]//2]) + gvec[0, shape[1]//2] = rt2 * gvec[0, shape[1]//2].real + if shape[0] % 2 == 0: # y dimension even + gvec[shape[0]//2, 0] = rt2 * gvec[shape[0]//2, 0].real + # Both dimensions even + if shape[1] % 2 == 0: + gvec[shape[0]//2, shape[1]//2] = rt2 * gvec[shape[0]//2, shape[1]//2].real + # Finally generate and return noise using the irfft + return np.fft.irfft2(gvec * rootps, s=shape) + + +### +# Then we define the CorrelatedNoise, which generates a correlation function by estimating it +# directly from images: +# +
[docs]class CorrelatedNoise(BaseCorrelatedNoise): + """A class that represents 2D correlated noise fields calculated from an input `Image`. + + This class stores an internal representation of a 2D, discrete correlation function, and allows + a number of subsequent operations including interpolation, shearing, magnification and rendering + of the correlation function profile into an output `Image`. + + The class also allows correlated Gaussian noise fields to be generated according to the + correlation function, and added to an `Image`: see `BaseCorrelatedNoise.applyTo`. + + It also provides methods for whitening or imposing N-fold symmetry on pre-existing noise that + shares the same spatial correlations: see `BaseCorrelatedNoise.whitenImage` and + `BaseCorrelatedNoise.symmetrizeImage`, respectively. + + It also allows the combination of multiple correlation functions by addition, and for the + scaling of the total variance they represent by scalar factors. + + Parameters: + image: The image from which to derive the correlated noise profile + rng: A `BaseDeviate` instance to use for generating the random numbers. + scale: If provided, use this as the pixel scale. Normally, the scale (or wcs) + is taken from the image.wcs field, but you may override that by + providing either scale or wcs. [default: use image.wcs if defined, + else 1.0, unless ``wcs`` is provided] + wcs: If provided, use this as the wcs for the image. At most one of + ``scale`` or ``wcs`` may be provided. [default: None] + x_interpolant: The interpolant to use for interpolating the image of the correlation + function. (See below.) [default: galsim.Linear()] + correct_periodicity: Whether to correct for the effects of periodicity. (See below.) + [default: True] + subtract_mean: Whether to subtract off the mean value from the image before computing + the correlation function. [default: False] + gsparams: An optional `GSParams` argument. [default: None] + + **Basic example**:: + + >>> cn = galsim.CorrelatedNoise(image, rng=rng) + + Instantiates a CorrelatedNoise using the pixel scale information contained in ``image.scale`` + (assumes the scale is unity if ``image.scale <= 0.``) by calculating the correlation function + in the input ``image``. The input ``rng`` must be a `BaseDeviate` or derived class instance, + setting the random number generation for the noise. + + **Optional Inputs**:: + + >>> cn = galsim.CorrelatedNoise(image, rng=rng, scale=0.2) + + The example above instantiates a CorrelatedNoise, but forces the use of the pixel scale + ``scale`` to set the units of the internal lookup table.:: + + >>> cn = galsim.CorrelatedNoise(image, rng=rng, x_interpolant=galsim.Lanczos(5)) + + The example above instantiates a CorrelatedNoise, but forces use of a non-default interpolant + for interpolation of the internal lookup table in real space. + + The default ``x_interpolant`` is ``galsim.Linear()``, which uses bilinear interpolation. + The use of this interpolant is an approximation that gives good empirical results without + requiring internal convolution of the correlation function profile by a `Pixel` object when + applying correlated noise to images: such an internal convolution has been found to be + computationally costly in practice, requiring the Fourier transform of very large arrays. + + The use of the bilinear interpolants means that the representation of correlated noise will be + noticeably inaccurate in at least the following two regimes: + + i) If the pixel scale of the desired final output (e.g. the target image of + `BaseCorrelatedNoise.drawImage`, `BaseCorrelatedNoise.applyTo` or + `BaseCorrelatedNoise.whitenImage`) is small relative to the separation between pixels + in the ``image`` used to instantiate ``cn`` as shown above. + ii) If the CorrelatedNoise instance ``cn`` was instantiated with an image of scale comparable + to that in the final output, and ``cn`` has been rotated or otherwise transformed (e.g. + via the `BaseCorrelatedNoise.rotate`, `BaseCorrelatedNoise.shear` methods; see below). + + Conversely, the approximation will work best in the case where the correlated noise used to + instantiate the ``cn`` is taken from an input image for which ``image.scale`` is smaller than + that in the desired output. This is the most common use case in the practical treatment of + correlated noise when simulating galaxies from space telescopes, such as COSMOS. + + Changing from the default bilinear interpolant is made possible, but not recommended. + In our validation tests, we found that the Linear interpolant usually gave the most + accurate results. + + There is also an option to switch off an internal correction for assumptions made about the + periodicity in the input noise image. If you wish to turn this off you may, e.g.:: + + >>> cn = galsim.CorrelatedNoise(image, rng=rng, correct_periodicity=False) + + The default and generally recommended setting is ``correct_periodicity=True``. + + Users should note that the internal calculation of the discrete correlation function in + ``image`` will assume that ``image`` is periodic across its boundaries, introducing a dilution + bias in the estimate of inter-pixel correlations that increases with separation. Unless you + know that the noise in ``image`` is indeed periodic (perhaps because you generated it to be so), + you will not generally wish to use the ``correct_periodicity=False`` option. + + By default, the image is not mean subtracted before the correlation function is estimated. To + do an internal mean subtraction, you can set the ``subtract_mean`` keyword to ``True``, e.g.:: + + >>> cn = galsim.CorrelatedNoise(image, rng=rng, subtract_mean=True) + + Using the ``subtract_mean`` option will introduce a small underestimation of variance and other + correlation function values due to a bias on the square of the sample mean. This bias reduces + as the input image becomes larger, and in the limit of uncorrelated noise tends to the constant + term ``variance/N**2`` for an N x N sized ``image``. + + It is therefore recommended that a background/sky subtraction is applied to the ``image`` before + it is given as an input to the `CorrelatedNoise`, allowing the default ``subtract_mean=False``. + If such a background model is global or based on large regions on sky then assuming that the + image has a zero population mean will be reasonable, and won't introduce a bias in covariances + from an imperfectly-estimated sample mean subtraction. If this is not possible, just be aware + that ``subtract_mean=True`` will bias the correlation function low to some level. + + You may also specify a gsparams argument. See the docstring for `GSParams` for more + information about this option. + + **Methods**: + + The main way that a CorrelatedNoise is used is to add correlated noise to an image. + The syntax:: + + >>> image.addNoise(cn) + + is preferred, although:: + + >>> cn.applyTo(image) + + is equivalent. See the `Image.addNoise` method docstring for more information. The + ``image.scale`` is used to get the pixel scale of the input image unless this is <= 0, in which + case a scale of 1 is assumed. + + A number of methods familiar from `GSObject` instances have also been implemented directly as + ``cn`` methods, so that the following commands are all legal:: + + >>> image = cn.drawImage(im, scale) + >>> cn = cn.shear(s) + >>> cn = cn.expand(m) + >>> cn = cn.rotate(theta * galsim.degrees) + >>> cn = cn.transform(dudx, dudy, dvdx, dvdy) + + See the individual method docstrings for more details. The ``shift`` method is not available + since a correlation function must always be centred and peaked at the origin. + + The methods:: + + >>> var = cn.getVariance() + >>> cn1 = cn.withVariance(variance) + >>> cn2 = cn.withScaledVariance(variance_ratio) + + can be used to get and set the point variance of the correlated noise, equivalent to the zero + separation distance correlation function value. + + The `BaseCorrelatedNoise.withVariance` method scales the whole internal correlation function so + that its point variance matches ``variance``. + + Similarly, `BaseCorrelatedNoise.withScaledVariance` scales the entire function by the given + factor. + + **Arithmetic Operators**: + + Addition, multiplication and division operators are defined to work in an intuitive way for + correlation functions. + + Addition works simply to add the internally-stored correlation functions, so that:: + + >>> cn3 = cn2 + cn1 + >>> cn4 += cn5 + + provides a representation of the correlation function of two linearly summed fields represented + by the individual correlation function operands. + + What happens to the internally stored random number generators in the examples above? For all + addition operations it is the `BaseDeviate` belonging to the instance on the *left-hand side* + of the operator that is retained. + + In the example above therefore, it is the random number generator from ``cn2`` that will be + stored and used by ``cn3``, and ``cn4`` will retain its random number generator after in-place + addition of ``cn5``. The random number generator of ``cn5`` is not affected by the operation. + + The multiplication and division operators, e.g.:: + + >>> cn1 /= 3. + >>> cn2 = cn1 * 3 + + scale the overall correlation function by a scalar operand. The random number generators are + not affected by these scaling operations. + """ + def __init__(self, image, rng=None, scale=None, wcs=None, x_interpolant=None, + correct_periodicity=True, subtract_mean=False, gsparams=None): + + # Check that the input image is in fact a galsim.ImageSIFD class instance + if not isinstance(image, Image): + raise TypeError("Input image not a galsim.Image object") + # Build a noise correlation function (CF) from the input image, using DFTs + # Calculate the power spectrum then a (preliminary) CF + ft_array = np.fft.rfft2(image.array) + ps_array = np.abs(ft_array)**2 # Using timeit abs() seems to have the slight speed edge over + # all other options tried, cf. results described by MJ in + # the optics.psf() function in optics.py + + # Need to normalize ps due to one-directional 1/N^2 in FFT conventions and the fact that + # we *squared* the ft_array to get ps_array: + ps_array /= np.prod(image.array.shape) + + if subtract_mean: # Quickest non-destructive way to make the PS correspond to the + # mean-subtracted case + ps_array[0, 0] = 0. + + # Then calculate the CF by inverse DFT + cf_array_prelim = np.fft.irfft2(ps_array, s=image.array.shape) + + store_rootps = True # Currently the ps_array above corresponds to cf, but this may change... + + # Apply a correction for the DFT assumption of periodicity unless user requests otherwise + if correct_periodicity: + cf_array_prelim *= _cf_periodicity_dilution_correction(cf_array_prelim.shape) + store_rootps = False + + # Roll CF array to put the centre in image centre. Remember that numpy stores data [y,x] + cf_array_prelim = utilities.roll2d( + cf_array_prelim, (cf_array_prelim.shape[0] // 2, cf_array_prelim.shape[1] // 2)) + + # The underlying C++ object is expecting the CF to be represented by an odd-dimensioned + # array with the central pixel denoting the zero-distance correlation (variance), even + # even if the input image was even-dimensioned on one or both sides. + # We therefore copy-paste and zero pad the CF calculated above to ensure that these + # expectations are met. + # + # Determine the largest dimension of the input image, and use it to generate an empty CF + # array for final output, padding by one to make odd if necessary: + cf_array = np.zeros(( + 1 + 2 * (cf_array_prelim.shape[0] // 2), + 1 + 2 * (cf_array_prelim.shape[1] // 2))) # using integer division + + # Then put the data from the prelim CF into this array + cf_array[0:cf_array_prelim.shape[0], 0:cf_array_prelim.shape[1]] = cf_array_prelim + # Then copy-invert-paste data from the leftmost column to the rightmost column, and lowest + # row to the uppermost row, if the original CF had even dimensions in the x and y + # directions, respectively (remembering again that NumPy stores data [y,x] in arrays) + if cf_array_prelim.shape[1] % 2 == 0: # first do x + lhs_column = cf_array[:, 0] + cf_array[:, cf_array_prelim.shape[1]] = lhs_column[::-1] # inverts order as required + if cf_array_prelim.shape[0] % 2 == 0: # then do y + bottom_row = cf_array[0, :] + cf_array[cf_array_prelim.shape[0], :] = bottom_row[::-1] # inverts order as required + + # Wrap correlation function in an image + cf_image = Image(np.ascontiguousarray(cf_array)) + + # Set the wcs if necessary + if wcs is not None: + if scale is not None: + raise GalSimIncompatibleValuesError("Cannot provide both wcs and scale", + scale=scale, wcs=scale) + if not isinstance(wcs, BaseWCS): + raise TypeError("wcs must be a BaseWCS instance") + if not wcs._isUniform: + raise GalSimValueError("Cannot provide non-uniform wcs", wcs) + cf_image.wcs = wcs + elif scale is not None: + cf_image.scale = scale + elif image is not None and image.wcs is not None: + cf_image.wcs = image.wcs.local(image.true_center) + + # If wcs is still None at this point or is a PixelScale <= 0., use scale=1. + if cf_image.wcs is None or (cf_image.wcs._isPixelScale and cf_image.wcs.scale <= 0): + cf_image.scale = 1. + + # If x_interpolant not specified on input, use bilinear + if x_interpolant is None: + x_interpolant = Linear() + else: + x_interpolant = utilities.convert_interpolant(x_interpolant) + + # Then initialize... + cf_object = InterpolatedImage( + cf_image, x_interpolant=x_interpolant, normalization="sb", + calculate_stepk=False, calculate_maxk=False, #<-these internal calculations do not seem + gsparams=gsparams) # to do very well with often sharp-peaked + # correlation function images... + BaseCorrelatedNoise.__init__(self, rng, cf_object, cf_image.wcs) + + if store_rootps: + # If it corresponds to the CF above, store in the cache + self._profile_for_cached = self._profile + shape = ps_array.shape + half_shape = (shape[0], shape[1] // 2 + 1) + key = (half_shape, cf_image.wcs) + self._rootps_cache[key] = np.sqrt(ps_array) + + self._image = image + + def __str__(self): + return "galsim.CorrelatedNoise(%s, wcs=%s)"%(self._image, self.wcs)
+ + +def _cf_periodicity_dilution_correction(cf_shape): + """Return an array containing the correction factor required for wrongly assuming periodicity + around noise field edges in an DFT estimate of the discrete correlation function. + + Uses the result calculated by MJ on GalSim Pull Request #366. + See https://github.com/GalSim-developers/GalSim/pull/366. + + Returns a 2D NumPy array with the same shape as the input parameter tuple ``cf_shape``. This + array contains the correction factor by which elements in the naive CorrelatedNoise estimate of + the discrete correlation function should be multiplied to correct for the erroneous assumption + of periodic boundaries in an input noise field. + + Note this should be applied only to correlation functions that have *not* been rolled to place + the origin at the array centre. The convention used here is that the lower left corner is the + [0, 0] origin, following standard FFT conventions (see e.g numpy.fft.fftfreq). You should + therefore only apply this correction before using galsim.utilities.roll2d() to recentre the + image of the correlation function. + """ + # First calculate the Delta_x, Delta_y + deltax, deltay = np.meshgrid( # Remember NumPy array shapes are [y, x] + np.fft.fftfreq(cf_shape[1]) * float(cf_shape[1]), + np.fft.fftfreq(cf_shape[0]) * float(cf_shape[0])) + # Then get the dilution correction + correction = ( + cf_shape[1] * cf_shape[0] / (cf_shape[1] - np.abs(deltax)) / (cf_shape[0] - np.abs(deltay))) + return correction + + +# Free function for returning a COSMOS noise field correlation function +
[docs]def getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0., x_interpolant=None, + gsparams=None): + """Returns a representation of correlated noise in the HST COSMOS F814W unrotated science coadd + images. + + See http://cosmos.astro.caltech.edu/astronomer/hst.html for information about the COSMOS survey, + and Leauthaud et al (2007) for detailed information about the unrotated F814W coadds used for + weak lensing science. + + This function uses a stacked estimate of the correlation function in COSMOS noise fields. + The correlation function was computed by the GalSim team as described in:: + + GalSim/devel/external/hst/make_cosmos_cfimage.py + + The resulting file is distributed with GalSim as:: + + os.path.join('galsim.meta_data.share_dir', 'acs_I_unrot_sci_20_cf.fits') + + Parameters: + file_name: If provided, override the usual location of the file with the given + file name. [default: None] + rng: If provided, a random number generator to use as the random number + generator of the resulting noise object. (may be any kind of + `BaseDeviate` object) [default: None, in which case, one will be + automatically created, using the time as a seed.] + cosmos_scale: COSMOS ACS F814W coadd image pixel scale in the units you are using to + describe `GSObject` instances and image scales in GalSim. [default: 0.03 + (arcsec), see below for more information.] + variance: Scales the correlation function so that its point variance, equivalent + to its value at zero separation distance, matches this value. + [default: 0., which means to use the variance in the original COSMOS + noise fields.] + x_interpolant: Forces use of a non-default interpolant for interpolation of the + internal lookup table in real space. See below for more details. + [default: galsim.Linear()] + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + a `BaseCorrelatedNoise` instance representing correlated noise in F814W COSMOS images. + + The default ``x_interpolant`` is a ``galsim.Linear()``, which uses bilinear interpolation. + The use of this interpolant is an approximation that gives good empirical results without + requiring internal convolution of the correlation function profile by a `Pixel` object when + applying correlated noise to images: such an internal convolution has been found to be + computationally costly in practice, requiring the Fourier transform of very large arrays. + + The use of the bilinear interpolants means that the representation of correlated noise will be + noticeably inaccurate in at least the following two regimes: + + 1. If the pixel scale of the desired final output (e.g. the target image of + `BaseCorrelatedNoise.drawImage`, `BaseCorrelatedNoise.applyTo` or + `BaseCorrelatedNoise.whitenImage`) is small relative to the separation between pixels in + the ``image`` used to instantiate ``cn`` as shown above. + 2. If the `BaseCorrelatedNoise` instance ``cn`` was instantiated with an image of scale + comparable to that in the final output, and ``cn`` has been rotated or otherwise transformed + (e.g. via the `BaseCorrelatedNoise.rotate`, `BaseCorrelatedNoise.shear` methods; see below). + + Conversely, the approximation will work best in the case where the correlated noise used to + instantiate the ``cn`` is taken from an input image for which ``image.scale`` is smaller than + that in the desired output. This is the most common use case in the practical treatment of + correlated noise when simulating galaxies from COSMOS, for which this function is expressly + designed. + + Changing from the default bilinear interpolant is made possible, but not recommended. + In our validation tests, we found that the Linear interpolant usually gave the most + accurate results. + + You may also specify a gsparams argument. See the docstring for `GSParams` for more + information about this option. + + .. note:: + + The ACS coadd images in COSMOS have a pixel scale of 0.03 arcsec, and so the pixel scale + ``cosmos_scale`` adopted in the representation of of the correlation function takes a + default value ``cosmos_scale = 0.03`` + + If you wish to use other units, ensure that the input keyword ``cosmos_scale`` takes the + value corresponding to 0.03 arcsec in your chosen system. + + **Example**: + + The following commands use this function to generate a 300 pixel x 300 pixel image of noise with + HST COSMOS correlation properties (substitute in your own file and path for the ``filestring``):: + + >>> rng = galsim.BaseDeviate(123456) + >>> noise = galsim.getCOSMOSNoise(rng=rng) + >>> image = galsim.ImageD(nx, ny, scale=0.03) + >>> image.addNoise(cf) + + If your image has some other pixel scale or a complicated WCS, then the applied noise will + have the correct correlations in world coordinates, which may not be what you wanted if you + expected the pixel-to-pixel correlations to match the COSMOS noise profile. However, in + this case, you would want to view your image with the COSMOS pixel scale when you apply + the noise:: + + >>> image = galsim.Image(nx, ny, wcs=complicated_wcs) + >>> noise = galsim.getCOSMOSNoise(rng=rng) + >>> image.view(wcs=noise.wcs).addNoise(noise) + + The FITS file ``out.fits`` should then contain an image of randomly-generated, COSMOS-like noise. + """ + if file_name is None: + file_name = os.path.join(meta_data.share_dir,'acs_I_unrot_sci_20_cf.fits') + + return BaseCorrelatedNoise.from_file(file_name, cosmos_scale, + rng=rng, variance=variance, + x_interpolant=x_interpolant, + gsparams=gsparams)
+ +
[docs]class UncorrelatedNoise(BaseCorrelatedNoise): + """A class that represents 2D correlated noise fields that are actually (at least initially) + uncorrelated. Subsequent applications of things like `BaseCorrelatedNoise.shear` or + `BaseCorrelatedNoise.convolvedWith` will induce correlations. + + The noise is characterized by a variance in each image pixel and a pixel size and shape. + The ``variance`` value refers to the noise variance in each pixel. If the pixels are square + (the usual case), you can specify the size using the ``scale`` parameter. If not, they + are effectively specified using the local wcs function that defines the pixel shape. i.e.:: + + >>> world_pix = wcs.toWorld(Pixel(1.)) + + should return the pixel profile in world coordinates. + + Parameters: + variance: The noise variance value to model as being uniform and uncorrelated + over the whole image. + rng: If provided, a random number generator to use as the random number + generator of the resulting noise object. (may be any kind of + `BaseDeviate` object) [default: None, in which case, one will be + automatically created, using the time as a seed.] + scale: If provided, use this as the pixel scale. [default: 1.0, unless ``wcs`` is + provided] + wcs: If provided, use this as the wcs for the image. At most one of ``scale`` + or ``wcs`` may be provided. [default: None] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, variance, rng=None, scale=None, wcs=None, gsparams=None): + + if variance < 0: + raise GalSimRangeError("Specified variance must be zero or positive.", + variance, 0, None) + + if wcs is not None: + if scale is not None: + raise GalSimIncompatibleValuesError("Cannot provide both wcs and scale", + scale=scale, wcs=wcs) + if not isinstance(wcs, BaseWCS): + raise TypeError("wcs must be a BaseWCS instance") + if not wcs._isUniform: + raise GalSimValueError("Cannot provide non-uniform wcs", wcs) + elif scale is not None: + wcs = PixelScale(scale) + else: + wcs = PixelScale(1.0) + + # Save the things that won't get saved by the base class, for use in repr. + self.variance = variance + self._gsparams = GSParams.check(gsparams) + + # Need variance == xvalue(0,0) after autoconvolution + # So the Pixel needs to have an amplitude of sigma at (0,0) + sigma = math.sqrt(variance) + pix = Pixel(scale=1.0, flux=sigma, gsparams=gsparams) + cf = AutoConvolve(pix, real_space=True, gsparams=gsparams) + world_cf = wcs.profileToWorld(cf) + # This gets the shape right, but not the amplitude. Need to rescale by the pixel area + world_cf *= wcs.pixelArea() + BaseCorrelatedNoise.__init__(self, rng, world_cf, wcs) + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams and not kwargs: return self + gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + return UncorrelatedNoise(self.variance, self.rng, wcs=self.wcs, gsparams=gsparams)
+ + def __repr__(self): + return "galsim.UncorrelatedNoise(%r, %r, wcs=%r, gsparams=%r)"%( + self.variance, self.rng, self.wcs, self._gsparams) + + def __str__(self): + return "galsim.UncorrelatedNoise(variance=%r, wcs=%s)"%(self.variance, self.wcs)
+ + +
[docs]class CovarianceSpectrum: + """Class to hold a `ChromaticSum` noise covariance spectrum (which is a generalization of a + power spectrum or equivalently a correlation function). + + Analogous to how a `galsim.CorrelatedNoise` object stores the variance and covariance of a + `galsim.Image` object, a `galsim.CovarianceSpectrum` stores the variance and covariance of the + Fourier mode amplitudes in different components of a `ChromaticSum`. + + Note that the covariance in question exists between different `SED` components of the + `ChromaticSum`, and not between different Fourier modes, which are assumed to be uncorrelated. + This structure arises naturally for a `ChromaticRealGalaxy` (see devel/modules/CGNotes.pdf for + more details). + + Parameters: + Sigma: A dictionary whose keys are tuples numerically indicating a pair of + `ChromaticSum` components whose Fourier mode amplitude covariances are + described by the corresponding `GSObject` values. + SEDs: `SED` instances of associated `ChromaticSum` components. + """ + def __init__(self, Sigma, SEDs): + self.Sigma = Sigma + self.SEDs = SEDs + + def __mul__(self, variance_ratio): + return self.withScaledVariance(variance_ratio) + + def transform(self, dudx, dudy, dvdx, dvdy): + Sigma = {} + for k, v in self.Sigma.items(): + Sigma[k] = v.transform(dudx, dudy, dvdx, dvdy) + return CovarianceSpectrum(Sigma, self.SEDs) + + def withScaledVariance(self, variance_ratio): + Sigma = {} + for k, v in self.Sigma.items(): + Sigma[k] = v * variance_ratio + return CovarianceSpectrum(Sigma, self.SEDs) + +
[docs] def toNoise(self, bandpass, PSF, wcs, rng=None): + """Derive the `CorrelatedNoise` object for the associated `ChromaticSum` when convolved + with ``PSF`` and drawn through ``bandpass`` onto pixels with specified ``wcs``. + + Parameters: + bandpass: `Bandpass` object representing filter image is drawn through. + PSF: output chromatic PSF to convolve by. + wcs: WCS of output pixel scale. + rng: Random number generator to forward to resulting CorrelatedNoise object. + + Returns: + CorrelatedNoise object. + """ + NSED = len(self.SEDs) + maxk = np.min([PSF.evaluateAtWavelength(bandpass.blue_limit).maxk, + PSF.evaluateAtWavelength(bandpass.red_limit).maxk]) + stepk = np.max([PSF.evaluateAtWavelength(bandpass.blue_limit).stepk, + PSF.evaluateAtWavelength(bandpass.red_limit).stepk]) + nk = 2*int(np.ceil(maxk/stepk)) + + PSF_eff_kimgs = np.empty((NSED, nk, nk), dtype=np.complex128) + for i, sed in enumerate(self.SEDs): + # Assume that PSF does not yet include pixel contribution, so add it in. + conv = Convolve(PSF, Pixel(wcs.scale, gsparams=PSF.gsparams)) * sed + PSF_eff_kimgs[i] = conv.drawKImage(bandpass, nx=nk, ny=nk, scale=stepk).array + pkout = np.zeros((nk, nk), dtype=np.float64) + for i in range(NSED): + for j in range(i, NSED): + s = self.Sigma[(i, j)].drawKImage(nx=nk, ny=nk, scale=stepk).array + pkout += (np.conj(PSF_eff_kimgs[i]) * s * PSF_eff_kimgs[j] * + (2 if i != j else 1)).real + pk = Image(pkout + 0j, scale=stepk, dtype=complex) # imag part should be zero + iki = InterpolatedKImage(pk) + iki *= wcs.pixelArea()**2 # determined this empirically + return BaseCorrelatedNoise(rng, iki, wcs)
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, CovarianceSpectrum) and + self.SEDs == other.SEDs and + self.Sigma == other.Sigma)) + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): + return hash(("galsim.CovarianceSpectrum", tuple(self.SEDs), + frozenset(self.Sigma.items()))) + + def __repr__(self): + return "galsim.CovarianceSpectrum(%r, %r)" % (self.Sigma, self.SEDs) + + def __str__(self): + sigma_str = '{' + ', '.join([':'.join((str(k),str(v))) for k,v in self.Sigma.items()]) + '}' + seds_str = '[' + ', '.join([str(s) for s in self.SEDs]) + ']' + return "galsim.CovarianceSpectrum(%s, %s)" % (sigma_str, seds_str)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/dcr.html b/docs/_build/html/_modules/galsim/dcr.html new file mode 100644 index 00000000000..8ba318e30de --- /dev/null +++ b/docs/_build/html/_modules/galsim/dcr.html @@ -0,0 +1,269 @@ + + + + + + galsim.dcr — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.dcr

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+from .errors import GalSimIncompatibleValuesError
+from .celestial import CelestialCoord
+from .angle import degrees, Angle
+
+
[docs]def air_refractive_index_minus_one(wave, pressure=69.328, temperature=293.15, H2O_pressure=1.067): + """Return the refractive index of air as function of wavelength. + + Uses the formulae given in Filippenko (1982), which appear to come from Edlen (1953), + and Coleman, Bozman, and Meggers (1960). The units of the original formula are non-SI, + being mmHg for pressure (and water vapor pressure), and degrees C for temperature. This + function accepts SI units, however, and transforms them when plugging into the formula. + + The default values for temperature, pressure and water vapor pressure are expected to be + appropriate for LSST at Cerro Pachon, Chile, but they are broadly reasonable for most + observatories. + + Parameters: + wave: Wavelength array in nanometers + pressure: Air pressure in kiloPascals. + temperature: Temperature in Kelvins. + H2O_pressure: Water vapor pressure in kiloPascals. + + Returns: + the refractive index minus 1. + """ + P = pressure * 7.50061683 # kPa -> mmHg + T = temperature - 273.15 # K -> C + W = H2O_pressure * 7.50061683 # kPa -> mmHg + + sigma_squared = 1.0 / (wave * 1.e-3)**2.0 # inverse wavenumber squared in micron^-2 + n_minus_one = (64.328 + (29498.1 / (146.0 - sigma_squared)) + + (255.4 / (41.0 - sigma_squared))) * 1.e-6 + n_minus_one *= P * (1.0 + (1.049 - 0.0157 * T) * 1.e-6 * P) / (720.883 * (1.0 + 0.003661 * T)) + n_minus_one -= (0.0624 - 0.000680 * sigma_squared)/(1.0 + 0.003661 * T) * W * 1.e-6 + return n_minus_one
+ +
[docs]def get_refraction(wave, zenith_angle, **kwargs): + """Compute the angle of refraction for a photon entering the atmosphere. + + Photons refract when transitioning from space, where the refractive index n = 1.0 exactly, to + air, where the refractive index is slightly greater than 1.0. This function computes the + change in zenith angle for a photon with a given wavelength. Output is a positive number of + radians, even though the apparent zenith angle technically decreases due to this effect. + + Parameters: + wave: Wavelength array in nanometers + zenith_angle: `Angle` from object to zenith + **kwargs: Keyword arguments to pass to air_refractive_index() to override default + pressure, temperature, and/or H2O_pressure. + + Returns: + the absolute value of change in zenith angle in radians. + """ + nm1 = air_refractive_index_minus_one(wave, **kwargs) + # The following line is equivalent to: + # n_squared = (nm1 + 1)**2 + # r0 = (n_squared - 1.0) / (2.0 * n_squared) + r0 = nm1 * (nm1+2) / 2.0 / (nm1**2 + 2*nm1 + 1) + return r0 * zenith_angle.tan()
+ +
[docs]def zenith_parallactic_angles(obj_coord, zenith_coord=None, HA=None, latitude=None): + """Compute the zenith angle and parallactic angle of a celestial coordinate, given either + the celestial coordinate of the zenith, or equivalently, the hour angle of the coordinate and + the latitude of the observer. This is useful for the function `ChromaticAtmosphere`. + + Parameters: + obj_coord: A `CelestialCoord` object for which the zenith and parallactic + angles will be computed. + zenith_coord: A `CelestialCoord` indicating the coordinates of the zenith. + HA: The hour angle (as an `Angle`) of the coordinate for which the + zenith and parallactic angles will be computed. + latitude: The observer's latitude, as an `Angle`. + + Returns: + the tuple (zenith_angle, parallactic_angle), each of which is an `Angle`. + """ + if zenith_coord is None: + if HA is None or latitude is None: + raise GalSimIncompatibleValuesError( + "Must provide either zenith_coord or (HA, latitude).", + HA=HA, latitude=latitude, zenith_coord=zenith_coord) + zenith_coord = CelestialCoord(HA + obj_coord.ra, latitude) + else: + if HA is not None or latitude is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both zenith_coord and (HA, latitude).", + HA=HA, latitude=latitude, zenith_coord=zenith_coord) + zenith_angle = obj_coord.distanceTo(zenith_coord) + NCP = CelestialCoord(0.0*degrees, 90*degrees) + parallactic_angle = obj_coord.angleBetween(NCP, zenith_coord) + return zenith_angle, parallactic_angle
+ +
[docs]def parse_dcr_angles(**kwargs): + """Parse the various options for specifying the zenith angle and parallactic angle + in `ChromaticAtmosphere`. + + Parameters: + zenith_angle: `Angle` from object to zenith [default: 0] + parallactic_angle: Parallactic angle, i.e. the position angle of the zenith, measured + from North through East. [default: 0] + obj_coord: Celestial coordinates of the object being drawn as a + `CelestialCoord`. [default: None] + zenith_coord: Celestial coordinates of the zenith as a `CelestialCoord`. + [default: None] + HA: Hour angle of the object as an `Angle`. [default: None] + latitude: Latitude of the observer as an `Angle`. [default: None] + **kwargs: For convenience, any other kwargs are returned back for further + processing. + + Returns: + zenith_angle, parallactic_angle, kw, where kw is any other kwargs that aren't relevant. + """ + if 'zenith_angle' in kwargs: + zenith_angle = kwargs.pop('zenith_angle') + parallactic_angle = kwargs.pop('parallactic_angle', 0.0*degrees) + if not isinstance(zenith_angle, Angle): + raise TypeError("zenith_angle must be a galsim.Angle.") + if not isinstance(parallactic_angle, Angle): + raise TypeError("parallactic_angle must be a galsim.Angles.") + elif 'obj_coord' in kwargs: + obj_coord = kwargs.pop('obj_coord') + zenith_coord = kwargs.pop('zenith_coord', None) + HA = kwargs.pop('HA', None) + latitude = kwargs.pop('latitude', None) + zenith_angle, parallactic_angle = zenith_parallactic_angles( + obj_coord=obj_coord, zenith_coord=zenith_coord, HA=HA, latitude=latitude) + else: + raise TypeError("Need to specify zenith_angle and parallactic_angle.") + return zenith_angle, parallactic_angle, kwargs
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/deltafunction.html b/docs/_build/html/_modules/galsim/deltafunction.html new file mode 100644 index 00000000000..cd4a2a5bc45 --- /dev/null +++ b/docs/_build/html/_modules/galsim/deltafunction.html @@ -0,0 +1,224 @@ + + + + + + galsim.deltafunction — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.deltafunction

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'DeltaFunction' ]
+
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import doc_inherit
+
+
+
[docs]class DeltaFunction(GSObject): + """A class describing a DeltaFunction surface brightness profile. + + The DeltaFunction surface brightness profile is characterized by a single property, + its ``flux``. + + A DeltaFunction can be initialized with a specified flux. + + Parameters: + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _opt_params = { "flux" : float } + + _mock_inf = 1.e300 # Some arbitrary very large number to use when we need infinity. + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, flux=1., gsparams=None): + self._gsparams = GSParams.check(gsparams) + self._flux = float(flux) + + def __eq__(self, other): + return (self is other or + (isinstance(other, DeltaFunction) and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.DeltaFunction", self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.DeltaFunction(flux=%r, gsparams=%r)'%(self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.DeltaFunction(' + if self.flux != 1.0: + s += 'flux=%s'%self.flux + s += ')' + return s + + @property + def _maxk(self): + return DeltaFunction._mock_inf + + @property + def _stepk(self): + return DeltaFunction._mock_inf + + @property + def _max_sb(self): + return DeltaFunction._mock_inf + + def _xValue(self, pos): + if pos.x == 0. and pos.y == 0.: + return DeltaFunction._mock_inf + else: + return 0. + + def _kValue(self, kpos): + return self.flux + + def _shoot(self, photons, rng): + flux_per_photon = self.flux / len(photons) + photons.x = 0. + photons.y = 0. + photons.flux = flux_per_photon + + def _drawKImage(self, image, jac=None): + image.array[:,:] = self.flux + +
[docs] @doc_inherit + def withFlux(self, flux): + return DeltaFunction(flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/des/des_meds.html b/docs/_build/html/_modules/galsim/des/des_meds.html new file mode 100644 index 00000000000..63adfd05206 --- /dev/null +++ b/docs/_build/html/_modules/galsim/des/des_meds.html @@ -0,0 +1,701 @@ + + + + + + galsim.des.des_meds — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.des.des_meds

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+import sys
+import time
+
+from ..errors import GalSimValueError, GalSimError, GalSimConfigError
+from ..wcs import UniformWCS, AffineTransform
+from ..image import Image, ImageI
+from ..fits import writeFile
+from ..position import PositionD
+from ..config import OutputBuilder, ExtraOutputBuilder, BuildImages, GetFinalExtraOutput
+from ..config import ParseValue, GetAllParams, GetCurrentValue
+from ..config import RegisterOutputType, RegisterExtraOutput
+from .._pyfits import pyfits
+
+# these image stamp sizes are available in MEDS format
+BOX_SIZES = [32,48,64,96,128,192,256]
+# while creating the meds file, all the data is stored in memory, and then written to disc once all
+# the necessary images have been created.
+# You can control the amound of memory used to prevent jamming your system.
+MAX_MEMORY = 1e9
+# Maximum number of exposures allowed per galaxy (incl. coadd)
+MAX_NCUTOUTS = 11
+# flags for unavailable data
+EMPTY_START_INDEX = 9999
+EMPTY_JAC_diag    = 1
+EMPTY_JAC_offdiag = 0
+EMPTY_SHIFT = 0
+
+
[docs]class MultiExposureObject: + """ + A class containing exposures for single object, along with other information. + + The MultiExposureObject class represents multiple exposure data for a single object for the + purpose of writing the images to a MEDS file. + + The `WriteMEDS` function can be used to write a list of MultiExposureObjects to a MEDS file. + + Parameters: + images: List of images of the object. + weight: List of weight images. [default: None] + badpix: List of bad pixel masks. [default: None] + seg: List of segmentation maps. [default: None] + psf: List of psf images. [default: None] + wcs: List of WCS transformations. [default: None] + id: Galaxy id. [default: 0] + + Attributes: + self.images: List of images of the object. + self.weight: List of weight maps. + self.seg: List of segmentation maps. + self.psf: List of psf images. + self.wcs: List of WCS transformations. + self.n_cutouts: Number of exposures. + self.box_size: Size of each exposure image. + """ + + def __init__(self, images, weight=None, badpix=None, seg=None, psf=None, + wcs=None, id=0, cutout_row=None, cutout_col=None): + + # Check that images is valid + if not isinstance(images,list): + raise TypeError('images should be a list') + if len(images) == 0: + raise GalSimValueError('no cutouts in this object', images) + + # Check that the box sizes are valid + for i in range(len(images)): + s = images[i].array.shape + if s[0] != s[1]: + raise GalSimValueError('Array shape %s is invalid. Must be square'%(str(s)), + images[i]) + if s[0] not in BOX_SIZES: + raise GalSimValueError('Array shape %s is invalid. Size must be in %s'%( + str(s),str(BOX_SIZES)), images[i]) + if i > 0 and s != images[0].array.shape: + raise GalSimValueError('Images must all be the same shape', images) + + # The others are optional, but if given, make sure they are ok. + for lst, name, isim in ( (weight, 'weight', True), (badpix, 'badpix', True), + (seg, 'seg', True), (psf, 'psf', False), (wcs, 'wcs', False) ): + if lst is not None: + if not isinstance(lst,list): + raise TypeError('%s should be a list'%name) + if len(lst) != len(images): + raise GalSimValueError('%s is the wrong length'%name, lst) + if isim: + for i in range(len(images)): + im1 = lst[i] + im2 = images[i] + if (im1.array.shape != im2.array.shape): + raise GalSimValueError( + "%s[%d] has the wrong shape."%(name, i), im1) + + # The PSF images don't have to be the same shape as the main images. + # But make sure all psf images are square and the same shape + if psf is not None: + s = psf[i].array.shape + if s[0] != s[1]: + raise GalSimValueError( + 'PSF array shape %s is invalid. Must be square'%(str(s)), psf[i]) + if s[0] not in BOX_SIZES: + raise GalSimValueError( + 'PSF array shape %s is invalid. Size must be in %s'%( + str(s),str(BOX_SIZES)), psf[i]) + if i > 0 and s != psf[0].array.shape: + raise GalSimValueError('PSF images must all be the same shape', psf[i]) + + # Check that wcs are Uniform and convert them to AffineTransforms in case they aren't. + if wcs is not None: + for i in range(len(wcs)): + if not isinstance(wcs[i], UniformWCS): + raise GalSimValueError('wcs list should contain UniformWCS objects', wcs) + elif not isinstance(wcs[i], AffineTransform): + wcs[i] = wcs[i].affine() + + self.id = id + self.images = [ im.view() for im in images ] + # Convert to 0-based images as preferred by meds. + for im in self.images: + # Note: making the list of views above means this won't change the originals. + im.setOrigin(0,0) + self.box_size = self.images[0].array.shape[0] + self.n_cutouts = len(self.images) + if psf is not None: + self.psf_box_size = self.images[0].array.shape[0] + else: + self.psf_box_size = 0 + + # If weight is not provided, create something sensible. + if weight is not None: + self.weight = weight + else: + self.weight = [Image(self.box_size, self.box_size, init_value=1)]*self.n_cutouts + + # If badpix is provided, combine it into the weight image. + if badpix is not None: + for i in range(len(badpix)): + mask = badpix[i].array != 0 + self.weight[i].array[mask] = 0. + + # If seg is not provided, use all 1's. + if seg is not None: + self.seg = seg + else: + self.seg = [ImageI(self.box_size, self.box_size, init_value=1)]*self.n_cutouts + + # If wcs is not provided, get it from the images. + if wcs is not None: + self.wcs = wcs + else: + self.wcs = [ im.wcs.affine(image_pos=im.true_center) for im in self.images ] + + # Normally you would supply cutout_row/cutout_col, since we can't usually + # assume objects are centered on the stamp. If not supplied, set them to + # the wcs origin (here that is the center of the stamp). + if cutout_row is not None: + self.cutout_row = cutout_row + else: + self.cutout_row = [ w.origin.y for w in self.wcs ] + if cutout_col is not None: + self.cutout_col = cutout_col + else: + self.cutout_col = [ w.origin.x for w in self.wcs ] + + # psf is not required, so leave it as None if not provided. + self.psf = psf
+ + +
[docs]def WriteMEDS(obj_list, file_name, clobber=True): + """ + Writes a MEDS file from a list of `MultiExposureObject` instances. + + Parameters: + obj_list: List of `MultiExposureObject` instances + file_name: Name of meds file to be written + clobber: Setting ``clobber=True`` when ``file_name`` is given will silently overwrite + existing files. [default True] + """ + + # initialise the catalog + cat = {} + cat['id'] = [] + cat['box_size'] = [] + cat['ra'] = [] + cat['dec'] = [] + cat['ncutout'] = [] + cat['start_row'] = [] + cat['dudrow'] = [] + cat['dudcol'] = [] + cat['dvdrow'] = [] + cat['dvdcol'] = [] + cat['orig_start_row'] = [] + cat['orig_start_col'] = [] + cat['cutout_row'] = [] + cat['cutout_col'] = [] + cat['psf_box_size'] = [] + cat['psf_start_row'] = [] + + # initialise the image vectors + vec = {} + vec['image'] = [] + vec['seg'] = [] + vec['weight'] = [] + vec['psf'] = [] + + # initialise the image vector index + n_vec = 0 + psf_n_vec = 0 + + # get number of objects + n_obj = len(obj_list) + + # loop over objects + for obj in obj_list: + + # initialise the start indices for each image + start_rows = np.ones(MAX_NCUTOUTS)*EMPTY_START_INDEX + psf_start_rows = np.ones(MAX_NCUTOUTS)*EMPTY_START_INDEX + dudrow = np.ones(MAX_NCUTOUTS)*EMPTY_JAC_diag + dudcol = np.ones(MAX_NCUTOUTS)*EMPTY_JAC_offdiag + dvdrow = np.ones(MAX_NCUTOUTS)*EMPTY_JAC_offdiag + dvdcol = np.ones(MAX_NCUTOUTS)*EMPTY_JAC_diag + cutout_row = np.ones(MAX_NCUTOUTS)*EMPTY_SHIFT + cutout_col = np.ones(MAX_NCUTOUTS)*EMPTY_SHIFT + # get the number of cutouts (exposures) + n_cutout = obj.n_cutouts + + # append the catalog for this object + cat['id'].append(obj.id) + cat['box_size'].append(obj.box_size) + # TODO: If the config defines a world position, get the right ra, dec here. + cat['ra'].append(0.) + cat['dec'].append(0.) + cat['ncutout'].append(n_cutout) + cat['psf_box_size'].append(obj.psf_box_size) + + # loop over cutouts + for i in range(n_cutout): + + # assign the start row to the end of image vector + start_rows[i] = n_vec + psf_start_rows[i] = psf_n_vec + # update n_vec to point to the end of image vector + n_vec += len(obj.images[i].array.flatten()) + if obj.psf is not None: + psf_n_vec += len(obj.psf[i].array.flatten()) + + # append the image vectors + vec['image'].append(obj.images[i].array.flatten()) + vec['seg'].append(obj.seg[i].array.flatten()) + vec['weight'].append(obj.weight[i].array.flatten()) + if obj.psf is not None: + vec['psf'].append(obj.psf[i].array.flatten()) + + # append cutout_row/col + cutout_row[i] = obj.cutout_row[i] + cutout_col[i] = obj.cutout_col[i] + + # append the Jacobian + # col == x + # row == y + dudcol[i] = obj.wcs[i].dudx + dudrow[i] = obj.wcs[i].dudy + dvdcol[i] = obj.wcs[i].dvdx + dvdrow[i] = obj.wcs[i].dvdy + + # check if we are running out of memory + if sys.getsizeof(vec,0) > MAX_MEMORY: # pragma: no cover + raise GalSimError( + "Running out of memory > %1.0fGB - you can increase the limit by changing " + "galsim.des_meds.MAX_MEMORY"%(MAX_MEMORY/1.e9)) + + # update the start rows fields in the catalog + cat['start_row'].append(start_rows) + cat['psf_start_row'].append(psf_start_rows) + + # add cutout_row/col + cat['cutout_row'].append(cutout_row) + cat['cutout_col'].append(cutout_col) + + # add lists of Jacobians + cat['dudrow'].append(dudrow) + cat['dudcol'].append(dudcol) + cat['dvdrow'].append(dvdrow) + cat['dvdcol'].append(dvdcol) + + # concatenate list to one big vector + vec['image'] = np.concatenate(vec['image']) + vec['seg'] = np.concatenate(vec['seg']) + vec['weight'] = np.concatenate(vec['weight']) + if obj.psf is not None: + vec['psf'] = np.concatenate(vec['psf']) + + # get the primary HDU + primary = pyfits.PrimaryHDU() + + # second hdu is the object_data + # cf. https://github.com/esheldon/meds/wiki/MEDS-Format + cols = [] + cols.append( pyfits.Column(name='id', format='K', array=cat['id'] ) ) + cols.append( pyfits.Column(name='number', format='K', array=cat['id'] ) ) + cols.append( pyfits.Column(name='ra', format='D', array=cat['ra'] ) ) + cols.append( pyfits.Column(name='dec', format='D', array=cat['dec'] ) ) + cols.append( pyfits.Column(name='box_size', format='K', array=cat['box_size'] ) ) + cols.append( pyfits.Column(name='ncutout', format='K', array=cat['ncutout'] ) ) + cols.append( pyfits.Column(name='file_id', format='%dK' % MAX_NCUTOUTS, + array=[1]*n_obj) ) + cols.append( pyfits.Column(name='start_row', format='%dK' % MAX_NCUTOUTS, + array=np.array(cat['start_row'])) ) + cols.append( pyfits.Column(name='orig_row', format='%dD' % MAX_NCUTOUTS, + array=[[0]*MAX_NCUTOUTS]*n_obj ) ) + cols.append( pyfits.Column(name='orig_col', format='%dD' % MAX_NCUTOUTS, + array=[[0]*MAX_NCUTOUTS]*n_obj ) ) + cols.append( pyfits.Column(name='orig_start_row', format='%dK' % MAX_NCUTOUTS, + array=[[0]*MAX_NCUTOUTS]*n_obj ) ) + cols.append( pyfits.Column(name='orig_start_col', format='%dK' % MAX_NCUTOUTS, + array=[[0]*MAX_NCUTOUTS]*n_obj ) ) + cols.append( pyfits.Column(name='cutout_row', format='%dD' % MAX_NCUTOUTS, + array=np.array(cat['cutout_row']) ) ) + cols.append( pyfits.Column(name='cutout_col', format='%dD' % MAX_NCUTOUTS, + array=np.array(cat['cutout_col']) ) ) + cols.append( pyfits.Column(name='dudrow', format='%dD' % MAX_NCUTOUTS, + array=np.array(cat['dudrow']) ) ) + cols.append( pyfits.Column(name='dudcol', format='%dD' % MAX_NCUTOUTS, + array=np.array(cat['dudcol']) ) ) + cols.append( pyfits.Column(name='dvdrow', format='%dD' % MAX_NCUTOUTS, + array=np.array(cat['dvdrow']) ) ) + cols.append( pyfits.Column(name='dvdcol', format='%dD' % MAX_NCUTOUTS, + array=np.array(cat['dvdcol']) ) ) + cols.append( pyfits.Column(name='psf_box_size', format='K', array=cat['psf_box_size'] ) ) + cols.append( pyfits.Column(name='psf_start_row', format='%dK' % MAX_NCUTOUTS, + array=np.array(cat['psf_start_row'])) ) + + + # Depending on the version of pyfits, one of these should work: + try: + object_data = pyfits.BinTableHDU.from_columns(cols) + object_data.name = 'object_data' + except AttributeError: # pragma: no cover + object_data = pyfits.new_table(pyfits.ColDefs(cols)) + object_data.update_ext_name('object_data') + + # third hdu is image_info + cols = [] + cols.append( pyfits.Column(name='image_path', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='image_ext', format='I', array=[0] )) + cols.append( pyfits.Column(name='weight_path', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='weight_ext', format='I', array=[0] )) + cols.append( pyfits.Column(name='seg_path', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='seg_ext', format='I', array=[0] )) + cols.append( pyfits.Column(name='bmask_path', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='bmask_ext', format='I', array=[0] )) + cols.append( pyfits.Column(name='bkg_path', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='bkg_ext', format='I', array=[0] )) + cols.append( pyfits.Column(name='image_id', format='K', array=[-1] )) + cols.append( pyfits.Column(name='image_flags', format='K', array=[-1] )) + cols.append( pyfits.Column(name='magzp', format='E', array=[30.] )) + cols.append( pyfits.Column(name='scale', format='E', array=[1.] )) + # TODO: Not sure if this is right! + cols.append( pyfits.Column(name='position_offset', format='D', array=[0.] )) + try: + image_info = pyfits.BinTableHDU.from_columns(cols) + image_info.name = 'image_info' + except AttributeError: # pragma: no cover + image_info = pyfits.new_table(pyfits.ColDefs(cols)) + image_info.update_ext_name('image_info') + + # fourth hdu is metadata + # default values? + cols = [] + cols.append( pyfits.Column(name='magzp_ref', format='E', array=[30.] )) + cols.append( pyfits.Column(name='DESDATA', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='cat_file', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='coadd_image_id',format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='coadd_file', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='coadd_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='coadd_seg_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='coadd_srclist', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='coadd_wt_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='coaddcat_file', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='coaddseg_file', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='cutout_file', format='A256', array=['generated_by_galsim'] )) + cols.append( pyfits.Column(name='max_boxsize', format='A3', array=['-1'] )) + cols.append( pyfits.Column(name='medsconf', format='A3', array=['x'] )) + cols.append( pyfits.Column(name='min_boxsize', format='A2', array=['-1'] )) + cols.append( pyfits.Column(name='se_badpix_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='se_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='se_wt_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='seg_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='psf_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='sky_hdu', format='K', array=[9999] )) + cols.append( pyfits.Column(name='fake_coadd_seg',format='K', array=[9999] )) + try: + metadata = pyfits.BinTableHDU.from_columns(cols) + metadata.name = 'metadata' + except AttributeError: # pragma: no cover + metadata = pyfits.new_table(pyfits.ColDefs(cols)) + metadata.update_ext_name('metadata') + + # rest of HDUs are image vectors + image_cutouts = pyfits.ImageHDU( vec['image'] , name='image_cutouts') + weight_cutouts = pyfits.ImageHDU( vec['weight'], name='weight_cutouts') + seg_cutouts = pyfits.ImageHDU( vec['seg'] , name='seg_cutouts') + + hdu_list = [ + primary, + object_data, + image_info, + metadata, + image_cutouts, + weight_cutouts, + seg_cutouts, + ] + + if obj.psf is not None: + psf_cutouts = pyfits.ImageHDU( vec['psf'], name='psf') + hdu_list.append(psf_cutouts) + + writeFile(file_name, pyfits.HDUList(hdu_list))
+ + +# Make the class that will +
[docs]class MEDSBuilder(OutputBuilder): + """This class lets you use the `MultiExposureObject` very simply as a special ``output`` + type when using config processing. + + It requires the use of ``galsim.des`` in the config files ``modules`` section. + """ + +
[docs] def buildImages(self, config, base, file_num, image_num, obj_num, ignore, logger): + """ + Build a meds file as specified in config. + + Parameters: + config: The configuration dict for the output field. + base: The base configuration dict. + file_num: The current file_num. + image_num: The current image_num. + obj_num: The current obj_num. + ignore: A list of parameters that are allowed to be in config that we can ignore + here. + logger: If given, a logger object to log progress. + + Returns: + A list of MultiExposureObjects. + + """ + t1 = time.time() + + if base.get('image',{}).get('type', 'Single') != 'Single': + raise GalSimConfigError( + "MEDS files are not compatible with image type %s."%base['image']['type']) + + req = { 'nobjects' : int , 'nstamps_per_object' : int } + opt = {'first_id' : int } + ignore += [ 'file_name', 'dir', 'nfiles' ] + params = GetAllParams(config,base,ignore=ignore,req=req, opt=opt)[0] + first_id = params.get('first_id', obj_num) + + nobjects = params['nobjects'] + nstamps_per_object = params['nstamps_per_object'] + ntot = nobjects * nstamps_per_object + + main_images = BuildImages(ntot, base, image_num=image_num, obj_num=obj_num, logger=logger) + + # grab list of offsets for cutout_row/cutout_col. + offsets = GetFinalExtraOutput('meds_get_offset', base, logger) + # cutout_row/col is the stamp center (**with the center of the first pixel + # being (0,0)**) + offset + centers = [0.5*im.array.shape[0]-0.5 for im in main_images] + cutout_rows = [c+offset.y for c,offset in zip(centers,offsets)] + cutout_cols = [c+offset.x for c,offset in zip(centers,offsets)] + + weight_images = GetFinalExtraOutput('weight', base, logger) + if 'badpix' in config: + badpix_images = GetFinalExtraOutput('badpix', base, logger) + else: + badpix_images = None + psf_images = GetFinalExtraOutput('psf', base, logger) + + obj_list = [] + for i in range(nobjects): + k1 = i*nstamps_per_object + k2 = (i+1)*nstamps_per_object + if badpix_images is not None: + bpk = badpix_images[k1:k2] + else: + bpk = None + obj = MultiExposureObject(images = main_images[k1:k2], + weight = weight_images[k1:k2], + badpix = bpk, + psf = psf_images[k1:k2], + id = first_id + i, + cutout_row = cutout_rows[k1:k2], + cutout_col = cutout_cols[k1:k2]) + obj_list.append(obj) + + return obj_list
+ +
[docs] def writeFile(self, data, file_name, config, base, logger): + """Write the data to a file. In this case a MEDS file. + + Parameters: + data: The data to write. Here, this is a list of `MultiExposureObject`. + file_name: The file_name to write to. + config: The configuration dict for the output field. + base: The base configuration dict. + logger: If given, a logger object to log progress. + """ + WriteMEDS(data, file_name)
+ +
[docs] def getNImages(self, config, base, file_num, logger=None): + # This gets called before starting work on the file, so we can use this opportunity + # to make sure that weight and psf processing are turned on. + # We just add these as empty dicts, so there is no hdu or file_name parameter, which + # means they won't actually output anything, but the images will be built, so we can use + # them in BuildMEDS above. + if 'weight' not in config: + config['weight'] = {} + if 'psf' not in config: + config['psf'] = {} + + # We use an extra output type to get the offsets of objects in stamps. + # It doesn't need any parameters. Just getting its name into the config dict is sufficient. + if 'meds_get_offset' not in config: + config['meds_get_offset']={} + + nobjects = ParseValue(config,'nobjects',base,int)[0] + nstamps_per_object = ParseValue(config,'nstamps_per_object',base,int)[0] + + ntot = nobjects * nstamps_per_object + return ntot
+ +# This extra output type simply saves the values of the image offsets when an +# object is drawn into the stamp. +
[docs]class OffsetBuilder(ExtraOutputBuilder): + """This "extra" output builder saves the stamp offset values for later use. + + It is used as a ``meds_get_offset`` field in the ``output`` section. + """ + + # The function to call at the end of building each stamp +
[docs] def processStamp(self, obj_num, config, base, logger): + offset = base['stamp_offset'] + stamp = base['stamp'] + if 'offset' in stamp: + offset += GetCurrentValue('offset', base['stamp'], PositionD, base) + self.scratch[obj_num] = offset
+ + # The function to call at the end of building each file to finalize the truth catalog +
[docs] def finalize(self, config, base, main_data, logger): + offsets_list = [] + obj_nums = sorted(self.scratch.keys()) + for obj_num in obj_nums: + offsets_list.append(self.scratch[obj_num]) + return offsets_list
+ +# Register these +RegisterOutputType('MEDS', MEDSBuilder()) +RegisterExtraOutput('meds_get_offset', OffsetBuilder()) + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/des/des_psfex.html b/docs/_build/html/_modules/galsim/des/des_psfex.html new file mode 100644 index 00000000000..88d88f9a1d5 --- /dev/null +++ b/docs/_build/html/_modules/galsim/des/des_psfex.html @@ -0,0 +1,499 @@ + + + + + + galsim.des.des_psfex — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.des.des_psfex

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import os
+import numpy as np
+
+from ..errors import GalSimIncompatibleValuesError, GalSimConfigError
+from ..fits import FitsHeader
+from ..wcs import readFromFitsHeader, PixelScale
+from ..position import PositionD
+from ..image import Image
+from ..interpolatedimage import InterpolatedImage
+from ..interpolant import Lanczos
+from ..gsparams import GSParams
+from ..config import InputLoader, RegisterInputType, RegisterObjectType
+from ..config import GetAllParams, GetInputObj
+from .._pyfits import pyfits
+
+
[docs]class DES_PSFEx: + r"""Class that handles DES files describing interpolated principal component images + of the PSF. These are usually stored as \*_psfcat.psf files. + + PSFEx is software written by Emmanuel Bertin. If you want more detail about PSFEx, please + check out the web site: + + http://www.astromatic.net/software/psfex + + It builds PSF objects from images of stars in a given exposure, finds a reasonable basis + set to describe those images, and then fits the coefficient of these bases as a function + of the ``(x,y)`` position on the image. + + Note that while the interpolation is done in image coordinates, GalSim usually deals with + object profiles in world coordinates. However, PSFEx does not consider the WCS of the + image when building its bases. The bases are built in image coordinates. So there are + two options to get GalSim to handle this difference. + + 1. Ignore the WCS of the original image. In this case, the \*.psf files have all the + information you need: + + >>> des_psfex = galsim.des.DES_PSFEx(fitpsf_file_name) + >>> image_pos = galsim.PositionD(image_x, image_y) # position in pixels on the image + >>> # NOT in arcsec on the sky! + >>> psf = des_psfex.getPSF(image_pos) # profile is in image coordinates + + The psf profile that is returned will be in image coordinates. Therefore, it should be + drawn onto an image with no wcs. (Or equivalently, one with ``scale = 1``.) If you want + to use this to convolve a galaxy profile, you would want to either project the galaxy + (typically constructed in world coordinates) to the correct image coordinates or project + the PSF up into world coordinates. + + 2. Build the PSF in world coordinates directly. The DES_PSFEx constructor can take an + extra argument, either ``image_file_name`` or ``wcs``, to tell GalSim what WCS to use for + the coversion between image and world coordinates. The former option is the name of + the file from which to read the WCS, which will often be more convenient, but you can + also just pass in a WCS object directly. + + >>> des_psfex = galsim.des.DES_PSFEx(fitpsf_file_name, image_file_name) + >>> image_pos = galsim.PositionD(image_x, image_y) # position in pixels on the image + >>> # NOT in arcsec on the sky! + >>> psf = des_psfex.getPSF(image_pos) # profile is in world coordinates + + This time the psf profile that is returned will already be in world coordinates as + GalSim normally expects, so you can use it in the normal ways. If you want to draw it + (or a convolved object) onto an image with the original WCS at that location, you can use + ``des_psfex.getLocalWCS(image_pos)`` for the local wcs at the location of the PSF. + + Note that the returned psf here already includes the pixel. This is what is sometimes + called an "effective PSF". Thus, you should not convolve by the pixel profile again + (nor integrate over the pixel). This would effectively include the pixel twice! + + In GalSim, you should always pass ``method='no_pixel'`` when drawing images of objects + convolved with PSFs produced with this class. Other drawing methods, such as photon shooting + (``method='phot'``) or an FFT (``method='fft'``), will result in convolving the pixel twice. + + Parameters: + file_name: The file name to be read in, or a pyfits HDU in which case it is + used directly instead of being opened. + image_file_name: The name of the fits file of the original image (needed for the + WCS information in the header). If unavailable, you may omit this + (or use None), but then the returned profiles will be in image + coordinates, not world coordinates. [default: None] + wcs: Optional way to provide the WCS if you already have it loaded from + the image file. [default: None] + dir: Optionally a directory name can be provided if the ``file_name`` + does not already include it. (The image file is assumed to be in + the same directory.) [default: None]. + """ + # For config, image_file_name is required, since that always works in world coordinates. + _req_params = { 'file_name' : str } + _opt_params = { 'dir' : str, 'image_file_name' : str } + _single_params = [] + _takes_rng = False + + def __init__(self, file_name, image_file_name=None, wcs=None, dir=None): + + if dir: + if not isinstance(file_name, str): + raise TypeError("file_name must be a string") + file_name = os.path.join(dir,file_name) + if image_file_name is not None: + image_file_name = os.path.join(dir,image_file_name) + self.file_name = file_name + if image_file_name: + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both image_file_name and wcs", + image_file_name=image_file_name, wcs=wcs) + header = FitsHeader(file_name=image_file_name) + wcs, origin = readFromFitsHeader(header) + self.wcs = wcs + elif wcs: + self.wcs = wcs + else: + self.wcs = None + self.read() + + def read(self): + if isinstance(self.file_name, str): + hdu_list = pyfits.open(self.file_name) + hdu = hdu_list[1] + else: + hdu = self.file_name + hdu_list = None + # Number of parameters used for the interpolation. We require this to be 2. + pol_naxis = hdu.header['POLNAXIS'] + + # These are the names of the two axes. Should be X_IMAGE, Y_IMAGE. + # If they aren't, then the way we use the interpolation will be wrong. + # Well, really they can also be XWIN_IMAGE, etc. So just check that it + # starts with X and ends with IMAGE. + pol_name1 = hdu.header['POLNAME1'] + pol_name2 = hdu.header['POLNAME2'] + + # Zero points and scale. Interpolation is in terms of (x-x0)/xscale, (y-y0)/yscale + pol_zero1 = hdu.header['POLZERO1'] + pol_zero2 = hdu.header['POLZERO2'] + pol_scal1 = hdu.header['POLSCAL1'] + pol_scal2 = hdu.header['POLSCAL2'] + + # This defines the number of "context groups". + # Here is Emmanuel's explanation: + # + # POLNGRP is the number of "context groups". A group represents a set of variables + # (SExtractor measurements or FITS header parameters if preceded with ":") which share + # the same maximum polynomial degree. For instance if x and y are in group 1, and the + # degree of that group is 2, and z is in group 2 with degree 1, the polynomial will + # consist of: + # 1, x, x^2, y, y.x, y^2, z, z.x^2, z.y, z.y.x, z.y^2 + # (see eq 14 in https://www.astromatic.net/pubsvn/software/psfex/trunk/doc/psfex.pdf ) + # By default, POLNGRP is 1 and the group contains X_IMAGE and Y_IMAGE measurements + # from SExtractor. + # + # For now, we require this to be 1, since I didn't have any files with POLNGRP != 1 to + # test on. + pol_ngrp = hdu.header['POLNGRP'] + + # Which group each item is in. We require group 1. + pol_group1 = hdu.header['POLGRP1'] + pol_group2 = hdu.header['POLGRP2'] + + # The degree of the polynomial. E.g. POLDEG1 = 2 means the values will be: + # 1, x, x^2, y, xy, y^2 + # If we start allowing POLNGRP > 1, there is a separate degree for each group. + pol_deg = hdu.header['POLDEG1'] + + # The number of axes in the basis object. We require this to be 3. + psf_naxis = hdu.header['PSFNAXIS'] + + # The first two axes are the image size of the PSF postage stamp. + psf_axis1 = hdu.header['PSFAXIS1'] + psf_axis2 = hdu.header['PSFAXIS2'] + + # The third axis is the direction of the polynomial interpolation. So it should + # be equal to (d+1)(d+2)/2. + psf_axis3 = hdu.header['PSFAXIS3'] + + # This is the PSF "sample size". Again, from Emmanuel: + # + # PSF_SAMP is the sampling step of the PSF. PSF_SAMP=0.5 means that the PSF model has + # two samples per original image pixel (superresolution, so in automatic mode it is a + # sign that the original images were undersampled) + # + # In other words, it can be thought of as a unit conversion: + # "image pixels" / "psfex pixels" + # So 1 image pixel = (1/psf_samp) psfex pixels. + psf_samp = hdu.header['PSF_SAMP'] + + # The basis object is a data cube (assuming PSFNAXIS==3) + basis = hdu.data.field('PSF_MASK')[0] + + # Make sure to close the hdu before we might raise exceptions. + if hdu_list: + hdu_list.close() + + # Check for valid values of all these things. + # Not sure which of these are actually required in PSFEx files, but this implementation + # assumes these things are true, so if this fails, we probably need to rework some aspect + # of this code. + try: + assert pol_naxis == 2 + assert pol_name1.startswith('X') and pol_name1.endswith('IMAGE') + assert pol_name2.startswith('Y') and pol_name2.endswith('IMAGE') + assert pol_ngrp == 1 + assert pol_group1 == 1 + assert pol_group2 == 1 + assert psf_naxis == 3 + assert psf_axis3 == ((pol_deg+1)*(pol_deg+2))//2 + assert basis.shape[0] == psf_axis3 + assert basis.shape[1] == psf_axis2 + assert basis.shape[2] == psf_axis1 + except AssertionError as e: + raise OSError("PSFEx file %s is not as expected.\n%r"%(self.file_name, e)) + + # Save some of these values for use in building the interpolated images + self.basis = basis + self.fit_order = pol_deg + self.fit_size = psf_axis3 + self.x_zero = pol_zero1 + self.y_zero = pol_zero2 + self.x_scale = pol_scal1 + self.y_scale = pol_scal2 + self.sample_scale = psf_samp + + def getSampleScale(self): + return self.sample_scale + +
[docs] def getLocalWCS(self, image_pos): + """If the original image was provided to the constructor, this will return the local + WCS at a given location in that original image. If not, this will return None. + + Parameter: + image_pos (Position): The position in pixels in the image. + + Returns: + A LocalWCS or None + """ + if self.wcs: + return self.wcs.local(image_pos) + else: + return None
+ +
[docs] def getPSF(self, image_pos, gsparams=None): + """Returns the PSF at position ``image_pos``. + + Parameters: + image_pos: The position in image coordinates at which to build the PSF. + gsparams: A `GSParams` instance to pass to the constructed `GSObject`. + [defualt: None] + + Returns: + the PSF as a `GSObject` + """ + # Build an image version of the numpy array + im = Image(self.getPSFArray(image_pos)) + + # Build the PSF profile in the image coordinate system. + psf = InterpolatedImage(im, scale=self.sample_scale, flux=1, + x_interpolant=Lanczos(3), gsparams=gsparams) + + # This brings if from image coordinates to world coordinates. + if self.wcs: + psf = self.wcs.toWorld(psf, image_pos=image_pos) + + return psf
+ +
[docs] def getPSFArray(self, image_pos): + """Returns the PSF image as a numpy array at position image_pos in image coordinates. + """ + xto = self._define_xto( (image_pos.x - self.x_zero) / self.x_scale ) + yto = self._define_xto( (image_pos.y - self.y_zero) / self.y_scale ) + order = self.fit_order + P = np.array([ xto[nx] * yto[ny] for ny in range(order+1) for nx in range(order+1-ny) ]) + assert len(P) == self.fit_size + ar = np.tensordot(P,self.basis,(0,0)).astype(np.float32) + # Note: This is equivalent to: + # ar = self.basis[0].astype(numpy.float32) + # for n in range(1,self.fit_order+1): + # for ny in range(n+1): + # nx = n-ny + # k = nx+ny*(self.fit_order+1)-ny*(ny-1)/2 + # ar += xto[nx] * yto[ny] * self.basis[k] + # which is pretty much Peter's version of this code. + return ar
+ + def _define_xto(self, x): + xto = np.empty(self.fit_order+1) + xto[0] = 1 + for i in range(1,self.fit_order+1): + xto[i] = x*xto[i-1] + return xto
+ + +class PSFExLoader(InputLoader): + # Allow the user to not provide the image file. In this case, we'll grab the wcs from the + # config dict. + def getKwargs(self, config, base, logger): + req = { 'file_name' : str } + opt = { 'dir' : str, 'image_file_name' : str } + kwargs, safe = GetAllParams(config, base, req=req, opt=opt) + + if 'image_file_name' not in kwargs: + if 'wcs' in base: + kwargs['wcs'] = base['wcs'] + else: + # Then we aren't doing normal config processing, so just use pixel scale = 1. + kwargs['wcs'] = PixelScale(1.) + + return kwargs, safe + +# First we need to add the class itself as a valid input_type. +RegisterInputType('des_psfex', PSFExLoader(DES_PSFEx)) + +# Also make a builder to create the PSF object for a given position. +# The builders require 4 args. +# config is a dictionary that includes 'type' plus other items you might want to allow or require. +# base is the top level config dictionary where some global variables are stored. +# ignore is a list of key words that might be in the config dictionary that you should ignore. +def BuildDES_PSFEx(config, base, ignore, gsparams, logger): + """Build a GSObject representing the PSFex model at the correct location in the image in a + config-processing context. + + This is used as object type ``DES_PSFEx`` in a config file. + + It requires the use of the ``des_psfex`` input field. + """ + des_psfex = GetInputObj('des_psfex', config, base, 'DES_PSFEx') + + opt = { 'flux' : float , 'num' : int, 'image_pos' : PositionD } + params, safe = GetAllParams(config, base, opt=opt, ignore=ignore) + + if 'image_pos' in params: + image_pos = params['image_pos'] + elif 'image_pos' in base: + image_pos = base['image_pos'] + else: + raise GalSimConfigError("DES_PSFEx requested, but no image_pos defined in base.") + + # Convert gsparams from a dict to an actual GSParams object + if gsparams: gsparams = GSParams(**gsparams) + else: gsparams = None + + #psf = des_psfex.getPSF(image_pos, gsparams=gsparams) + # Because of serialization issues, the above call doesn't work. So we need to + # repeat the internals of getPSF here. + # Also, this is why we have getSampleScale and getLocalWCS. The multiprocessing.managers + # stuff only makes available methods of classes that are proxied, not all the attributes. + # So this is the only way to access these attributes. + im = Image(des_psfex.getPSFArray(image_pos)) + psf =InterpolatedImage(im, scale=des_psfex.getSampleScale(), flux=1, + x_interpolant=Lanczos(3), gsparams=gsparams) + psf = des_psfex.getLocalWCS(image_pos).toWorld(psf) + + if 'flux' in params: + psf = psf.withFlux(params['flux']) + + # The second item here is "safe", a boolean that declares whether the returned value is + # safe to save and use again for later objects. In this case, we wouldn't want to do + # that, since they will be at different positions, so the interpolated PSF will be different. + return psf, False + +# Register this builder with the config framework: +RegisterObjectType('DES_PSFEx', BuildDES_PSFEx, input_type='des_psfex') +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/des/des_shapelet.html b/docs/_build/html/_modules/galsim/des/des_shapelet.html new file mode 100644 index 00000000000..4b133601d9f --- /dev/null +++ b/docs/_build/html/_modules/galsim/des/des_shapelet.html @@ -0,0 +1,353 @@ + + + + + + galsim.des.des_shapelet — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.des.des_shapelet

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+import os
+
+from ..errors import GalSimBoundsError, GalSimConfigError
+from ..position import PositionD
+from ..bounds import BoundsD
+from ..shapelet import Shapelet
+from ..gsparams import GSParams
+from ..config import InputLoader, RegisterInputType, RegisterObjectType
+from ..config import GetAllParams, GetInputObj, SkipThisObject
+from .._pyfits import pyfits
+
+
[docs]class DES_Shapelet: + """Class that handles DES files describing interpolated polar shapelet decompositions. + These are stored as ``*_fitpsf.fits`` files. They are not used in DES anymore, so this + class is at best of historical interest + + The shapelet PSFs measure a shapelet decomposition of each star and interpolate the shapelet + coefficients over the image positions. + + Unlike PSFEx, these PSF models are built directly in world coordinates. The shapelets know + about the WCS, so they are able to fit the shapelet model directly in terms of arcsec. + Thus, the getPSF function always returns a profile in world coordinates. + + Typical usage: + + >>> des_shapelet = galsim.des.DES_Shapelet(fitpsf_file_name) + >>> image_pos = galsim.PositionD(image_x, image_y) # position in pixels on the image + >>> # NOT in arcsec on the sky! + >>> psf = des_shapelet.getPSF(image_pos) # profile is in world coordinates + + Note that the returned psf here already includes the pixel. This is what is sometimes + called an "effective PSF". Thus, you should not convolve by the pixel profile again + (nor integrate over the pixel). This would effectively include the pixel twice! + + This class will only interpolate within the defining bounds. It won't extrapolate + beyond the bounding box of where the stars defined the interpolation. + If you try to use it with an invalid position, it will throw an IndexError. + You can check whether a position is valid with + + >>> if des_shapelet.bounds.includes(pos): + >>> psf = des_shapelet.getPSF(pos) + >>> else: + >>> [...skip this object...] + + + Parameters: + file_name: The name of the file to be read in. + dir: Optionally a directory name can be provided if the file names do not + already include it. [default: None] + """ + _req_params = { 'file_name' : str } + _opt_params = { 'dir' : str } + _single_params = [] + _takes_rng = False + + def __init__(self, file_name, dir=None): + if dir: + file_name = os.path.join(dir,file_name) + self.file_name = file_name + self.read_fits() + +
[docs] def read_fits(self): + """Read in a DES_Shapelet stored in FITS file. + """ + with pyfits.open(self.file_name) as fits: + cat = fits[1].data + # These fields each only contain one element, hence the [0]'s. + self.psf_order = cat.field('psf_order')[0] + self.psf_size = (self.psf_order+1) * (self.psf_order+2) // 2 + self.sigma = cat.field('sigma')[0] + self.fit_order = cat.field('fit_order')[0] + self.fit_size = (self.fit_order+1) * (self.fit_order+2) // 2 + self.npca = cat.field('npca')[0] + + self.bounds = BoundsD( + float(cat.field('xmin')[0]), float(cat.field('xmax')[0]), + float(cat.field('ymin')[0]), float(cat.field('ymax')[0])) + + self.ave_psf = cat.field('ave_psf')[0] + assert self.ave_psf.shape == (self.psf_size,) + + # Note: older pyfits versions don't get the shape right. + # For newer pyfits versions the reshape command should be a no op. + self.rot_matrix = cat.field('rot_matrix')[0].reshape((self.psf_size,self.npca)).T + assert self.rot_matrix.shape == (self.npca, self.psf_size) + + self.interp_matrix = cat.field('interp_matrix')[0].reshape((self.npca,self.fit_size)).T + assert self.interp_matrix.shape == (self.fit_size, self.npca)
+ + def getBounds(self): + return self.bounds + + def getSigma(self): + return self.sigma + + def getOrder(self): + return self.psf_order + +
[docs] def getPSF(self, image_pos, gsparams=None): + """Returns the PSF at position image_pos + + Parameters: + image_pos: The position in pixel units for which to build the PSF. + gsparams: An optional `GSParams` instance to pass to the constructed GSObject. + [default: None] + + Returns: + the PSF as a galsim.Shapelet instance + """ + psf = Shapelet(self.sigma, self.psf_order, self.getB(image_pos), gsparams=gsparams) + + # The fitpsf files were built with respect to (u,v) = (ra,dec). The GalSim convention is + # to use sky coordinates with u = -ra. So we need to flip the profile across the v axis + # to take u -> -u. + psf = psf.transform(-1,0,0,1) + + return psf
+ +
[docs] def getB(self, pos): + """Get the B vector as a numpy array at position pos + """ + if not self.bounds.includes(pos): + raise GalSimBoundsError("position in DES_Shapelet.getPSF is out of bounds", + pos, self.bounds) + + Px = self._definePxy(pos.x,self.bounds.xmin,self.bounds.xmax) + Py = self._definePxy(pos.y,self.bounds.ymin,self.bounds.ymax) + order = self.fit_order + P = np.array([ Px[n-q] * Py[q] for n in range(order+1) for q in range(n+1) ]) + assert len(P) == self.fit_size + + # Note: This is equivalent to: + # + # P = numpy.empty(self.fit_size) + # k = 0 + # for n in range(self.fit_order+1): + # for q in range(n+1): + # P[k] = Px[n-q] * Py[q] + # k = k+1 + + b1 = np.dot(P,self.interp_matrix) + b = np.dot(b1,self.rot_matrix) + assert len(b) == self.psf_size + b += self.ave_psf + return b
+ + def _definePxy(self, x, min, max): + x1 = (2.*x-min-max)/(max-min) + temp = np.empty(self.fit_order+1) + temp[0] = 1 + if self.fit_order > 0: # pragma: no branch (always true for file we have for testing.) + temp[1] = x1 + for i in range(2,self.fit_order+1): + temp[i] = ((2.*i-1.)*x1*temp[i-1] - (i-1.)*temp[i-2]) / float(i) + return temp
+ +# First we need to add the class itself as a valid input_type. +RegisterInputType('des_shapelet', InputLoader(DES_Shapelet)) + +# Also make a builder to create the PSF object for a given position. +# The builders require 4 args. +# config is a dictionary that includes 'type' plus other items you might want to allow or require. +# base is the top level config dictionary where some global variables are stored. +# ignore is a list of key words that might be in the config dictionary that you should ignore. +def BuildDES_Shapelet(config, base, ignore, gsparams, logger): + """Build a GSObject representing the shapelet model at the correct location in the image in a + config-processing context. + + This is used as object type ``DES_Shapelet`` in a config file. + + It requires the use of the ``des_shapelet`` input field. + """ + des_shapelet = GetInputObj('des_shapelet', config, base, 'DES_Shapelet') + + opt = { 'flux' : float , 'num' : int, 'image_pos' : PositionD } + params, safe = GetAllParams(config, base, opt=opt, ignore=ignore) + + if 'image_pos' in params: + image_pos = params['image_pos'] + elif 'image_pos' in base: + image_pos = base['image_pos'] + else: + raise GalSimConfigError("DES_Shapelet requested, but no image_pos defined in base.") + + # Convert gsparams from a dict to an actual GSParams object + if gsparams: gsparams = GSParams(**gsparams) + else: gsparams = None + + if des_shapelet.getBounds().includes(image_pos): + #psf = des_shapelet.getPSF(image_pos, gsparams) + # Because of serialization issues, the above call doesn't work. So we need to + # repeat the internals of getPSF here. + b = des_shapelet.getB(image_pos) + sigma = des_shapelet.getSigma() + order = des_shapelet.getOrder() + psf = Shapelet(sigma, order, b, gsparams=gsparams).transform(-1,0,0,1) + else: + message = 'Position '+str(image_pos)+' not in interpolation bounds: ' + message += str(des_shapelet.getBounds()) + raise SkipThisObject(message) + + if 'flux' in params: + psf = psf.withFlux(params['flux']) + + # The second item here is "safe", a boolean that declares whether the returned value is + # safe to save and use again for later objects. In this case, we wouldn't want to do + # that, since they will be at different positions, so the interpolated PSF will be different. + return psf, False + +# Register this builder with the config framework: +RegisterObjectType('DES_Shapelet', BuildDES_Shapelet, input_type='des_shapelet') + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/errors.html b/docs/_build/html/_modules/galsim/errors.html new file mode 100644 index 00000000000..34b6cee8e03 --- /dev/null +++ b/docs/_build/html/_modules/galsim/errors.html @@ -0,0 +1,547 @@ + + + + + + galsim.errors — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.errors

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+# Define the class hierarchy for errors and warnings emitted by GalSim that aren't
+# obviously one of the standard python errors.
+
+__all__ = [ 'GalSimError', 'GalSimValueError', 'GalSimKeyError', 'GalSimIndexError',
+            'GalSimRangeError', 'GalSimBoundsError', 'GalSimUndefinedBoundsError',
+            'GalSimImmutableError', 'GalSimIncompatibleValuesError',
+            'GalSimSEDError', 'GalSimHSMError', 'GalSimFFTSizeError',
+            'GalSimConfigError', 'GalSimConfigValueError',
+            'GalSimNotImplementedError',
+            'GalSimWarning', 'GalSimDeprecationWarning',
+            'convert_cpp_errors', 'galsim_warn', ]
+
+import warnings
+from contextlib import contextmanager
+
+# Note to developers about which exception to throw.
+#
+# Aside from the below classes, which should be preferred for most errors, we also
+# throw the following in some cases.
+#
+# TypeError:            Use this for errors that in a more strongly typed language would probably
+#                       be a compiler error. For instance, it is used for the following errors:
+#                       - a parameter with the wrong type
+#                       - the wrong number of unnamed args when processing `*args` by hand.
+#                       - missing or invalid kwargs when processing `**kwargs` by hand.
+#
+# OSError:              Use this for errors related to I/O, disk access, etc.
+#
+# NotImplementedError:  Use this for code that is not implemented by design and which will never
+#                       be implemented. E.g. GSObject and Position use this for their __init__
+#                       implementations, since it is invalid to instantiate the base class.
+#                       Use GalSimNotImplementedError for features that might someday be
+#                       implemented.
+#
+# AttributeError:       Use this only for an attempt to access an attribute that an object does not
+#                       have. Like TypeError, this should be reserved for things that a more
+#                       strongly typed language would catch at compile time. We don't currently
+#                       raise this anywhere in GalSim.
+#
+# RuntimeError:         Don't use this. Use GalSimError (or a subclass) for any run-time errors.
+#
+# ValueError:           Don't use this. Use one of the below exceptions that derive from ValueError.
+#
+# KeyError:             Don't use this. Use GalSimKeyError instead
+#
+# IndexError:           Don't use this. Use GalSimIndexError instead.
+#
+# std::runtime_error:   Use this for errors in the C++ layer, and use the convert_cpp_errors()
+#                       context to convert these errors into GalSimErrors. E.g. GSFitsWCS._invert_pv
+#                       uses this for non-convergence, which gets converted into GalSimError in
+#                       the Python layer.
+#                       When possible, it is preferable to guard against any such events by making
+#                       appropriate checks in the Python layer before dropping down into C++.
+#                       E.g. Image checks for anything that might cause the C++ Image class to
+#                       throw an exception and raises some kind of GalSim exception first.
+#                       Nonetheless, it is good practice to use the `with convert_cpp_errors()`
+#                       context for all calls to the C++ layer, just in case.
+#
+# GalSim-specific error classes:
+# ------------------------------
+#
+# GalSimError:          Use this for what would normally be a RuntimeError. Usually some exceptional
+#                       occurrence in otherwise correct code. E.g. an algorithm not converging or
+#                       a singular matrix encountered. It can also be used when the program does
+#                       things out of order; e.g. PowerSpectrum raises this when getShear and the
+#                       like are called before `buildGrid`. This is also the catch-all exception
+#                       to use when none of the other GalSim exceptions are appropriate.
+#
+# GalSimValueError:     Use this when a user provides an invalid value for a parameter.
+#                       Note: it has an optional argument to give a list of allowed values when
+#                       that is appropriate.
+#
+# GalSimKeyError        Use this for accessing a dict-like object with an invalid key. E.g.
+#                       FitsHeader and Catalog raise this for accessing invalid columns.
+#
+# GalSimIndexError      Use this for the equivalent of accessing a list-like object with an
+#                       invalid index. E.g. RealGalaxyCatalog and Catalog raise this for accessing
+#                       invalid rows.
+#
+# GalSimRangeError:     Use this when a user provides an value outside of some allowed range.
+#                       You should also give the min/max values of the allowed range. The max
+#                       is optional, because it's not uncommon for there to be no upper limit.
+#                       If only the upper limit is relevant and not the lower limit, you may
+#                       use min=None to indicate this.
+#
+# GalSimBoundsError:    Use this when a position is outside its allowed bounds. It's basically
+#                       the same as GalSimRangeError, but in two dimensions.
+#
+# GalSimUndefinedBoundsError:   Use this when the user tries to perform an operation on an
+#                               Image with undefined bounds (and which requires the bounds to be
+#                               defined).
+#
+# GalSimImmutableError: Use this when the user tries to modify an immutable Image in some way.
+#
+# GalSimIncompatibleValuesError:    Use this when two or more parameters are invalid when used
+#                                   in combination. E.g. providing more than one size parameter
+#                                   to Moffat, Sersic, Gaussian, etc. The conflicting values
+#                                   should be given as extra keywords to the constructor, which
+#                                   are mentioned in the error message.
+#                                   Note: if one of the conflicting values is self (e.g. adding two
+#                                   SEDs with different redshifts), then don't name the kwarg self.
+#                                   Instead use something like `self_sed=self`.
+#
+# GalSimSEDError:       Use this when an SED is required to be either spectral or dimensionless,
+#                       and the other kind of SED is provided.
+#
+# GalSimHSMError:       Use this for errors from the HSM algorithm.  They are emitted in C++, but
+#                       we use `with convert_cpp_errors(GalSimHSMError):` to convert them.
+#
+# GalSimFFTSizeError:   Use this when a requested FFT would exceed the relevant maximum_fft_size
+#                       for the object, so the recommendation is raise this parameter if that
+#                       is possible.
+#
+# GalSimConfigError:    Use this for errors processing a config dict.
+#
+# GalSimConfigValueError:   Use this when a config dict has a value that is invalid. Basically,
+#                           whenever you would normally use GalSimValueError when processing
+#                           a config dict, you should use this instead.
+#
+# GalSimNotImplementedError:  Use this for features that we have not yet implemented, but which may
+#                             be implemented someday. So it's not a necessarily invalid usage, just
+#                             something that doesn't work currently.
+
+
[docs]class GalSimError(RuntimeError): + """The base class for GalSim-specific run-time errors. + """ + # Minimal version of these to make GalSimError reprable and picklable. + def __repr__(self): return 'galsim.GalSimError(%r)'%(str(self)) + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __hash__(self): return hash(repr(self))
+ + +
[docs]class GalSimValueError(GalSimError, ValueError): + """A GalSim-specific exception class indicating that some user-input value is invalid. + + Attributes: + value: The invalid value + allowed_values: A list of allowed values if appropriate (may be None) + """ + def __init__(self, message, value, allowed_values=None): + self.message = message + self.value = value + self.allowed_values = allowed_values + + message += " Value {0!s}".format(value) + if allowed_values: + message += " not in {0!s}".format(allowed_values) + super(GalSimValueError, self).__init__(message) + + def __repr__(self): + return 'galsim.GalSimValueError(%r,%r,%r)'%(self.message, self.value, self.allowed_values) + def __reduce__(self): # Need to override this whenever constructor take extra params + return GalSimValueError, (self.message, self.value, self.allowed_values)
+ + +
[docs]class GalSimKeyError(GalSimError, KeyError): + """A GalSim-specific exception class indicating an attempt to access a dict-like object + with an invalid key. + + Attributes: + key: The invalid key + """ + def __init__(self, message, key): + self.message = message + self.key = key + super(GalSimKeyError, self).__init__(message, key) # Need to pass key or pickle fails. + + def __str__(self): + return self.message + " Key {0!s}".format(self.key) + + def __repr__(self): + return 'galsim.GalSimKeyError(%r,%r)'%(self.message, self.key)
+ + +
[docs]class GalSimIndexError(GalSimError, IndexError): + """A GalSim-specific exception class indicating an attempt to access a list-like object + with an invalid index. + + Attributes: + index: The invalid index + """ + def __init__(self, message, index): + self.message = message + self.index = index + super(GalSimIndexError, self).__init__(message, index) + + def __str__(self): + return self.message + " Index {0!s}".format(self.index) + + def __repr__(self): + return 'galsim.GalSimIndexError(%r,%r)'%(self.message, self.index)
+ + +
[docs]class GalSimRangeError(GalSimError, ValueError): + """A GalSim-specific exception class indicating that some user-input value is + outside of the allowed range of values. + + Attributes: + value: The invalid value + min: The minimum allowed value (may be None) + max: The maximum allowed value (may be None) + """ + def __init__(self, message, value, min, max=None): + self.message = message + self.value = value + self.min = min + self.max = max + + message += " Value {0!s} not in range [{1!s}, {2!s}]".format(value, min, max) + super(GalSimRangeError, self).__init__(message) + + def __repr__(self): + return 'galsim.GalSimRangeError(%r,%r,%r,%r)'%(self.message, self.value, self.min, self.max) + def __reduce__(self): + return GalSimRangeError, (self.message, self.value, self.min, self.max)
+ + +
[docs]class GalSimBoundsError(GalSimError, ValueError): + """A GalSim-specific exception class indicating that some user-input position is + outside of the allowed bounds. + + Attributes: + pos: The invalid position + bounds: The bounds in which it was expected to fall + """ + def __init__(self, message, pos, bounds): + self.message = message + self.pos = pos + self.bounds = bounds + + message += " {0!s} not in {1!s}".format(pos, bounds) + super(GalSimBoundsError, self).__init__(message) + + def __repr__(self): + return 'galsim.GalSimBoundsError(%r,%r,%r)'%(self.message, self.pos, self.bounds) + def __reduce__(self): + return GalSimBoundsError, (self.message, self.pos, self.bounds)
+ + +
[docs]class GalSimUndefinedBoundsError(GalSimError): + """A GalSim-specific exception class indicating an attempt to access the extent of + a `Bounds` instance that has not yet been defined. + """ + def __repr__(self): + return 'galsim.GalSimUndefinedBoundsError(%r)'%(str(self))
+ + +
[docs]class GalSimImmutableError(GalSimError): + """A GalSim-specific exception class indicating an attempt to modify an immutable image. + + Attributes: + image: The image that the user attempted to modify + """ + def __init__(self, message, image): + self.message = message + self.image = image + + message += " Image: {0!s}".format(image) + super(GalSimImmutableError, self).__init__(message) + + def __repr__(self): + return 'galsim.GalSimImmutableError(%r,%r)'%(self.message, self.image) + def __reduce__(self): + return GalSimImmutableError, (self.message, self.image)
+ + +
[docs]class GalSimIncompatibleValuesError(GalSimError, ValueError, TypeError): + """A GalSim-specific exception class indicating that 2 or more user-input values are + incompatible as given. + + Attributes: + values: A dict of {name : value} giving the values that in combination are invalid. + """ + def __init__(self, message, values={}, **kwargs): + self.message = message + self.values = dict(values, **kwargs) + + message += " Values {0!s}".format(self.values) + super(GalSimIncompatibleValuesError, self).__init__(message) + + # Note: the repr of values can rearrange the items, but the dicts should compare equal. + def __eq__(self, other): + return (self is other or + (isinstance(other, GalSimIncompatibleValuesError) and + self.message == other.message and + self.values == other.values)) + def __repr__(self): + return 'galsim.GalSimIncompatibleValuesError(%r,%r)'%(self.message, self.values) + def __reduce__(self): + return GalSimIncompatibleValuesError, (self.message, self.values)
+ + +
[docs]class GalSimSEDError(GalSimError, TypeError): + """A GalSim-specific exception class indicating an attempt to do something invalid for the + kind of `SED` that is present. Typically involving a dimensionless `SED` where a spectral + `SED` is required (or vice versa). + + Attributes: + sed: The invalid `SED` + """ + def __init__(self, message, sed): + self.message = message + self.sed = sed + + message += " SED: {0!s}".format(sed) + super(GalSimSEDError, self).__init__(message) + + def __repr__(self): + return 'galsim.GalSimSEDError(%r,%r)'%(self.message, self.sed) + def __reduce__(self): + return GalSimSEDError, (self.message, self.sed)
+ + +
[docs]class GalSimHSMError(GalSimError): + """A GalSim-specific exception class indicating some kind of failure of the HSM algorithms + """ + def __repr__(self): + return 'galsim.GalSimHSMError(%r)'%(str(self))
+ + +
[docs]class GalSimFFTSizeError(GalSimError): + """A GalSim-specific exception class indicating that a requested FFT exceeds the relevant + maximum_fft_size. + + Attributes: + size: The size that was deemed too large + mem: The estimated memory that would be required (in GB) for the FFT. + """ + def __init__(self, message, size): + self.message = message + self.size = size + self.mem = size * size * 24. / 1024**3 + message += "\nThe required FFT size would be {0} x {0}, which requires ".format(size) + message += "{0:.2f} GB of memory.\n".format(self.mem) + message += "If you can handle the large FFT, you may update gsparams.maximum_fft_size." + super(GalSimFFTSizeError, self).__init__(message) + + def __repr__(self): + return 'galsim.GalSimFFTSizeError(%r,%r)'%(self.message, self.size) + def __reduce__(self): + return GalSimFFTSizeError, (self.message, self.size)
+ + +
[docs]class GalSimConfigError(GalSimError, ValueError): + """A GalSim-specific exception class indicating some kind of failure processing a + configuration file. + """ + def __repr__(self): + return 'galsim.GalSimConfigError(%r)'%(str(self))
+ + +
[docs]class GalSimConfigValueError(GalSimValueError, GalSimConfigError): + """A GalSim-specific exception class indicating that a config entry has an invalid value. + + Attributes: + value: The invalid value + allowed_values: A list of allowed values if appropriate (may be None) + """ + def __repr__(self): + return 'galsim.GalSimConfigValueError(%r,%r,%r)'%( + self.message, self.value, self.allowed_values) + def __reduce__(self): + return GalSimConfigValueError, (self.message, self.value, self.allowed_values)
+ + +
[docs]class GalSimNotImplementedError(GalSimError, NotImplementedError): + """A GalSim-specific exception class indicating that the feature being attempted is not + currently implemented. + + If this is a feature you feel you need, please open an issue about it at + + https://github.com/GalSim-developers/GalSim/issues + + Even better, feel free to offer to contribute code to implement the feature. + """ + def __repr__(self): + return 'galsim.GalSimNotImplementedError(%r)'%(str(self))
+ + +# Note: Can use galsim_warn to raise warnings with this warning class. +
[docs]class GalSimWarning(UserWarning): + """The base class for GalSim-emitted warnings. + """ + def __repr__(self): return 'galsim.GalSimWarning(%r)'%(str(self)) + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __hash__(self): return hash(repr(self))
+ + +# Note: By default python ignores DeprecationWarnings. Apparently they are really +# for python system deprecations. GalSim deprecations are thus only subclassed from +# GalSimWarning, not DeprecationWarning. +
[docs]class GalSimDeprecationWarning(GalSimWarning): + """A GalSim-specific warning class used for deprecation warnings. + """ + def __repr__(self): return 'galsim.GalSimDeprecationWarning(%r)'%(str(self))
+ +@contextmanager +def convert_cpp_errors(error_type=GalSimError): + try: + yield + except RuntimeError as err: + raise error_type(str(err)) from None + +def galsim_warn(message): + """A helper function for emitting a GalSimWarning with the given message + """ + warnings.warn(message, GalSimWarning) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/exponential.html b/docs/_build/html/_modules/galsim/exponential.html new file mode 100644 index 00000000000..d710240385d --- /dev/null +++ b/docs/_build/html/_modules/galsim/exponential.html @@ -0,0 +1,286 @@ + + + + + + galsim.exponential — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.exponential

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Exponential' ]
+
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimIncompatibleValuesError
+
+
+
[docs]class Exponential(GSObject): + r"""A class describing an exponential profile. + + Surface brightness profile with + + .. math:: + I(r) \sim e^{-r/r_0} + + where :math:`r_0` is the ``scale_radius``. This is a special case of the `Sersic` profile, + but is given a separate class since the Fourier transform has closed form and can be generated + without lookup tables. + + An Exponential can be initialized using one (and only one) of two possible size parameters: + ``scale_radius`` or ``half_light_radius``. Exactly one of these two is required. + + Parameters: + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + scale_radius: The scale radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _opt_params = { "flux" : float } + _single_params = [ { "scale_radius" : float , "half_light_radius" : float } ] + + # The half-light-radius is not analytic, but can be calculated numerically + # by iterative solution of equation: + # (re / r0) = ln[(re / r0) + 1] + ln(2) + _hlr_factor = 1.6783469900166605 + _one_third = 1./3. + _inv_twopi = 0.15915494309189535 + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, half_light_radius=None, scale_radius=None, flux=1., gsparams=None): + if half_light_radius is not None: + if scale_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius and half_light_radius may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) + else: + scale_radius = half_light_radius / Exponential._hlr_factor + elif scale_radius is None: + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) + self._r0 = float(scale_radius) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + self._inv_r0 = 1./self._r0 + self._norm = self._flux * Exponential._inv_twopi * self._inv_r0**2 + + @lazy_property + def _sbp(self): + return _galsim.SBExponential(self._r0, self._flux, self.gsparams._gsp) + + @property + def scale_radius(self): + """The scale radius of the profile. + """ + return self._r0 + @property + def half_light_radius(self): + """The half-light radius of the profile. + """ + return self._r0 * Exponential._hlr_factor + + def __eq__(self, other): + return (self is other or + (isinstance(other, Exponential) and + self.scale_radius == other.scale_radius and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Exponential", self.scale_radius, self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.Exponential(scale_radius=%r, flux=%r, gsparams=%r)'%( + self.scale_radius, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Exponential(scale_radius=%s'%self.scale_radius + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return (self.gsparams.maxk_threshold ** -Exponential._one_third) / self.scale_radius + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _max_sb(self): + return self._norm + + def _xValue(self, pos): + r = math.sqrt(pos.x**2 + pos.y**2) + return self._norm * math.exp(-r * self._inv_r0) + + def _kValue(self, kpos): + ksqp1 = (kpos.x**2 + kpos.y**2) * self._r0**2 + 1. + return self._flux / (ksqp1 * math.sqrt(ksqp1)) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Exponential(scale_radius=self.scale_radius, flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/fft.html b/docs/_build/html/_modules/galsim/fft.html new file mode 100644 index 00000000000..893db2c07a0 --- /dev/null +++ b/docs/_build/html/_modules/galsim/fft.html @@ -0,0 +1,409 @@ + + + + + + galsim.fft — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.fft

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+
+from . import _galsim
+from .image import Image, ImageD, ImageCD
+from .bounds import BoundsI
+from .errors import GalSimValueError, convert_cpp_errors
+
+
[docs]def fft2(a, shift_in=False, shift_out=False): + """Compute the 2-dimensional discrete Fourier Transform. + + For valid inputs, the result is equivalent to numpy.fft.fft2(a), but usually faster.:: + + >>> ka1 = numpy.fft.fft2(a) + >>> ka2 = galsim.fft.fft2(a) + + Restrictions on this version vs the numpy version: + + - The input array must be 2-dimensional. + - The size in each direction must be even. (Ideally 2^k or 3*2^k for speed, but this is + not required.) + - If it has a real dtype, it will be coerced to numpy.float64. + - If it has a complex dtype, it will be coerced to numpy.complex128. + + The returned array will be complex with dtype numpy.complex128. + + If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input.:: + + >>> ka1 = numpy.fft.fft2(numpy.fft.fftshift(a)) + >>> ka2 = galsim.fft.fft2(a, shift_in=True) + + If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output.:: + + >>> ka1 = numpy.fft.fftshift(numpy.fft.fft2(a)) + >>> ka2 = galsim.fft.fft2(a, shift_out=True) + + Parameters: + a: The input array to be transformed + shift_in: Whether to shift the input array so that the center is moved to (0,0). + [default: False] + shift_out: Whether to shift the output array so that the center is moved to (0,0). + [default: False] + + Returns: + a complex numpy array + """ + s = a.shape + if len(s) != 2: + raise GalSimValueError("Input array must be 2D.",s) + M, N = s + Mo2 = M // 2 + No2 = N // 2 + + if M != Mo2*2 or N != No2*2: + raise GalSimValueError("Input array must have even sizes.",s) + + if a.dtype.kind == 'c': + a = a.astype(np.complex128, copy=False) + xim = ImageCD(a, xmin = -No2, ymin = -Mo2) + kim = ImageCD(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) + with convert_cpp_errors(): + _galsim.cfft(xim._image, kim._image, False, shift_in, shift_out) + kar = kim.array + else: + a = a.astype(np.float64, copy=False) + xim = ImageD(a, xmin = -No2, ymin = -Mo2) + + # This works, but it's a bit slower. + #kim = ImageCD(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) + #_galsim.cfft(xim._image, kim._image, False, shift_in, shift_out) + #kar = kim.array + + # Faster to start with rfft2 version + rkim = ImageCD(BoundsI(0,No2,-Mo2,Mo2-1)) + with convert_cpp_errors(): + _galsim.rfft(xim._image, rkim._image, shift_in, shift_out) + # This only returns kx >= 0. Fill out the full image. + kar = np.empty( (M,N), dtype=np.complex128) + rkar = rkim.array + if shift_out: + kar[:,No2:N] = rkar[:,0:No2] + kar[0,0:No2] = rkar[0,No2:0:-1].conjugate() + kar[1:Mo2,0:No2] = rkar[M-1:Mo2:-1,No2:0:-1].conjugate() + kar[Mo2:M,0:No2] = rkar[Mo2:0:-1,No2:0:-1].conjugate() + else: + kar[:,0:No2] = rkar[:,0:No2] + kar[0,No2:N] = rkar[0,No2:0:-1].conjugate() + kar[1:M,No2:N] = rkar[M-1:0:-1,No2:0:-1].conjugate() + return kar
+ + +
[docs]def ifft2(a, shift_in=False, shift_out=False): + """Compute the 2-dimensional inverse discrete Fourier Transform. + + For valid inputs, the result is equivalent to numpy.fft.ifft2(a), but usually faster.:: + + >>> a1 = numpy.fft.ifft2(ka) + >>> a2 = galsim.fft.ifft2(ka) + + Restrictions on this version vs the numpy version: + + - The array must be 2-dimensional. + - The size in each direction must be even. (Ideally 2^k or 3*2^k for speed, but this is + not required.) + - The array is assumed to be Hermitian, which means the k values with kx<0 are assumed + to be equal to the conjuate of their inverse. This will always be the case if + a is an output of fft2 (with a real input array). i.e. + + - for kx >= N/2, ky > 0: a[ky, kx] == a[N-ky, N-kx].conjugate() + - for kx >= N/2, ky = 0: a[0, kx] == a[0, N-kx].conjugate() + + Only the elements a[:,0:N/2+1] are accessed by this function. + - If it has a real dtype, it will be coerced to numpy.float64. + - If it has a complex dtype, it will be coerced to numpy.complex128. + + The returned array will be complex with dtype numpy.complex128. + + If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input:: + + >>> a1 = numpy.fft.ifft2(numpy.fft.fftshift(ka)) + >>> a2 = galsim.fft.ifft2(ka, shift_in=True) + + If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output:: + + >>> a1 = numpy.fft.fftshift(numpy.fft.ifft2(ka)) + >>> a2 = galsim.fft.ifft2(ka, shift_out=True) + + Parameters: + a: The input array to be transformed + shift_in: Whether to shift the input array so that the center is moved to (0,0). + [default: False] + shift_out: Whether to shift the output array so that the center is moved to (0,0). + [default: False] + + Returns: + a complex numpy array + """ + s = a.shape + if len(s) != 2: + raise GalSimValueError("Input array must be 2D.",s) + M,N = s + Mo2 = M // 2 + No2 = N // 2 + + if M != Mo2*2 or N != No2*2: + raise GalSimValueError("Input array must have even sizes.",s) + + if a.dtype.kind == 'c': + a = a.astype(np.complex128, copy=False) + kim = ImageCD(a, xmin = -No2, ymin = -Mo2) + else: + a = a.astype(np.float64, copy=False) + kim = ImageD(a, xmin = -No2, ymin = -Mo2) + xim = ImageCD(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) + with convert_cpp_errors(): + _galsim.cfft(kim._image, xim._image, True, shift_in, shift_out) + return xim.array
+ + +
[docs]def rfft2(a, shift_in=False, shift_out=False): + """Compute the one-dimensional discrete Fourier Transform for real input. + + For valid inputs, the result is equivalent to numpy.fft.rfft2(a), but usually faster.:: + + >>> ka1 = numpy.fft.rfft2(a) + >>> ka2 = galsim.fft.rfft2(a) + + Restrictions on this version vs the numpy version: + + - The input array must be 2-dimensional. + - If it does not have dtype numpy.float64, it will be coerced to numpy.float64. + - The size in each direction must be even. (Ideally 2^k or 3*2^k for speed, but this is + not required.) + + The returned array will be complex with dtype numpy.complex128. + + If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input.:: + + >>> ka1 = numpy.fft.rfft2(numpy.fft.fftshift(a)) + >>> ka2 = galsim.fft.rfft2(a, shift_in=True) + + If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output.:: + + >>> ka1 = numpy.fft.fftshift(numpy.fft.rfft2(a),axes=(0,)) + >>> ka2 = galsim.fft.rfft2(a, shift_out=True) + + Parameters: + a: The input array to be transformed + shift_in: Whether to shift the input array so that the center is moved to (0,0). + [default: False] + shift_out: Whether to shift the output array so that the center is moved to (0,0). + [default: False] + + Returns: + a complex numpy array + """ + s = a.shape + if len(s) != 2: + raise GalSimValueError("Input array must be 2D.",s) + M,N = s + Mo2 = M // 2 + No2 = N // 2 + + if M != Mo2*2 or N != No2*2: + raise GalSimValueError("Input array must have even sizes.",s) + + a = a.astype(np.float64, copy=False) + xim = ImageD(a, xmin = -No2, ymin = -Mo2) + kim = ImageCD(BoundsI(0,No2,-Mo2,Mo2-1)) + with convert_cpp_errors(): + _galsim.rfft(xim._image, kim._image, shift_in, shift_out) + return kim.array
+ + +
[docs]def irfft2(a, shift_in=False, shift_out=False): + """Compute the 2-dimensional inverse FFT of a real array. + + For valid inputs, the result is equivalent to numpy.fft.irfft2(a), but usually faster.:: + + >>> a1 = numpy.fft.irfft2(ka) + >>> a2 = galsim.fft.irfft2(ka) + + Restrictions on this version vs the numpy version: + + - The array must be 2-dimensional. + - If it does not have dtype numpy.complex128, it will be coerced to numpy.complex128. + - It must have shape (M, N/2+1). + - The size M must be even. (Ideally 2^k or 3*2^k for speed, but this is not required.) + + The returned array will be real with dtype numpy.float64. + + If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input.:: + + >>> a1 = numpy.fft.irfft2(numpy.fft.fftshift(a, axes=(0,))) + >>> a2 = galsim.fft.irfft2(a, shift_in=True) + + If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output.:: + + >>> a1 = numpy.fft.fftshift(numpy.fft.irfft2(a)) + >>> a2 = galsim.fft.irfft2(a, shift_out=True) + + Parameters: + a: The input array to be transformed + shift_in: Whether to shift the input array so that the center is moved to (0,0). + [default: False] + shift_out: Whether to shift the output array so that the center is moved to (0,0). + [default: False] + + Returns: + a real numpy array + """ + s = a.shape + if len(s) != 2: + raise GalSimValueError("Input array must be 2D.",s) + M,No2 = s + No2 -= 1 # s is (M,No2+1) + Mo2 = M // 2 + + if M != Mo2*2: + raise GalSimValueError("Input array must have even sizes.",s) + + a = a.astype(np.complex128, copy=False) + kim = ImageCD(a, xmin = 0, ymin = -Mo2) + xim = ImageD(BoundsI(-No2,No2+1,-Mo2,Mo2-1)) + with convert_cpp_errors(): + _galsim.irfft(kim._image, xim._image, shift_in, shift_out) + xim = xim.subImage(BoundsI(-No2,No2-1,-Mo2,Mo2-1)) + return xim.array
+ + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/fits.html b/docs/_build/html/_modules/galsim/fits.html new file mode 100644 index 00000000000..636c713eb08 --- /dev/null +++ b/docs/_build/html/_modules/galsim/fits.html @@ -0,0 +1,1520 @@ + + + + + + galsim.fits — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.fits

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+# Most things require the full specification, e.g. galsim.fits.read.
+# Only FitsHeader is brought into the main galsim namespace.
+
+__all__ = [ 'FitsHeader' ]
+
+import os
+import io
+import numpy as np
+import copy
+import subprocess
+import gzip
+import bz2
+from io import BytesIO
+
+from .image import Image
+from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, galsim_warn
+from ._utilities import basestring, ensure_dir
+from ._pyfits import pyfits
+from .bounds import BoundsI
+from .wcs import readFromFitsHeader
+
+
+##############################################################################################
+#
+# We start off with some helper functions for some common operations that will be used in
+# more than one of our primary read and write functions.
+#
+##############################################################################################
+
+def _parse_compression(compression, file_name):
+    file_compress = None
+    pyfits_compress = None
+    if compression == 'rice' or compression == 'RICE_1': pyfits_compress = 'RICE_1'
+    elif compression == 'gzip_tile' or compression == 'GZIP_1': pyfits_compress = 'GZIP_1'
+    elif compression == 'hcompress' or compression == 'HCOMPRESS_1': pyfits_compress = 'HCOMPRESS_1'
+    elif compression == 'plio' or compression == 'PLIO_1': pyfits_compress = 'PLIO_1'
+    elif compression == 'gzip': file_compress = 'gzip'
+    elif compression == 'bzip2': file_compress = 'bzip2'
+    elif compression == 'none' or compression is None: pass
+    elif compression == 'auto':
+        if file_name:
+            if file_name.lower().endswith('.fz'): pyfits_compress = 'RICE_1'
+            elif file_name.lower().endswith('.gz'): file_compress = 'gzip'
+            elif file_name.lower().endswith('.bz2'): file_compress = 'bzip2'
+            else:  # pragma: no cover  (Not sure why codecov thinks this isn't covered.)
+                pass
+    else:
+        raise GalSimValueError("Invalid compression", compression,
+                               ('rice', 'gzip_tile', 'hcompress', 'plio', 'gzip', 'bzip2',
+                                'none', 'auto'))
+    return file_compress, pyfits_compress
+
+# This is a class rather than a def, since we want to store some variable, and that's easier
+# to do with a class than a function.  But this will be used as though it were a normal
+# function: _read_file(file, file_compress)
+class _ReadFile:
+
+    # There are two methods available for each of gzip and bzip2. Each is its own function.
+    # 1. The _call functions call out to an external program.  gunzip, bunzip2 as appropriate.
+    # 2. The _in_mem functions use the corresponding python modules to do things in memory.
+    #
+    # As of commit 2e2d643b47fa27dbdcfcb1ba7bd, the write functions were generally faster using
+    # the in memory functions, but the reads were faster with the external functions.
+    # cf. GalSim/devel/time_zip.py for details about the timing tests.
+
+    def gunzip_call(self, file):
+        # cf. http://bugs.python.org/issue7471
+        # We use gunzip -c rather than zcat, since the latter is sometimes called gzcat
+        # (with zcat being a symlink to uncompress instead).
+        # Also, I'd rather all these use `with subprocess.Popen(...) as p:`, but that's not
+        # supported in 2.7.  So need to keep things this way for now.
+        try:
+            p = subprocess.Popen(["gunzip", "-c", file], stdout=subprocess.PIPE,
+                                 stderr=subprocess.PIPE, close_fds=True)
+        except OSError:
+            # This OSError should mean that the gunzip call itself was invalid on this system.
+            # Convert to a NotImplementedError, so we can try a different method.
+            raise NotImplementedError()
+        ret = p.communicate()
+        if ret[0] == b'':  # pragma: no cover
+            raise OSError("Error running gunzip. stderr output = %s"%ret[1])
+        if p.returncode != 0:  # pragma: no cover
+            raise OSError("Error running gunzip. Return code = %s"%p.returncode)
+        fin = BytesIO(ret[0])
+        p.wait()
+        try:
+            hdu_list = pyfits.open(fin, 'readonly')
+        except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover
+            # In case astropy fails.
+            raise NotImplementedError()
+        return hdu_list, fin
+
+    def gzip_in_mem(self, file): # pragma: no cover
+        fin = gzip.open(file, 'rb')
+        hdu_list = pyfits.open(fin, 'readonly')
+        # pyfits doesn't actually read the file yet, so we can't close fin here.
+        # Need to pass it back to the caller and let them close it when they are
+        # done with hdu_list.
+        return hdu_list, fin
+
+    def bunzip2_call(self, file):
+        try:
+            p = subprocess.Popen(["bunzip2", "-c", file], stdout=subprocess.PIPE,
+                                 stderr=subprocess.PIPE, close_fds=True)
+        except OSError:
+            # This OSError should mean that the gunzip call itself was invalid on this system.
+            # Convert to a NotImplementedError, so we can try a different method.
+            raise NotImplementedError()
+        ret = p.communicate()
+        if ret[0] == b'':  # pragma: no cover
+            raise OSError("Error running bunzip2. stderr output = %s"%ret[1])
+        if p.returncode != 0:  # pragma: no cover
+            raise OSError("Error running bunzip2. Return code = %s"%p.returncode)
+        fin = BytesIO(ret[0])
+        p.wait()
+        try:
+            hdu_list = pyfits.open(fin, 'readonly')
+        except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover
+            # In case astropy fails.
+            raise NotImplementedError()
+        return hdu_list, fin
+
+    def bz2_in_mem(self, file): # pragma: no cover
+        fin = bz2.BZ2File(file, 'rb')
+        hdu_list = pyfits.open(fin, 'readonly')
+        return hdu_list, fin
+
+    def __init__(self):
+        # We used to have multiple options for gzip and bzip2.  However, with recent versions of
+        # astropy for the fits I/O, the in memory version should always work.  So we first
+        # try the command line method, which is usually faster.  Then if that fails, we let
+        # astropy do the compression.
+        self.gz_index = 0
+        self.bz2_index = 0
+        self.gz_methods = [self.gunzip_call, self.gzip_in_mem]
+        self.bz2_methods = [self.bunzip2_call, self.bz2_in_mem]
+        self.gz = self.gz_methods[0]
+        self.bz2 = self.bz2_methods[0]
+
+    def __call__(self, file, dir, file_compress):
+        if dir:
+            file = os.path.join(dir,file)
+
+        if not os.path.isfile(file):
+            raise OSError("File %s not found"%file)
+
+        if not file_compress:
+            hdu_list = pyfits.open(file, 'readonly')
+            return hdu_list, None
+        elif file_compress == 'gzip':
+            # First make sure the file exists and is readable.
+            # The easiest way to do this is to try to open it.  Just let the open command return
+            # its normal error message if the file doesn't exist or cannot be opened.
+            with open(file) as fid: pass
+            while self.gz_index < len(self.gz_methods):
+                try:
+                    return self.gz(file)
+                except (ImportError, NotImplementedError): # pragma: no cover
+                    if self.gz_index == len(self.gz_methods)-1:
+                        raise
+                    else:
+                        self.gz_index += 1
+                        self.gz = self.gz_methods[self.gz_index]
+            else:  # pragma: no cover
+                raise GalSimError("None of the options for gunzipping were successful.")
+        elif file_compress == 'bzip2':
+            with open(file) as fid: pass
+            while self.bz2_index < len(self.bz2_methods):
+                try:
+                    return self.bz2(file)
+                except (ImportError, NotImplementedError): # pragma: no cover
+                    if self.bz2_index == len(self.bz2_methods)-1:
+                        raise
+                    else:
+                        self.bz2_index += 1
+                        self.bz2 = self.bz2_methods[self.bz2_index]
+            else:  # pragma: no cover
+                raise GalSimError("None of the options for bunzipping were successful.")
+        else:  # pragma: no cover  (can't get here from public API)
+            raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2'))
+_read_file = _ReadFile()
+
+# Do the same trick for _write_file(file,hdu_list,clobber,file_compress,pyfits_compress):
+class _WriteFile:
+    # kwargs to use for the writeto function
+    kw = {'output_verify' : 'silentfix+ignore'}
+
+    # There are two methods available for each of gzip and bzip2. Each is its own function.
+    # 1. The _call functions call out to an external program.  gzip, bzip2 as appropriate.
+    # 2. The _in_mem functions use the corresponding python modules to do things in memory.
+    #
+    # As of commit 2e2d643b47fa27dbdcfcb1ba7bd, the write functions were generally faster using
+    # the in memory functions, but the reads were faster with the external functions.
+    # cf. GalSim/devel/time_zip.py for details about the timing tests.
+    #
+    # As a result, we no longer try multple functions here (as we still do in _ReadFile).
+    # If the timing status chages, the above mentioned commit has the old code, which first tried
+    # the external function and the reverted to the in-memory version if that failed.
+
+    # No longer used, but preserved for timing tests to see if we should revisit this.
+    def gzip_call(self, hdu_list, file):  # pragma: no cover
+        with open(file, 'wb') as fout:
+            try:
+                p = subprocess.Popen(["gzip", "-"], stdin=subprocess.PIPE, stdout=fout,
+                                     close_fds=True)
+                hdu_list.writeto(p.stdin, **self.kw)
+            except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover
+                # This OSError should mean that the gunzip call itself was invalid on this system.
+                # Convert to a NotImplementedError, so we can try a different method.
+                # The others are in case astropy fails.
+                raise NotImplementedError()
+            p.communicate()
+            if p.returncode != 0:  # pragma: no cover
+                raise OSError("Error running gzip. Return code = %s"%p.returncode)
+            p.wait()
+
+    def gzip_in_mem(self, hdu_list, file):
+        # The compression routines work better if we first write to an internal buffer
+        # and then output that to a file.
+        buf = io.BytesIO()
+        hdu_list.writeto(buf, **self.kw)
+        data = buf.getvalue()
+        # There is a compresslevel option (for both gzip and bz2), but we just use the
+        # default.
+        with gzip.open(file, 'wb') as fout:
+            fout.write(data)
+
+    def bzip2_call(self, hdu_list, file):  # pragma: no cover
+        with open(file, 'wb') as fout:
+            try:
+                p = subprocess.Popen(["bzip2"], stdin=subprocess.PIPE, stdout=fout, close_fds=True)
+                hdu_list.writeto(p.stdin, **self.kw)
+            except (OSError, AttributeError, TypeError, ValueError): # pragma: no cover
+                # This OSError should mean that the gunzip call itself was invalid on this system.
+                # Convert to a NotImplementedError, so we can try a different method.
+                # The others are in case astropy fails.
+                raise NotImplementedError()
+            p.communicate()
+            if p.returncode != 0:  # pragma: no cover
+                raise OSError("Error running bzip2. Return code = %s"%p.returncode)
+            p.wait()
+
+    def bz2_in_mem(self, hdu_list, file):
+        buf = io.BytesIO()
+        hdu_list.writeto(buf, **self.kw)
+        data = buf.getvalue()
+        with bz2.BZ2File(file, 'wb') as fout:
+            fout.write(data)
+
+    def __call__(self, file, dir, hdu_list, clobber, file_compress, pyfits_compress):
+        if dir:
+            file = os.path.join(dir,file)
+
+        ensure_dir(file)
+        if os.path.isfile(file):
+            if clobber:
+                os.remove(file)
+            else:
+                raise OSError('File %r already exists'%file)
+
+        if not file_compress:
+            hdu_list.writeto(file, **self.kw)
+        elif file_compress == 'gzip':
+            return self.gzip_in_mem(hdu_list, file)
+        elif file_compress == 'bzip2':
+            return self.bz2_in_mem(hdu_list, file)
+        else:  # pragma: no cover  (can't get here from public API)
+            raise GalSimValueError("Unknown file_compression", file_compress, ('gzip', 'bzip2'))
+
+_write_file = _WriteFile()
+
+def _add_hdu(hdu_list, data, pyfits_compress):
+    if pyfits_compress:
+        if len(hdu_list) == 0:
+            hdu_list.append(pyfits.PrimaryHDU())  # Need a blank PrimaryHDU
+        hdu = pyfits.CompImageHDU(data, compression_type=pyfits_compress)
+    else:
+        if len(hdu_list) == 0:
+            hdu = pyfits.PrimaryHDU(data)
+        else:
+            hdu = pyfits.ImageHDU(data)
+    hdu_list.append(hdu)
+    return hdu
+
+def _add_to_header(hdu, image):
+    h = FitsHeader(hdu.header)
+    if hasattr(image, 'header'):
+        h.extend(image.header)
+    if image.wcs:
+        image.wcs.writeToFitsHeader(h, image.bounds)
+
+
+def _check_hdu(hdu, pyfits_compress, header_only=False):
+    """Check that an input ``hdu`` is valid
+    """
+    # Check for fixable verify errors
+    # Astropy will automatically fix any errors it finds by just accessing the header and data.
+    hdu.header
+    if not header_only:
+        try:
+            hdu.data
+        except pyfits.VerifyError:  # pragma: no cover
+            # If just reading the data raises a VerifyError, try again with fix.
+            # This seems to be OS-specific, and probably astropy-version specific.
+            # But it's at least possible for this to happen and the verify step to help.
+            hdu.verify('fix')
+            hdu.data
+
+    # Check that the specified compression is right for the given hdu type.
+    if pyfits_compress:
+        if not isinstance(hdu, pyfits.CompImageHDU):
+            raise OSError('Found invalid HDU type reading FITS file (expected a CompImageHDU)')
+    else:
+        if not isinstance(hdu, (pyfits.CompImageHDU, pyfits.ImageHDU, pyfits.PrimaryHDU)):
+            raise OSError('Found invalid HDU type reading FITS file (expected an ImageHDU)')
+
+
+def _get_hdu(hdu_list, hdu, pyfits_compress, header_only=False):
+    if isinstance(hdu_list, pyfits.HDUList):
+        # Note: Nothing special needs to be done when reading a compressed hdu.
+        # However, such compressed hdu's may not be the PrimaryHDU, so if we think we are
+        # reading a compressed file, skip to hdu 1.
+        if hdu is None:
+            if pyfits_compress:
+                if len(hdu_list) <= 1:
+                    raise OSError('Expecting at least one extension HDU in galsim.read')
+                hdu = 1
+            else:
+                hdu = 0
+        if len(hdu_list) <= hdu:
+            raise OSError('Expecting at least %d HDUs in galsim.read'%(hdu+1))
+        hdu = hdu_list[hdu]
+    elif isinstance(hdu_list, (pyfits.ImageHDU, pyfits.PrimaryHDU, pyfits.CompImageHDU)):
+        hdu = hdu_list
+    else:
+        raise TypeError("Invalid hdu_list: %s",hdu_list)
+    _check_hdu(hdu, pyfits_compress, header_only)
+    return hdu
+
+
+# Unlike the other helpers, this one doesn't start with an underscore, since we make it
+# available to people who use the function ReadFile.
+
[docs]def closeHDUList(hdu_list, fin): + """If necessary, close the file handle that was opened to read in the ``hdu_list``""" + hdu_list.close() + if fin: + fin.close()
+ +############################################################################################## +# +# Now the primary write functions. We have: +# write(image, ...) +# writeMulti(image_list, ...) +# writeCube(image_list, ...) +# writeFile(hdu_list, ...) +# +############################################################################################## + + +
[docs]def write(image, file_name=None, dir=None, hdu_list=None, clobber=True, compression='auto'): + """Write a single image to a FITS file. + + Write the `Image` instance ``image`` to a FITS file, with details depending on the arguments. + This function can be called directly as ``galsim.fits.write(image, ...)``, with the image as the + first argument, or as an `Image` method: ``image.write(...)``. + + Parameters: + image: The `Image` to write to file. Per the description of this method, it may be + given explicitly via ``galsim.fits.write(image, ...)`` or the method may be + called directly as an image method, ``image.write(...)``. Note that if the + image has a 'header' attribute containing a `FitsHeader`, then the + `FitsHeader` is written to the header in the PrimaryHDU, followed by the + WCS as usual. + file_name: The name of the file to write to. [Either ``file_name`` or ``hdu_list`` is + required.] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: An astropy.io.fits.HDUList. If this is provided instead of ``file_name``, + then the `Image` is appended to the end of the HDUList as a new HDU. In + that case, the user is responsible for calling either + ``hdu_list.writeto(...)`` or ``galsim.fits.writeFile(...)`` afterwards. + [Either ``file_name`` or ``hdu_list`` is required.] + clobber: Setting ``clobber=True`` will silently overwrite existing files. + [default: True] + compression: Which compression scheme to use (if any). Options are: + + - None or 'none' = no compression + - 'rice' = use rice compression in tiles (preserves header readability) + - 'gzip' = use gzip to compress the full file + - 'bzip2' = use bzip2 to compress the full file + - 'gzip_tile' = use gzip in tiles (preserves header readability) + - 'hcompress' = use hcompress in tiles (only valid for 2-d images) + - 'plio' = use plio compression in tiles (only valid for pos integer data) + - 'auto' = determine the compression from the extension of the file name + (requires ``file_name`` to be given): + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + """ + if image.iscomplex: + raise GalSimValueError("Cannot write complex Images to a fits file. " + "Write image.real and image.imag separately.", image) + + file_compress, pyfits_compress = _parse_compression(compression,file_name) + + if file_name and hdu_list is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + if not (file_name or hdu_list is not None): + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) + + if hdu_list is None: + hdu_list = pyfits.HDUList() + + hdu = _add_hdu(hdu_list, image.array, pyfits_compress) + _add_to_header(hdu, image) + + if file_name: + _write_file(file_name, dir, hdu_list, clobber, file_compress, pyfits_compress)
+ + +
[docs]def writeMulti(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, + compression='auto'): + """Write a Python list of images to a multi-extension FITS file. + + The details of how the images are written to file depends on the arguments. + + .. note:: + + This function along with `readMulti` can be used to effect the equivalent of a simple + version of fpack or funpack. To Rice compress a fits file, you can call:: + + fname = 'some_image_file.fits' + galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname+'.fz') + + To uncompress:: + + fname = 'some_image_file.fits.fz' + galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname[:-3]) + + Parameters: + image_list: A Python list of `Image` instances. (For convenience, some items in this + list may be HDUs already. Any `Image` will be converted into an + astropy.io.fits.HDU.) + file_name: The name of the file to write to. [Either ``file_name`` or ``hdu_list`` is + required.] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: An astropy.io.fits.HDUList. If this is provided instead of ``file_name``, + then the `Image` is appended to the end of the HDUList as a new HDU. In + that case, the user is responsible for calling either + ``hdu_list.writeto(...)`` or ``galsim.fits.writeFile(...)`` afterwards. + [Either ``file_name`` or ``hdu_list`` is required.] + clobber: Setting ``clobber=True`` will silently overwrite existing files. + [default: True] + compression: Which compression scheme to use (if any). Options are: + + - None or 'none' = no compression + - 'rice' = use rice compression in tiles (preserves header readability) + - 'gzip' = use gzip to compress the full file + - 'bzip2' = use bzip2 to compress the full file + - 'gzip_tile' = use gzip in tiles (preserves header readability) + - 'hcompress' = use hcompress in tiles (only valid for 2-d images) + - 'plio' = use plio compression in tiles (only valid for pos integer data) + - 'auto' = determine the compression from the extension of the file name + (requires ``file_name`` to be given): + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + """ + if any(image.iscomplex for image in image_list if isinstance(image, Image)): + raise GalSimValueError("Cannot write complex Images to a fits file. " + "Write image.real and image.imag separately.", image_list) + + file_compress, pyfits_compress = _parse_compression(compression,file_name) + + if file_name and hdu_list is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + if not (file_name or hdu_list is not None): + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) + + if hdu_list is None: + hdu_list = pyfits.HDUList() + + for image in image_list: + if isinstance(image, Image): + hdu = _add_hdu(hdu_list, image.array, pyfits_compress) + _add_to_header(hdu, image) + else: + # Assume that image is really an HDU. If not, this should give a reasonable error + # message. (The base type of HDUs vary among versions of pyfits, so it's hard to + # check explicitly with an isinstance call. For newer pyfits versions, it is + # pyfits.hdu.base.ExtensionHDU, but not in older versions.) + hdu_list.append(image) + + if file_name: + _write_file(file_name, dir, hdu_list, clobber, file_compress, pyfits_compress)
+ + +
[docs]def writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, + compression='auto'): + """Write a Python list of images to a FITS file as a data cube. + + The details of how the images are written to file depends on the arguments. Unlike for + writeMulti, when writing a data cube it is necessary that each `Image` in ``image_list`` has + the same size ``(nx, ny)``. No check is made to confirm that all images have the same origin + and pixel scale (or WCS). + + In fact, the WCS of the first image is the one that gets put into the FITS header (since only + one WCS can be put into a FITS header). Thus, if the images have different WCS functions, + only the first one will be rendered correctly by plotting programs such as ds9. The FITS + standard does not support any way to have the various images in a data cube to have different + WCS solutions. + + Parameters: + image_list: The ``image_list`` can also be either an array of NumPy arrays or a 3d NumPy + array, in which case this is written to the fits file directly. In the + former case, no explicit check is made that the NumPy arrays are all the + same shape, but a NumPy exception will be raised which we let pass upstream + unmolested. + file_name: The name of the file to write to. [Either ``file_name`` or ``hdu_list`` is + required.] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: An astropy.io.fits.HDUList. If this is provided instead of ``file_name``, + then the cube is appended to the end of the HDUList as a new HDU. In that + case, the user is responsible for calling either ``hdu_list.writeto(...)`` + or ``galsim.fits.writeFile(...)`` afterwards. [Either ``file_name`` or + ``hdu_list`` is required.] + clobber: Setting ``clobber=True`` will silently overwrite existing files. + [default: True] + compression: Which compression scheme to use (if any). Options are: + + - None or 'none' = no compression + - 'rice' = use rice compression in tiles (preserves header readability) + - 'gzip' = use gzip to compress the full file + - 'bzip2' = use bzip2 to compress the full file + - 'gzip_tile' = use gzip in tiles (preserves header readability) + - 'hcompress' = use hcompress in tiles (only valid for 2-d images) + - 'plio' = use plio compression in tiles (only valid for pos integer data) + - 'auto' = determine the compression from the extension of the file name + (requires ``file_name`` to be given): + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + """ + if isinstance(image_list, np.ndarray): + is_all_numpy = True + if image_list.dtype.kind == 'c': + raise GalSimValueError("Cannot write complex numpy arrays to a fits file. " + "Write array.real and array.imag separately.", image_list) + elif len(image_list) == 0: + raise GalSimValueError("In writeCube: image_list has no images", image_list) + elif all(isinstance(item, np.ndarray) for item in image_list): + is_all_numpy = True + if any(a.dtype.kind == 'c' for a in image_list): + raise GalSimValueError("Cannot write complex numpy arrays to a fits file. " + "Write array.real and array.imag separately.", image_list) + else: + is_all_numpy = False + if any(im.iscomplex for im in image_list): + raise GalSimValueError("Cannot write complex images to a fits file. " + "Write image.real and image.imag separately.", image_list) + + file_compress, pyfits_compress = _parse_compression(compression,file_name) + + if file_name and hdu_list is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + if not (file_name or hdu_list is not None): + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) + + if hdu_list is None: + hdu_list = pyfits.HDUList() + + if is_all_numpy: + cube = np.asarray(image_list) + nimages = cube.shape[0] + nx = cube.shape[1] + ny = cube.shape[2] + # Use default values for bounds + bounds = BoundsI(1,nx,1,ny) + wcs = None + else: + nimages = len(image_list) + im = image_list[0] + dtype = im.array.dtype + nx = im.xmax - im.xmin + 1 + ny = im.ymax - im.ymin + 1 + # Use the first image's wcs and bounds + wcs = im.wcs + bounds = im.bounds + # Note: numpy shape is y,x + array_shape = (nimages, ny, nx) + cube = np.zeros(array_shape, dtype=dtype) + for k in range(nimages): + im = image_list[k] + nx_k = im.xmax-im.xmin+1 + ny_k = im.ymax-im.ymin+1 + if nx_k != nx or ny_k != ny: + raise GalSimValueError("In writeCube: image %d has the wrong shape. " + "Shape is (%d,%d) should be (%d,%d)"%(k,nx_k,ny_k,nx,ny), + im) + cube[k,:,:] = image_list[k].array + + + hdu = _add_hdu(hdu_list, cube, pyfits_compress) + if not is_all_numpy: + # Use any header/wcs in the first image + _add_to_header(hdu, image_list[0]) + + if file_name: + _write_file(file_name, dir, hdu_list, clobber, file_compress, pyfits_compress)
+ + +
[docs]def writeFile(file_name, hdu_list, dir=None, clobber=True, compression='auto'): + """Write a Pyfits hdu_list to a FITS file, taking care of the GalSim compression options. + + If you have used the write(), writeMulti() or writeCube() functions with the ``hdu_list`` + option rather than writing directly to a file, you may subsequently use the command + ``hdu_list.writeto(...)``. However, it may be more convenient to use this function, writeFile() + instead, since it treats the compression option consistently with how that option is handled in + the above functions. + + Parameters: + file_name: The name of the file to write to. + hdu_list: An astropy.io.fits.HDUList. + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + clobber: Setting ``clobber=True`` will silently overwrite existing files. + [default: True] + compression: Which compression scheme to use (if any). Options are: + + - None or 'none' = no compression + - 'gzip' = use gzip to compress the full file + - 'bzip2' = use bzip2 to compress the full file + - 'auto' = determine the compression from the extension of the file name + (requires ``file_name`` to be given): + + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + Note that the other options, such as 'rice', that operate on the image + directly are not available at this point. If you want to use one of them, + it must be applied when writing each hdu. + [default: 'auto'] + """ + file_compress, pyfits_compress = _parse_compression(compression,file_name) + if pyfits_compress and compression != 'auto': + # If compression is auto and it determined that it should use rice, then we + # should presume that the hdus were already rice compressed, so we can ignore it here. + # Otherwise, any pyfits_compression options are invalid. + raise GalSimValueError("Compression %s is invalid for writeFile",compression) + _write_file(file_name, dir, hdu_list, clobber, file_compress, pyfits_compress)
+ + +############################################################################################## +# +# Now the primary read functions. We have: +# image = read(...) +# image_list = readMulti(...) +# image_list = readCube(...) +# hdu, hdu_list, fin = readFile(...) +# +############################################################################################## + + +
[docs]def read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto', + read_header=False, suppress_warning=True): + """Construct an `Image` from a FITS file or HDUList. + + The normal usage for this function is to read a fits file and return the image contained + therein, automatically decompressing it if necessary. However, you may also pass it + an HDUList, in which case it will select the indicated hdu (with the ``hdu`` parameter) + from that. + + Not all FITS pixel types are supported -- only ``short``, ``int``, ``unsigned short``, + ``unsigned int``, ``float``, and ``double``. + + If the FITS header has keywords that start with ``GS_``, these will be used to initialize the + bounding box and WCS. If these are absent, the code will try to read whatever WCS is given + in the FITS header. cf. `galsim.wcs.readFromFitsHeader`. The default bounding box will have + ``(xmin,ymin)`` at ``(1,1)``. The default WCS, if there is no WCS information in the FITS + file, will be PixelScale(1.0). + + This function is called as ``im = galsim.fits.read(...)`` + + Parameters: + file_name: The name of the file to read in. [Either ``file_name`` or ``hdu_list`` is + required.] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: Either an astropy.io.fits.HDUList, astropy.io.fits.PrimaryHDU, or + astropy.io.fits..ImageHDU. In the former case, the ``hdu`` in the list + will be selected. In the latter two cases, the ``hdu`` parameter is + ignored. [Either ``file_name`` or ``hdu_list`` is required.] + hdu: The number of the HDU to return. [default: None, which means to return + either the primary or first extension as appropriate for the given + compression. (e.g. for 'rice', the first extension is the one you normally + want.)] + compression: Which decompression scheme to use (if any). Options are: + + - None or 'none' = no decompression + - 'rice' = use rice decompression in tiles + - 'gzip' = use gzip to decompress the full file + - 'bzip2' = use bzip2 to decompress the full file + - 'gzip_tile' = use gzip decompression in tiles + - 'hcompress' = use hcompress decompression in tiles + - 'plio' = use plio decompression in tiles + - 'auto' = determine the decompression from the extension of the file name + (requires ``file_name`` to be given). + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + read_header: Whether to read the header and store it as image.header.[default: False] + suppress_warning: Whether to suppress a warning that the WCS could not be read from the + FITS header, so the WCS defaulted to either a `PixelScale` or + `AffineTransform`. [default: True] + + Returns: + the image as an `Image` instance. + """ + file_compress, pyfits_compress = _parse_compression(compression,file_name) + + if file_name and hdu_list is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + if not (file_name or hdu_list is not None): + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) + + if file_name: + hdu_list, fin = _read_file(file_name, dir, file_compress) + + try: + hdu = _get_hdu(hdu_list, hdu, pyfits_compress) + + if hdu.data is None: + raise OSError("HDU is empty. (data is None)") + + wcs, origin = readFromFitsHeader(hdu.header, suppress_warning) + dt = hdu.data.dtype.type + if dt in Image.valid_dtypes: + data = hdu.data + else: + galsim_warn("No C++ Image template instantiation for data type %s. " + "Using numpy.float64 instead."%(dt)) + data = hdu.data.astype(np.float64) + + image = Image(array=data) + image.setOrigin(origin) + image.wcs = wcs + + if read_header: + image.header = FitsHeader(hdu.header) + + finally: + # If we opened a file, don't forget to close it. + if file_name: + closeHDUList(hdu_list, fin) + + return image
+ +
[docs]def readMulti(file_name=None, dir=None, hdu_list=None, compression='auto', + read_headers=False, suppress_warning=True): + """Construct a list of `Image` instances from a FITS file or HDUList. + + The normal usage for this function is to read a fits file and return a list of all the images + contained therein, automatically decompressing them if necessary. However, you may also pass + it an HDUList, in which case it will build the images from these directly. + + Not all FITS pixel types are supported -- only ``short``, ``int``, ``unsigned short``, + ``unsigned int``, ``float``, and ``double``. + + If the FITS header has keywords that start with ``GS_``, these will be used to initialize the + bounding box and WCS. If these are absent, the code will try to read whatever WCS is given + in the FITS header. cf. `galsim.wcs.readFromFitsHeader`. The default bounding box will have + ``(xmin,ymin)`` at ``(1,1)``. The default WCS, if there is no WCS information in the FITS + file, will be PixelScale(1.0). + + This function is called as ``im = galsim.fits.readMulti(...)`` + + .. note:: + + This function along with `writeMulti` can be used to effect the equivalent of a simple + version of fpack or funpack. To Rice compress a fits file, you can call:: + + fname = 'some_image_file.fits' + galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname+'.fz') + + To uncompress:: + + fname = 'some_image_file.fits.fz' + galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname[:-3]) + + Parameters: + file_name: The name of the file to read in. [Either ``file_name`` or ``hdu_list`` is + required.] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: An astropy.io.fits.HDUList from which to read the images. [Either + ``file_name`` or ``hdu_list`` is required.] + compression: Which decompression scheme to use (if any). Options are: + + - None or 'none' = no decompression + - 'rice' = use rice decompression in tiles + - 'gzip' = use gzip to decompress the full file + - 'bzip2' = use bzip2 to decompress the full file + - 'gzip_tile' = use gzip decompression in tiles + - 'hcompress' = use hcompress decompression in tiles + - 'plio' = use plio decompression in tiles + - 'auto' = determine the decompression from the extension of the file name + (requires ``file_name`` to be given). + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + read_headers: Whether to read the headers and store them as image.header.[default: False] + suppress_warning: Whether to suppress a warning that the WCS could not be read from the + FITS header, so the WCS defaulted to either a `PixelScale` or + `AffineTransform`. [default: True] + + Returns: + a Python list of `Image` instances. + """ + file_compress, pyfits_compress = _parse_compression(compression,file_name) + + if file_name and hdu_list is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + if not (file_name or hdu_list is not None): + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) + + if file_name: + hdu_list, fin = _read_file(file_name, dir, file_compress) + elif not isinstance(hdu_list, pyfits.HDUList): + raise TypeError("In readMulti, hdu_list is not an HDUList") + + try: + image_list = [] + if pyfits_compress: + first = 1 + if len(hdu_list) <= 1: + raise OSError('Expecting at least one extension HDU in galsim.read') + else: + first = 0 + if len(hdu_list) < 1: + raise OSError('Expecting at least one HDU in galsim.readMulti') + for hdu in range(first,len(hdu_list)): + image_list.append(read(hdu_list=hdu_list, hdu=hdu, compression=pyfits_compress, + read_header=read_headers, suppress_warning=suppress_warning)) + + finally: + # If we opened a file, don't forget to close it. + if file_name: + closeHDUList(hdu_list, fin) + + return image_list
+ +
[docs]def readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto', + suppress_warning=True): + """Construct a Python list of `Image` instances from a FITS data cube. + + Not all FITS pixel types are supported -- only ``short``, ``int``, ``unsigned short``, + ``unsigned int``, ``float``, and ``double``. + + If the FITS header has keywords that start with ``GS_``, these will be used to initialize the + bounding box and WCS. If these are absent, the code will try to read whatever WCS is given + in the FITS header. cf. `galsim.wcs.readFromFitsHeader`. The default bounding box will have + ``(xmin,ymin)`` at ``(1,1)``. The default WCS, if there is no WCS information in the FITS + file, will be PixelScale(1.0). + + This function is called as ``image_list = galsim.fits.readCube(...)`` + + Parameters: + file_name: The name of the file to read in. [Either ``file_name`` or ``hdu_list`` is + required.] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: Either an astropy.io.fits.HDUList, an astropy.io.fits.PrimaryHDU, or + astropy.io.fits.ImageHDU. In the former case, the ``hdu`` in the list will + be selected. In the latter two cases, the ``hdu`` parameter is ignored. + [Either ``file_name`` or ``hdu_list`` is required.] + hdu: The number of the HDU to return. [default: None, which means to return + either the primary or first extension as appropriate for the given + compression. (e.g. for rice, the first extension is the one you normally + want.)] + compression: Which decompression scheme to use (if any). Options are: + + - None or 'none' = no decompression + - 'rice' = use rice decompression in tiles + - 'gzip' = use gzip to decompress the full file + - 'bzip2' = use bzip2 to decompress the full file + - 'gzip_tile' = use gzip decompression in tiles + - 'hcompress' = use hcompress decompression in tiles + - 'plio' = use plio decompression in tiles + - 'auto' = determine the decompression from the extension of the file name + (requires ``file_name`` to be given). + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + suppress_warning: Whether to suppress a warning that the WCS could not be read from the + FITS header, so the WCS defaulted to either a `PixelScale` or + `AffineTransform`. [default: True] + + Returns: + a Python list of `Image` instances. + """ + file_compress, pyfits_compress = _parse_compression(compression,file_name) + + if file_name and hdu_list is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + if not (file_name or hdu_list is not None): + raise GalSimIncompatibleValuesError( + "Must provide either file_name or hdu_list", file_name=file_name, hdu_list=hdu_list) + + if file_name: + hdu_list, fin = _read_file(file_name, dir, file_compress) + + try: + hdu = _get_hdu(hdu_list, hdu, pyfits_compress) + + if hdu.data is None: + raise OSError("HDU is empty. (data is None)") + + wcs, origin = readFromFitsHeader(hdu.header, suppress_warning) + dt = hdu.data.dtype.type + if dt in Image.valid_dtypes: + data = hdu.data + else: + galsim_warn("No C++ Image template instantiation for data type %s. " + "Using numpy.float64 instead."%(dt)) + data = hdu.data.astype(np.float64) + data = np.atleast_3d(data) + + nimages = data.shape[0] + image_list = [] + for k in range(nimages): + image = Image(array=data[k,:,:]) + image.setOrigin(origin) + image.wcs = wcs + image_list.append(image) + + finally: + # If we opened a file, don't forget to close it. + if file_name: + closeHDUList(hdu_list, fin) + + return image_list
+ +
[docs]def readFile(file_name, dir=None, hdu=None, compression='auto'): + """Read in a Pyfits hdu_list from a FITS file, taking care of the GalSim compression options. + + If you want to do something different with an hdu or hdu_list than one of our other read + functions, you can use this function. It handles the compression options in the standard + GalSim way and just returns the hdu (and hdu_list) for you to use as you see fit. + + This function is called as:: + + >>> hdu, hdu_list, fin = galsim.fits.readFile(...) + + The first item in the returned tuple is the specified hdu (or the primary if none was + specifically requested). The other two are returned so you can properly close them. + They are the full HDUList and possibly a file handle. The appropriate cleanup can be + done with:: + + >>> galsim.fits.closeHDUList(hdu_list, fin) + + Parameters: + file_name: The name of the file to read in. + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu: The number of the HDU to return. [default: None, which means to return + either the primary or first extension as appropriate for the given + compression. (e.g. for rice, the first extension is the one you normally + want.)] + compression: Which decompression scheme to use (if any). Options are: + + - None or 'none' = no decompression + - 'rice' = use rice decompression in tiles + - 'gzip' = use gzip to decompress the full file + - 'bzip2' = use bzip2 to decompress the full file + - 'gzip_tile' = use gzip decompression in tiles + - 'hcompress' = use hcompress decompression in tiles + - 'plio' = use plio decompression in tiles + - 'auto' = determine the decompression from the extension of the file name + (requires ``file_name`` to be given). + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + + Returns: + a tuple with three items: ``(hdu, hdu_list, fin)``. + """ + file_compress, pyfits_compress = _parse_compression(compression,file_name) + hdu_list, fin = _read_file(file_name, dir, file_compress) + hdu = _get_hdu(hdu_list, hdu, pyfits_compress) + return hdu, hdu_list, fin
+ + +############################################################################################## +# +# Finally, we have a class for handling FITS headers called FitsHeader. +# +############################################################################################## + + +
[docs]class FitsHeader: + """A class storing key/value pairs from a FITS Header + + This class works a lot like the regular read() function, but rather than returning + the image part of the FITS file, it gives you access to the header information. + + After construction, you can access a header value by:: + + >>> value = fits_header[key] + + or write to it with:: + + >>> fits_header[key] = value # If you just want to set a value. + >>> fits_header[key] = (value, comment) # If you want to include a comment field. + + In fact, most of the normal functions available for a dict are available::: + + >>> keys = fits_header.keys() + >>> items = fits_header.items() + >>> for key in fits_header: + >>> value = fits_header[key] + >>> value = fits_header.get(key, default) + >>> del fits_header[key] + >>> etc. + + .. note:: + This used to be a particularly useful abstraction, since PyFITS and then AstroPy used to + keep changing their syntax for how to write to a fits header rather often, so this class + had numerous checks for which version of PPyFITS or AstroPy was installed and call things + the right way depending on the version. Thus, it was able to maintain a stable, intuitive + API that would work with any version on the backend. We no longer support PyFITS or older + versions of AstroPy, so now much of the syntax of this class is very similar in interface + to the current version of astropy.io.fits.Header. Indeed it is now a rather light wrapper + around their Header class with just a few convenience features to make it easier to work + with GalSim objects. + + The underlying Header object is available as a ``.header`` attribute:: + + >>> apy_header = fits_header.header + + A FitsHeader may be constructed from a file name, an open PyFits (or astropy.io.fits) HDUList + object, or a PyFits (or astropy.io.fits) Header object. It can also be constructed with + no parameters, in which case a blank Header will be constructed with no keywords yet if + you want to add the keywords you want by hand.:: + + >>> h1 = galsim.FitsHeader(file_name = file_name) + >>> h2 = galsim.FitsHeader(header = header) + >>> h3 = galsim.FitsHeader(hdu_list = hdu_list) + >>> h4 = galsim.FitsHeader() + + For convenience, the first parameter may be unnamed as either a header or a file_name:: + + >>> h1 = galsim.FitsHeader(file_name) + >>> h2 = galsim.FitsHeader(header) + + Parameters: + header: An astropy.io.fits.Header object or in fact any dict-like object or list of + (key,value) pairs. [default: None] + file_name: The name of the file to read in. [default: None] + dir: Optionally a directory name can be provided if ``file_name`` does not + already include it. [default: None] + hdu_list: Either an astropy.io.fits.HDUList, an astropy.io.fits.PrimaryHDU, or + astropy.io.fits.ImageHDU. In the former case, the ``hdu`` in the list will + be selected. In the latter two cases, the ``hdu`` parameter is ignored. + [default: None] + hdu: The number of the HDU to return. [default: None, which means to return + either the primary or first extension as appropriate for the given + compression. (e.g. for rice, the first extension is the one you normally + want.)] + compression: Which decompression scheme to use (if any). Options are: + + - None or 'none' = no decompression + - 'rice' = use rice decompression in tiles + - 'gzip' = use gzip to decompress the full file + - 'bzip2' = use bzip2 to decompress the full file + - 'gzip_tile' = use gzip decompression in tiles + - 'hcompress' = use hcompress decompression in tiles + - 'plio' = use plio decompression in tiles + - 'auto' = determine the decompression from the extension of the file name + (requires ``file_name`` to be given). + + - '.fz' => 'rice' + - '.gz' => 'gzip' + - '.bz2' => 'bzip2' + - otherwise None + + [default: 'auto'] + text_file: Normally a file is taken to be a fits file, but you can also give it a + text file with the header information (like the .head file output from + SCamp). In this case you should set ``text_file = True`` to tell GalSim + to parse the file this way. [default: False] + """ + _req_params = { 'file_name' : str } + _opt_params = { 'dir' : str , 'hdu' : int , 'compression' : str , 'text_file' : bool } + + def __init__(self, header=None, file_name=None, dir=None, hdu_list=None, hdu=None, + compression='auto', text_file=False): + if header and file_name: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and header", file_name=file_name, header=header) + if header and hdu_list: + raise GalSimIncompatibleValuesError( + "Cannot provide both hdu_list and header", hdu_list=hdu_list, header=header) + if file_name and hdu_list: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and hdu_list", file_name=file_name, hdu_list=hdu_list) + + # Interpret a string header as though it were passed as file_name. + if isinstance(header, basestring): + file_name = header + header = None + + file_compress, pyfits_compress = _parse_compression(compression,file_name) + self._tag = None # Used for the repr + + if file_name is not None: + if dir is not None: + self._tag = 'file_name='+repr(os.path.join(dir,file_name)) + else: + self._tag = 'file_name='+repr(file_name) + if hdu is not None: + self._tag += ', hdu=%r'%hdu + if compression != 'auto': + self._tag += ', compression=%r'%compression + + if text_file: + self._tag += ', text_file=True' + if dir is not None: + file_name = os.path.join(dir,file_name) + with open(file_name,"r") as fin: + lines = [ line.strip() for line in fin ] + # Don't include END (or later lines) + end = lines.index('END') if 'END' in lines else len(lines) + lines = lines[:end] + # Later pyfits versions changed this to a class method, so you can write + # pyfits.Card.fromstring(text). But in older pyfits versions, it was + # a regular method. This syntax should work in both cases. + cards = [ pyfits.Card().fromstring(line) for line in lines ] + header = pyfits.Header(cards) + else: + hdu_list, fin = _read_file(file_name, dir, file_compress) + + if hdu_list: + hdu = _get_hdu(hdu_list, hdu, pyfits_compress, header_only=True) + header = hdu.header + + if file_name and not text_file: + # If we opened a file, don't forget to close it. + # Also need to make a copy of the header to keep it available. + # If we construct a FitsHeader from an hdu_list, then we don't want to do this, + # since we want the header to remain attached to the original hdu. + self.header = copy.copy(header) + closeHDUList(hdu_list, fin) + elif isinstance(header, pyfits.Header): + # If header is a pyfits.Header, then we just use it. + self.header = header + else: + # Otherwise, header may be any kind of dict-like object or list of (key,value) pairs. + self.header = pyfits.Header() + if header is not None: + if hasattr(header, 'items'): + # update() should handle anything that acts like a dict. + self.update(header) + else: + for card in header: + self.header.append(card, end=True) + + # The rest of the functions are typical non-mutating functions for a dict, for which we + # generally just pass the request along to self.header. + def __len__(self): + return len(self.header) + + def __contains__(self, key): + return key in self.header + + def __delitem__(self, key): + self._tag = None + del self.header[key] + + def __getitem__(self, key): + return self.header[key] + + def __iter__(self): + return self.header.__iter__() + + def __setitem__(self, key, value): + self._tag = None + self.header[key.upper()] = value + +
[docs] def comment(self, key): + """Get the comment field for the given key. + + Parameter: + key: The header key for which to get the comment field. + + Returns: + the comment field. + """ + return self.header.comments[key.upper()]
+ +
[docs] def clear(self): + """Clear all values in the header. Works like dict.clear. + """ + self._tag = None + self.header.clear()
+ +
[docs] def get(self, key, default=None): + """Get the value of a given key. Works like dict.get. + + Parameters: + key: The header key for which to get the value + default: Optionally, A value to use if the key is not present. [default: None] + + Returns: + the value of the given key + """ + return self.header.get(key, default)
+ +
[docs] def pop(self, key, default=None): + """Pop off a value from the header. Works like dict.pop. + + Parameters: + key: The header key for which to get the value + default: Optionally, A value to use if the key is not present. [default: None] + + Returns: + the value of the given key + """ + return self.header.pop(key, default)
+ +
[docs] def items(self): + """Get all header items. Works like dict.items. + + Returns: + A list of (key, value) tuples. + """ + return self.header.items()
+ +
[docs] def iteritems(self): + """Synonym for self.items() + """ + return self.items()
+ +
[docs] def iterkeys(self): + """Synonym for self.keys() + """ + return self.keys()
+ +
[docs] def itervalues(self): + """Synonym for self.values() + """ + return self.values()
+ +
[docs] def keys(self): + """Get all header keys. Works like dict.keys + + Returns: + A list of keys. + """ + return self.header.keys()
+ +
[docs] def update(self, dict2): + """Update the header with a dict-like object. Works like dict.update. + + If there are any items in ``dict2`` that are duplicates of items already in the header, + the current items will ber overwritten. + + Parameters: + dict2: Another header or dict-like object with keys and values to update. + """ + self._tag = None + for key, item in dict2.items(): + self.header[key.upper()] = item
+ +
[docs] def values(self): + """Get all header values. Works like dict.values. + + Returns: + A list of values. + """ + return self.header.values()
+ +
[docs] def append(self, key, value='', comment=None, useblanks=True): + """Append an item to the end of the header. + + This breaks convention a bit by treating the header more like a list than a dict, + but sometimes that is necessary to get the header structured the way you want it. + + Parameters: + key: The key of the entry to append + value: The value of the entry to append [default: ''] + comment: A comment field if desired [default: None] + useblanks: If there are blank entries currently at the end, should they be + overwritten with the new entry? [default: True] + """ + self._tag = None + self.header.append((key.upper(), value, comment), useblanks=useblanks)
+ +
[docs] def extend(self, other, replace=False, useblanks=True): + """Extend this `FitsHeader` with items from another `FitsHeader`. + + Equivalent to appending all the other's items to the end of this one with the + exception that it ignores items that are already in self. + If you want to replace existing values rather than ignore duplicates, use + ``replace=True``. + + Parameters: + other: Another FitsHeader object. + replace: Replace duplicate entries rather than ignore them. [default: False] + useblanks: If there are blank entries currently at the end, should they be + overwritten with the new entry? [default: True] + """ + self._tag = None + self.header.extend(other.header, unique=not replace, update=replace, useblanks=useblanks)
+ + def __repr__(self): + if self._tag is None: + return "galsim.FitsHeader(header=%r)"%list(self.items()) + else: + return "galsim.FitsHeader(%s)"%self._tag + + def __str__(self): + if self._tag is None: + return "galsim.FitsHeader(header=<Header object at %s>)"%id(self.header) + else: + return "galsim.FitsHeader(%s)"%self._tag + + def __eq__(self, other): + return (self is other or + (isinstance(other,FitsHeader) and + list(self.header.items()) == list(other.header.items()))) + + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): + return hash(tuple(sorted(self.items())))
+ +# inject write as method of Image class +Image.write = write +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/fitswcs.html b/docs/_build/html/_modules/galsim/fitswcs.html new file mode 100644 index 00000000000..c533e57f024 --- /dev/null +++ b/docs/_build/html/_modules/galsim/fitswcs.html @@ -0,0 +1,2029 @@ + + + + + + galsim.fitswcs — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.fitswcs

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'AstropyWCS', 'PyAstWCS', 'WcsToolsWCS', 'GSFitsWCS',
+            'FitsWCS', 'TanWCS', 'FittedSIPWCS', ]
+
+import warnings
+import os
+import numpy as np
+import astropy.wcs
+import subprocess
+import copy
+
+from .wcs import CelestialWCS, JacobianWCS, AffineTransform, PixelScale, OffsetWCS
+from .position import PositionD, _PositionD
+from .angle import radians, arcsec, degrees, AngleUnit
+from . import _galsim
+from . import fits
+from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError
+from .errors import GalSimNotImplementedError, convert_cpp_errors, galsim_warn
+from .utilities import horner2d, least_squares
+from .celestial import CelestialCoord
+from ._pyfits import pyfits
+
+
+#########################################################################################
+#
+# We have the following WCS classes that know how to read the WCS from a FITS file:
+#
+#     AstropyWCS
+#     PyAstWCS
+#     WcsToolsWCS
+#     GSFitsWCS
+#
+# As for all CelestialWCS classes, they must define the following:
+#
+#     _radec            function returning (ra, dec) in _radians_ at position (x,y)
+#     _xy               function returning (x, y) given (ra, dec) in _radians_.
+#     _writeHeader      function that writes the WCS to a fits header.
+#     _readHeader       static function that reads the WCS from a fits header.
+#     copy              return a copy
+#     __eq__            check if this equals another WCS
+#
+#########################################################################################
+
+
+
[docs]class AstropyWCS(CelestialWCS): + """This WCS uses astropy.wcs to read WCS information from a FITS file. + It requires the astropy.wcs python module to be installed. + + Astropy may be installed using pip, fink, or port:: + + >>> pip install astropy + >>> fink install astropy-py27 + >>> port install py27-astropy + + It also comes by default with Enthought and Anaconda. For more information, see their website: + + http://www.astropy.org/ + + An AstropyWCS is initialized with one of the following commands:: + + >>> wcs = galsim.AstropyWCS(file_name=file_name) # Open a file on disk + >>> wcs = galsim.AstropyWCS(header=header) # Use an existing pyfits header + >>> wcs = galsim.AstropyWCS(wcs=wcs) # Use an existing astropy.wcs.WCS instance + + Exactly one of the parameters ``file_name``, ``header`` or ``wcs`` is required. Also, since + the most common usage will probably be the first, you can also give a ``file_name`` without it + being named:: + + >>> wcs = galsim.AstropyWCS(file_name) + + Parameters: + file_name: The FITS file from which to read the WCS information. This is probably + the usual parameter to provide. [default: None] + dir: Optional directory to prepend to ``file_name``. [default: None] + hdu: Optionally, the number of the HDU to use if reading from a file. + The default is to use either the primary or first extension as + appropriate for the given compression. (e.g. for rice, the first + extension is the one you normally want.) [default: None] + header: The header of an open pyfits (or astropy.io) hdu. Or, it can be + a FitsHeader object. [default: None] + compression: Which decompression scheme to use (if any). See galsim.fits.read() + for the available options. [default: 'auto'] + wcs: An existing astropy.wcs.WCS instance [default: None] + origin: Optional origin position for the image coordinate system. + If provided, it should be a PositionD or PositionI. + [default: None] + """ + _req_params = { "file_name" : str } + _opt_params = { "dir" : str, "hdu" : int, "origin" : PositionD, + "compression" : str } + + def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression='auto', + wcs=None, origin=None): + with warnings.catch_warnings(): + warnings.filterwarnings("ignore",category=RuntimeWarning) + import scipy # AstropyWCS constructor will do this, so check now. + import scipy.optimize # Check this too, since it's actually what we need from scipy. + + self._color = None + self._tag = None # Write something useful here (see below). This is just used for the repr. + self._set_origin(origin) + + # Read the file if given. + if file_name is not None: + if dir is not None: + self._tag = repr(os.path.join(dir,file_name)) + else: + self._tag = repr(file_name) + if hdu is not None: + self._tag += ', hdu=%r'%hdu + if compression != 'auto': + self._tag += ', compression=%r'%compression + if header is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and wcs", file_name=file_name, wcs=wcs) + hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) + + try: + if file_name is not None: + header = hdu.header + + # Load the wcs from the header. + if header is not None: + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both pyfits header and wcs", header=header, wcs=wcs) + + # These can mess things up later if they stick around. + header.pop('BZERO', None) + header.pop('BSCALE', None) + + self.header = fits.FitsHeader(header) + try: + wcs = self._load_from_header(self.header) + except Exception as e: # pragma: no cover + # Not sure if this can still trigger. There used to be input files that + # caused various errors in astropy, but that no longer seems to be true + # with astropy 4.x. Leave this check here though, so the user can potentially + # get a more comprehensible error message if astropy fails. + raise OSError("Astropy failed to read WCS from %s. Original error: %s"%( + file_name, e)) + else: + # New kind of error starting in astropy 2.0.5 (I think). Sometimes, it + # gets through the above, but doesn't actually load the right WCS. + # E.g. ZPX gets marked as just a ZPN. + # As of version 4.0, ZPX is now the only one known to not work. TPV has + # a similar behavior, but we can make it work by an adjustment to the header + # in _load_from_header. + if 'ZPX' in header.get('CTYPE1','') and 'ZPX' not in wcs.wcs.ctype[0]: + raise OSError( + "Cannot read WCS in %s with astropy. "%(file_name) + + "As of astropy version 4.0.1, ZPX WCS's were still not being " + + "correctly read by astropy.wcs. If you believe this has been " + + "fixed, please open a GalSim issue to remove this check.") + else: + self.header = None + + if wcs is None: + raise GalSimIncompatibleValuesError( + "Must provide one of file_name, header, or wcs", + file_name=file_name, header=header, wcs=wcs) + + finally: + if file_name is not None: + fits.closeHDUList(hdu_list, fin) + + if not wcs.is_celestial: + raise GalSimError("The WCS read in does not define a pair of celestial axes" ) + self._wcs = wcs + + def _load_from_header(self, header): + if 'TAN' in header.get('CTYPE1','') and 'PV1_1' in header: + header['CTYPE1'] = header['CTYPE1'].replace('TAN','TPV') + header['CTYPE2'] = header['CTYPE2'].replace('TAN','TPV') + with warnings.catch_warnings(): + # The constructor might emit warnings if it wants to fix the header + # information (e.g. RADECSYS -> RADESYSa). We'd rather ignore these + # warnings, since we don't much care if the input file is non-standard + # so long as we can make it work. + warnings.simplefilter("ignore") + wcs = astropy.wcs.WCS(header.header) + return wcs + + @property + def wcs(self): + """The underlying ``astropy.wcs.WCS`` object. + """ + return self._wcs + + @property + def origin(self): + """The origin in image coordinates of the WCS function. + """ + return self._origin + + def _radec(self, x, y, color=None): + x1 = np.atleast_1d(x) + y1 = np.atleast_1d(y) + + ra, dec = self.wcs.all_pix2world(x1, y1, 1, ra_dec_order=True) + + # astropy outputs ra, dec in degrees. Need to convert to radians. + factor = degrees / radians + + if np.ndim(x) == np.ndim(y) == 0: + return ra[0] * factor, dec[0] * factor + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(x) == np.ndim(y) + assert x.shape == y.shape + ra *= factor + dec *= factor + return ra, dec + + def _xy(self, ra, dec, color=None): + factor = radians / degrees + + r1 = np.atleast_1d(ra) * factor + d1 = np.atleast_1d(dec) * factor + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + x, y = self.wcs.all_world2pix(r1, d1, 1, ra_dec_order=True) + + if np.ndim(ra) == np.ndim(dec) == 0: + return x[0], y[0] + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(ra) == np.ndim(dec) + assert ra.shape == dec.shape + return x, y + + def _newOrigin(self, origin): + ret = self.copy() + ret._origin = origin + return ret + + def _writeHeader(self, header, bounds): + # Make a new header with the contents of this WCS. + # Note: relax = True means to write out non-standard FITS types. + # Weirdly, this is the default when reading the header, but not when writing. + header.update(self.wcs.to_header(relax=True)) + + # And write the name as a special GalSim key + header["GS_WCS"] = ("AstropyWCS", "GalSim WCS name") + # And the image origin. + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") + return header + + @staticmethod + def _readHeader(header): + x0 = header.get("GS_X0",0.) + y0 = header.get("GS_Y0",0.) + return AstropyWCS(header=header, origin=_PositionD(x0,y0)) + + def copy(self): + ret = AstropyWCS.__new__(AstropyWCS) + ret.__dict__.update(self.__dict__) + return ret + + def __eq__(self, other): + return (self is other or + (isinstance(other, AstropyWCS) and + self.wcs.to_header(relax=True) == other.wcs.to_header(relax=True) and + self.origin == other.origin)) + + def __repr__(self): + if self._tag is not None: + tag = self._tag + elif self.header is not None: + tag = 'header=%r'%self.header + else: + tag = 'wcs=%r'%self.wcs + return "galsim.AstropyWCS(%s, origin=%r)"%(tag, self.origin) + + def __hash__(self): return hash(repr(self)) + + def __getstate__(self): + d = self.__dict__.copy() + del d['_wcs'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self._wcs = self._load_from_header(self.header)
+ + +
[docs]class PyAstWCS(CelestialWCS): + """This WCS uses PyAst (the python front end for the Starlink AST code) to read WCS + information from a FITS file. It requires the starlink.Ast python module to be installed. + + Starlink may be installed using pip:: + + >>> pip install starlink-pyast + + For more information, see their website: + + https://pypi.python.org/pypi/starlink-pyast/ + + A PyAstWCS is initialized with one of the following commands:: + + >>> wcs = galsim.PyAstWCS(file_name=file_name) # Open a file on disk + >>> wcs = galsim.PyAstWCS(header=header) # Use an existing pyfits header + >>> wcs = galsim.PyAstWCS(wcsinfo=wcsinfo) # Use an existing starlink.Ast.FrameSet + + Exactly one of the parameters ``file_name``, ``header`` or ``wcsinfo`` is required. Also, + since the most common usage will probably be the first, you can also give a file name without + it being named:: + + >>> wcs = galsim.PyAstWCS(file_name) + + Parameters: + file_name: The FITS file from which to read the WCS information. This is probably + the usual parameter to provide. [default: None] + dir: Optional directory to prepend to ``file_name``. [default: None] + hdu: Optionally, the number of the HDU to use if reading from a file. + The default is to use either the primary or first extension as + appropriate for the given compression. (e.g. for rice, the first + extension is the one you normally want.) [default: None] + header: The header of an open pyfits (or astropy.io) hdu. Or, it can be + a FitsHeader object. [default: None] + compression: Which decompression scheme to use (if any). See galsim.fits.read() + for the available options. [default:'auto'] + wcsinfo: An existing starlink.Ast.FrameSet [default: None] + origin: Optional origin position for the image coordinate system. + If provided, it should be a PositionD or PositionI. + [default: None] + """ + _req_params = { "file_name" : str } + _opt_params = { "dir" : str, "hdu" : int, "origin" : PositionD, + "compression" : str } + + def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression='auto', + wcsinfo=None, origin=None): + self._color = None + self._tag = None # Write something useful here (see below). This is just used for the repr. + self._set_origin(origin) + + # Read the file if given. + if file_name is not None: + if dir is not None: + self._tag = repr(os.path.join(dir,file_name)) + else: + self._tag = repr(file_name) + if hdu is not None: + self._tag += ', hdu=%r'%hdu + if compression != 'auto': + self._tag += ', compression=%r'%compression + if header is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) + if wcsinfo is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and wcsinfo", + file_name=file_name, wcsinfo=wcsinfo) + hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) + + try: + if file_name is not None: + header = hdu.header + + # Load the wcs from the header. + if header is not None: + if wcsinfo is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both pyfits header and wcsinfo", + header=header, wcsinfo=wcsinfo) + + # These can mess things up later if they stick around. + header.pop('BZERO', None) + header.pop('BSCALE', None) + + self.header = fits.FitsHeader(header) + wcsinfo = self._load_from_header(self.header) + else: + self.header = None + + if wcsinfo is None: + raise GalSimIncompatibleValuesError( + "Must provide one of file_name, header, or wcsinfo", + file_name=file_name, header=header, wcsinfo=wcsinfo) + + # We can only handle WCS with 2 pixel axes (given by Nin) and 2 WCS axes + # (given by Nout). + if wcsinfo.Nin != 2 or wcsinfo.Nout != 2: # pragma: no cover + raise GalSimError("The world coordinate system is not 2-dimensional") + + finally: + if file_name is not None: + fits.closeHDUList(hdu_list, fin) + + self._wcsinfo = wcsinfo + + def _load_from_header(self, header): + import starlink.Atl + # Note: For much of this class implementation, I've followed the example provided here: + # http://dsberry.github.io/starlink/node4.html + self._fix_header(header) + + # PyFITSAdapter requires an hdu, not a header, so just put it in a pyfits header object. + # It turns out there are subtle differences between this and using the original FITS + # file hdu that we read in above. So there is a slight inefficiency here in creating + # a new blank PrimaryHDU for this. But in return we gain more reliable serializability. + hdu = pyfits.PrimaryHDU() + fits.FitsHeader(hdu_list=hdu).update(header) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + # They aren't so good at keeping up with the latest pyfits and numpy syntax, so + # this next line can emit deprecation warnings. + # We can safely ignore them (for now...) + fc = starlink.Ast.FitsChan(starlink.Atl.PyFITSAdapter(hdu)) + # Read a FrameSet from the FITS header. + wcsinfo = fc.read() + + if wcsinfo is None: + raise OSError("Failed to read WCS information from fits file") + + # The PyAst WCS might not have (RA,Dec) axes, which we want. It might for instance have + # (Dec, RA) instead. If it's possible to convert to an (RA,Dec) system, this next line + # will do so. And if not, the result will be None. + # cf. https://github.com/timj/starlink-pyast/issues/8 + wcsinfo = wcsinfo.findframe(starlink.Ast.SkyFrame()) + if wcsinfo is None: + raise GalSimError("The WCS read in does not define a pair of celestial axes" ) + + return wcsinfo + + @property + def wcsinfo(self): + """The underlying ``starlink.Ast.FrameSet`` for this object. + """ + return self._wcsinfo + + @property + def origin(self): + """The origin in image coordinates of the WCS function. + """ + return self._origin + + def _fix_header(self, header): + # We allow for the option to fix up the header information when a modification can + # make it readable by PyAst. + + # There was an older proposed standard that used TAN with PV values, which is used by + # SCamp, so we want to support it if possible. The standard is now called TPV, which + # PyAst understands. All we need to do is change the names of the CTYPE values. + if ( 'CTYPE1' in header and header['CTYPE1'].endswith('TAN') and + 'CTYPE2' in header and header['CTYPE2'].endswith('TAN') and + 'PV1_1' in header ): + header['CTYPE1'] = header['CTYPE1'].replace('TAN','TPV') + header['CTYPE2'] = header['CTYPE2'].replace('TAN','TPV') + + def _radec(self, x, y, color=None): + # Need this to look like + # [ [ x1, x2, x3... ], [ y1, y2, y3... ] ] + # if input is either scalar x,y or two arrays. + xy = np.array([np.atleast_1d(x), np.atleast_1d(y)], dtype=float) + + ra, dec = self.wcsinfo.tran( xy ) + # PyAst returns ra, dec in radians, so we're good. + + if np.ndim(x) == np.ndim(y) == 0: + return ra[0], dec[0] + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(x) == np.ndim(y) + assert x.shape == y.shape + return ra, dec + + def _xy(self, ra, dec, color=None): + rd = np.array([np.atleast_1d(ra), np.atleast_1d(dec)], dtype=float) + x, y = self.wcsinfo.tran( rd, False ) + + if np.ndim(ra) == np.ndim(dec) == 0: + return x[0], y[0] + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(ra) == np.ndim(dec) + assert ra.shape == dec.shape + return x, y + + def _newOrigin(self, origin): + ret = self.copy() + ret._origin = origin + return ret + + def _writeHeader(self, header, bounds): + import starlink.Atl + # See https://github.com/Starlink/starlink/issues/24 for helpful information from + # David Berry, who assisted me in getting this working. + + hdu = pyfits.PrimaryHDU() + with warnings.catch_warnings(): + # Again, we can get deprecation warnings here. Safe to ignore. + warnings.simplefilter("ignore") + fc = starlink.Ast.FitsChan(None, starlink.Atl.PyFITSAdapter(hdu) , "Encoding=FITS-WCS") + # Let Ast know how big the image is that we'll be writing. + for key in ('NAXIS', 'NAXIS1', 'NAXIS2'): + if key in header: # pragma: no branch + fc[key] = header[key] + success = fc.write(self.wcsinfo) + # PyAst doesn't write out TPV or ZPX correctly. It writes them as TAN and ZPN + # respectively. However, if the maximum error is less than 0.1 pixel, it claims + # success nonetheless. This doesn't seem accurate enough for many purposes, + # so we need to countermand that. + # The easiest way I found to check for them is that the string TPN is in the string + # version of wcsinfo. So check for that and set success = False in that case. + if 'TPN' in str(self.wcsinfo): success = False + # Likewise for SIP. MPF seems to be an appropriate string to look for. + if 'MPF' in str(self.wcsinfo): success = False + if not success: + # This should always work, since it uses starlinks own proprietary encoding, but + # it won't necessarily be readable by ds9. + fc = starlink.Ast.FitsChan(None, starlink.Atl.PyFITSAdapter(hdu)) + fc.write(self.wcsinfo) + fc.writefits() + header.update(hdu.header) + + # And write the name as a special GalSim key + header["GS_WCS"] = ("PyAstWCS", "GalSim WCS name") + # And the image origin. + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") + return header + + @staticmethod + def _readHeader(header): + x0 = header.get("GS_X0",0.) + y0 = header.get("GS_Y0",0.) + return PyAstWCS(header=header, origin=_PositionD(x0,y0)) + + def copy(self): + ret = PyAstWCS.__new__(PyAstWCS) + ret.__dict__.update(self.__dict__) + return ret + + def __eq__(self, other): + return (self is other or + (isinstance(other, PyAstWCS) and + repr(self.wcsinfo) == repr(other.wcsinfo) and + self.origin == other.origin)) + + def __repr__(self): + if self._tag is not None: + tag = self._tag + elif self.header is not None: + tag = 'header=%r'%self.header + else: + # Ast doesn't have a good repr for a FrameSet, so do it ourselves. + tag = 'wcsinfo=<starlink.Ast.FrameSet at %s>'%id(self.wcsinfo) + return "galsim.PyAstWCS(%s, origin=%r)"%(tag, self.origin) + + def __hash__(self): return hash(repr(self)) + + def __getstate__(self): + d = self.__dict__.copy() + del d['_wcsinfo'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self._wcsinfo = self._load_from_header(self.header)
+ + +# I can't figure out how to get wcstools installed in the travis environment (cf. .travis.yml). +# So until that gets resolved, we omit this class from the coverage report. +# This class was mostly useful as a refernce implementation anyway. It's much too slow for most +# users to ever want to use it. +
[docs]class WcsToolsWCS(CelestialWCS): # pragma: no cover + """This WCS uses wcstools executables to perform the appropriate WCS transformations + for a given FITS file. It requires wcstools command line functions to be installed. + + Note: It uses the wcstools executables xy2sky and sky2xy, so it can be quite a bit less + efficient than other options that keep the WCS in memory. + + See their website for information on downloading and installing wcstools: + + http://tdc-www.harvard.edu/software/wcstools/ + + A WcsToolsWCS is initialized with the following command:: + + >>> wcs = galsim.WcsToolsWCS(file_name) + + Parameters: + file_name: The FITS file from which to read the WCS information. + dir: Optional directory to prepend to ``file_name``. [default: None] + origin: Optional origin position for the image coordinate system. + If provided, it should be a PositionD or PositionI. + [default: None] + """ + _req_params = { "file_name" : str } + _opt_params = { "dir" : str, "origin" : PositionD } + + def __init__(self, file_name, dir=None, origin=None): + self._color = None + self._set_origin(origin) + + if dir: + file_name = os.path.join(dir, file_name) + if not os.path.isfile(file_name): + raise OSError('Cannot find file '+file_name) + self._file_name = file_name + + # Check wcstools is installed and that it can read the file. + # If xy2sky is not installed, this will raise an OSError + p = subprocess.Popen(['xy2sky', '-d', '-n', '10', file_name, '0', '0'], + stdout=subprocess.PIPE) + results = p.communicate()[0].decode() + p.stdout.close() + if len(results) == 0 or 'cannot' in results: + raise OSError('wcstools (specifically xy2sky) was unable to read '+file_name) + + # wcstools supports LINEAR WCS's, but we don't want to allow them, since then + # the CelestialWCS base class is inappropriate. The clue to detect this is that + # the results only have 4 values, rather than use usual 5 (missing epoch). + if len(results.split()) == 4: + raise GalSimError("The WCS read in does not define a pair of celestial axes" ) + + @property + def file_name(self): + """The file name of the FITS file with the WCS information. + """ + return self._file_name + + @property + def origin(self): + """The origin in image coordinates of the WCS function. + """ + return self._origin + + def _radec(self, x, y, color=None): + # Need this to look like + # [ x1, y1, x2, y2, ... ] + # if input is either scalar x,y or two arrays. + xy = np.array([x, y], dtype=float).transpose().ravel() + + # The OS cannot handle arbitrarily long command lines, so we may need to split up + # the list into smaller chunks. + if 'SC_ARG_MAX' in os.sysconf_names: + arg_max = os.sysconf('SC_ARG_MAX') + else: + # A conservative guess. My machines have 131072, 262144, and 2621440 + arg_max = 32768 + + # Sometimes SC_ARG_MAX is listed as -1. Apparently that means "the configuration name + # is known, but the value is not defined." So, just go with the above conservative value. + if arg_max <= 0: + arg_max = 32768 + + # Just in case something weird happened. This should be _very_ conservative. + # It's the smallest value in this list of values for a bunch of systems: + # http://www.in-ulm.de/~mascheck/various/argmax/ + if arg_max < 4096: + arg_max = 4096 + + # This corresponds to the total number of characters in the line. + # But we really need to know how many arguments we are allowed to use in each call. + # Lets be conservative again and assume each argument is at most 20 characters. + # (We ignore the few characters at the start for the command name and such.) + nargs = int(arg_max / 40) * 2 # Make sure it is even! + + xy_strs = [ str(z) for z in xy ] + ra = [] + dec = [] + + for i in range(0,len(xy_strs),nargs): + xy1 = xy_strs[i:i+nargs] + # We'd like to get the output to 10 digits of accuracy. This corresponds to + # an accuracy of about 1.e-6 arcsec. But sometimes xy2sky cannot handle it, + # in which case the output will start with *************. If this happens, just + # decrease digits and try again. + for digits in range(10,5,-1): + # If xy2sky is not installed, this will raise an OSError + p = subprocess.Popen(['xy2sky', '-d', '-n', str(digits), self._file_name] + xy1, + stdout=subprocess.PIPE) + results = p.communicate()[0].decode() + p.stdout.close() + if len(results) == 0: + raise OSError('wcstools command xy2sky was unable to read '+ self._file_name) + if results[0] != '*': break + if results[0] == '*': + raise OSError('wcstools command xy2sky was unable to read '+self._file_name) + lines = results.splitlines() + + # Each line of output should looke like: + # x y J2000 ra dec + # But if there was an error, the J200 might be missing or the output might look like + # Off map x y + for line in lines: + vals = line.split() + if len(vals) != 5: + raise GalSimError('wcstools xy2sky returned invalid result near %s'%(xy1)) + ra.append(float(vals[0])) + dec.append(float(vals[1])) + + # wcstools reports ra, dec in degrees, so convert to radians + factor = degrees / radians + + if np.ndim(x) == np.ndim(y) == 0: + return ra[0]*factor, dec[0]*factor + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(x) == np.ndim(y) + assert x.shape == y.shape + return np.array(ra)*factor, np.array(dec)*factor + + def _xy(self, ra, dec, color=None): + rd = np.array([ra, dec], dtype=float).transpose().ravel() + rd *= radians / degrees + + # The boilerplate here is exactly the same as in _radec. See that function for an + # explanation of how this works. + if 'SC_ARG_MAX' in os.sysconf_names: + arg_max = os.sysconf('SC_ARG_MAX') + else: + arg_max = 32768 + if arg_max <= 0: arg_max = 32768 + if arg_max < 4096: arg_max = 4096 + nargs = int(arg_max / 40) * 2 + + rd_strs = [ str(z) for z in rd ] + x = [] + y = [] + + for i in range(0,len(rd_strs),nargs): + rd1 = rd_strs[i:i+nargs] + for digits in range(10,5,-1): + p = subprocess.Popen(['sky2xy', '-n', str(digits), self._file_name] + rd1, + stdout=subprocess.PIPE) + results = p.communicate()[0].decode() + p.stdout.close() + if len(results) == 0: + raise OSError('wcstools command sky2xy was unable to read '+self._file_name) + if results[0] != '*': break + if results[0] == '*': + raise OSError('wcstools command sky2xy was unable to read '+self._file_name) + + lines = results.splitlines() + + # Each line of output should looke like: + # ra dec J2000 -> x y + # However, if there was an error, the J200 might be missing. + for line in lines: + vals = line.split() + if len(vals) < 6: + raise GalSimError('wcstools sky2xy returned invalid result for %f,%f'%(ra,dec)) + if len(vals) > 6: + galsim_warn("wcstools sky2xy indicates that %f,%f is off the image. " + "output is %r"%(ra,dec,results)) + x.append(float(vals[4])) + y.append(float(vals[5])) + + if np.ndim(ra) == np.ndim(dec) == 0: + return x[0], y[0] + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(ra) == np.ndim(dec) + assert ra.shape == dec.shape + return np.array(x), np.array(y) + + def _newOrigin(self, origin): + ret = self.copy() + ret._origin = origin + return ret + + def _writeHeader(self, header, bounds): + # These are all we need to load it back. Just use the original file. + header["GS_WCS"] = ("WcsToolsWCS", "GalSim WCS name") + header["GS_FILE"] = (self._file_name, "GalSim original file with WCS data") + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") + + # We also copy over some of the fields we need. wcstools doesn't seem to have something + # that lists _all_ the keys that define the WCS. This just gets the approximate WCS. + p = subprocess.Popen(['wcshead', self._file_name], stdout=subprocess.PIPE) + results = p.communicate()[0].decode() + p.stdout.close() + v = results.split() + header["CTYPE1"] = v[3] + header["CTYPE2"] = v[4] + header["CRVAL1"] = v[5] + header["CRVAL2"] = v[6] + header["CRPIX1"] = v[8] + header["CRPIX2"] = v[9] + header["CDELT1"] = v[10] + header["CDELT2"] = v[11] + header["CROTA2"] = v[12] + return header + + @staticmethod + def _readHeader(header): + file = header["GS_FILE"] + x0 = header["GS_X0"] + y0 = header["GS_Y0"] + return WcsToolsWCS(file, origin=_PositionD(x0,y0)) + + def copy(self): + # The copy module version of copying the dict works fine here. + return copy.copy(self) + + def __eq__(self, other): + return (self is other or + (isinstance(other, WcsToolsWCS) and + self._file_name == other.file_name and + self.origin == other.origin)) + + def __repr__(self): + return "galsim.WcsToolsWCS(%r, origin=%r)"%(self._file_name, self.origin) + + def __hash__(self): return hash(repr(self))
+ + +
[docs]class GSFitsWCS(CelestialWCS): + """This WCS uses a GalSim implementation to read a WCS from a FITS file. + + It doesn't do nearly as many WCS types as the other options, and it does not try to be + as rigorous about supporting all possible valid variations in the FITS parameters. + However, it does several popular WCS types properly, and it doesn't require any additional + python modules to be installed, which can be helpful. + + Currrently, it is able to parse the following WCS types: TAN, STG, ZEA, ARC, TPV, TNX + + A GSFitsWCS is initialized with one of the following commands:: + + >>> wcs = galsim.GSFitsWCS(file_name=file_name) # Open a file on disk + >>> wcs = galsim.GSFitsWCS(header=header) # Use an existing pyfits header + + Also, since the most common usage will probably be the first, you can also give a file name + without it being named:: + + >>> wcs = galsim.GSFitsWCS(file_name) + + In addition to reading from a FITS file, there is also a factory function that builds + a GSFitsWCS object implementing a TAN projection. See the docstring of `TanWCS` for + more details. + + Parameters: + file_name: The FITS file from which to read the WCS information. This is probably + the usual parameter to provide. [default: None] + dir: Optional directory to prepend to ``file_name``. [default: None] + hdu: Optionally, the number of the HDU to use if reading from a file. + The default is to use either the primary or first extension as + appropriate for the given compression. (e.g. for rice, the first + extension is the one you normally want.) [default: None] + header: The header of an open pyfits (or astropy.io) hdu. Or, it can be + a FitsHeader object. [default: None] + compression: Which decompression scheme to use (if any). See galsim.fits.read() + for the available options. [default: 'auto'] + origin: Optional origin position for the image coordinate system. + If provided, it should be a PositionD or PositionI. + [default: None] + """ + _req_params = { "file_name" : str } + _opt_params = { "dir" : str, "hdu" : int, "origin" : PositionD, + "compression" : str } + + def __init__(self, file_name=None, dir=None, hdu=None, header=None, compression='auto', + origin=None, _data=None, _doiter=True): + # Note: _data is not intended for end-user use. It enables the equivalent of a + # private constructor of GSFitsWCS by the function TanWCS. The details of its + # use are intentionally not documented above. + + self._color = None + self._tag = None # Write something useful here (see below). This is just used for the str. + self._doiter = _doiter + + # If _data is given, copy the data and we're done. + if _data is not None: + self.wcs_type = _data[0] + self.crpix = _data[1] + self.cd = _data[2] + self.center = _data[3] + self.pv = _data[4] + self.ab = _data[5] + self.abp = _data[6] + if self.wcs_type in ('TAN', 'TPV', 'TNX', 'TAN-SIP'): + self.projection = 'gnomonic' + elif self.wcs_type in ('STG', 'STG-SIP'): + self.projection = 'stereographic' + elif self.wcs_type in ('ZEA', 'ZEA-SIP'): + self.projection = 'lambert' + elif self.wcs_type in ('ARC', 'ARC-SIP'): + self.projection = 'postel' + else: + raise ValueError("Invalid wcs_type in _data") + return + + # Read the file if given. + if file_name is not None: + if dir is not None: + self._tag = repr(os.path.join(dir,file_name)) + else: + self._tag = repr(file_name) + if hdu is not None: + self._tag += ', hdu=%r'%hdu + if compression != 'auto': + self._tag += ', compression=%r'%compression + if header is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) + hdu, hdu_list, fin = fits.readFile(file_name, dir, hdu, compression) + + try: + if file_name is not None: + header = hdu.header + + if header is None: + raise GalSimIncompatibleValuesError( + "Must provide either file_name or header", file_name=file_name, header=header) + + # Read the wcs information from the header. + self._read_header(header) + + finally: + if file_name is not None: + fits.closeHDUList(hdu_list, fin) + + if origin is not None: + self.crpix += [ origin.x, origin.y ] + + # The origin is a required attribute/property, since it is used by some functions like + # shiftOrigin to get the current origin value. We don't use it in this class, though, so + # just make origin a dummy property that returns 0,0. + @property + def origin(self): + """The origin in image coordinates of the WCS function. + """ + return _PositionD(0.,0.) + + def _read_header(self, header): + # Start by reading the basic WCS stuff that most types have. + ctype1 = header.get('CTYPE1','') + ctype2 = header.get('CTYPE2','') + if ctype1.startswith('DEC--') and ctype2.startswith('RA---'): + flip = True + elif ctype1.startswith('RA---') and ctype2.startswith('DEC--'): + flip = False + else: + raise GalSimError( + "GSFitsWCS only supports celestial coordinate systems." + "Expecting CTYPE1,2 to start with RA--- and DEC--. Got %s, %s"%(ctype1, ctype2)) + if ctype1[5:] != ctype2[5:]: # pragma: no cover + raise OSError("ctype1, ctype2 do not seem to agree on the WCS type") + self.wcs_type = ctype1[5:] + if self.wcs_type in ('TAN', 'TPV', 'TNX', 'TAN-SIP'): + self.projection = 'gnomonic' + elif self.wcs_type in ('STG', 'STG-SIP'): + self.projection = 'stereographic' + elif self.wcs_type in ('ZEA', 'ZEA-SIP'): + self.projection = 'lambert' + elif self.wcs_type in ('ARC', 'ARC-SIP'): + self.projection = 'postel' + else: + raise GalSimValueError("GSFitsWCS cannot read files using given wcs_type.", + self.wcs_type, + ('TAN', 'TPV', 'TNX', 'TAN-SIP', 'STG', 'STG-SIP', 'ZEA', + 'ZEA-SIP', 'ARC', 'ARC-SIP')) + crval1 = float(header['CRVAL1']) + crval2 = float(header['CRVAL2']) + crpix1 = float(header['CRPIX1']) + crpix2 = float(header['CRPIX2']) + if 'CD1_1' in header: + cd11 = float(header['CD1_1']) + cd12 = float(header['CD1_2']) + cd21 = float(header['CD2_1']) + cd22 = float(header['CD2_2']) + elif 'CDELT1' in header: + if 'PC1_1' in header: + cd11 = float(header['PC1_1']) * float(header['CDELT1']) + cd12 = float(header['PC1_2']) * float(header['CDELT1']) + cd21 = float(header['PC2_1']) * float(header['CDELT2']) + cd22 = float(header['PC2_2']) * float(header['CDELT2']) + else: + cd11 = float(header['CDELT1']) + cd12 = 0. + cd21 = 0. + cd22 = float(header['CDELT2']) + else: # pragma: no cover (all our test files have either CD or CDELT) + cd11 = 1. + cd12 = 0. + cd21 = 0. + cd22 = 1. + + # Usually the units are degrees, but make sure + if 'CUNIT1' in header: + cunit1 = header['CUNIT1'] + cunit2 = header['CUNIT2'] + ra_units = AngleUnit.from_name(cunit1) + dec_units = AngleUnit.from_name(cunit2) + else: + ra_units = degrees + dec_units = degrees + + if flip: + crval1, crval2 = crval2, crval1 + ra_units, dec_units = dec_units, ra_units + cd11, cd21 = cd21, cd11 + cd12, cd22 = cd22, cd12 + + self.crpix = np.array( [ crpix1, crpix2 ] ) + self.cd = np.array( [ [ cd11, cd12 ], + [ cd21, cd22 ] ] ) + + self.center = CelestialCoord(crval1 * ra_units, crval2 * dec_units) + + # There was an older proposed standard that used TAN with PV values, which is used by + # SCamp, so we want to support it if possible. The standard is now called TPV, so + # use that for our wcs_type if we see the PV values with TAN. + if self.wcs_type == 'TAN' and 'PV1_1' in header: + self.wcs_type = 'TPV' + + self.pv = None + self.ab = None + self.abp = None + if self.wcs_type == 'TPV': + self._read_tpv(header) + elif self.wcs_type == 'TNX': + self._read_tnx(header) + elif self.wcs_type in ('TAN-SIP', 'STG-SIP', 'ZEA-SIP', 'ARC-SIP'): + self._read_sip(header) + + # I think the CUNIT specification applies to the CD matrix as well, but I couldn't actually + # find good documentation for this. Plus all the examples I saw used degrees anyway, so + # it's hard to tell. Hopefully this will never matter, but if CUNIT is not deg, this + # next bit might be wrong. + # I did see documentation that the PV matrices always use degrees, so at least we shouldn't + # have to worry about that. + if ra_units != degrees: # pragma: no cover + self.cd[0,:] *= 1. * ra_units / degrees + if dec_units != degrees: # pragma: no cover + self.cd[1,:] *= 1. * dec_units / degrees + + def _read_tpv(self, header): + # See http://fits.gsfc.nasa.gov/registry/tpvwcs/tpv.html for details about how + # the TPV standard is defined. + + # The standard includes an option to have odd powers of r, which kind of screws + # up the numbering of these coefficients. We don't implement these terms, so + # before going further, check to make sure none are present. + odd_indices = [3, 11, 23, 39] + if any((header.get('PV%s_%s'%(i,j), 0.) != 0. for i in [1,2] for j in odd_indices)): + raise GalSimNotImplementedError("TPV not implemented for odd powers of r") + + pv1 = [ float(header.get('PV1_%s'%k, 0.)) for k in range(40) if k not in odd_indices ] + pv2 = [ float(header.get('PV2_%s'%k, 0.)) for k in range(40) if k not in odd_indices ] + + maxk = max(np.nonzero(pv1)[0][-1], np.nonzero(pv2)[0][-1]) + # maxk = (order+1) * (order+2) / 2 - 1 + order = int(np.floor(np.sqrt(2*(maxk+1)))) - 1 + self.pv = np.zeros((2,order+1,order+1)) + + # Another strange thing is that the two matrices are defined in the opposite order + # with respect to their element ordering. But at least now, without the odd terms, + # we can just proceed in order in the k indices. So what we call k=3..9 here were + # originally PVi_4..10. + # For reference, here is what it would look like for order = 3: + # self.pv = np.array( [ [ [ pv1[0], pv1[2], pv1[5], pv1[9] ], + # [ pv1[1], pv1[4], pv1[8], 0. ], + # [ pv1[3], pv1[7], 0. , 0. ], + # [ pv1[6], 0. , 0. , 0. ] ], + # [ [ pv2[0], pv2[1], pv2[3], pv2[6] ], + # [ pv2[2], pv2[4], pv2[7], 0. ], + # [ pv2[5], pv2[8], 0. , 0. ], + # [ pv2[9], 0. , 0. , 0. ] ] ] ) + k = 0 + for N in range(order+1): + for j in range(N+1): + i = N-j + self.pv[0,i,j] = pv1[k] + self.pv[1,j,i] = pv2[k] + k = k+1 + + def _read_sip(self, header): + a_order = int(header['A_ORDER']) + b_order = int(header['B_ORDER']) + order = max(a_order,b_order) # Use the same order for both + a = [ float(header.get('A_'+str(i)+'_'+str(j),0.)) + for i in range(order+1) for j in range(order+1) ] + a = np.array(a).reshape((order+1,order+1)) + b = [ float(header.get('B_'+str(i)+'_'+str(j),0.)) + for i in range(order+1) for j in range(order+1) ] + b = np.array(b).reshape((order+1,order+1)) + a[1,0] += 1 # Standard A,B are a differential calculation. It's more convenient to + b[0,1] += 1 # keep this as an absolute calculation like PV does. + self.ab = np.array([a, b]) + + # The reverse transformation is not required to be there. + if 'AP_ORDER' in header: + ap_order = int(header['AP_ORDER']) + bp_order = int(header['BP_ORDER']) + order = max(ap_order,bp_order) # Use the same order for both + ap = [ float(header.get('AP_'+str(i)+'_'+str(j),0.)) + for i in range(order+1) for j in range(order+1) ] + ap = np.array(ap).reshape((order+1,order+1)) + bp = [ float(header.get('BP_'+str(i)+'_'+str(j),0.)) + for i in range(order+1) for j in range(order+1) ] + bp = np.array(bp).reshape((order+1,order+1)) + ap[1,0] += 1 + bp[0,1] += 1 + self.abp = np.array([ap, bp]) + + def _read_tnx(self, header): + + # TNX has a few different options. Rather than keep things in the native format, + # we actually convert to the equivalent of TPV to make the actual operations faster. + # See http://iraf.noao.edu/projects/ccdmosaic/tnx.html for details. + + # First, parse the input values, which are stored in WAT keywords: + k = 1 + wat1 = "" + key = 'WAT1_%03d'%k + while key in header: + wat1 += header[key] + k = k+1 + key = 'WAT1_%03d'%k + wat1 = wat1.split() + + k = 1 + wat2 = "" + key = 'WAT2_%03d'%k + while key in header: + wat2 += header[key] + k = k+1 + key = 'WAT2_%03d'%k + wat2 = wat2.split() + + if ( len(wat1) < 12 or + wat1[0] != 'wtype=tnx' or + wat1[1] != 'axtype=ra' or + wat1[2] != 'lngcor' or + wat1[3] != '=' or + not wat1[4].startswith('"') or + not wat1[-1].endswith('"') ): # pragma: no cover + raise GalSimError("TNX WAT1 was not as expected") + if ( len(wat2) < 12 or + wat2[0] != 'wtype=tnx' or + wat2[1] != 'axtype=dec' or + wat2[2] != 'latcor' or + wat2[3] != '=' or + not wat2[4].startswith('"') or + not wat2[-1].endswith('"') ): # pragma: no cover + raise GalSimError("TNX WAT2 was not as expected") + + # Break the next bit out into another function, since it is the same for x and y. + pv1 = self._parse_tnx_data(wat1[4:]) + pv2 = self._parse_tnx_data(wat2[4:]) + + # Those just give the adjustments to the position, not the matrix that gives the final + # position. i.e. the TNX standard uses u = u + [1 u u^2 u^3] PV [1 v v^2 v^3]T. + # So we need to add 1 to the correct term in each matrix to get what we really want. + pv1[1,0] += 1. + pv2[0,1] += 1. + + # Finally, store these as our pv 3-d array. + self.pv = np.array([pv1, pv2]) + + # We've now converted this to TPV, so call it that when we output to a fits header. + self.wcs_type = 'TPV' + + def _parse_tnx_data(self, data): + + # I'm not sure if there is any requirement on there being a space before the final " and + # not before the initial ". But both the example in the description of the standard and + # the one we have in our test directory are this way. Here, if the " is by itself, I + # remove the item, and if it is part of a longer string, I just strip it off. Seems the + # most sensible thing to do. + if data[0] == '"': # pragma: no cover + data = data[1:] + else: + data[0] = data[0][1:] + if data[-1] == '"': + data = data[:-1] + else: # pragma: no cover + data[-1] = data[-1][:-1] + + code = int(data[0].strip('.')) # Weirdly, these integers are given with decimal points. + xorder = int(data[1].strip('.')) + yorder = int(data[2].strip('.')) + cross = int(data[3].strip('.')) + if cross != 2: # pragma: no cover + raise GalSimNotImplementedError("TNX only implemented for half-cross option.") + if xorder != 4 or yorder != 4: # pragma: no cover + raise GalSimNotImplementedError("TNX only implemented for order = 4") + # Note: order = 4 really means cubic. order is how large the pv matrix is, i.e. 4x4. + + xmin = float(data[4]) + xmax = float(data[5]) + ymin = float(data[6]) + ymax = float(data[7]) + + pv1 = [ float(x) for x in data[8:] ] + if len(pv1) != 10: # pragma: no cover + raise GalSimError("Wrong number of items found in WAT data") + + # Put these into our matrix formulation. + pv = np.array( [ [ pv1[0], pv1[4], pv1[7], pv1[9] ], + [ pv1[1], pv1[5], pv1[8], 0. ], + [ pv1[2], pv1[6], 0. , 0. ], + [ pv1[3], 0. , 0. , 0. ] ] ) + + # Convert from Legendre or Chebyshev polynomials into regular polynomials. + if code < 3: # pragma: no branch (The only test file I can find has code = 1) + # Instead of 1, x, x^2, x^3, Chebyshev uses: 1, x', 2x'^2 - 1, 4x'^3 - 3x + # where x' = (2x - xmin - xmax) / (xmax-xmin). + # Similarly, with y' = (2y - ymin - ymin) / (ymax-ymin) + # We'd like to convert the pv matrix from being in terms of x' and y' to being + # in terms of just x, y. To see how this works, look at what pv[1,1] means: + # + # First, let's say we can write x as (a + bx), and we can write y' as (c + dy). + # Then the term for pv[1,1] is: + # + # term = x' * pv[1,1] * y' + # = (a + bx) * pv[1,1] * (d + ey) + # = a * pv[1,1] * c + a * pv[1,1] * d * y + # + x * b * pv[1,1] * c + x * b * pv[1,1] * d * y + # + # So the single term initially will contribute to 4 different terms in the final + # matrix. And the contributions will just be pv[1,1] times the outer product + # [a b]T [d e]. So if we can determine the matrix that converts from + # [1, x, x^2, x^3] to the Chebyshev vector, the the matrix we want is simply + # xmT pv ym. + a = -(xmax+xmin)/(xmax-xmin) + b = 2./(xmax-xmin) + c = -(ymax+ymin)/(ymax-ymin) + d = 2./(ymax-ymin) + xm = np.zeros((4,4)) + ym = np.zeros((4,4)) + xm[0,0] = 1. + xm[1,0] = a + xm[1,1] = b + ym[0,0] = 1. + ym[1,0] = c + ym[1,1] = d + if code == 1: + for m in range(2,4): + # The recursion rule is Pm = 2 x' Pm-1 - Pm-2 + # Pm = 2 a Pm-1 - Pm-2 + x * 2 b Pm-1 + xm[m] = 2. * a * xm[m-1] - xm[m-2] + xm[m,1:] += 2. * b * xm[m-1,:-1] + ym[m] = 2. * c * ym[m-1] - ym[m-2] + ym[m,1:] += 2. * d * ym[m-1,:-1] + else: # pragma: no cover + # code == 2 means Legendre. The same argument applies, but we have a + # different recursion rule. + # WARNING: This branch has not been tested! I don't have any TNX files + # with Legendre functions to test it on. I think it's right, but beware! + for m in range(2,4): + # The recursion rule is Pm = ((2m-1) x' Pm-1 - (m-1) Pm-2) / m + # Pm = ((2m-1) a Pm-1 - (m-1) Pm-2) / m + # + x * ((2m-1) b Pm-1) / m + xm[m] = ((2.*m-1.) * a * xm[m-1] - (m-1.) * xm[m-2]) / m + xm[m,1:] += ((2.*m-1.) * b * xm[m-1,:-1]) / m + ym[m] = ((2.*m-1.) * c * ym[m-1] - (m-1.) * ym[m-2]) / m + ym[m,1:] += ((2.*m-1.) * d * ym[m-1,:-1]) / m + + pv2 = np.dot(xm.T , np.dot(pv, ym)) + return pv2 + + def _apply_ab(self, x, y, ab): + # Note: this is used for both pv and ab, since the action is the same. + # They just occur at two different places in the calculation. + x1 = horner2d(x, y, ab[0], triangle=True) + y1 = horner2d(x, y, ab[1], triangle=True) + return x1, y1 + + def _apply_cd(self, x, y): + # Do this in C++ layer for speed. + nx = len(x.ravel()) + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _cd = self.cd.__array_interface__['data'][0] + _galsim.ApplyCD(nx, _x, _y, _cd) + return x, y + + def _uv(self, x, y): + # Most of the work for _radec. But stop at (u,v). + + # Start with (u,v) = the image position + x = np.ascontiguousarray(x, dtype=float) + y = np.ascontiguousarray(y, dtype=float) + + x -= self.crpix[0] + y -= self.crpix[1] + + if self.ab is not None: + x, y = self._apply_ab(x, y, self.ab) + + # This converts to (u,v) in the tangent plane + # Expanding this out is a bit faster than using np.dot for 2x2 matrix. + u, v = self._apply_cd(x, y) + + if self.pv is not None: + u, v = self._apply_ab(u, v, self.pv) + + # Convert (u,v) from degrees to radians + # Also, the FITS standard defines u,v backwards relative to our standard. + # They have +u increasing to the east, not west. Hence the - for u. + factor = 1. * degrees / radians + u *= -factor + v *= factor + return u, v + + def _radec(self, x, y, color=None): + # Get the position in the tangent plane + u, v = self._uv(x, y) + # Then convert from (u,v) to (ra, dec) using the appropriate projection. + ra, dec = self.center.deproject_rad(u, v, projection=self.projection) + + if np.ndim(x) == np.ndim(y) == 0: + return ra[0], dec[0] + else: + # Sanity checks that the inputs are the same shape. + assert np.ndim(x) == np.ndim(y) + assert x.shape == y.shape + return ra, dec + + def _invert_ab(self, u, v, ab, abp=None): + # This is used both for inverting (u,v) = PV (u',v') + # and for inverting (x,y) = AB (x',y') + # Here (and in C++) the notation is (u,v) = AB(x,y), even though both (u,v) and (x,y) + # in this context are either in CCD coordinates (normally called x,y) or tangent plane + # coordinates (normally called u,v). + # abp is an optional set of coefficients to make a good guess for x,y + + uu = np.ascontiguousarray(u) # Don't overwrite the given u,v, since we need it at the end + vv = np.ascontiguousarray(v) # to check it we were provided scalars or arrays. + + x = np.atleast_1d(u.copy()) # Start with x,y = u,v. + y = np.atleast_1d(v.copy()) # This may be updated below if abp is provided. + + nab = ab.shape[1] + nabp = abp.shape[1] if abp is not None else 0 + nx = len(x.ravel()) + _uu = uu.__array_interface__['data'][0] + _vv = vv.__array_interface__['data'][0] + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _ab = ab.__array_interface__['data'][0] + _abp = 0 if abp is None else abp.__array_interface__['data'][0] + with convert_cpp_errors(): + _galsim.InvertAB(nx, nab, _uu, _vv, _ab, _x, _y, self._doiter, nabp, _abp) + + # Return the right type for u,v + try: + len(u) + except TypeError: + return x[0], y[0] + else: + return x, y + + + def _xy(self, ra, dec, color=None): + u, v = self.center.project_rad(ra, dec, projection=self.projection) + + # Again, FITS has +u increasing to the east, not west. Hence the - for u. + factor = radians / degrees + u *= -factor + v *= factor + + if self.pv is not None: + u, v = self._invert_ab(u, v, self.pv) + + if not hasattr(self, 'cdinv'): + self.cdinv = np.linalg.inv(self.cd) + # This is a bit faster than using np.dot for 2x2 matrix. + x = self.cdinv[0,0] * u + self.cdinv[0,1] * v + y = self.cdinv[1,0] * u + self.cdinv[1,1] * v + + if self.ab is not None: + x, y = self._invert_ab(x, y, self.ab, abp=self.abp) + + x += self.crpix[0] + y += self.crpix[1] + + return x, y + + # Override the version in CelestialWCS, since we can do this more efficiently. + def _local(self, image_pos, color=None): + + if image_pos is None: + raise TypeError("origin must be a PositionD or PositionI argument") + + # The key lemma here is that chain rule for jacobians is just matrix multiplication. + # i.e. if s = s(u,v), t = t(u,v) and u = u(x,y), v = v(x,y), then + # ( dsdx dsdy ) = ( dsdu dudx + dsdv dvdx dsdu dudy + dsdv dvdy ) + # ( dtdx dtdy ) = ( dtdu dudx + dtdv dvdx dtdu dudy + dtdv dvdy ) + # = ( dsdu dsdv ) ( dudx dudy ) + # ( dtdu dtdv ) ( dvdx dvdy ) + # + # So if we can find the jacobian for each step of the process, we just multiply the + # jacobians. + # + # We also need to keep track of the position along the way, so we have to repeat many + # of the steps in _radec. + + p1 = np.array([image_pos.x, image_pos.y], dtype=float) + + # Start with unit jacobian + jac = np.diag([1,1]) + + # No effect on the jacobian from this step. + p1 -= self.crpix + + if self.ab is not None: + x = p1[0] + y = p1[1] + order = len(self.ab[0])-1 + xpow = x ** np.arange(order+1) + ypow = y ** np.arange(order+1) + p1 = np.dot(np.dot(self.ab, ypow), xpow) + + dxpow = np.zeros(order+1) + dypow = np.zeros(order+1) + dxpow[1:] = (np.arange(order)+1.) * xpow[:-1] + dypow[1:] = (np.arange(order)+1.) * ypow[:-1] + j1 = np.transpose([ np.dot(np.dot(self.ab, ypow), dxpow) , + np.dot(np.dot(self.ab, dypow), xpow) ]) + jac = np.dot(j1,jac) + + # The jacobian here is just the cd matrix. + p2 = np.dot(self.cd, p1) + jac = np.dot(self.cd, jac) + + if self.pv is not None: + # Now we apply the distortion terms + u = p2[0] + v = p2[1] + order = len(self.pv[0])-1 + + upow = u ** np.arange(order+1) + vpow = v ** np.arange(order+1) + + p2 = np.dot(np.dot(self.pv, vpow), upow) + + # The columns of the jacobian for this step are the same function with dupow + # or dvpow. + dupow = np.zeros(order+1) + dvpow = np.zeros(order+1) + dupow[1:] = (np.arange(order)+1.) * upow[:-1] + dvpow[1:] = (np.arange(order)+1.) * vpow[:-1] + j1 = np.transpose([ np.dot(np.dot(self.pv, vpow), dupow) , + np.dot(np.dot(self.pv, dvpow), upow) ]) + jac = np.dot(j1,jac) + + unit_convert = [ -1 * degrees / radians, 1 * degrees / radians ] + p2 *= unit_convert + # Subtle point: Don't use jac *= ..., because jac might currently be self.cd, and + # that would change self.cd! + jac = jac * np.transpose( [ unit_convert ] ) + + # Finally convert from (u,v) to (ra, dec). We have a special function that computes + # the jacobian of this step in the CelestialCoord class. + j2 = self.center.jac_deproject_rad(p2[0], p2[1], projection=self.projection) + jac = np.dot(j2,jac) + + # This now has units of radians/pixel. We want instead arcsec/pixel. + jac *= radians / arcsec + + return JacobianWCS(jac[0,0], jac[0,1], jac[1,0], jac[1,1]) + + + def _newOrigin(self, origin): + ret = self.copy() + ret.crpix = ret.crpix + [ origin.x, origin.y ] + return ret + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("GSFitsWCS", "GalSim WCS name") + header["CTYPE1"] = 'RA---' + self.wcs_type + header["CTYPE2"] = 'DEC--' + self.wcs_type + header["CRPIX1"] = self.crpix[0] + header["CRPIX2"] = self.crpix[1] + header["CD1_1"] = self.cd[0][0] + header["CD1_2"] = self.cd[0][1] + header["CD2_1"] = self.cd[1][0] + header["CD2_2"] = self.cd[1][1] + header["CUNIT1"] = 'deg' + header["CUNIT2"] = 'deg' + header["CRVAL1"] = self.center.ra / degrees + header["CRVAL2"] = self.center.dec / degrees + if self.pv is not None: + order = len(self.pv[0])-1 + k = 0 + odd_indices = [3, 11, 23, 39] + for n in range(order+1): + for j in range(n+1): + i = n-j + header["PV1_" + str(k)] = self.pv[0, i, j] + header["PV2_" + str(k)] = self.pv[1, j, i] + k = k + 1 + if k in odd_indices: k = k + 1 + if self.ab is not None: + order = len(self.ab[0])-1 + header["A_ORDER"] = order + for i in range(order+1): + for j in range(order+1): + aij = self.ab[0,i,j] + if i==1 and j==0: aij -= 1 # Turn back into standard form. + if aij != 0.: + header["A_"+str(i)+"_"+str(j)] = aij + header["B_ORDER"] = order + for i in range(order+1): + for j in range(order+1): + bij = self.ab[1,i,j] + if i==0 and j==1: bij -= 1 + if bij != 0.: + header["B_"+str(i)+"_"+str(j)] = bij + if self.abp is not None: + order = len(self.abp[0])-1 + header["AP_ORDER"] = order + for i in range(order+1): + for j in range(order+1): + apij = self.abp[0,i,j] + if i==1 and j==0: apij -= 1 + if apij != 0.: + header["AP_"+str(i)+"_"+str(j)] = apij + header["BP_ORDER"] = order + for i in range(order+1): + for j in range(order+1): + bpij = self.abp[1,i,j] + if i==0 and j==1: bpij -= 1 + if bpij != 0.: + header["BP_"+str(i)+"_"+str(j)] = bpij + return header + + @staticmethod + def _readHeader(header): + return GSFitsWCS(header=header) + + def copy(self): + # The copy module version of copying the dict works fine here. + return copy.copy(self) + + def __eq__(self, other): + return (self is other or + (isinstance(other, GSFitsWCS) and + self.wcs_type == other.wcs_type and + np.array_equal(self.crpix,other.crpix) and + np.array_equal(self.cd,other.cd) and + self.center == other.center and + np.array_equal(self.pv,other.pv) and + np.array_equal(self.ab,other.ab) and + np.array_equal(self.abp,other.abp))) + + def __repr__(self): + if self.pv is None: + pv_repr = repr(self.pv) + else: + pv_repr = 'array(%r)'%self.pv.tolist() + if self.ab is None: + ab_repr = repr(self.ab) + else: + ab_repr = 'array(%r)'%self.ab.tolist() + if self.abp is None: + abp_repr = repr(self.abp) + else: + abp_repr = 'array(%r)'%self.abp.tolist() + return "galsim.GSFitsWCS(_data = [%r, array(%r), array(%r), %r, %s, %s, %s])"%( + self.wcs_type, self.crpix.tolist(), self.cd.tolist(), self.center, + pv_repr, ab_repr, abp_repr) + + def __str__(self): + if self._tag is None: + return self.__repr__() + else: + return "galsim.GSFitsWCS(%s)"%(self._tag) + + def __hash__(self): return hash(repr(self))
+ + +
[docs]def TanWCS(affine, world_origin, units=arcsec): + """This is a function that returns a `GSFitsWCS` object for a TAN WCS projection. + + The TAN projection is essentially an affine transformation from image coordinates to + Euclidean (u,v) coordinates on a tangent plane, and then a "deprojection" of this plane + onto the sphere given a particular RA, Dec for the location of the tangent point. + The tangent point will correspond to the location of (u,v) = (0,0) in the intermediate + coordinate system. + + Parameters: + affine: An `AffineTransform` defining the transformation from image coordinates + to the coordinates on the tangent plane. + world_origin: A `CelestialCoord` defining the location on the sphere where the + tangent plane is centered. + units: The angular units of the (u,v) intermediate coordinate system. + [default: galsim.arcsec] + + Returns: + a `GSFitsWCS` describing this WCS. + """ + # These will raise the appropriate errors if affine is not the right type. + dudx = affine.dudx * units / degrees + dudy = affine.dudy * units / degrees + dvdx = affine.dvdx * units / degrees + dvdy = affine.dvdy * units / degrees + origin = affine.origin + # The - signs are because the Fits standard is in terms of +u going east, rather than west + # as we have defined. So just switch the sign in the CD matrix. + cd = np.array([[ -dudx, -dudy ], [ dvdx, dvdy ]], dtype=float) + crpix = np.array([ origin.x, origin.y ], dtype=float) + + # We also need to absorb the affine world_origin back into crpix, since GSFits is expecting + # crpix to be the location of the tangent point in image coordinates. i.e. where (u,v) = (0,0) + # (u,v) = CD * (x-x0,y-y0) + (u0,v0) + # (0,0) = CD * (x0',y0') - CD * (x0,y0) + (u0,v0) + # CD (x0',y0') = CD (x0,y0) - (u0,v0) + # (x0',y0') = (x0,y0) - CD^-1 (u0,v0) + uv = np.array( [ affine.world_origin.x * units / degrees, + affine.world_origin.y * units / degrees ] ) + crpix -= np.dot(np.linalg.inv(cd) , uv) + + # Invoke the private constructor of GSFits using the _data kwarg. + data = ('TAN', crpix, cd, world_origin, None, None, None) + return GSFitsWCS(_data=data)
+ + +# This is a list of all the WCS types that can potentially read a WCS from a FITS file. +# The function FitsWCS will try each of these in order and return the first one that +# succeeds. AffineTransform should be last, since it will always succeed. +# The list is defined here at global scope so that external modules can add extra +# WCS types to the list if desired. + +fits_wcs_types = [ + + GSFitsWCS, # This doesn't work for very many WCS types, but it works for the very common + # TAN projection, and also TPV, which is used by SCamp. If it does work, it + # is a good choice, since it is easily the fastest of any of these. + + PyAstWCS, # This requires ``import starlink.Ast`` to succeed. This handles the largest + # number of WCS types of any of these. In fact, it worked for every one + # we tried in our unit tests (which was not exhaustive). + + AstropyWCS, # This requires ``import astropy.wcs`` to succeed. It doesn't support quite as + # many WCS types as PyAst. It's also usually a little slower, so we prefer + # PyAstWCS when it is available. + + WcsToolsWCS, # This requires the wcstool command line functions to be installed. + # It is very slow, so it should only be used as a last resort. + +] + + +
[docs]def FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', + text_file=False, suppress_warning=False): + """This factory function will try to read the WCS from a FITS file and return a WCS that will + work. It tries a number of different WCS classes until it finds one that succeeds in reading + the file. + + If none of them work, then the last class it tries, `AffineTransform`, is guaranteed to succeed, + but it will only model the linear portion of the WCS (the CD matrix, CRPIX, and CRVAL), using + reasonable defaults if even these are missing. If you think that you have the right software + for one of the WCS types, but FitsWCS still defaults to `AffineTransform`, it may be helpful to + update your installation of astropy and/or starlink (if you don't already have the latest + version). + + Note: The list of classes this function will try may be edited, e.g. by an external module + that wants to add an additional WCS type. The list is ``galsim.fitswcs.fits_wcs_types``. + + Parameters: + file_name: The FITS file from which to read the WCS information. This is probably + the usual parameter to provide. [default: None] + dir: Optional directory to prepend to ``file_name``. [default: None] + hdu: Optionally, the number of the HDU to use if reading from a file. + The default is to use either the primary or first extension as + appropriate for the given compression. (e.g. for rice, the first + extension is the one you normally want.) [default: None] + header: The header of an open pyfits (or astropy.io) hdu. Or, it can be + a FitsHeader object. [default: None] + compression: Which decompression scheme to use (if any). See galsim.fits.read() + for the available options. [default: 'auto'] + text_file: Normally a file is taken to be a fits file, but you can also give it a + text file with the header information (like the .head file output from + SCamp). In this case you should set ``text_file = True`` to tell GalSim + to parse the file this way. [default: False] + suppress_warning: Whether to suppress a warning that the WCS could not be read from the + FITS header, so the WCS defaulted to either a `PixelScale` or + `AffineTransform`. [default: False] + (Note: this is (by default) set to True when this function is implicitly + called from one of the galsim.fits.read* functions.) + """ + if file_name is not None: + if header is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both file_name and pyfits header", + file_name=file_name, header=header) + header = fits.FitsHeader(file_name=file_name, dir=dir, hdu=hdu, compression=compression, + text_file=text_file) + else: + file_name = 'header' # For sensible error messages below. + if header is None: + raise GalSimIncompatibleValuesError( + "Must provide either file_name or header", file_name=file_name, header=header) + if not isinstance(header, fits.FitsHeader): + header = fits.FitsHeader(header) + + if 'CTYPE1' not in header and 'CDELT1' not in header: + if not suppress_warning: + galsim_warn("No WCS information found in %r. Defaulting to PixelScale(1.0)"%(file_name)) + return PixelScale(1.0) + + # For linear WCS specifications, AffineTransformation should work. + # Note: Most files will have CTYPE1,2, but old style with only CDELT1,2 sometimes omits it. + if header.get('CTYPE1', 'LINEAR') == 'LINEAR': + wcs = AffineTransform._readHeader(header) + # Convert to PixelScale if possible. + if (wcs.dudx == wcs.dvdy and wcs.dudy == wcs.dvdx == 0): + if wcs.x0 == wcs.y0 == wcs.u0 == wcs.v0 == 0: + wcs = PixelScale(wcs.dudx) + else: + wcs = OffsetWCS(wcs.dudx, wcs.origin, wcs.world_origin) + return wcs + + # Otherwise (and typically), try the various wcs types that can read celestial coordinates. + for wcs_type in fits_wcs_types: + try: + wcs = wcs_type._readHeader(header) + # Give it a better tag for the repr if appropriate. + if hasattr(wcs,'_tag') and file_name != 'header': + if dir is not None: + wcs._tag = repr(os.path.join(dir,file_name)) + else: + wcs._tag = repr(file_name) + if hdu is not None: + wcs._tag += ', hdu=%r'%hdu + if compression != 'auto': + wcs._tag += ', compression=%r'%compression + return wcs + except Exception as err: + pass + else: + # Finally, this one is really the last resort, since it only reads in the linear part of the + # WCS. It defaults to the equivalent of a pixel scale of 1.0 if even these are not present. + if not suppress_warning: + galsim_warn("All the fits WCS types failed to read %r. Using AffineTransform " + "instead, which will not really be correct."%(file_name)) + return AffineTransform._readHeader(header)
+ +# Let this function work like a class in config. +FitsWCS._req_params = { "file_name" : str } +FitsWCS._opt_params = { "dir" : str, "hdu" : int, "compression" : str, 'text_file' : bool } + +
[docs]def FittedSIPWCS(x, y, ra, dec, wcs_type='TAN', order=3, center=None): + """A WCS constructed from a list of reference celestial and image + coordinates. + + Parameters: + x: Image x-coordinates of reference stars in pixels + y: Image y-coordinates of reference stars in pixels + ra: Right ascension of reference stars in radians + dec: Declination of reference stars in radians + wcs_type: The type of the tangent plane projection to use. Should be + one of ['TAN', 'STG', 'ZEA', or 'ARC']. [default: 'TAN'] + order: The order of the Simple Imaging Polynomial (SIP) used to + describe the WCS distortion. SIP coefficients kick in when + order >= 2. If you supply order=1, then just fit a WCS + without any SIP coefficients. [default: 3] + center: A `CelestialCoord` defining the location on the sphere where + the tangent plane is centered. [default: None, which means + use the average position of the list of reference stars] + """ + if order < 1: + raise GalSimValueError("Illegal SIP order", order) + + nstar = len(x) + # Make sure we have enough stars. + # We need 1 star for crpix, 2 more for cd, and then + # (order+2)*(order+1)/2 - 3 for ab. The total is then (order+1)*(order+2)/2 + nrequire = (order+1)*(order+2)/2 + if nstar < nrequire: + raise GalSimError( + "Require at least {:0} stars for SIP order {:1}" + .format(nrequire, order) + ) + + if center is None: + # Use deprojected 3D mean of ra/dec unit sphere points as center + wx = np.mean(np.cos(dec)*np.cos(ra)) + wy = np.mean(np.cos(dec)*np.sin(ra)) + wz = np.mean(np.sin(dec)) + center = CelestialCoord.from_xyz(wx, wy, wz) + + # Project radec onto uv so we can linearly fit the CRPIX and CD matrix + # initial guesses + u, v = center.project_rad(ra, dec) + a = np.array(np.broadcast_arrays(1., -u, v)).T + b = np.array([x, y]).T + r, _, _, _ = np.linalg.lstsq(a, b, rcond=None) + crpix_guess = r[0] + cd_guess = np.linalg.inv(np.deg2rad(r[1:])) + + # SIP coefficient initial guesses are just 0.0 + ab_guess = [] + for i in range(order+1): + for j in range(order+1): + if (i+j > 1) and (i+j <= order): + ab_guess.extend([0.0, 0.0]) + ab_guess = np.array(ab_guess) + guess = np.hstack([crpix_guess, cd_guess.ravel(), ab_guess.ravel()]) + + def _getWCS(wcs_type, center, crpix, cd, ab=None, abp=None, doiter=False): + _data = [ + wcs_type if order == 1 else wcs_type+'-SIP', + crpix, + cd, + center, + None, # pv, unused + ab, + abp + ] + return GSFitsWCS(_data=_data, _doiter=doiter) + + def _decodeSIP(order, params, min=2): + if order == 1: + return None + k = 0 + a = [] + b = [] + for i in range(order+1): + for j in range(order+1): + if (i+j < min) or (i+j > order): + a.append(0.0) + b.append(0.0) + else: + a.append(params[k]) + b.append(params[k+1]) + k += 2 + a = np.array(a).reshape((order+1, order+1)) + b = np.array(b).reshape((order+1, order+1)) + a[1,0] += 1 # GSFitsWCS wants these with the identity included. + b[0,1] += 1 + ab = np.array([a, b]) + return ab + + def _abLoss(params, order, wcs_type, center, x, y, u, v): + crpix = params[:2] + cd = params[2:6].reshape(2, 2) + ab = _decodeSIP(order, params[6:]) + wcs = _getWCS(wcs_type, center, crpix, cd, ab) + ra_p, dec_p = wcs.xyToradec(x, y, units='rad') + u_p, v_p = center.project_rad(ra_p, dec_p) + resid = np.hstack([u-u_p, v-v_p]) + resid = np.rad2deg(resid)*3600*1e6 # Work in microarcseconds + return resid + + result = least_squares( + _abLoss, + guess, + args=(order, wcs_type, center, x, y, u, v) + ) + # rmse = np.sqrt(2*result.cost/len(u)) + # print(f"rmse: {rmse:.2f} microarcsec") + crpix = result.x[0:2] + cd = result.x[2:6].reshape(2, 2) + ab = _decodeSIP(order, result.x[6:]) + + if order == 1: + return _getWCS(wcs_type, center, crpix, cd) + + # Now go back holding crpix, cd, and ab constant, and solve for inverse + # coefficients abp + # ABP SIP coefficient initial guesses are just 0.0 + abp_guess = [] + for i in range(order+1): + for j in range(order+1): + if (i+j > 0) and (i+j <= order): + abp_guess.extend([0.0, 0.0]) + abp_guess = np.array(abp_guess) + + def _abpLoss(params, order, wcs_type, center, crpix, cd, ab, ra, dec, x, y): + abp = _decodeSIP(order, params, min=1) + wcs = _getWCS(wcs_type, center, crpix, cd, ab, abp) + x_p, y_p = wcs.radecToxy(ra, dec, units='rad') + resid = np.hstack([x-x_p, y-y_p])*1e6 # work in micropixels + return resid + result = least_squares( + _abpLoss, abp_guess, + args=(order, wcs_type, center, crpix, cd, ab, ra, dec, x, y) + ) + # rmse = np.sqrt(2*result.cost/len(u)) + # print(f"rmse: {rmse:.2f} micropixels") + abp = _decodeSIP(order, result.x, min=1) + return _getWCS(wcs_type, center, crpix, cd, ab, abp, doiter=True)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/fouriersqrt.html b/docs/_build/html/_modules/galsim/fouriersqrt.html new file mode 100644 index 00000000000..61b1cf788fa --- /dev/null +++ b/docs/_build/html/_modules/galsim/fouriersqrt.html @@ -0,0 +1,323 @@ + + + + + + galsim.fouriersqrt — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.fouriersqrt

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'FourierSqrt', 'FourierSqrtProfile' ]
+
+import numpy as np
+import copy
+
+from .gsparams import GSParams
+from .gsobject import GSObject
+from .utilities import lazy_property
+from .errors import galsim_warn
+from . import chromatic as chrom
+
+
+
[docs]def FourierSqrt(obj, gsparams=None, propagate_gsparams=True): + """A function for computing the Fourier-space square root of either a `GSObject` or + `ChromaticObject`. + + The FourierSqrt function is principally used for doing an optimal coaddition algorithm + originally developed by Nick Kaiser (but unpublished) and also described by Zackay & Ofek 2015 + (http://adsabs.harvard.edu/abs/2015arXiv151206879Z). See the script make_coadd.py in the + GalSim/examples directory for an example of how it works. + + This function will inspect its input argument to decide if a `FourierSqrtProfile` object or a + `ChromaticFourierSqrtProfile` object is required to represent the operation applied to a surface + brightness profile. + + Parameters: + obj: The object to compute the Fourier-space square root of. + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the transformed object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + a `FourierSqrtProfile` or `ChromaticFourierSqrtProfile` instance as appropriate. + """ + + if isinstance(obj, chrom.ChromaticObject): + return chrom.ChromaticFourierSqrtProfile(obj, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + elif isinstance(obj, GSObject): + return FourierSqrtProfile(obj, gsparams=gsparams, propagate_gsparams=propagate_gsparams) + else: + raise TypeError("Argument to FourierSqrt must be either a GSObject or a ChromaticObject.")
+ + +
[docs]class FourierSqrtProfile(GSObject): + """A class for computing the Fourier-space sqrt of a `GSObject`. + + The FourierSqrtProfile class represents the Fourier-space square root of another profile. + Note that the FourierSqrtProfile class, or compound objects (Sum, Convolution) that include a + FourierSqrtProfile as one of the components cannot be photon-shot using the 'phot' method of + `GSObject.drawImage` method. + + You may also specify a ``gsparams`` argument. See the docstring for `GSParams` for more + information about this option. Note: if ``gsparams`` is unspecified (or None), then the + FourierSqrtProfile instance inherits the same `GSParams` as the object being operated on. + + The normal way to use this class is to use the `FourierSqrt` factory function:: + + >>> fourier_sqrt = galsim.FourierSqrt(obj) + + Parameters: + obj: The object to compute Fourier-space square root of. + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the transformed object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + """ + _sqrt2 = 1.4142135623730951 + + _has_hard_edges = False + _is_analytic_x = False + + def __init__(self, obj, gsparams=None, propagate_gsparams=True): + if not isinstance(obj, GSObject): + raise TypeError("Argument to FourierSqrtProfile must be a GSObject.") + + # Save the original object as an attribute, so it can be inspected later if necessary. + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + self._orig_obj = obj.withGSParams(self._gsparams) + else: + self._orig_obj = obj + + @property + def orig_obj(self): + """The original object being Fourier sqrt-ed. + """ + return self._orig_obj + + @property + def _noise(self): + if self.orig_obj.noise is not None: + galsim_warn("Unable to propagate noise in galsim.FourierSqrtProfile") + return None + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of the wrapped component object. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._orig_obj = self._orig_obj.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, FourierSqrtProfile) and + self.orig_obj == other.orig_obj and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.FourierSqrtProfile", self.orig_obj, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.FourierSqrtProfile(%r, gsparams=%r, propagate_gsparams=%r)'%( + self.orig_obj, self.gsparams, self._propagate_gsparams) + + def __str__(self): + return 'galsim.FourierSqrt(%s)'%self.orig_obj + + def _prepareDraw(self): + self.orig_obj._prepareDraw() + + @property + def _maxk(self): + return self.orig_obj.maxk + + @property + def _stepk(self): + return self.orig_obj.stepk * self._sqrt2 + + @property + def _is_axisymmetric(self): + return self.orig_obj.is_axisymmetric + + @property + def _is_analytic_k(self): + return self.orig_obj.is_analytic_k + + @property + def _centroid(self): + return 0.5 * self.orig_obj.centroid + + @property + def _flux(self): + return np.sqrt(self.orig_obj.flux) + + @property + def _positive_flux(self): + return np.sqrt(self.orig_obj.positive_flux) + + @property + def _negative_flux(self): + return np.sqrt(self.orig_obj.negative_flux) + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @property + def _max_sb(self): + # In this case, we want the autoconvolution of this object to get back to the + # maxSB value of the original obj + # flux * maxSB / 2 = maxSB_orig + # maxSB = 2 * maxSB_orig / flux + return 2. * self.orig_obj.max_sb / self.flux + + def _kValue(self, pos): + return np.sqrt(self.orig_obj._kValue(pos)) + + def _drawKImage(self, image, jac=None): + self.orig_obj._drawKImage(image, jac) + image.array[:,:] = np.sqrt(image.array)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/galaxy_sample.html b/docs/_build/html/_modules/galsim/galaxy_sample.html new file mode 100644 index 00000000000..d19699cedaa --- /dev/null +++ b/docs/_build/html/_modules/galsim/galaxy_sample.html @@ -0,0 +1,1017 @@ + + + + + + galsim.galaxy_sample — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.galaxy_sample

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'GalaxySample', 'COSMOSCatalog', ]
+
+import numpy as np
+import math
+import os
+
+from .real import RealGalaxy, RealGalaxyCatalog, _parse_files_dirs
+from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError
+from .errors import GalSimNotImplementedError, galsim_warn
+from .utilities import lazy_property
+from ._pyfits import pyfits
+from .random import BaseDeviate
+from . import utilities
+from .bandpass import Bandpass
+from .sed import SED
+from .angle import radians
+from .exponential import Exponential
+from .sersic import DeVaucouleurs, Sersic
+
+# Below is a number that is needed to relate the COSMOS parametric galaxy fits to quantities that
+# GalSim needs to make a GSObject representing that fit.  It is simply the pixel scale, in arcsec,
+# in the COSMOS weak lensing reductions used for the fits.
+# Note: This isn't used anywhere.  This is just informational, really.
+cosmos_pix_scale = 0.03
+
+
[docs]class GalaxySample: + """ + A class representing a random subsample of galaxies from an arbitrary deep survey. + + Depending on the keyword arguments, particularly ``use_real``, a `GalaxySample` may be able to + generate real galaxies, parametric galaxies, or both. + + The original version of this functionality is now in the subclass `COSMOSCatalog`, which + specializes to HST observations of the COSMOS field. `GalaxySample` is a generalization of + that, which works for arbitrary data sets, although the user is responsible for building + the appropriate input files to use with it. + + Unlike `COSMOSCatalog`, which has easy options for picking out one of the two galaxy subsets + that are available for download using ``galsim_download_cosmos``, for this class you need to + manually specify the file name. + + >>> sample = galsim.GalaxySample(file_name) + + Other than this difference, the functionality of this class is the same as `COSMOSCatalog`. + See the documentation of that function for more detail. + + Parameters: + file_name: The file containing the catalog. + dir: The directory with the catalog file and, if making realistic galaxies, + the image and noise files (or symlinks to them). [default: None, which + will look in $PREFIX/share/galsim.] + preload: Keyword that is only used for real galaxies, not parametric ones, to + choose whether to preload the header information. If ``preload=True``, + the bulk of the I/O time is in the constructor. If ``preload=False``, + there is approximately the same total I/O time (assuming you eventually + use most of the image files referenced in the catalog), but it is spread + over the calls to makeGalaxy(). [default: False] + orig_exptime: The exposure time (in seconds) of the original observations. + [default: 1] + orig_area: The effective collecting area (in cm^2) of the original observations. + [default: 1] + use_real: Enable the use of realistic galaxies? [default: True] + If this parameter is False, then ``makeGalaxy(gal_type='real')`` will + not be allowed, and there will be a (modest) decrease in RAM and time + spent on I/O when initializing the COSMOSCatalog. If the real + catalog is not available for some reason, it will still be possible to + make parametric images. + exclusion_level: Level of additional cuts to make on the galaxies based on the quality + of postage stamp definition and/or parametric fit quality [beyond the + minimal cuts imposed when making the catalog - see Mandelbaum et + al. (2012, MNRAS, 420, 1518) for details]. Options: + + - "none": No cuts. + - "bad_stamp": Apply cuts to eliminate galaxies that have failures in + postage stamp definition. These cuts may also eliminate a small + subset of the good postage stamps as well. + - "bad_fits": Apply cuts to eliminate galaxies that have failures in the + parametric fits. These cuts may also eliminate a small + subset of the good parametric fits as well. + - "marginal": Apply the above cuts, plus ones that eliminate some more + marginal cases. + + Use of "bad_stamp" or "marginal" requires a ``CATALOG_selection.fits`` + file (where CATALOG is ``file_name`` without the ".fits" extension). + [default: "none"] + min_hlr: Exclude galaxies whose fitted half-light radius is smaller than this + value (in arcsec). [default: 0, meaning no limit] + max_hlr: Exclude galaxies whose fitted half-light radius is larger than this + value (in arcsec). [default: 0, meaning no limit] + min_flux: Exclude galaxies whose fitted flux is smaller than this value. + [default: 0, meaning no limit] + max_flux: Exclude galaxies whose fitted flux is larger than this value. + [default: 0, meaning no limit] + cut_ratio: For the "bad_stamp" exclusions, cut out any stamps with average + adjacent pixels larger than this fraction of the peak pixel count. + [default: 0.8] + sn_limit: For the "bad_stamp" exclusions, cut out any stamps with estimated + S/N for an elliptical Gaussian less than this limit. [default: 10.] + min_mask_dist: For the "bad_stamp" exclusions, remove any stamps that have some + masked pixels closer to the center than this minimum distance + (in pixels). [default: 10] + exptime: The exposure time (in seconds) to assume when creating galaxies. + [default: None, which means to use orig_exptime] + area: The effective collecting area (in cm^2) to assume when creating + galaxies. [default: None, which means to use orig_area] + + After construction, the following attributes are available: + + Attributes: + nobjects: The number of objects in the sample + """ + _opt_params = { 'file_name' : str, 'dir' : str, + 'orig_exptime': float, 'orig_area': float, + 'preload' : bool, 'use_real' : bool, + 'exclusion_level' : str, 'min_hlr' : float, 'max_hlr' : float, + 'min_flux' : float, 'max_flux' : float, + 'cut_ratio' : float, 'sn_limit' : float, 'min_mask_dist': float, + } + + def __init__(self, file_name, dir=None, preload=False, + orig_exptime=1., orig_area=1., + use_real=True, exclusion_level='marginal', min_hlr=0, max_hlr=0., + min_flux=0., max_flux=0., cut_ratio=0.8, sn_limit=10., min_mask_dist=11, + exptime=None, area=None, _use_sample=None): + + self.use_real = use_real + self.preload = preload + self.cut_ratio = cut_ratio + self.sn_limit = sn_limit + self.min_mask_dist = min_mask_dist + self.orig_exptime = orig_exptime + self.orig_area = orig_area + self.exptime = exptime + self.area = area + + # We'll set these up if and when we need them. + self._bandpass = None + self._sed = None + + if exclusion_level not in ('none', 'bad_stamp', 'bad_fits', 'marginal'): + raise GalSimValueError("Invalid value of exclusion_level.", exclusion_level, + ('none', 'bad_stamp', 'bad_fits', 'marginal')) + + # Parse the file name + self.full_file_name, _, _ = _parse_files_dirs(file_name, dir, None) + self.use_sample = _use_sample + + try: + # Read in data. + with pyfits.open(self.full_file_name) as fits: + self.param_cat = fits[1].data + # Check if this was the right file. It should have a 'fit_status' column. + self.param_cat['fit_status'] + except KeyError: + # But if that doesn't work, then the name might be the name of the real catalog, + # so try adding _fits to it as above. + param_file_name = self.full_file_name.replace('.fits', '_fits.fits') + with pyfits.open(param_file_name) as fits: + self.param_cat = fits[1].data + + # NB. The pyfits FITS_Rec class has a bug where it makes a copy of the full + # record array in each record (e.g. in getParametricRecord) and then doesn't + # garbage collect it until the top-level FITS_Record goes out of scope. + # This leads to a memory leak of order 10MB or so each time we make a parametric + # galaxy. + # cf. https://mail.scipy.org/pipermail/astropy/2014-June/003218.html + # also https://github.com/astropy/astropy/pull/520 + # The simplest workaround seems to be to convert it to a regular numpy recarray. + # (This also makes it run much faster, as an extra bonus!) + self.param_cat = np.array(self.param_cat, copy=True) + + self.orig_index = np.arange(len(self.param_cat)) + self._apply_exclusion(exclusion_level, min_hlr, max_hlr, min_flux, max_flux) + + @lazy_property + def real_cat(self): + if self.use_real: + return RealGalaxyCatalog(self.full_file_name, preload=self.preload) + else: + return None + + def _apply_exclusion(self, exclusion_level, min_hlr=0, max_hlr=0, min_flux=0, max_flux=0): + mask = np.ones(len(self.orig_index), dtype=bool) + if exclusion_level in ('marginal', 'bad_stamp'): + # First, read in what we need to impose selection criteria, if the appropriate + # exclusion_level was chosen. + + # This should work if the user passed in (or we defaulted to) the real galaxy + # catalog name: + selection_file_name = self.full_file_name.replace('.fits', '_selection.fits') + try: + with pyfits.open(selection_file_name) as fits: + self.selection_cat = fits[1].data + except (OSError): + # There's one more option: full_file_name might be the parametric fit file, so + # we have to strip off the _fits.fits (instead of just the .fits) + selection_file_name = self.full_file_name.replace('_fits', '_selection') + try: + with pyfits.open(selection_file_name) as fits: + self.selection_cat = fits[1].data + except (OSError): # pragma: no cover + if self.use_sample is None: + raise + else: + raise OSError("File with GalSim selection criteria not found. " + "Run the program `galsim_download_cosmos -s %s` to get the " + "necessary selection file."%(self.use_sample)) + + # We proceed to select galaxies in a way that excludes suspect postage stamps (e.g., + # with deblending issues), suspect parametric model fits, or both of the above plus + # marginal ones. These two options for 'exclusion_level' involve placing cuts on + # the S/N of the object detection in the original postage stamp, and on issues with + # masking that can indicate deblending or detection failures. These cuts were used + # in GREAT3. In the case of the masking cut, in some cases there are messed up ones + # that have a 0 for self.selection_cat['peak_image_pixel_count']. To make sure we + # don't divide by zero (generating a RuntimeWarning), and still eliminate those, we + # will first set that column to 1.e-5. We choose a sample-dependent mask ratio cut, + # since this depends on the peak object flux, which will differ for the two samples + # (and we can't really cut on this for arbitrary user-defined samples). + div_val = self.selection_cat['peak_image_pixel_count'] + div_val[div_val == 0.] = 1.e-5 + mask &= ( (self.selection_cat['sn_ellip_gauss'] >= self.sn_limit) & + ((self.selection_cat['min_mask_dist_pixels'] > self.min_mask_dist) | + (self.selection_cat['average_mask_adjacent_pixel_count'] / \ + div_val < self.cut_ratio)) ) + + # Finally, impose a cut that the total flux in the postage stamp should be positive, + # which excludes a tiny number of galaxies (of order 10 in each sample) with some sky + # subtraction or deblending errors. Some of these are eliminated by other cuts when + # using exclusion_level='marginal'. + if self.real_cat is not None: + mask &= self.real_cat.stamp_flux > 0 + + if exclusion_level in ('bad_fits', 'marginal'): + # This 'exclusion_level' involves eliminating failed parametric fits (bad fit status + # flags). In this case we only get rid of those with failed bulge+disk AND failed + # Sersic fits, so there is no viable parametric model for the galaxy. + sersicfit_status = self.param_cat['fit_status'][:,4] + bulgefit_status = self.param_cat['fit_status'][:,0] + mask &= ( ((sersicfit_status > 0) & + (sersicfit_status < 5)) | + ((bulgefit_status > 0) & + (bulgefit_status < 5)) ) + + if exclusion_level == 'marginal': + # We have already placed some cuts (above) in this case, but we'll do some more. For + # example, a failed bulge+disk fit often indicates difficulty in fit convergence due to + # noisy surface brightness profiles, so we might want to toss out those that have a + # failure in EITHER fit. + mask &= ( ((sersicfit_status > 0) & + (sersicfit_status < 5)) & + ((bulgefit_status > 0) & + (bulgefit_status < 5)) ) + + # Some fit parameters can indicate a likely sky subtraction error: very high sersic n + # AND abnormally large half-light radius (>1 arcsec). + if 'hlr' not in self.param_cat.dtype.names and self.use_sample is not None: # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + hlr = self.param_cat['hlr'][:,0] + n = self.param_cat['sersicfit'][:,2] + mask &= ( (n < 5) | (hlr < 1.) ) + + # Major flux differences in the parametric model vs. the COSMOS catalog can indicate fit + # issues, deblending problems, etc. + mask &= ( np.abs(self.selection_cat['dmag']) < 0.8) + + if min_hlr > 0. or max_hlr > 0. or min_flux > 0. or max_flux > 0.: + if 'hlr' not in self.param_cat.dtype.names and self.use_sample is not None: # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + + hlr = self.param_cat['hlr'][:,0] # sersic half-light radius + flux = self.param_cat['flux'][:,0] + + if min_hlr > 0.: + mask &= (hlr > min_hlr) + if max_hlr > 0.: + mask &= (hlr < max_hlr) + if min_flux > 0.: + mask &= (flux > min_flux) + if max_flux > 0.: + mask &= (flux < max_flux) + + self.orig_index = self.orig_index[mask] + self.nobjects = len(self.orig_index) + + # We need this method because the config apparatus will use this via a Proxy, and they cannot + # access attributes directly -- just call methods. So this is how we get nobjects there. + def getNObjects(self) : return self.nobjects + def getUseSample(self): return self.use_sample + def getOrigIndex(self, index): return self.orig_index[index] + def getNTot(self) : return len(self.param_cat) + def __len__(self): return self.nobjects + +
[docs] def makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size=5, + deep=False, sersic_prec=0.05, rng=None, n_random=None, gsparams=None): + """ + Routine to construct one or more `GSObject` instances corresponding to the catalog entry + with a particular index or indices. + + The flux of the galaxy corresponds to a 1 second exposure time with the Hubble Space + Telescope. Users who wish to simulate F814W images with a different telescope and an + exposure time longer than 1 second should multiply by that exposure time, and by the square + of the ratio of the effective diameter of their telescope compared to that of HST. + (Effective diameter may differ from the actual diameter if there is significant + obscuration.) See demo11.py for an example that explicitly takes this normalization into + account. + + Due to the adopted flux normalization, drawing into an image with the COSMOS bandpass, + zeropoint of 25.94, and pixel scale should give the right pixel values to mimic the actual + COSMOS science images. The COSMOS science images that we use are normalized to a count rate + of 1 second, which is why there is no need to rescale to account for the COSMOS exposure + time. + + There is an option to make chromatic objects (``chromatic=True``); however, it is important + to bear in mind that we do not actually have spatially-resolved color information for these + galaxies, so this keyword can only be True if we are using parametric galaxies. Even then, + we simply do the most arbitrary thing possible, which is to assign bulges an elliptical + `SED`, disks a disk-like `SED`, and `Sersic` galaxies with intermediate values of n some + intermediate `SED`. We assume that the photometric redshift is the correct redshift for + these galaxies (which is a good assumption for COSMOS 30-band photo-z for these bright + galaxies). For the given `SED` and redshift, we then normalize to give the right (observed) + flux in F814W. Note that for a mock "deep" sample, the redshift distributions of the + galaxies would be modified, which is not included here. + + For this chromatic option, it is still the case that the output flux normalization is + appropriate for the HST effective telescope diameter and a 1 second exposure time, so users + who are simulating another scenario should account for this. + + Note that the returned objects use arcsec for the units of their linear dimension. If you + are using a different unit for other things (the PSF, WCS, etc.), then you should dilate + the resulting object with ``gal.dilate(galsim.arcsec / scale_unit)``. + + Parameters: + index: Index of the desired galaxy in the catalog for which a `GSObject` + should be constructed. You may also provide a list or array of + indices, in which case a list of objects is returned. If None, + then a random galaxy (or more: see n_random kwarg) is chosen, + correcting for catalog-level selection effects if weights are + available. [default: None] + gal_type: Either 'real' or 'parametric'. This determines which kind of + galaxy model is made. [If catalog was loaded with ``use_real=False``, + then this defaults to 'parametric', and in fact 'real' is + not allowed. If catalog was loaded with ``use_real=True``, then + this defaults to 'real'.] + chromatic: Make this a chromatic object, or not? [default: False] + noise_pad_size: For realistic galaxies, the size of region to pad with noise, + in arcsec. [default: 5, an arbitrary, but not completely + ridiculous choice.] + deep: Modify fluxes and sizes of galaxies from the F814W<23.5 sample in + order to roughly simulate an F814W<25 sample but with higher S/N, as + in GREAT3? [default: False] Note that this keyword will be ignored + (except for issuing a warning) if the input catalog already + represents the F814W<25.2 sample. + sersic_prec: The desired precision on the Sersic index n in parametric galaxies. + GalSim is significantly faster if it gets a smallish number of + Sersic values, so it can cache some of the calculations and use + them again the next time it gets a galaxy with the same index. + If ``sersic_prec`` is 0.0, then use the exact value of index n from + the catalog. But if it is >0, then round the index to that + precision. [default: 0.05] + rng: A random number generator to use for selecting a random galaxy + (may be any kind of `BaseDeviate` or None) and to use in generating + any noise field when padding. [default: None] + n_random: The number of random galaxies to build, if 'index' is None. + [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + Either a `GSObject` or a `ChromaticObject` depending on the value of ``chromatic``, + or a list of them if ``index`` is an iterable. + """ + return self._makeGalaxy(self, index, gal_type, chromatic, noise_pad_size, + deep, sersic_prec, self.exptime, self.area, + rng, n_random, gsparams)
+ + @staticmethod + def _makeGalaxy(self, index=None, gal_type=None, chromatic=False, noise_pad_size=5, + deep=False, sersic_prec=0.05, exptime=None, area=None, + rng=None, n_random=None, gsparams=None): + if not self.canMakeReal(): + if gal_type is None: + gal_type = 'parametric' + elif gal_type != 'parametric': + raise GalSimIncompatibleValuesError( + "Only 'parametric' galaxy type is allowed when use_real == False", + gal_type=gal_type, use_real=self.canMakeReal()) + else: + if gal_type is None: + gal_type = 'real' + + if gal_type not in ('real', 'parametric'): + raise GalSimValueError("Invalid galaxy type %r", gal_type, ('real', 'parametric')) + + # Make rng if we will need it. + if index is None or gal_type == 'real': + if rng is None: + rng = BaseDeviate() + elif not isinstance(rng, BaseDeviate): + raise TypeError("The rng provided to makeGalaxy is not a BaseDeviate") + + # Select random indices if necessary (no index given). + if index is None: + if n_random is None: n_random = 1 + index = self.selectRandomIndex(n_random, rng=rng) + else: + if n_random is not None: + raise GalSimIncompatibleValuesError( + "Cannot specify both index and n_random", n_random=n_random, index=index) + + if hasattr(index, '__iter__'): + indices = index + else: + indices = [index] + + # Check whether this is a COSMOSCatalog meant to represent real or parametric objects, then + # call the appropriate helper routine for that case. + if gal_type == 'real': + if chromatic: + raise GalSimNotImplementedError("Cannot yet make real chromatic galaxies!") + gal_list = [] + for idx in indices: + real_params = self.getRealParams(idx) + gal = RealGalaxy(real_params, noise_pad_size=noise_pad_size, rng=rng, + gsparams=gsparams) + gal_list.append(gal) + + else: + if chromatic: + bandpass = self.getBandpass() + sed = self.getSED() + else: + bandpass = None + sed = None + gal_list = [] + for idx in indices: + record = self.getParametricRecord(idx) + gal = COSMOSCatalog._buildParametric(record, sersic_prec, gsparams, + chromatic, bandpass, sed) + gal_list.append(gal) + + flux_scaling = 1. + if exptime is not None: + flux_scaling *= exptime / self.orig_exptime + if area is not None: + flux_scaling *= area / self.orig_area + if flux_scaling != 1.: + gal_list = [gal * flux_scaling for gal in gal_list] + + # If trying to use the 23.5 sample and "fake" a deep sample, rescale the size and flux as + # suggested in the GREAT3 handbook. + if deep: + if self.getUseSample() == '23.5': + # Rescale the flux to get a limiting mag of 25 in F814W when starting with a + # limiting mag of 23.5. Make the galaxies a factor of 0.6 smaller and appropriately + # fainter. + flux_factor = 10.**(-0.4*1.5) + size_factor = 0.6 + gal_list = [ gal.dilate(size_factor) * flux_factor for gal in gal_list ] + elif self.getUseSample() == '25.2': + galsim_warn("Ignoring `deep` argument, because the sample being used already " + "corresponds to a flux limit of F814W<25.2") + else: + galsim_warn("Ignoring `deep` argument, because the sample being used does not " + "corresponds to a flux limit of F814W<23.5") + + # Store the orig_index as gal.index regardless of whether we have a RealGalaxy or not. + # It gets set as part of making a real galaxy, but not by _buildParametric. + # And if we are doing the deep scaling, then it gets messed up by that. + # So just put it in here at the end to be sure. + for gal, idx in zip(gal_list, indices): + gal.index = self.getOrigIndex(idx) + if hasattr(gal, 'original'): gal.original.index = gal.index + + if hasattr(index, '__iter__'): + return gal_list + else: + return gal_list[0] + +
[docs] def selectRandomIndex(self, n_random=1, rng=None, _n_rng_calls=False): + """ + Routine to select random indices out of the catalog. This routine does a weighted random + selection with replacement (i.e., there is no guarantee of uniqueness of the selected + indices). Weighting uses the weight factors available in the catalog, if any; these weights + are typically meant to remove any selection effects in the catalog creation process. + + Parameters: + n_random: Number of random indices to return. [default: 1] + rng: A random number generator to use for selecting a random galaxy + (may be any kind of `BaseDeviate` or None). [default: None] + + Returns: + A single index if n_random==1 or a NumPy array containing the randomly-selected + indices if n_random>1. + """ + # Set up the random number generator. + if rng is None: + rng = BaseDeviate() + + if self.real_cat is not None: + use_weights = self.real_cat.weight[self.orig_index] + else: + galsim_warn("Selecting random object without correcting for catalog-level " + "selection effects. This correction requires the existence of " + "real catalog with valid weights in addition to parametric one. " + "Create the COSMOSCatalog with use_real=True to avoid this warning.") + use_weights = None + + # By default, get the number of RNG calls. We then decide whether or not to return them + # based on _n_rng_calls. + index, n_rng_calls = utilities.rand_with_replacement( + n_random, self.nobjects, rng, use_weights, _n_rng_calls=True) + + if n_random>1: + if _n_rng_calls: + return index, n_rng_calls + else: + return index + else: + if _n_rng_calls: + return index[0], n_rng_calls + else: + return index[0]
+ + def getBandpass(self): + # Defer making the Bandpass and reading in SEDs until we actually are going to use them. + # It's not a huge calculation, but the thin() call especially isn't trivial. + + if self._bandpass is None: + # We have to set an appropriate zeropoint. This is slightly complicated: The + # nominal COSMOS zeropoint for single-orbit depth (2000s of usable exposure time, + # across 4 dithered exposures) is supposedly 25.94. But the science images that we + # are using were normalized to count rate, not counts, meaning that an object with + # mag=25.94 has a count rate of 1 photon/sec, not 1 photon total. Since we've + # declared our flux normalization for the outputs to be appropriate for a 1s + # exposure, we use this zeropoint directly. + # This means that when drawing chromatic parametric galaxies, the outputs will be + # properly normalized in terms of counts. + zp = 25.94 + self._bandpass = Bandpass('ACS_wfc_F814W.dat', wave_type='nm').withZeropoint(zp) + return self._bandpass + + def getSED(self): + if self._sed is None: + # Read in some SEDs. We are using some fairly truncated and thinned ones, because + # in any case the SED assignment here is somewhat arbitrary and should not be taken + # too seriously. + self._sed = [ + # bulge + SED('CWW_E_ext_more.sed', wave_type='Ang', flux_type='flambda'), + # disk + SED('CWW_Scd_ext_more.sed', wave_type='Ang', flux_type='flambda'), + # intermediate + SED('CWW_Sbc_ext_more.sed', wave_type='Ang', flux_type='flambda') + ] + return self._sed + + @staticmethod + def _round_sersic(n, sersic_prec): + return float(int(n/sersic_prec + 0.5)) * sersic_prec + + @staticmethod + def _buildParametric(record, sersic_prec, gsparams, chromatic, bandpass=None, sed=None): + # Get fit parameters. For 'sersicfit', the result is an array of 8 numbers for each + # galaxy: + # SERSICFIT[0]: intensity of light profile at the half-light radius. + # SERSICFIT[1]: half-light radius measured along the major axis, in units of pixels + # in the COSMOS lensing data reductions (0.03 arcsec). + # SERSICFIT[2]: Sersic n. + # SERSICFIT[3]: q, the ratio of minor axis to major axis length. + # SERSICFIT[4]: boxiness, currently fixed to 0, meaning isophotes are all + # elliptical. + # SERSICFIT[5]: x0, the central x position in pixels. + # SERSICFIT[6]: y0, the central y position in pixels. + # SERSICFIT[7]: phi, the position angle in radians. If phi=0, the major axis is + # lined up with the x axis of the image. + # For 'bulgefit', the result is an array of 16 parameters that comes from doing a + # 2-component sersic fit. The first 8 are the parameters for the disk, with n=1, and + # the last 8 are for the bulge, with n=4. + bparams = record['bulgefit'] + sparams = record['sersicfit'] + if 'hlr' not in record and self.use_sample is not None: # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.use_sample)) + + use_bulgefit = record['use_bulgefit'] + if not use_bulgefit and not record['viable_sersic']: # pragma: no cover + # This shouldn't be possible I think... + raise GalSimError("Cannot make parametric model for this galaxy!") + + if use_bulgefit: + # Bulge parameters: + # Minor-to-major axis ratio: + bulge_q = bparams[11] + # Position angle, now represented as a galsim.Angle: + bulge_beta = bparams[15]*radians + disk_q = bparams[3] + disk_beta = bparams[7]*radians + bulge_hlr = record['hlr'][1] + bulge_flux = record['flux'][1] + disk_hlr = record['hlr'][2] + disk_flux = record['flux'][2] + + # Make sure the bulge-to-total flux ratio is not nonsense. + bfrac = bulge_flux/(bulge_flux+disk_flux) + if bfrac < 0 or bfrac > 1 or np.isnan(bfrac): # pragma: no cover + # This shouldn't be possible I think... + raise GalSimError("Cannot make parametric model for this galaxy") + + # Then combine the two components of the galaxy. + if chromatic: + # We define the GSObjects with flux=1, then multiply by an SED defined to have + # the appropriate (observed) magnitude at the redshift in the COSMOS passband. + z = record['zphot'] + target_bulge_mag = record['mag_auto']-2.5*math.log10(bfrac) + bulge_sed = sed[0].atRedshift(z).withMagnitude( + target_bulge_mag, bandpass) + bulge = DeVaucouleurs(half_light_radius=bulge_hlr, gsparams=gsparams) + bulge *= bulge_sed + target_disk_mag = record['mag_auto']-2.5*math.log10((1.-bfrac)) + disk_sed = sed[1].atRedshift(z).withMagnitude(target_disk_mag, bandpass) + disk = Exponential(half_light_radius=disk_hlr, gsparams=gsparams) + disk *= disk_sed + else: + bulge = DeVaucouleurs(flux=bulge_flux, half_light_radius=bulge_hlr, + gsparams=gsparams) + disk = Exponential(flux=disk_flux, half_light_radius=disk_hlr, + gsparams=gsparams) + + # Apply shears for intrinsic shape. + if bulge_q < 1.: # pragma: no branch + bulge = bulge.shear(q=bulge_q, beta=bulge_beta) + if disk_q < 1.: # pragma: no branch + disk = disk.shear(q=disk_q, beta=disk_beta) + + gal = bulge + disk + else: + # Do a similar manipulation to the stored quantities for the single Sersic profiles. + gal_n = sparams[2] + # Fudge this if it is at the edge of the allowed n values. Since GalSim (as of #325 and + # #449) allow Sersic n in the range 0.3<=n<=6, the only problem is that the fits + # occasionally go as low as n=0.2. The fits in this file only go to n=6, so there is no + # issue with too-high values, but we also put a guard on that side in case other samples + # are swapped in that go to higher value of sersic n. + if gal_n < 0.3: gal_n = 0.3 + if gal_n > 6.0: gal_n = 6.0 + # GalSim is much more efficient if only a finite number of Sersic n values are used. + # This (optionally given constructor args) rounds n to the nearest 0.05. + if sersic_prec > 0.: + gal_n = COSMOSCatalog._round_sersic(gal_n, sersic_prec) + gal_q = sparams[3] + gal_beta = sparams[7]*radians + gal_hlr = record['hlr'][0] + gal_flux = record['flux'][0] + + if chromatic: + gal = Sersic(gal_n, flux=1., half_light_radius=gal_hlr, gsparams=gsparams) + if gal_n < 1.5: + use_sed = sed[1] # disk + elif gal_n >= 1.5 and gal_n < 3.0: + use_sed = sed[2] # intermediate + else: + use_sed = sed[0] # bulge + target_mag = record['mag_auto'] + z = record['zphot'] + gal *= use_sed.atRedshift(z).withMagnitude(target_mag, bandpass) + else: + gal = Sersic(gal_n, flux=gal_flux, half_light_radius=gal_hlr, gsparams=gsparams) + + # Apply shears for intrinsic shape. + if gal_q < 1.: # pragma: no branch + gal = gal.shear(q=gal_q, beta=gal_beta) + + return gal + +
[docs] def getRealParams(self, index): + """Get the parameters needed to make a `RealGalaxy` for a given index.""" + # Used by COSMOSGalaxy to circumvent making the RealGalaxy here and potentially having + # to pickle the result. These raw materials should be smaller, so quicker to pickle. + orig_index = self.orig_index[index] + gal_image = self.real_cat.getGalImage(orig_index) + psf_image = self.real_cat.getPSFImage(orig_index) + noise_image, pixel_scale, var = self.real_cat.getNoiseProperties(orig_index) + return (gal_image, psf_image, noise_image, pixel_scale, var)
+ +
[docs] def getParametricRecord(self, index): + """Get the parametric record for a given index""" + # Used by _makeGalaxy to circumvent pickling the result. + record = self.param_cat[self.orig_index[index]] + # Convert to a dict, since on some systems, the numpy record doesn't seem to + # pickle correctly. + record_dict = { k:record[k] for k in record.dtype.names } + return record_dict
+ +
[docs] def getValue(self, key, index): + """Get the value in the parametric catalog at the given key and index""" + # Used by _makeGalaxy to circumvent pickling the result. + record = self.param_cat[self.orig_index[index]] + return record[key]
+ +
[docs] def canMakeReal(self): + """Is it permissible to call makeGalaxy with gal_type='real'?""" + return self.use_real
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, GalaxySample) and + self.use_real == other.use_real and + self.real_cat == other.real_cat and + np.array_equal(self.param_cat, other.param_cat) and + np.array_equal(self.orig_index, other.orig_index)))
+ +
[docs]class COSMOSCatalog(GalaxySample): + """ + A class representing a random subsample of galaxies from the COSMOS sample with F814W<25.2 + (default), or alternatively the entire sample with F814W<23.5. + + Depending on the keyword arguments, particularly ``use_real``, the catalog may be able to + generate real galaxies, parametric galaxies, or both. To use this with either type of + galaxies, you need to get the COSMOS datasets in the format that GalSim recognizes; see + + https://github.com/GalSim-developers/GalSim/wiki/RealGalaxy-Data + + option (1) for more information. Note that if you want to make real galaxies you need to + download and store the full tarball with all galaxy images, whereas if you want to make + parametric galaxies you only need the catalog real_galaxy_catalog_25.2_fits.fits (and the + selection file real_galaxy_catalog_25.2_selection.fits if you want to place cuts on the + postage stamp quality) and can delete the galaxy and PSF image files. + + Finally, we provide a program that will download the large COSMOS sample for you and + put it in the $PREFIX/share/galsim directory of your installation path. The program is:: + + galsim_download_cosmos + + which gets installed in the $PREFIX/bin directory when you install GalSim. If you use + this program to download the COSMOS catalog, then you can use it with:: + + cat = galsim.COSMOSCatalog() + + GalSim knows the location of the installation share directory, so it will automatically + look for it there. + + In addition to the option of specifying catalog names, this class also accepts a keyword + argument ``sample`` that can be used to switch between the samples with limiting magnitudes of + 23.5 and 25.2. + + After getting the catalogs, there is a method makeGalaxy() that can make a `GSObject` + corresponding to any chosen galaxy in the catalog (whether real or parametric). See + `GalaxySample.makeGalaxy` for more information. As an interesting application and example of + the usage of these routines, consider the following code:: + + >>> im_size = 64 + >>> pix_scale = 0.05 + >>> bp_file = os.path.join(galsim.meta_data.share_dir, 'wfc_F814W.dat.gz') + >>> bandpass = galsim.Bandpass(bp_file, wave_type='ang').thin().withZeropoint(25.94) + >>> cosmos_cat = galsim.COSMOSCatalog() + >>> psf = galsim.OpticalPSF(diam=2.4, lam=1000.) # bigger than HST F814W PSF. + >>> indices = np.arange(10) + >>> real_gal_list = cosmos_cat.makeGalaxy(indices, gal_type='real', + ... noise_pad_size=im_size*pix_scale) + >>> param_gal_list = cosmos_cat.makeGalaxy(indices, gal_type='parametric', chromatic=True) + >>> for ind in indices: + >>> real_gal = galsim.Convolve(real_gal_list[ind], psf) + >>> param_gal = galsim.Convolve(param_gal_list[ind], psf) + >>> im_real = galsim.Image(im_size, im_size) + >>> im_param = galsim.Image(im_size, im_size) + >>> real_gal.drawImage(image=im_real, scale=pix_scale) + >>> param_gal.drawImage(bandpass, image=im_param, scale=pix_scale) + >>> im_real.write('im_real_'+str(ind)+'.fits') + >>> im_param.write('im_param_'+str(ind)+'.fits') + + This code snippet will draw images of the first 10 entries in the COSMOS catalog, at slightly + lower resolution than in COSMOS, with a real image and its parametric representation for each of + those objects. + + When using the 'real' rather than 'parametric' option, please read the documentation for the + `RealGalaxy` class for additional caveats about the available drawing methods and + the need to convolve with a suitable PSF. + + Parameters: + file_name: The file containing the catalog. [default: None, which will look for the + F814W<25.2 COSMOS catalog in $PREFIX/share/galsim. It will raise an + exception if the catalog is not there telling you to run + galsim_download_cosmos.] + sample: A keyword argument that can be used to specify the sample to use, i.e., + "23.5" or "25.2". At most one of ``file_name`` and ``sample`` should be + specified. [default: None, which results in the same default as + ``file_name=None``.] + dir: The directory with the catalog file and, if making realistic galaxies, + the image and noise files (or symlinks to them). [default: None, which + will look in $PREFIX/share/galsim.] + preload: Keyword that is only used for real galaxies, not parametric ones, to + choose whether to preload the header information. If ``preload=True``, + the bulk of the I/O time is in the constructor. If ``preload=False``, + there is approximately the same total I/O time (assuming you eventually + use most of the image files referenced in the catalog), but it is spread + over the calls to makeGalaxy(). [default: False] + use_real: Enable the use of realistic galaxies? [default: True] + If this parameter is False, then ``makeGalaxy(gal_type='real')`` will + not be allowed, and there will be a (modest) decrease in RAM and time + spent on I/O when initializing the COSMOSCatalog. If the real + catalog is not available for some reason, it will still be possible to + make parametric images. + exclusion_level: Level of additional cuts to make on the galaxies based on the quality + of postage stamp definition and/or parametric fit quality [beyond the + minimal cuts imposed when making the catalog - see Mandelbaum et + al. (2012, MNRAS, 420, 1518) for details]. Options: + + - "none": No cuts. + - "bad_stamp": Apply cuts to eliminate galaxies that have failures in + postage stamp definition. These cuts may also eliminate a small + subset of the good postage stamps as well. + - "bad_fits": Apply cuts to eliminate galaxies that have failures in the + parametric fits. These cuts may also eliminate a small + subset of the good parametric fits as well. + - "marginal": Apply the above cuts, plus ones that eliminate some more + marginal cases. + + [default: "marginal"] + min_hlr: Exclude galaxies whose fitted half-light radius is smaller than this + value (in arcsec). [default: 0, meaning no limit] + max_hlr: Exclude galaxies whose fitted half-light radius is larger than this + value (in arcsec). [default: 0, meaning no limit] + min_flux: Exclude galaxies whose fitted flux is smaller than this value. + [default: 0, meaning no limit] + max_flux: Exclude galaxies whose fitted flux is larger than this value. + [default: 0, meaning no limit] + exptime: The exposure time (in seconds) to assume when creating galaxies. + .. note:: + + The processed COSMOS ACS/HST science images have units of + counts/second; i.e. they have an effective exposure time of 1 + second in terms of their flux levels. The default value + corresponds to a 1 second exposure on HST, which will match + these processed images. + + [default: 1] + area: The effective collecting area (in cm^2) to assume when creating + galaxies. [default: None, which means to use the original HST + collecting area = pi/4 * 240**2 * (1.-0.33**2)] + + After construction, the following attributes are available: + + Attributes: + nobjects: The number of objects in the catalog + """ + _opt_params = { 'file_name' : str, 'sample' : str, 'dir' : str, + 'preload' : bool, 'use_real' : bool, + 'exclusion_level' : str, 'min_hlr' : float, 'max_hlr' : float, + 'min_flux' : float, 'max_flux' : float + } + + hst_eff_area = math.pi * 120**2 * (1-0.33**2) + + def __init__(self, file_name=None, sample=None, dir=None, **kwargs): + if sample is not None and file_name is not None: + raise GalSimIncompatibleValuesError( + "Cannot specify both the sample and file_name.", + sample=sample, file_name=file_name) + + # Parse the file name + file_name, _, use_sample = _parse_files_dirs(file_name, dir, sample) + + if use_sample == "23.5": + cut_ratio = 0.2 + sn_limit = 20.0 + min_mask_dist = 11. + else: + cut_ratio = 0.8 + sn_limit = 12.0 + min_mask_dist = 11. + + super().__init__(file_name, _use_sample=use_sample, + orig_exptime=1., orig_area=self.hst_eff_area, + cut_ratio=cut_ratio, sn_limit=sn_limit, min_mask_dist=min_mask_dist, + **kwargs)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/gaussian.html b/docs/_build/html/_modules/galsim/gaussian.html new file mode 100644 index 00000000000..f112b7111fe --- /dev/null +++ b/docs/_build/html/_modules/galsim/gaussian.html @@ -0,0 +1,305 @@ + + + + + + galsim.gaussian — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.gaussian

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Gaussian' ]
+
+import numpy as np
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimIncompatibleValuesError
+
+
+
[docs]class Gaussian(GSObject): + r"""A class describing a 2D Gaussian surface brightness profile. + + The Gaussian surface brightness profile is characterized by two properties, its ``flux`` + and the characteristic size ``sigma`` where the radial profile of the circular Gaussian + drops off as + + .. math:: + I(r) \sim e^{-\frac{r^2}{2 \sigma^2}} + + A Gaussian can be initialized using one (and only one) of three possible size parameters: + ``sigma``, ``fwhm``, or ``half_light_radius``. Exactly one of these three is required. + + Parameters: + sigma: The value of sigma of the profile. Typically given in arcsec. + [One of ``sigma``, ``fwhm``, or ``half_light_radius`` is required.] + fwhm: The full-width-half-max of the profile. Typically given in arcsec. + [One of ``sigma``, ``fwhm``, or ``half_light_radius`` is required.] + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``sigma``, ``fwhm``, or ``half_light_radius`` is required.] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _opt_params = { "flux" : float } + _single_params = [ { "sigma" : float, "half_light_radius" : float, "fwhm" : float } ] + + # The FWHM of a Gaussian is 2 sqrt(2 ln2) sigma + _fwhm_factor = 2.3548200450309493 + # The half-light-radius is sqrt(2 ln2) sigma + _hlr_factor = 1.1774100225154747 + # 1/(2pi) + _inv_twopi = 0.15915494309189535 + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, half_light_radius=None, sigma=None, fwhm=None, flux=1., gsparams=None): + if fwhm is not None : + if sigma is not None or half_light_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of sigma, fwhm, and half_light_radius may be specified", + fwhm=fwhm, sigma=sigma, half_light_radius=half_light_radius) + else: + sigma = fwhm / Gaussian._fwhm_factor + elif half_light_radius is not None: + if sigma is not None: + raise GalSimIncompatibleValuesError( + "Only one of sigma, fwhm, and half_light_radius may be specified", + fwhm=fwhm, sigma=sigma, half_light_radius=half_light_radius) + else: + sigma = half_light_radius / Gaussian._hlr_factor + elif sigma is None: + raise GalSimIncompatibleValuesError( + "One of sigma, fwhm, and half_light_radius must be specified", + fwhm=fwhm, sigma=sigma, half_light_radius=half_light_radius) + + self._sigma = float(sigma) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + self._sigsq = sigma**2 + self._inv_sigsq = 1./self._sigsq + self._norm = self.flux * self._inv_sigsq * Gaussian._inv_twopi + + @lazy_property + def _sbp(self): + return _galsim.SBGaussian(self._sigma, self._flux, self.gsparams._gsp) + + @property + def sigma(self): + """The sigma of this Gaussian profile + """ + return self._sigma + + @property + def half_light_radius(self): + """The half-light radius of this Gaussian profile + """ + return self.sigma * Gaussian._hlr_factor + + @property + def fwhm(self): + """The FWHM of this Gaussian profile + """ + return self.sigma * Gaussian._fwhm_factor + + def __eq__(self, other): + return (self is other or + (isinstance(other, Gaussian) and + self.sigma == other.sigma and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Gaussian", self.sigma, self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.Gaussian(sigma=%r, flux=%r, gsparams=%r)'%( + self.sigma, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Gaussian(sigma=%s'%self.sigma + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return math.sqrt(-2.*math.log(self.gsparams.maxk_threshold))/self.sigma + + @property + def _stepk(self): + R = max(math.sqrt(-2.*math.log(self.gsparams.folding_threshold)), + self.gsparams.stepk_minimum_hlr * Gaussian._hlr_factor) + return math.pi / (R * self.sigma) + + @property + def _max_sb(self): + return self._norm + + def _xValue(self, pos): + rsq = pos.x**2 + pos.y**2 + return self._norm * math.exp(-0.5 * rsq * self._inv_sigsq) + + def _kValue(self, kpos): + ksq = (kpos.x**2 + kpos.y**2) * self._sigsq + return self._flux * math.exp(-0.5 * ksq) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Gaussian(sigma=self.sigma, flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/gsobject.html b/docs/_build/html/_modules/galsim/gsobject.html new file mode 100644 index 00000000000..7167acdb292 --- /dev/null +++ b/docs/_build/html/_modules/galsim/gsobject.html @@ -0,0 +1,2732 @@ + + + + + + galsim.gsobject — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.gsobject

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'GSObject' ]
+
+import numpy as np
+import math
+import copy
+
+from . import _galsim
+from .gsparams import GSParams
+from .position import _PositionD, _PositionI, Position, parse_pos_args
+from ._utilities import lazy_property
+from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError
+from .errors import GalSimFFTSizeError, GalSimNotImplementedError, convert_cpp_errors, galsim_warn
+from .image import Image, ImageD, ImageF, ImageCD, ImageCF
+from .shear import Shear, _Shear
+from .angle import Angle
+from .bounds import BoundsI, _BoundsI
+from .random import BaseDeviate
+from .sensor import Sensor
+from .random import PoissonDeviate
+from .wcs import BaseWCS, PixelScale
+
+
+
[docs]class GSObject: + """Base class for all GalSim classes that represent some kind of surface brightness profile. + + A GSObject is not intended to be constructed directly. Normally, you would use whatever + derived class is appropriate for the surface brightness profile you want:: + + >>> gal = galsim.Sersic(n=4, half_light_radius=4.3) + >>> psf = galsim.Moffat(beta=3, fwhm=2.85) + >>> conv = galsim.Convolve([gal,psf]) + + All of these classes are subclasses of GSObject, so you should see those docstrings for + more details about how to construct the various profiles. Here we discuss attributes and + methods that are common to all GSObjects. + + GSObjects are always defined in sky coordinates. So all sizes and other linear dimensions + should be in terms of some kind of units on the sky, arcsec for instance. Only later (when + they are drawn) is the connection to pixel coordinates established via a pixel scale or WCS. + (See the documentation for galsim.BaseWCS for more details about how to specify various kinds + of world coordinate systems more complicated than a simple pixel scale.) + + For instance, if you eventually draw onto an image that has a pixel scale of 0.2 arcsec/pixel, + then the normal thing to do would be to define your surface brightness profiles in terms of + arcsec and then draw with ``pixel_scale=0.2``. However, while arcsec are the usual choice of + units for the sky coordinates, if you wanted, you could instead define the sizes of all your + galaxies and PSFs in terms of radians and then use ``pixel_scale=0.2/206265`` when you draw + them. + + **Transforming methods**: + + The GSObject class uses an "immutable" design[1], so all methods that would potentially modify + the object actually return a new object instead. This uses pointers and such behind the + scenes, so it all happens efficiently, but it makes using the objects a bit simpler, since + you don't need to worry about some function changing your object behind your back. + + In all cases below, we just give an example usage. See the docstrings for the methods for + more details about how to use them.:: + + >>> obj = obj.shear(shear) # Apply a shear to the object. + >>> obj = obj.dilate(scale) # Apply a flux-preserving dilation. + >>> obj = obj.magnify(mu) # Apply a surface-brightness-preserving magnification. + >>> obj = obj.rotate(theta) # Apply a rotation. + >>> obj = obj.shift(dx,dy) # Shft the object in real space. + >>> obj = obj.transform(dudx,dudy,dvdx,dvdy) # Apply a general jacobian transformation. + >>> obj = obj.lens(g1,g2,mu) # Apply both a lensing shear and magnification. + >>> obj = obj.withFlux(flux) # Set a new flux value. + >>> obj = obj * ratio # Scale the surface brightness profile by some factor. + + **Access Methods**: + + There are some access methods and properties that are available for all GSObjects. + Again, see the docstrings for each method for more details.:: + + >>> obj.flux + >>> obj.centroid + >>> obj.nyquist_scale + >>> obj.stepk + >>> obj.maxk + >>> obj.has_hard_edges + >>> obj.is_axisymmetric + >>> obj.is_analytic_x + >>> obj.is_analytic_k + >>> obj.xValue(x,y) or obj.xValue(pos) + >>> obj.kValue(kx,ky) os obj.kValue(kpos) + + Most subclasses have additional methods that are available for values that are particular to + that specific surface brightness profile. e.g. ``sigma = gauss.sigma``. However, note + that class-specific methods are not available after performing one of the above transforming + operations.:: + + >>> gal = galsim.Gaussian(sigma=5) + >>> gal = gal.shear(g1=0.2, g2=0.05) + >>> sigma = gal.sigma # This will raise an exception. + + It is however possible to access the original object that was transformed via the + ``original`` attribute.:: + + >>> sigma = gal.original.sigma # This works. + + No matter how many transformations are performed, the ``original`` attribute will contain the + _original_ object (not necessarily the most recent ancestor). + + **Drawing Methods**: + + The main thing to do with a GSObject once you have built it is to draw it onto an image. + There are two methods that do this. In both cases, there are lots of optional parameters. + See the docstrings for these methods for more details.:: + + >>> image = obj.drawImage(...) + >>> kimage = obj.drawKImage(...) + + There two attributes that may be available for a GSObject. + + Attributes: + original: This was mentioned above as a way to access the original object that has + been transformed by one of the transforming methods. + noise: Some types, like `RealGalaxy`, set this attribute to be the intrinsic noise that + is already inherent in the profile and will thus be present when you draw the + object. The noise is propagated correctly through the various transforming + methods, as well as convolutions and flux rescalings. Note that the ``noise`` + attribute can be set directly by users even for GSObjects that do not naturally + have one. The typical use for this attribute is to use it to whiten the noise in + the image after drawing. See `BaseCorrelatedNoise` for more details. + + **GSParams**: + + All GSObject classes take an optional ``gsparams`` argument, so we document that feature here. + For all documentation about the specific derived classes, please see the docstring for each + one individually. + + The ``gsparams`` argument can be used to specify various numbers that govern the tradeoff + between accuracy and speed for the calculations made in drawing a GSObject. The numbers are + encapsulated in a class called `GSParams`, and the user should make careful choices whenever + they opt to deviate from the defaults. For more details about the parameters and their default + values, please see the docstring of the `GSParams` class. + + For example, let's say you want to do something that requires an FFT larger than 4096 x 4096 + (and you have enough memory to handle it!). Then you can create a new `GSParams` object with a + larger ``maximum_fft_size`` and pass that to your GSObject on construction:: + + >>> gal = galsim.Sersic(n=4, half_light_radius=4.3) + >>> psf = galsim.Moffat(beta=3, fwhm=2.85) + >>> conv = galsim.Convolve([gal,psf]) + >>> im = galsim.Image(1000,1000, scale=0.02) # Note the very small pixel scale! + >>> im = conv.drawImage(image=im) # This uses the default GSParams. + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + File "galsim/gsobject.py", line 1666, in drawImage + added_photons = prof.drawFFT(draw_image, add) + File "galsim/gsobject.py", line 1877, in drawFFT + kimage, wrap_size = self.drawFFT_makeKImage(image) + File "galsim/gsobject.py", line 1802, in drawFFT_makeKImage + raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk) + galsim.errors.GalSimFFTSizeError: drawFFT requires an FFT that is too large. + The required FFT size would be 12288 x 12288, which requires 3.38 GB of memory. + If you can handle the large FFT, you may update gsparams.maximum_fft_size. + >>> big_fft_params = galsim.GSParams(maximum_fft_size=12300) + >>> conv = galsim.Convolve([gal,psf],gsparams=big_fft_params) + >>> im = conv.drawImage(image=im) # Now it works (but is slow!) + >>> im.write('high_res_sersic.fits') + + Note that for compound objects such as `Convolution` or `Sum`, not all `GSParams` can be + changed when the compound object is created. In the example given here, it is possible to + change parameters related to the drawing, but not the Fourier space parameters for the + components that go into the `Convolution`. To get better sampling in Fourier space, + for example, the ``gal`` and/or ``psf`` should be created with ``gsparams`` that have a + non-default value of ``folding_threshold``. This statement applies to the threshold and + accuracy parameters. + """ + _gsparams_opt = { 'minimum_fft_size' : int, + 'maximum_fft_size' : int, + 'folding_threshold' : float, + 'stepk_minimum_hlr' : float, + 'maxk_threshold' : float, + 'kvalue_accuracy' : float, + 'xvalue_accuracy' : float, + 'realspace_relerr' : float, + 'realspace_abserr' : float, + 'integration_relerr' : float, + 'integration_abserr' : float, + 'shoot_accuracy' : float, + 'allowed_flux_variation' : float, + 'range_division_for_extrema' : int, + 'small_fraction_of_flux' : float + } + redshift = 0 # For backwards compatibility with old atRedshift usage. Can be overwritten. + + def __init__(self): + raise NotImplementedError("The GSObject base class should not be instantiated directly.") + + # Note: subclasses are expected to define the following attributes or properties: + # + # Required for all profiles: + # + # _flux (the object's flux, natch) + # _gsparams (use GSParams.check(None) if you just want the default) + # _stepk (the sampling in k space necessary to avoid folding of image in x space) + # _maxk (the value of k beyond which aliasing can be neglected) + # _has_hard_edges (true if should use real-space convolution with another hard edge profile) + # _is_axisymmetric (true if f(x,y) = f(r)) + # _is_analytic_x (true if _xValue and _drawReal are implemented) + # _is_analytic_k (true if _kValue and _drawKImage are implemented) + # + # Required and typically class attributes, but there are defaults in each case: + # + # _req_params (dict of required config parameters: name : type, default: {}) + # _opt_params (dict of optional config parameters: name : type, default: {}) + # _single_params (list of dicts for parameters where exactly one of several is required, + # default: []) + # _takes_rng (bool specifying whether rng is an input parameter, default: False) + # + # Optional + # + # _centroid (default = PositionD(0,0), which is often the right value) + # _positive_flux (default = _flux + _negative_flux) + # _negative_flux (default = 0; note: this should be absolute value of the negative flux) + # _max_sb (default 1.e500, which in this context is equivalent to "unknown") + # _noise (default None) + # + # In addition, subclasses should typically define most of the following methods. + # The default in each case is to raise a NotImplementedError, so if you cannot implement one, + # you may simply not define it. + # + # _xValue(self, pos) + # _kValue(self, kpos) + # _drawReal(self, image) + # _shoot(self, photons, rng): + # _drawKImage(self, image) + # + # Required for real-space convolution + # + # _sbp which must be an attribute or property providing a C++-layer SBProfile instance. + # + # Note that most objects don't need to implement real-space convolution, so use of a C++-layer + # SBProfile sub-class is usually only an implementation detail to improve efficiency. + # + # TODO: For now, _sbp is also required for transformations, but this is expected to be + # addressed in a future PR. + + @property + def flux(self): + """The flux of the profile. + """ + return self._flux + + @property + def gsparams(self): + """A `GSParams` object that sets various parameters relevant for speed/accuracy trade-offs. + """ + return self._gsparams + + @property + def maxk(self): + """The value of k beyond which aliasing can be neglected. + """ + return self._maxk + + @property + def stepk(self): + """The sampling in k space necessary to avoid folding of image in x space. + """ + return self._stepk + + @property + def nyquist_scale(self): + """The pixel spacing that does not alias maxk. + """ + return math.pi / self.maxk + + @property + def has_hard_edges(self): + """Whether there are any hard edges in the profile, which would require very small k + spacing when working in the Fourier domain. + """ + return self._has_hard_edges + + @property + def is_axisymmetric(self): + """Whether the profile is axially symmetric; affects efficiency of evaluation. + """ + return self._is_axisymmetric + + @property + def is_analytic_x(self): + """Whether the real-space values can be determined immediately at any position without + requiring a Discrete Fourier Transform. + """ + return self._is_analytic_x + + @property + def is_analytic_k(self): + """Whether the k-space values can be determined immediately at any position without + requiring a Discrete Fourier Transform. + """ + return self._is_analytic_k + + @property + def centroid(self): + """The (x, y) centroid of an object as a `PositionD`. + """ + return self._centroid + + @lazy_property + def _centroid(self): + # Most profiles are centered at 0,0, so make this the default. + return _PositionD(0,0) + + @property + def positive_flux(self): + """The expectation value of flux in positive photons. + + Some profiles, when rendered with photon shooting, need to shoot both positive- and + negative-flux photons. For such profiles, this method returns the total flux + of the positive-valued photons. + + For profiles that don't have this complication, this is equivalent to getFlux(). + + It should be generally true that ``obj.positive_flux - obj.negative_flux`` returns the same + thing as ``obj.flux``. Small difference may accrue from finite numerical accuracy in + cases involving lookup tables, etc. + """ + return self._positive_flux + + @property + def negative_flux(self): + """Returns the expectation value of flux in negative photons. + + Some profiles, when rendered with photon shooting, need to shoot both positive- and + negative-flux photons. For such profiles, this method returns the total absolute flux + of the negative-valued photons (i.e. as a positive value). + + For profiles that don't have this complication, this returns 0. + + It should be generally true that ``obj.positive_flux - obj.negative_flux`` returns the same + thing as ``obj.flux``. Small difference may accrue from finite numerical accuracy in + cases involving lookup tables, etc. + """ + return self._negative_flux + + @lazy_property + def _positive_flux(self): + # The usual case. + return self.flux + self._negative_flux + + @lazy_property + def _negative_flux(self): + # The usual case. + return 0. + + @lazy_property + def _flux_per_photon(self): + # The usual case. + return 1. + + def _calculate_flux_per_photon(self): + # If negative_flux is overriden, then _flux_per_photon should be overridden as well + # to return this calculation. + posflux = self.positive_flux + negflux = self.negative_flux + eta = negflux / (posflux + negflux) + return 1.-2.*eta + + @property + def max_sb(self): + """An estimate of the maximum surface brightness of the object. + + Some profiles will return the exact peak SB, typically equal to the value of + obj.xValue(obj.centroid). However, not all profiles (e.g. Convolution) know how to + calculate this value without just drawing the image and checking what the maximum value is. + Clearly, this would be inefficient, so in these cases, some kind of estimate is returned, + which will generally be conservative on the high side. + + This routine is mainly used by the photon shooting process, where an overestimate of + the maximum surface brightness is acceptable. + + Note, for negative-flux profiles, this will return the absolute value of the most negative + surface brightness. Technically, it is an estimate of the maximum deviation from zero, + rather than the maximum value. For most profiles, these are the same thing. + """ + return self._max_sb + + @lazy_property + def _max_sb(self): + # The way this is used, overestimates are conservative. + # So the default value of 1.e500 will skip the optimization involving the maximum sb. + return 1.e500 + + @property + def noise(self): + """An estimate of the noise already in the profile. + + Some profiles have some noise already in their definition. E.g. those that come from + observations of galaxies in real data. In GalSim, `RealGalaxy` objects are an example of + this. In these cases, the noise attribute gives an estimate of the Noise object that + would generate noise consistent with that already in the profile. + + It is permissible to attach a noise estimate to an existing object with:: + + >>> obj.noise = noise # Some BaseNoise instance + """ + return self._noise + + @noise.setter + def noise(self, n): + # We allow the user to set the noise with obj.noise = n + self._noise = n + + @lazy_property + def _noise(self): + # Most profiles don't have any noise. + return None + + # a few definitions for using GSObjects as duck-typed ChromaticObjects + @property + def separable(self): return True + @property + def interpolated(self): return False + @property + def deinterpolated(self): return self + @property + def sed(self): + return sed.SED(self.flux, 'nm', '1') + @property + def SED(self): + from .deprecated import depr + depr('obj.SED', 2.5, 'obj.sed') + return self.sed + @property + def spectral(self): return False + @property + def dimensionless(self): return True + @property + def wave_list(self): return np.array([], dtype=float) + def _fiducial_profile(self, bandpass): return bandpass.effective_wavelength, self + + # Also need these methods to duck-type as a ChromaticObject + def evaluateAtWavelength(self, wave): + return self + def _approxWavelength(self, wave): + return wave, self + + # Make op+ of two GSObjects work to return an Add object + # Note: we don't define __iadd__ and similar. Let python handle this automatically + # to make obj += obj2 be equivalent to obj = obj + obj2. +
[docs] def __add__(self, other): + """Add two GSObjects. + + Equivalent to Add(self, other) + """ + return _sum.Add([self, other])
+ + # op- is unusual, but allowed. It subtracts off one profile from another. +
[docs] def __sub__(self, other): + """Subtract two GSObjects. + + Equivalent to Add(self, -1 * other) + """ + return _sum.Add([self, (-1. * other)])
+ + # Make op* work to adjust the flux of an object +
[docs] def __mul__(self, other): + """Scale the flux of the object by the given factor. + + obj * flux_ratio is equivalent to obj.withScaledFlux(flux_ratio) + + It creates a new object that has the same profile as the original, but with the + surface brightness at every location scaled by the given amount. + + You can also multiply by an `SED`, which will create a `ChromaticObject` where the `SED` + acts like a wavelength-dependent ``flux_ratio``. + """ + return self.withScaledFlux(other)
+ +
[docs] def __rmul__(self, other): + """Equivalent to obj * other. See `__mul__` for details.""" + return self.__mul__(other)
+ + # Likewise for op/ +
[docs] def __div__(self, other): + """Equivalent to obj * (1/other). See `__mul__` for details.""" + return self * (1. / other)
+ + __truediv__ = __div__ + + def __neg__(self): + return -1. * self + + # Some calculations that can be done for all GSObjects. +
[docs] def calculateHLR(self, size=None, scale=None, centroid=None, flux_frac=0.5): + """Returns the half-light radius of the object. + + If the profile has a half_light_radius attribute, it will just return that, but in the + general case, we draw the profile and estimate the half-light radius directly. + + This function (by default at least) is only accurate to a few percent, typically. + Possibly worse depending on the profile being measured. If you care about a high + precision estimate of the half-light radius, the accuracy can be improved using the + optional parameter scale to change the pixel scale used to draw the profile. + + The default scale is half the Nyquist scale, which were found to produce results accurate + to a few percent on our internal tests. Using a smaller scale will be more accurate at + the expense of speed. + + In addition, you can optionally specify the size of the image to draw. The default size is + None, which means `drawImage` will choose a size designed to contain around 99.5% of the + flux. This is overkill for this calculation, so choosing a smaller size than this may + speed up this calculation somewhat. + + Also, while the name of this function refers to the half-light radius, in fact it can also + calculate radii that enclose other fractions of the light, according to the parameter + ``flux_frac``. E.g. for r90, you would set flux_frac=0.90. + + The default scale should usually be acceptable for things like testing that a galaxy + has a reasonable resolution, but they should not be trusted for very fine grain + discriminations. + + Parameters: + size: If given, the stamp size to use for the drawn image. [default: None, + which will let `drawImage` choose the size automatically] + scale: If given, the pixel scale to use for the drawn image. [default: + 0.5 * self.nyquist_scale] + centroid: The position to use for the centroid. [default: self.centroid] + flux_frac: The fraction of light to be enclosed by the returned radius. + [default: 0.5] + + Returns: + an estimate of the half-light radius in physical units + """ + try: + # It there is a half_light_radius attribute, use that. + return self.half_light_radius + except (AttributeError, GalSimError): + # Otherwise, or (e.g. with Airy where it is only implemented for obscuration=0) + # if there is an error trying to use it, then keep going with this calculation. + pass + + if scale is None: + scale = self.nyquist_scale * 0.5 + + if centroid is None: + centroid = self.centroid + + # Draw the image. Note: need a method that integrates over pixels to get flux right. + # The offset is to make all the rsq values different to help the precision a bit. + offset = _PositionD(0.2, 0.33) + im = self.drawImage(nx=size, ny=size, scale=scale, offset=offset, dtype=float) + + center = im.true_center + offset + centroid/scale + return im.calculateHLR(center=center, flux=self.flux, flux_frac=flux_frac)
+ +
[docs] def calculateMomentRadius(self, size=None, scale=None, centroid=None, rtype='det'): + """Returns an estimate of the radius based on unweighted second moments. + + The second moments are defined as: + + Q_ij = int( I(x,y) i j dx dy ) / int( I(x,y) dx dy ) + where i,j may be either x or y. + + If I(x,y) is a Gaussian, then T = Tr(Q) = Qxx + Qyy = 2 sigma^2. Thus, one reasonable + choice for a "radius" for an arbitrary profile is sqrt(T/2). + + In addition, det(Q) = sigma^4. So another choice for an arbitrary profile is det(Q)^1/4. + + This routine can return either of these measures according to the value of the ``rtype`` + parameter. ``rtype='trace'`` will cause it to return sqrt(T/2). ``rtype='det'`` will cause + it to return det(Q)^1/4. And ``rtype='both'`` will return a tuple with both values. + + Note that for the special case of a Gaussian profile, no calculation is necessary, and + the ``sigma`` attribute will be used in both cases. In the limit as scale->0, this + function will return the same value, but because finite pixels are drawn, the results + will not be precisely equal for real use cases. The approximation being made is that + the integral of I(x,y) i j dx dy over each pixel can be approximated as + int(I(x,y) dx dy) * i_center * j_center. + + This function (by default at least) is only accurate to a few percent, typically. + Possibly worse depending on the profile being measured. If you care about a high + precision estimate of the radius, the accuracy can be improved using the optional + parameters size and scale to change the size and pixel scale used to draw the profile. + + The default is to use the the Nyquist scale for the pixel scale and let `drawImage` + choose a size for the stamp that will enclose at least 99.5% of the flux. These + were found to produce results accurate to a few percent on our internal tests. + Using a smaller scale and larger size will be more accurate at the expense of speed. + + The default parameters should usually be acceptable for things like testing that a galaxy + has a reasonable resolution, but they should not be trusted for very fine grain + discriminations. For a more accurate estimate, see galsim.hsm.FindAdaptiveMom. + + Parameters: + size: If given, the stamp size to use for the drawn image. [default: None, + which will let `drawImage` choose the size automatically] + scale: If given, the pixel scale to use for the drawn image. [default: + self.nyquist_scale] + centroid: The position to use for the centroid. [default: self.centroid] + rtype: There are three options for this parameter: + - 'trace' means return sqrt(T/2) + - 'det' means return det(Q)^1/4 + - 'both' means return both: (sqrt(T/2), det(Q)^1/4) + [default: 'det'] + + Returns: + an estimate of the radius in physical units (or both estimates if rtype == 'both') + """ + if rtype not in ('trace', 'det', 'both'): + raise GalSimValueError("Invalid rtype.", rtype, ('trace', 'det', 'both')) + + if hasattr(self, 'sigma'): + if rtype == 'both': + return self.sigma, self.sigma + else: + return self.sigma + + if scale is None: + scale = self.nyquist_scale + + if centroid is None: + centroid = self.centroid + + # Draw the image. Note: need a method that integrates over pixels to get flux right. + im = self.drawImage(nx=size, ny=size, scale=scale, dtype=float) + + center = im.true_center + centroid/scale + return im.calculateMomentRadius(center=center, flux=self.flux, rtype=rtype)
+ +
[docs] def calculateFWHM(self, size=None, scale=None, centroid=None): + """Returns the full-width half-maximum (FWHM) of the object. + + If the profile has a fwhm attribute, it will just return that, but in the general case, + we draw the profile and estimate the FWHM directly. + + As with `calculateHLR` and `calculateMomentRadius`, this function optionally takes size and + scale values to use for the image drawing. The default is to use the the Nyquist scale + for the pixel scale and let `drawImage` choose a size for the stamp that will enclose at + least 99.5% of the flux. These were found to produce results accurate to well below + one percent on our internal tests, so it is unlikely that you will want to adjust + them for accuracy. However, using a smaller size than default could help speed up + the calculation, since the default is usually much larger than is needed. + + Parameters: + size: If given, the stamp size to use for the drawn image. [default: None, + which will let `drawImage` choose the size automatically] + scale: If given, the pixel scale to use for the drawn image. [default: + self.nyquist_scale] + centroid: The position to use for the centroid. [default: self.centroid] + + Returns: + an estimate of the full-width half-maximum in physical units + """ + if hasattr(self, 'fwhm'): + return self.fwhm + + if scale is None: + scale = self.nyquist_scale + + if centroid is None: + centroid = self.centroid + + # Draw the image. Note: draw with method='sb' here, since the fwhm is a property of the + # raw surface brightness profile, not integrated over pixels. + # The offset is to make all the rsq values different to help the precision a bit. + offset = _PositionD(0.2, 0.33) + + im = self.drawImage(nx=size, ny=size, scale=scale, offset=offset, method='sb', dtype=float) + + # Get the maximum value, assuming the maximum is at the centroid. + if self.is_analytic_x: + Imax = self.xValue(centroid) + else: + im1 = self.drawImage(nx=1, ny=1, scale=scale, method='sb', offset=-centroid/scale) + Imax = im1(1,1) + + center = im.true_center + offset + centroid/scale + return im.calculateFWHM(center=center, Imax=Imax)
+ +
[docs] def xValue(self, *args, **kwargs): + """Returns the value of the object at a chosen 2D position in real space. + + This function returns the surface brightness of the object at a particular position + in real space. The position argument may be provided as a `PositionD` or `PositionI` + argument, or it may be given as x,y (either as a tuple or as two arguments). + + The object surface brightness profiles are typically defined in world coordinates, so + the position here should be in world coordinates as well. + + Not all `GSObject` classes can use this method. Classes like Convolution that require a + Discrete Fourier Transform to determine the real space values will not do so for a single + position. Instead a GalSimError will be raised. The xValue() method is available if and + only if ``obj.is_analytic_x == True``. + + Users who wish to use the xValue() method for an object that is the convolution of other + profiles can do so by drawing the convolved profile into an image, using the image to + initialize a new `InterpolatedImage`, and then using the xValue() method for that new + object. + + Parameters: + position: The position at which you want the surface brightness of the object. + + Returns: + the surface brightness at that position. + """ + pos = parse_pos_args(args,kwargs,'x','y') + return self._xValue(pos)
+ +
[docs] def _xValue(self, pos): + """Equivalent to `xValue`, but ``pos`` must be a `galsim.PositionD` instance + + Parameters: + pos: The position at which you want the surface brightness of the object. + + Returns: + the surface brightness at that position. + """ + raise NotImplementedError("%s does not implement xValue"%self.__class__.__name__)
+ +
[docs] def kValue(self, *args, **kwargs): + """Returns the value of the object at a chosen 2D position in k space. + + This function returns the amplitude of the fourier transform of the surface brightness + profile at a given position in k space. The position argument may be provided as a + `Position` argument, or it may be given as kx,ky (either as a tuple or as two arguments). + + Technically, kValue() is available if and only if the given obj has ``obj.is_analytic_k + == True``, but this is the case for all `GSObject` classes currently, so that should never + be an issue (unlike for `xValue`). + + Parameters: + position: The position in k space at which you want the fourier amplitude. + + Returns: + the amplitude of the fourier transform at that position. + """ + kpos = parse_pos_args(args,kwargs,'kx','ky') + return self._kValue(kpos)
+ +
[docs] def _kValue(self, kpos): # pragma: no cover (all our classes override this) + """Equivalent to `kValue`, but ``kpos`` must be a `galsim.PositionD` instance. + """ + raise NotImplementedError("%s does not implement kValue"%self.__class__.__name__)
+ +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given `GSParams`. + + You may either provide a `GSParams` instance:: + + >>> gsparams = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4) + >>> obj = obj.withGSParams(gsparams) + + or one or more named parameters as keyword arguments:: + + >>> obj = obj.withGSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4) + + .. note:: + + The latter style will leave all non-named parameters at their current + values. It only updates the named parameters to the given values. + """ + # Note to developers: objects that wrap other objects should override this in order + # to apply the new gsparams to the components. + # This implementation relies on getstate/setstate clearing out any _sbp or similar + # attribute that depends on the details of gsparams. If there are stored calculations + # aside from these, you should also clear them as well, or update them. + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + return ret
+ +
[docs] def withFlux(self, flux): + """Create a version of the current object with a different flux. + + This function is equivalent to ``obj.withScaledFlux(flux / obj.flux)``. + + It creates a new object that has the same profile as the original, but with the + surface brightness at every location rescaled such that the total flux will be + the given value. Note that if ``flux`` is an `SED`, the return value will be a + `ChromaticObject` with specified `SED`. + + Parameters: + flux: The new flux for the object. + + Returns: + the object with the new flux + """ + return self.withScaledFlux(flux / self.flux)
+ +
[docs] def withScaledFlux(self, flux_ratio): + """Create a version of the current object with the flux scaled by the given ``flux_ratio``. + + This function is equivalent to ``obj.withFlux(flux_ratio * obj.flux)``. Indeed, withFlux() + is implemented in terms of this one. + + It creates a new object that has the same profile as the original, but with the + surface brightness at every location scaled by the given amount. If ``flux_ratio`` is an + `SED`, then the returned object is a `ChromaticObject` with the `SED` multiplied by + its current ``flux``. + + Note that in this case the ``flux`` attribute of the `GSObject` being scaled gets + interpreted as being dimensionless, instead of having its normal units of [photons/s/cm^2]. + The photons/s/cm^2 units are (optionally) carried by the `SED` instead, or even left out + entirely if the `SED` is dimensionless itself (see discussion in the `ChromaticObject` + docstring). The `GSObject` ``flux`` attribute *does* still contribute to the + `ChromaticObject` normalization, though. For example, the following are equivalent:: + + >>> chrom_obj = gsobj.withScaledFlux(sed * 3.0) + >>> chrom_obj2 = (gsobj * 3.0).withScaledFlux(sed) + + An equivalent, and usually simpler, way to effect this scaling is:: + + >>> obj = obj * flux_ratio + + Parameters: + flux_ratio: The ratio by which to rescale the flux of the object when creating a new + one. + + Returns: + the object with the new flux. + """ + # Prohibit non-SED callable flux_ratio here as most likely an error. + if hasattr(flux_ratio, '__call__') and not isinstance(flux_ratio, sed.SED): + raise TypeError('callable flux_ratio must be an SED.') + + if flux_ratio == 1: + return self + else: + return transform.Transform(self, flux_ratio=flux_ratio)
+ +
[docs] def expand(self, scale): + """Expand the linear size of the profile by the given ``scale`` factor, while preserving + surface brightness. + + e.g. ``half_light_radius`` <-- ``half_light_radius * scale`` + + This doesn't correspond to either of the normal operations one would typically want to do to + a galaxy. The functions dilate() and magnify() are the more typical usage. But this + function is conceptually simple. It rescales the linear dimension of the profile, while + preserving surface brightness. As a result, the flux will necessarily change as well. + + See dilate() for a version that applies a linear scale factor while preserving flux. + + See magnify() for a version that applies a scale factor to the area while preserving surface + brightness. + + Parameters: + scale: The factor by which to scale the linear dimension of the object. + + Returns: + the expanded object. + """ + return transform.Transform(self, jac=[scale, 0., 0., scale])
+ +
[docs] def dilate(self, scale): + """Dilate the linear size of the profile by the given ``scale`` factor, while preserving + flux. + + e.g. ``half_light_radius`` <-- ``half_light_radius * scale`` + + See expand() and magnify() for versions that preserve surface brightness, and thus + changes the flux. + + Parameters: + scale: The linear rescaling factor to apply. + + Returns: + the dilated object. + """ + # equivalent to self.expand(scale) * (1./scale**2) + return transform.Transform(self, jac=[scale, 0., 0., scale], flux_ratio=scale**-2)
+ +
[docs] def magnify(self, mu): + """Create a version of the current object with a lensing magnification applied to it, + scaling the area and flux by ``mu`` at fixed surface brightness. + + This process applies a lensing magnification mu, which scales the linear dimensions of the + image by the factor sqrt(mu), i.e., ``half_light_radius`` <-- + ``half_light_radius * sqrt(mu)`` while increasing the flux by a factor of mu. Thus, + magnify() preserves surface brightness. + + See dilate() for a version that applies a linear scale factor while preserving flux. + + See expand() for a version that applies a linear scale factor while preserving surface + brightness. + + Parameters: + mu: The lensing magnification to apply. + + Returns: + the magnified object. + """ + return self.expand(math.sqrt(mu))
+ +
[docs] def shear(self, *args, **kwargs): + """Create a version of the current object with an area-preserving shear applied to it. + + The arguments may be either a `Shear` instance or arguments to be used to initialize one. + + For more details about the allowed keyword arguments, see the `Shear` docstring. + + The shear() method precisely preserves the area. To include a lensing distortion with + the appropriate change in area, either use shear() with magnify(), or use lens(), which + combines both operations. + + Parameters: + shear: The `Shear` to be applied. Or, as described above, you may instead supply + parameters do construct a shear directly. eg. ``obj.shear(g1=g1,g2=g2)``. + + Returns: + the sheared object. + """ + if len(args) == 1: + if kwargs: + raise TypeError("Error, gave both unnamed and named arguments to GSObject.shear!") + if not isinstance(args[0], Shear): + raise TypeError("Error, unnamed argument to GSObject.shear is not a Shear!") + shear = args[0] + elif len(args) > 1: + raise TypeError("Error, too many unnamed arguments to GSObject.shear!") + elif len(kwargs) == 0: + raise TypeError("Error, shear argument is required") + else: + shear = Shear(**kwargs) + return transform.Transform(self, shear.getMatrix())
+ +
[docs] def _shear(self, shear): + """Equivalent to `GSObject.shear`, but without the overhead of sanity checks or other + ways to input the ``shear`` value. + + Also, it won't propagate any noise attribute. + + Parameters: + shear: The `Shear` to be applied. + + Returns: + the sheared object. + """ + return transform._Transform(self, shear.getMatrix())
+ +
[docs] def lens(self, g1, g2, mu): + """Create a version of the current object with both a lensing shear and magnification + applied to it. + + This `GSObject` method applies a lensing (reduced) shear and magnification. The shear must + be specified using the g1, g2 definition of shear (see `Shear` for more details). + This is the same definition as the outputs of the PowerSpectrum and NFWHalo classes, which + compute shears according to some lensing power spectrum or lensing by an NFW dark matter + halo. The magnification determines the rescaling factor for the object area and flux, + preserving surface brightness. + + Parameters: + g1: First component of lensing (reduced) shear to apply to the object. + g2: Second component of lensing (reduced) shear to apply to the object. + mu: Lensing magnification to apply to the object. This is the factor by which + the solid angle subtended by the object is magnified, preserving surface + brightness. + + Returns: + the lensed object. + """ + shear = Shear(g1=g1, g2=g2) + return transform.Transform(self, shear.getMatrix() * math.sqrt(mu))
+ + def _lens(self, g1, g2, mu): + """Equivalent to `GSObject.lens`, but without the overhead of some of the sanity checks. + + Also, it won't propagate any noise attribute. + + Parameters: + g1: First component of lensing (reduced) shear to apply to the object. + g2: Second component of lensing (reduced) shear to apply to the object. + mu: Lensing magnification to apply to the object. This is the factor by which + the solid angle subtended by the object is magnified, preserving surface + brightness. + + Returns: + the lensed object. + """ + shear = _Shear(g1 + 1j*g2) + return transform._Transform(self, shear.getMatrix() * math.sqrt(mu)) + +
[docs] def rotate(self, theta): + """Rotate this object by an `Angle` ``theta``. + + Parameters: + theta: Rotation angle (`Angle` object, positive means anticlockwise). + + Returns: + the rotated object. + """ + if not isinstance(theta, Angle): + raise TypeError("Input theta should be an Angle") + s, c = theta.sincos() + return transform.Transform(self, jac=[c, -s, s, c])
+ +
[docs] def transform(self, dudx, dudy, dvdx, dvdy): + """Create a version of the current object with an arbitrary Jacobian matrix transformation + applied to it. + + This applies a Jacobian matrix to the coordinate system in which this object + is defined. It changes a profile defined in terms of (x,y) to one defined in + terms of (u,v) where: + + u = dudx x + dudy y + v = dvdx x + dvdy y + + That is, an arbitrary affine transform, but without the translation (which is + easily effected via the `shift` method). + + Note that this function is similar to expand in that it preserves surface brightness, + not flux. If you want to preserve flux, you should also do:: + + >>> prof *= 1./abs(dudx*dvdy - dudy*dvdx) + + Parameters: + dudx: du/dx, where (x,y) are the current coords, and (u,v) are the new coords. + dudy: du/dy, where (x,y) are the current coords, and (u,v) are the new coords. + dvdx: dv/dx, where (x,y) are the current coords, and (u,v) are the new coords. + dvdy: dv/dy, where (x,y) are the current coords, and (u,v) are the new coords. + + Returns: + the transformed object + """ + return transform.Transform(self, jac=[dudx, dudy, dvdx, dvdy])
+ +
[docs] def shift(self, *args, **kwargs): + """Create a version of the current object shifted by some amount in real space. + + After this call, the caller's type will be a `GSObject`. + This means that if the caller was a derived type that had extra methods or properties + beyond those defined in `GSObject` (e.g. `Gaussian.sigma`), then these methods are no + longer available. + + Note: in addition to the dx,dy parameter names, you may also supply dx,dy as a tuple, + or as a `Position` object. + + The shift coordinates here are sky coordinates. `GSObject` profiles are always defined in + sky coordinates and only later (when they are drawn) is the connection to pixel coordinates + established (via a pixel_scale or WCS). So a shift of dx moves the object horizontally + in the sky (e.g. west in the local tangent plane of the observation), and dy moves the + object vertically (north in the local tangent plane). + + The units are typically arcsec, but we don't enforce that anywhere. The units here just + need to be consistent with the units used for any size values used by the `GSObject`. + The connection of these units to the eventual image pixels is defined by either the + ``pixel_scale`` or the ``wcs`` parameter of `GSObject.drawImage`. + + Note: if you want to shift the object by a set number (or fraction) of pixels in the + drawn image, you probably want to use the ``offset`` parameter of `GSObject.drawImage` + rather than this method. + + Parameters: + dx: Horizontal shift to apply. + dy: Vertical shift to apply. + + Alternatively, you may supply a single parameter as a `Position` instance, rather than + the two components separately if that is more convenient. + + Parameter: + offset: The shift to apply, given as PositionD(dx,dy) or PositionI(dx,dy) + + Returns: + the shifted object. + """ + offset = parse_pos_args(args, kwargs, 'dx', 'dy') + return transform.Transform(self, offset=offset)
+ +
[docs] def _shift(self, dx, dy): + """Equivalent to `shift`, but without the overhead of sanity checks or option + to give the shift as a PositionD. + + Also, it won't propagate any noise attribute. + + Parameters: + dx: The x-component of the shift to apply + dy: The y-component of the shift to apply + + Returns: + the shifted object. + """ + new_obj = transform._Transform(self, offset=(dx,dy)) + return new_obj
+ +
[docs] def atRedshift(self, redshift): + """Create a version of the current object with a different redshift. + + For regular GSObjects, this method doesn't do anything aside from setting a ``redshift`` + attribute with the given value. But this allows duck typing with ChromaticObjects + where this function will adjust the SED appropriately. + + .. warning:: + + This method has been deprecated as of version 2.5.3. + + Returns: + the object with the new redshift + """ + from .deprecated import depr + depr('atRedshift', '2.5.3', 'obj = copy.copy(obj); obj.redshift = redshift') + ret = copy.copy(self) + ret.redshift = redshift + return ret
+ + # Make sure the image is defined with the right size and wcs for drawImage() + def _setup_image(self, image, nx, ny, bounds, add_to_image, dtype, center, odd=False): + # If image is given, check validity of nx,ny,bounds: + if image is not None: + if bounds is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide bounds if image is provided", bounds=bounds, image=image) + if nx is not None or ny is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide nx,ny if image is provided", nx=nx, ny=ny, image=image) + if dtype is not None and image.array.dtype != dtype: + raise GalSimIncompatibleValuesError( + "Cannot specify dtype != image.array.dtype if image is provided", + dtype=dtype, image=image) + + # Resize the given image if necessary + if not image.bounds.isDefined(): + # Can't add to image if need to resize + if add_to_image: + raise GalSimIncompatibleValuesError( + "Cannot add_to_image if image bounds are not defined", + add_to_image=add_to_image, image=image) + N = self.getGoodImageSize(1.0) + if odd: N += 1 + bounds = _BoundsI(1,N,1,N) + image.resize(bounds) + # Else use the given image as is + + # Otherwise, make a new image + else: + # Can't add to image if none is provided. + if add_to_image: + raise GalSimIncompatibleValuesError( + "Cannot add_to_image if image is None", add_to_image=add_to_image, image=image) + # Use bounds or nx,ny if provided + if bounds is not None: + if nx is not None or ny is not None: + raise GalSimIncompatibleValuesError( + "Cannot set both bounds and (nx, ny)", nx=nx, ny=ny, bounds=bounds) + if not bounds.isDefined(): + raise GalSimValueError("Cannot use undefined bounds", bounds) + image = Image(bounds=bounds, dtype=dtype) + elif nx is not None or ny is not None: + if nx is None or ny is None: + raise GalSimIncompatibleValuesError( + "Must set either both or neither of nx, ny", nx=nx, ny=ny) + image = Image(nx, ny, dtype=dtype) + if center is not None: + image.shift(_PositionI(np.floor(center.x+0.5-image.true_center.x), + np.floor(center.y+0.5-image.true_center.y))) + else: + N = self.getGoodImageSize(1.0) + if odd: N += 1 + image = Image(N, N, dtype=dtype) + if center is not None: + image.setCenter(_PositionI(np.ceil(center.x), np.ceil(center.y))) + + return image + + def _local_wcs(self, wcs, image, offset, center, use_true_center, new_bounds): + # Get the local WCS at the location of the object. + + if wcs._isUniform: + return wcs.local() + elif image is None: + bounds = new_bounds + else: + bounds = image.bounds + if not bounds.isDefined(): + raise GalSimIncompatibleValuesError( + "Cannot provide non-local wcs with automatically sized image", + wcs=wcs, image=image, bounds=new_bounds) + elif center is not None: + obj_cen = center + elif use_true_center: + obj_cen = bounds.true_center + else: + obj_cen = bounds.center + # Convert from PositionI to PositionD + obj_cen = _PositionD(obj_cen.x, obj_cen.y) + # _parse_offset has already turned offset=None into PositionD(0,0), so it is safe to add. + obj_cen += offset + return wcs.local(image_pos=obj_cen) + + def _parse_offset(self, offset): + if offset is None: + return _PositionD(0,0) + elif isinstance(offset, Position): + return _PositionD(offset.x, offset.y) + else: + # Let python raise the appropriate exception if this isn't valid. + return _PositionD(offset[0], offset[1]) + + def _parse_center(self, center): + # Almost the same as _parse_offset, except we leave it as None in that case. + if center is None: + return None + elif isinstance(center, Position): + return _PositionD(center.x, center.y) + else: + # Let python raise the appropriate exception if this isn't valid. + return _PositionD(center[0], center[1]) + + def _get_new_bounds(self, image, nx, ny, bounds, center): + if image is not None and image.bounds.isDefined(): + return image.bounds + elif nx is not None and ny is not None: + b = BoundsI(1,nx,1,ny) + if center is not None: + b = b.shift(_PositionI(np.floor(center.x+0.5)-b.center.x, + np.floor(center.y+0.5)-b.center.y)) + return b + elif bounds is not None and bounds.isDefined(): + return bounds + else: + return BoundsI() + + def _adjust_offset(self, new_bounds, offset, center, use_true_center): + # Note: this assumes self is in terms of image coordinates. + if center is not None: + if new_bounds.isDefined(): + offset += center - new_bounds.center + else: + # Then will be created as even sized image. + offset += _PositionD(center.x-np.ceil(center.x), center.y-np.ceil(center.y)) + elif use_true_center: + # For even-sized images, the SBProfile draw function centers the result in the + # pixel just up and right of the real center. So shift it back to make sure it really + # draws in the center. + # Also, remember that numpy's shape is ordered as [y,x] + dx = offset.x + dy = offset.y + shape = new_bounds.numpyShape() + if shape[1] % 2 == 0: dx -= 0.5 + if shape[0] % 2 == 0: dy -= 0.5 + offset = _PositionD(dx,dy) + return offset + + def _determine_wcs(self, scale, wcs, image, default_wcs=None): + # Determine the correct wcs given the input scale, wcs and image. + if wcs is not None: + if scale is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both wcs and scale", wcs=wcs, scale=scale) + if not isinstance(wcs, BaseWCS): + raise TypeError("wcs must be a BaseWCS instance") + if image is not None: image.wcs = None + elif scale is not None: + wcs = PixelScale(scale) + if image is not None: image.wcs = None + elif image is not None and image.wcs is not None: + wcs = image.wcs + + # If the input scale <= 0, or wcs is still None at this point, then use the Nyquist scale: + if wcs is None or (wcs._isPixelScale and not wcs.scale > 0): + if default_wcs is None: + wcs = PixelScale(self.nyquist_scale) + else: + wcs = default_wcs + + return wcs + + def _prepareDraw(self): + # Do any work that was postponed until drawImage. + pass + +
[docs] def drawImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, wcs=None, dtype=None, + method='auto', area=1., exptime=1., gain=1., add_to_image=False, + center=None, use_true_center=True, offset=None, + n_photons=0., rng=None, max_extra_noise=0., + poisson_flux=None, sensor=None, photon_ops=None, n_subsample=3, maxN=None, + save_photons=False, bandpass=None, setup_only=False, + surface_ops=None): + """Draws an `Image` of the object. + + The drawImage() method is used to draw an `Image` of the current object using one of several + possible rendering methods (see below). It can create a new `Image` or can draw onto an + existing one if provided by the ``image`` parameter. If the ``image`` is given, you can + also optionally add to the given `Image` if ``add_to_image = True``, but the default is to + replace the current contents with new values. + + **Providing an input image**: + + Note that if you provide an ``image`` parameter, it is the image onto which the profile + will be drawn. The provided image *will be modified*. A reference to the same image + is also returned to provide a parallel return behavior to when ``image`` is ``None`` + (described above). + + This option is useful in practice because you may want to construct the image first and + then draw onto it, perhaps multiple times. For example, you might be drawing onto a + subimage of a larger image. Or you may want to draw different components of a complex + profile separately. In this case, the returned value is typically ignored. For example:: + + >>> im1 = bulge.drawImage() + >>> im2 = disk.drawImage(image=im1, add_to_image=True) + >>> assert im1 is im2 + + >>> full_image = galsim.Image(2048, 2048, scale=pixel_scale) + >>> b = galsim.BoundsI(x-32, x+32, y-32, y+32) + >>> stamp = obj.drawImage(image = full_image[b]) + >>> assert (stamp.array == full_image[b].array).all() + + **Letting drawImage create the image for you**: + + If drawImage() will be creating the image from scratch for you, then there are several ways + to control the size of the new image. If the ``nx`` and ``ny`` keywords are present, then + an image with these numbers of pixels on a side will be created. Similarly, if the ``bounds`` + keyword is present, then an image with the specified bounds will be created. Note that it + is an error to provide an existing `Image` when also specifying ``nx``, ``ny``, or + ``bounds``. In the absence of ``nx``, ``ny``, and ``bounds``, drawImage will decide a good + size to use based on the size of the object being drawn. Basically, it will try to use an + area large enough to include at least 99.5% of the flux. + + .. note:: + + This value 0.995 is really ``1 - folding_threshold``. You can change the value of + ``folding_threshold`` for any object via `GSParams`. + + You can set the pixel scale of the constructed image with the ``scale`` parameter, or set + a WCS function with ``wcs``. If you do not provide either ``scale`` or ``wcs``, then + drawImage() will default to using the Nyquist scale for the current object. + + You can also set the data type used in the new `Image` with the ``dtype`` parameter that has + the same options as for the `Image` constructor. + + **The drawing "method"**: + + There are several different possible methods drawImage() can use for rendering the image. + This is set by the ``method`` parameter. The options are: + + auto + This is the default, which will normally be equivalent to 'fft'. However, + if the object being rendered is simple (no convolution) and has hard edges + (e.g. a Box or a truncated Moffat or Sersic), then it will switch to + 'real_space', since that is often both faster and more accurate in these + cases (due to ringing in Fourier space). + fft + The integration of the light within each pixel is mathematically equivalent + to convolving by the pixel profile (a `Pixel` object) and sampling the result + at the centers of the pixels. This method will do that convolution using + a discrete Fourier transform. Furthermore, if the object (or any component + of it) has been transformed via shear(), dilate(), etc., then these + transformations are done in Fourier space as well. + real_space + This uses direct integrals (using the Gauss-Kronrod-Patterson algorithm) + in real space for the integration over the pixel response. It is usually + slower than the 'fft' method, but if the profile has hard edges that cause + ringing in Fourier space, it can be faster and/or more accurate. If you + use 'real_space' with something that is already a Convolution, then this + will revert to 'fft', since the double convolution that is required to also + handle the pixel response is far too slow to be practical using real-space + integrals. + phot + This uses a technique called photon shooting to render the image. + Essentially, the object profile is taken as a probability distribution + from which a finite number of photons are "shot" onto the image. Each + photon's flux gets added to whichever pixel the photon hits. This process + automatically accounts for the integration of the light over the pixel + area, since all photons that hit any part of the pixel are counted. + Convolutions and transformations are simple geometric processes in this + framework. However, there are two caveats with this method: (1) the + resulting image will have Poisson noise from the finite number of photons, + and (2) it is not available for all object types (notably anything that + includes a Deconvolution). + no_pixel + Instead of integrating over the pixels, this method will sample the profile + at the centers of the pixels and multiply by the pixel area. If there is + a convolution involved, the choice of whether this will use an FFT or + real-space calculation is governed by the ``real_space`` parameter of the + Convolution class. This method is the appropriate choice if you are using + a PSF that already includes a convolution by the pixel response. For + example, if you are using a PSF from an observed image of a star, then it + has already been convolved by the pixel, so you would not want to do so + again. Note: The multiplication by the pixel area gets the flux + normalization right for the above use case. cf. ``method = 'sb'``. + sb + This is a lot like 'no_pixel', except that the image values will simply be + the sampled object profile's surface brightness, not multiplied by the + pixel area. This does not correspond to any real observing scenario, but + it could be useful if you want to view the surface brightness profile of an + object directly, without including the pixel integration. + + The 'phot' method has a few extra parameters that adjust how it functions. The total + number of photons to shoot is normally calculated from the object's flux. This flux is + taken to be given in photons/cm^2/s, so for most simple profiles, this times area * exptime + will equal the number of photons shot. (See the discussion in Rowe et al, 2015, for why + this might be modified for `InterpolatedImage` and related profiles.) However, you can + manually set a different number of photons with ``n_photons``. You can also set + ``max_extra_noise`` to tell drawImage() to use fewer photons than normal (and so is faster) + such that no more than that much extra noise is added to any pixel. This is particularly + useful if you will be subsequently adding sky noise, and you can thus tolerate more noise + than the normal number of photons would give you, since using fewer photons is of course + faster. Finally, the default behavior is to have the total flux vary as a Poisson random + variate, which is normally appropriate with photon shooting. But you can turn this off with + ``poisson_flux=False``. It also defaults to False if you set an explicit value for + ``n_photons``. + + Given the periodicity implicit in the use of FFTs, there can occasionally be artifacts due + to wrapping at the edges, particularly for objects that are quite extended (e.g., due to + the nature of the radial profile). See `GSParams` for parameters that you can use to reduce + the level of these artifacts, in particular ``folding_threshold`` may be helpful if you see + such artifacts in your images. + + Setting the offset: + + The object will by default be drawn with its nominal center at the center location of the + image. There is thus a qualitative difference in the appearance of the rendered profile + when drawn on even- and odd-sized images. For a profile with a maximum at (0,0), this + maximum will fall in the central pixel of an odd-sized image, but in the corner of the four + central pixels of an even-sized image. There are three parameters that can affect this + behavior. First, you can specify any arbitrary pixel position to center the object using + the ``center`` parameter. If this is None, then it will pick one of the two potential + "centers" of the image, either ``image.true_center`` or ``image.center``. The latter is + an integer position, which always corresponds to the center of some pixel, which for even + sized images won't (cannot) be the actual "true" center of the image. You can choose which + of these two centers you want to use with the ``use_true_center`` parameters, which + defaults to False. You can also arbitrarily offset the profile from the image center with + the ``offset`` parameter to handle any aribtrary offset you want from the chosen center. + (Typically, one would use only one of ``center`` or ``offset`` but it is permissible to use + both.) + + Setting the overall normalization: + + Normally, the flux of the object should be equal to the sum of all the pixel values in the + image, less some small amount of flux that may fall off the edge of the image (assuming you + don't use ``method='sb'``). However, you may optionally set a ``gain`` value, which + converts between photons and ADU (so-called analog-to-digital units), the units of the + pixel values in real images. Normally, the gain of a CCD is in electrons/ADU, but in + GalSim, we fold the quantum efficiency into the gain as well, so the units are photons/ADU. + + Another caveat is that, technically, flux is really in units of photons/cm^2/s, not photons. + So if you want, you can keep track of this properly and provide an ``area`` and ``exptime`` + here. This detail is more important with chromatic objects where the `SED` is typically + given in erg/cm^2/s/nm, so the exposure time and area are important details. With achromatic + objects however, it is often more convenient to ignore these details and just consider the + flux to be the total number of photons for this exposure, in which case, you would leave the + area and exptime parameters at their default value of 1. + + On return, the image will have an attribute ``added_flux``, which will be set to the total + flux added to the image. This may be useful as a sanity check that you have provided a + large enough image to catch most of the flux. For example:: + + >>> obj.drawImage(image) + >>> assert image.added_flux > 0.99 * obj.flux + + The appropriate threshold will depend on your particular application, including what kind + of profile the object has, how big your image is relative to the size of your object, + whether you are keeping ``poisson_flux=True``, etc. + + The following code snippet illustrates how ``gain``, ``exptime``, ``area``, and ``method`` + can all influence the relationship between the ``flux`` attribute of a `GSObject` and + both the pixel values and ``.added_flux`` attribute of an `Image` drawn with + ``drawImage()``:: + + >>> obj = galsim.Gaussian(fwhm=1) + >>> obj.flux + 1.0 + >>> im = obj.drawImage() + >>> im.added_flux + 0.9999630988657515 + >>> im.array.sum() + 0.99996305 + >>> im = obj.drawImage(exptime=10, area=10) + >>> im.added_flux + 0.9999630988657525 + >>> im.array.sum() + 99.996315 + >>> im = obj.drawImage(exptime=10, area=10, method='sb', scale=0.5, nx=10, ny=10) + >>> im.added_flux + 0.9999973790505298 + >>> im.array.sum() + 399.9989 + >>> im = obj.drawImage(exptime=10, area=10, gain=2) + >>> im.added_flux + 0.9999630988657525 + >>> im.array.sum() + 49.998158 + + Using a non-trivial sensor: + + Normally the sensor is modeled as an array of pixels where any photon that hits a given + pixel is accumulated into that pixel. The final pixel value then just reflects the total + number of pixels that hit each sensor. However, real sensors do not (quite) work this way. + + In real CCDs, the photons travel some distance into the silicon before converting to + electrons. Then the electrons diffuse laterally some amount as they are pulled by the + electric field toward the substrate. Finally, previous electrons that have already been + deposited will repel subsequent electrons, both slowing down their descent, leading to + more diffusion, and pushing them laterally toward neighboring pixels, which is called + the brighter-fatter effect. + + Users interested in modeling this kind of effect can supply a ``sensor`` object to use + for the accumulation step. See `SiliconSensor` for a class that models silicon-based CCD + sensors. + + Some related effects may need to be done to the photons at the surface layer before being + passed into the sensor object. For instance, the photons may need to be given appropriate + incidence angles according to the optics of the telescope (since this matters for where the + photons are converted to electrons). You may also need to give the photons wavelengths + according to the `SED` of the object. Such steps are specified in a ``photon_ops`` + parameter, which should be a list of any such operations you wish to perform on the photon + array before passing them to the sensor. See `FRatioAngles` and `WavelengthSampler` for + two examples of such photon operators. + + Since the sensor deals with photons, it is most natural to use this feature in conjunction + with photon shooting (``method='phot'``). However, it is allowed with FFT methods too. + But there is a caveat one should be aware of in this case. The FFT drawing is used to + produce an intermediate image, which is then converted to a `PhotonArray` using the + factory function `PhotonArray.makeFromImage`. This assigns photon positions randomly + within each pixel where they were drawn, which isn't always a particularly good + approximation. + + To improve this behavior, the intermediate image is drawn with smaller pixels than the + target image, so the photons are given positions closer to their true locations. The + amount of subsampling is controlled by the ``n_subsample`` parameter, which defaults to 3. + Larger values will be more accurate at the expense of larger FFTs (i.e. slower and using + more memory). + + Parameters: + image: If provided, this will be the image on which to draw the profile. + If ``image`` is None, then an automatically-sized `Image` will be + created. If ``image`` is given, but its bounds are undefined (e.g. if + it was constructed with ``image = galsim.Image()``), then it will be + resized appropriately based on the profile's size [default: None]. + nx: If provided and ``image`` is None, use to set the x-direction size of + the image. Must be accompanied by ``ny``. + ny: If provided and ``image`` is None, use to set the y-direction size of + the image. Must be accompanied by ``nx``. + bounds: If provided and ``image`` is None, use to set the bounds of the image. + scale: If provided, use this as the pixel scale for the image. + If ``scale`` is None and ``image`` is given, then take the provided + image's pixel scale. + If ``scale`` is None and ``image`` is None, then use the Nyquist scale. + If ``scale <= 0`` (regardless of ``image``), then use the Nyquist scale. + If ``scale > 0`` and ``image`` is given, then override ``image.scale`` + with the value given as a keyword. [default: None] + wcs: If provided, use this as the wcs for the image (possibly overriding any + existing ``image.wcs``). At most one of ``scale`` or ``wcs`` may be + provided. [default: None] + dtype: The data type to use for an automatically constructed image. Only + valid if ``image`` is None. [default: None, which means to use + numpy.float32] + method: Which method to use for rendering the image. See discussion above + for the various options and what they do. [default: 'auto'] + area: Collecting area of telescope in cm^2. [default: 1.] + exptime: Exposure time in s. [default: 1.] + gain: The number of photons per ADU ("analog to digital units", the units of + the numbers output from a CCD). [default: 1] + add_to_image: Whether to add flux to the existing image rather than clear out + anything in the image before drawing. + Note: This requires that ``image`` be provided and that it have defined + bounds. [default: False] + center: The position on the image at which to place the nominal center of the + object (usually the centroid, but not necessarily). [default: None] + use_true_center: If ``center`` is None, then the object is normally centered at the + true center of the image (using the property image.true_center). + If you would rather use the integer center (given by image.center), + set this to ``False``. [default: True] + offset: The offset in pixel coordinates at which to center the profile being + drawn relative to either ``center`` (if given) or the center of the + image (either the true center or integer center according to the + ``use_true_center`` parameter). [default: None] + n_photons: If provided, the number of photons to use for photon shooting. + If not provided (i.e. ``n_photons = 0``), use as many photons as + necessary to result in an image with the correct Poisson shot + noise for the object's flux. For positive definite profiles, this + is equivalent to ``n_photons = flux``. However, some profiles need + more than this because some of the shot photons are negative + (usually due to interpolants). + [default: 0] + rng: If provided, a random number generator to use for photon shooting, + which may be any kind of `BaseDeviate` object. If ``rng`` is None, one + will be automatically created. [default: None] + max_extra_noise: If provided, the allowed extra noise in each pixel when photon + shooting. This is only relevant if ``n_photons=0``, so the number of + photons is being automatically calculated. In that case, if the image + noise is dominated by the sky background, then you can get away with + using fewer shot photons than the full ``n_photons = flux``. + Essentially each shot photon can have a ``flux > 1``, which increases + the noise in each pixel. The ``max_extra_noise`` parameter specifies + how much extra noise per pixel is allowed because of this approximation. + A typical value for this might be ``max_extra_noise = sky_level / 100`` + where ``sky_level`` is the flux per pixel due to the sky. Note that + this uses a "variance" definition of noise, not a "sigma" definition. + [default: 0.] + poisson_flux: Whether to allow total object flux scaling to vary according to + Poisson statistics for ``n_photons`` samples when photon shooting. + [default: True, unless ``n_photons`` is given, in which case the default + is False] + sensor: An optional `Sensor` instance, which will be used to accumulate the + photons onto the image. [default: None] + photon_ops: A list of operators that can modify the photon array that will be + applied in order before accumulating the photons on the sensor. + [default: None] + n_subsample: The number of sub-pixels per final pixel to use for fft drawing when + using a sensor. The sensor step needs to know the sub-pixel positions + of the photons, which is lost in the fft method. So using smaller + pixels for the fft step keeps some of that information, making the + assumption of uniform flux per pixel less bad of an approximation. + [default: 3] + maxN: Sets the maximum number of photons that will be added to the image + at a time. (Memory requirements are proportional to this number.) + + .. note:: + + Using this parameter will not necessarily produce identical + results as when not using it due to potentially different order + of various random number generations in either the photon_ops, + or the sensor, or (for method='fft') the conversion of the FFT + image to photons. + + [default: None, which means no limit] + save_photons: If True, save the `PhotonArray` as ``image.photons``. Only valid if + method is 'phot' or sensor is not None. [default: False] + bandpass: This parameter is ignored, but is allowed to enable duck typing + eqivalence between this method and the ChromaticObject.drawImage + method. [default: None] + setup_only: Don't actually draw anything on the image. Just make sure the image + is set up correctly. This is used internally by GalSim, but there + may be cases where the user will want the same functionality. + [default: False] + + Returns: + the drawn `Image`. + """ + if surface_ops is not None: + from .deprecated import depr + depr('surface_ops', 2.3, 'photon_ops') + photon_ops = surface_ops + + # Check that image is sane + if image is not None and not isinstance(image, Image): + raise TypeError("image is not an Image instance", image) + + # Make sure (gain, area, exptime) have valid values: + if not gain > 0.: + raise GalSimRangeError("Invalid gain <= 0.", gain, 0., None) + if not area > 0.: + raise GalSimRangeError("Invalid area <= 0.", area, 0., None) + if not exptime > 0.: + raise GalSimRangeError("Invalid exptime <= 0.", exptime, 0., None) + + if method not in ('auto', 'fft', 'real_space', 'phot', 'no_pixel', 'sb'): + raise GalSimValueError("Invalid method name", method, + ('auto', 'fft', 'real_space', 'phot', 'no_pixel', 'sb')) + + # Check that the user isn't convolving by a Pixel already. This is almost always an error. + if method == 'auto' and isinstance(self, convolve.Convolution): + if any([ isinstance(obj, box.Pixel) for obj in self.obj_list ]): + galsim_warn( + "You called drawImage with ``method='auto'`` " + "for an object that includes convolution by a Pixel. " + "This is probably an error. Normally, you should let GalSim " + "handle the Pixel convolution for you. If you want to handle the Pixel " + "convolution yourself, you can use method=no_pixel. Or if you really meant " + "for your profile to include the Pixel and also have GalSim convolve by " + "an _additional_ Pixel, you can suppress this warning by using method=fft.") + + # Some parameters are only relevant for method == 'phot' + if method != 'phot': + if n_photons != 0.: + raise GalSimIncompatibleValuesError( + "n_photons is only relevant for method='phot'", + method=method, sensor=sensor, n_photons=n_photons) + if max_extra_noise != 0.: + raise GalSimIncompatibleValuesError( + "max_extra_noise is only relevant for method='phot'", + method=method, sensor=sensor, max_extra_noise=max_extra_noise) + if poisson_flux is not None: + raise GalSimIncompatibleValuesError( + "poisson_flux is only relevant for method='phot'", + method=method, sensor=sensor, poisson_flux=poisson_flux) + + # If using photon ops with fft, then need a sensor. + # Note: "if photon_ops" so photon_ops being [] or () is the same as None. + if photon_ops and method != 'phot' and sensor is None: + sensor = Sensor() + if photon_ops is None: + # Easier to just make it an empty tuple, rather than deal with None below. + photon_ops = () + + # Some parameters are only relevant for either phot or when using a sensor. + if method != 'phot' and sensor is None: + if rng is not None: + raise GalSimIncompatibleValuesError( + "rng is only relevant for method='phot' or when using a sensor", + method=method, sensor=sensor, rng=rng) + if maxN != None: + raise GalSimIncompatibleValuesError( + "maxN is only relevant for method='phot' or when using a sensor", + method=method, sensor=sensor, maxN=maxN) + if save_photons: + raise GalSimIncompatibleValuesError( + "save_photons is only valid for method='phot' or when using a sensor", + method=method, sensor=sensor, save_photons=save_photons) + else: + # If we want to save photons, it doesn't make sense to limit the number per shoot call. + if save_photons and maxN is not None: + raise GalSimIncompatibleValuesError( + "Setting maxN is incompatible with save_photons=True") + + # Do any delayed computation needed by fft or real_space drawing. + if method != 'phot': + self._prepareDraw() + + # Figure out what wcs we are going to use. + wcs = self._determine_wcs(scale, wcs, image) + + # Make sure offset and center are PositionD, converting from other formats (tuple, array,..) + # Note: If None, offset is converted to PositionD(0,0), but center will remain None. + offset = self._parse_offset(offset) + center = self._parse_center(center) + + # Determine the bounds of the new image for use below (if it can be known yet) + new_bounds = self._get_new_bounds(image, nx, ny, bounds, center) + + # Get the local WCS, accounting for the offset correctly. + local_wcs = self._local_wcs(wcs, image, offset, center, use_true_center, new_bounds) + + # Account for area and exptime. + flux_scale = area * exptime + # For surface brightness normalization, also scale by the pixel area. + if method == 'sb': + flux_scale /= local_wcs.pixelArea() + # Only do the gain here if not photon shooting, since need the number of photons to + # reflect that actual photons, not ADU. + if gain != 1 and method != 'phot' and sensor is None: + flux_scale /= gain + + # Determine the offset, and possibly fix the centering for even-sized images + offset = self._adjust_offset(new_bounds, offset, center, use_true_center) + + # Convert the profile in world coordinates to the profile in image coordinates: + prof = local_wcs.profileToImage(self, flux_ratio=flux_scale, offset=offset) + if offset != _PositionD(0,0): + local_wcs = local_wcs.shiftOrigin(offset) + + # If necessary, convolve by the pixel + if method in ('auto', 'fft', 'real_space'): + if method == 'auto': + real_space = None + elif method == 'fft': + real_space = False + else: + real_space = True + prof_no_pixel = prof + prof = convolve.Convolve(prof, box.Pixel(scale=1.0, gsparams=self.gsparams), + real_space=real_space, gsparams=self.gsparams) + + # Make sure image is setup correctly + image = prof._setup_image(image, nx, ny, bounds, add_to_image, dtype, center) + image.wcs = wcs + + if setup_only: + image.added_flux = 0. + return image + + # Making a view of the image lets us change the center without messing up the original. + imview = image._view() + imview._shift(-image.center) # equiv. to setCenter(0,0), but faster + imview.wcs = PixelScale(1.0) + orig_center = image.center # Save the original center to pass to sensor.accumulate + if method == 'phot': + added_photons, photons = prof.drawPhot(imview, gain, add_to_image, + n_photons, rng, max_extra_noise, poisson_flux, + sensor, photon_ops, maxN, + orig_center, local_wcs) + else: + # If not using phot, but doing sensor, then make a copy. + if sensor is not None: + if imview.dtype in (np.float32, np.float64): + dtype = None + else: + dtype = np.float64 + draw_image = imview.real.subsample(n_subsample, n_subsample, dtype=dtype) + draw_image._shift(-draw_image.center) # eqiv. to setCenter(0,0) + if method in ('auto', 'fft', 'real_space'): + # Need to reconvolve by the new smaller pixel instead + pix = box.Pixel(scale=1.0/n_subsample, gsparams=self.gsparams) + prof = convolve.Convolve(prof_no_pixel, pix, real_space=real_space, + gsparams=self.gsparams) + elif n_subsample != 1: + # We can't just pull off the pixel-free version, so we need to deconvolve + # by the original pixel and reconvolve by the smaller one. + dec = convolve.Deconvolve(box.Pixel(scale=1.0, gsparams=self.gsparams)) + pix = box.Pixel(scale=1.0/n_subsample, gsparams=self.gsparams) + prof = convolve.Convolve(prof, dec, pix, gsparams=self.gsparams) + add = False + if not add_to_image: imview.setZero() + else: + draw_image = imview + add = add_to_image + + if prof.is_analytic_x: + added_photons = prof.drawReal(draw_image, add) + else: + added_photons = prof.drawFFT(draw_image, add) + + if sensor is not None: + if maxN is not None and maxN < added_photons: + niter = int(np.ceil(added_photons / maxN)) + draw_image /= niter + else: + niter = 1 + + added_photons = 0 + resume = False + for it in range(niter): + photons = pa.PhotonArray.makeFromImage(draw_image, rng=rng) + + for op in photon_ops: + op.applyTo(photons, local_wcs, rng) + + if imview.dtype in (np.float32, np.float64): + added_photons += sensor.accumulate(photons, imview, orig_center, + resume=resume) + resume = True # Resume from this point if there are any further iterations. + else: + # Need a temporary + im1 = ImageD(bounds=imview.bounds) + added_photons += sensor.accumulate(photons, im1, orig_center) + imview += im1 + + image.added_flux = added_photons / flux_scale + if save_photons: + image.photons = photons + + return image
+ +
[docs] def drawReal(self, image, add_to_image=False): + """ + Draw this profile into an `Image` by direct evaluation at the location of each pixel. + + This is usually called from the `drawImage` function, rather than called directly by the + user. In particular, the input image must be already set up with defined bounds. The + profile will be drawn centered on whatever pixel corresponds to (0,0) with the given + bounds, not the image center (unlike `drawImage`). The image also must have a `PixelScale` + wcs. The profile being drawn should have already been converted to image coordinates via:: + + >>> image_profile = original_wcs.toImage(original_profile) + + Note that the image produced by ``drawReal`` represents the profile sampled at the center + of each pixel and then multiplied by the pixel area. That is, the profile is NOT + integrated over the area of the pixel. This is equivalent to method='no_pixel' in + `drawImage`. If you want to render a profile integrated over the pixel, you can convolve + with a `Pixel` first and draw that. + + Parameters: + image: The `Image` onto which to place the flux. [required] + add_to_image: Whether to add flux to the existing image rather than clear out + anything in the image before drawing. [default: False] + + Returns: + The total flux drawn inside the image bounds. + """ + if image.wcs is None or not image.wcs._isPixelScale: + raise GalSimValueError("drawReal requires an image with a PixelScale wcs", image) + + if image.dtype in (np.float64, np.float32) and not add_to_image and image.iscontiguous: + self._drawReal(image) + return image.array.sum(dtype=float) + else: + # Need a temporary + if image.dtype in (np.complex128, np.int32, np.uint32): + im1 = ImageD(bounds=image.bounds, scale=image.scale) + else: + im1 = ImageF(bounds=image.bounds, scale=image.scale) + self._drawReal(im1) + if add_to_image: + image += im1 + else: + image._copyFrom(im1) + return im1.array.sum(dtype=float)
+ +
[docs] def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + """A version of `drawReal` without the sanity checks or some options. + + This is nearly equivalent to the regular ``drawReal(image, add_to_image=False)``, but + the image's dtype must be either float32 or float64, and it must have a c_contiguous array + (``image.iscontiguous`` must be True). + """ + raise NotImplementedError("%s does not implement drawReal"%self.__class__.__name__)
+ +
[docs] def getGoodImageSize(self, pixel_scale): + """Return a good size to use for drawing this profile. + + The size will be large enough to cover most of the flux of the object. Specifically, + at least (1-gsparams.folding_threshold) (i.e. 99.5% by default) of the flux should fall + in the image. + + Also, the returned size is always an even number, which is usually desired in practice. + Of course, if you prefer an odd-sized image, you can add 1 to the result. + + Parameters: + pixel_scale: The desired pixel scale of the image to be built. + + Returns: + N, a good (linear) size of an image on which to draw this object. + """ + # Start with a good size from stepk and the pixel scale + Nd = 2. * math.pi / (pixel_scale * self.stepk) + + # Make it an integer + # (Some slop to keep from getting extra pixels due to roundoff errors in calculations.) + N = int(math.ceil(Nd*(1.-1.e-12))) + + # Round up to an even value + N = 2 * ((N+1) // 2) + return N
+ +
[docs] def drawFFT_makeKImage(self, image): + """ + This is a helper routine for drawFFT that just makes the (blank) k-space image + onto which the profile will be drawn. This can be useful if you want to break + up the calculation into parts for extra efficiency. E.g. save the k-space image of + the PSF so drawing many models of the galaxy with the given PSF profile can avoid + drawing the PSF each time. + + Parameters: + image: The `Image` onto which to place the flux. + + Returns: + (kimage, wrap_size), where wrap_size is either the size of kimage or smaller if + the result should be wrapped before doing the inverse fft. + """ + # Start with what this profile thinks a good size would be given the image's pixel scale. + N = self.getGoodImageSize(image.scale) + + # We must make something big enough to cover the target image size: + image_N = max(np.max(np.abs((image.bounds._getinitargs()))) * 2, + np.max(image.bounds.numpyShape())) + N = max(N, image_N) + + # Round up to a good size for making FFTs: + N = image.good_fft_size(N) + + # Make sure we hit the minimum size specified in the gsparams. + N = max(N, self.gsparams.minimum_fft_size) + + dk = 2.*np.pi / (N * image.scale) + + maxk = self.maxk + if N*dk/2 > maxk: + Nk = N + else: + # There will be aliasing. Make a larger image and then wrap it. + Nk = int(np.ceil(maxk/dk)) * 2 + + if Nk > self.gsparams.maximum_fft_size: + raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk) + + bounds = _BoundsI(0,Nk//2,-Nk//2,Nk//2) + if image.dtype in (np.complex128, np.float64, np.int32, np.uint32): + kimage = ImageCD(bounds=bounds, scale=dk) + else: + kimage = ImageCF(bounds=bounds, scale=dk) + return kimage, N
+ +
[docs] def drawFFT_finish(self, image, kimage, wrap_size, add_to_image): + """ + This is a helper routine for drawFFT that finishes the calculation, based on the + drawn k-space image. + + It applies the Fourier transform to ``kimage`` and adds the result to ``image``. + + Parameters: + image: The `Image` onto which to place the flux. + kimage: The k-space `Image` where the object was drawn. + wrap_size: The size of the region to wrap kimage, which must be either the same + size as kimage or smaller. + add_to_image: Whether to add flux to the existing image rather than clear out + anything in the image before drawing. + + Returns: + The total flux drawn inside the image bounds. + """ + # Wrap the full image to the size we want for the FT. + # Even if N == Nk, this is useful to make this portion properly Hermitian in the + # N/2 column and N/2 row. + bwrap = _BoundsI(0, wrap_size//2, -wrap_size//2, wrap_size//2-1) + kimage_wrap = kimage._wrap(bwrap, True, False) + + # Perform the fourier transform. + breal = _BoundsI(-wrap_size//2, wrap_size//2+1, -wrap_size//2, wrap_size//2-1) + real_image = Image(breal, dtype=float) + with convert_cpp_errors(): + _galsim.irfft(kimage_wrap._image, real_image._image, True, True) + + # Add (a portion of) this to the original image. + temp = real_image.subImage(image.bounds) + if add_to_image: + image += temp + else: + image._copyFrom(temp) + added_photons = temp.array.sum(dtype=float) + return added_photons
+ +
[docs] def drawFFT(self, image, add_to_image=False): + """ + Draw this profile into an `Image` by computing the k-space image and performing an FFT. + + This is usually called from the `drawImage` function, rather than called directly by the + user. In particular, the input image must be already set up with defined bounds. The + profile will be drawn centered on whatever pixel corresponds to (0,0) with the given + bounds, not the image center (unlike `drawImage`). The image also must have a `PixelScale` + wcs. The profile being drawn should have already been converted to image coordinates via:: + + >>> image_profile = original_wcs.toImage(original_profile) + + Note that the `Image` produced by drawFFT represents the profile sampled at the center + of each pixel and then multiplied by the pixel area. That is, the profile is NOT + integrated over the area of the pixel. This is equivalent to method='no_pixel' in + `drawImage`. If you want to render a profile integrated over the pixel, you can convolve + with a `Pixel` first and draw that. + + Parameters: + image: The `Image` onto which to place the flux. [required] + add_to_image: Whether to add flux to the existing image rather than clear out + anything in the image before drawing. [default: False] + + Returns: + The total flux drawn inside the image bounds. + """ + if image.wcs is None or not image.wcs._isPixelScale: + raise GalSimValueError("drawPhot requires an image with a PixelScale wcs", image) + + kimage, wrap_size = self.drawFFT_makeKImage(image) + self._drawKImage(kimage) + return self.drawFFT_finish(image, kimage, wrap_size, add_to_image)
+ +
[docs] def _calculate_nphotons(self, n_photons, poisson_flux, max_extra_noise, rng): + """Calculate how many photons to shoot and what flux_ratio (called g) each one should + have in order to produce an image with the right S/N and total flux. + + This routine is normally called by `drawPhot`. + + Returns: + n_photons, g + """ + # For profiles that are positive definite, then N = flux. Easy. + # + # However, some profiles shoot some of their photons with negative flux. This means that + # we need a few more photons to get the right S/N = sqrt(flux). Take eta to be the + # fraction of shot photons that have negative flux. + # + # S^2 = (N+ - N-)^2 = (N+ + N- - 2N-)^2 = (Ntot - 2N-)^2 = Ntot^2(1 - 2 eta)^2 + # N^2 = Var(S) = (N+ + N-) = Ntot + # + # So flux = (S/N)^2 = Ntot (1-2eta)^2 + # Ntot = flux / (1-2eta)^2 + # + # However, if each photon has a flux of 1, then S = (1-2eta) Ntot = flux / (1-2eta). + # So in fact, each photon needs to carry a flux of g = 1-2eta to get the right + # total flux. + # + # That's all the easy case. The trickier case is when we are sky-background dominated. + # Then we can usually get away with fewer shot photons than the above. In particular, + # if the noise from the photon shooting is much less than the sky noise, then we can + # use fewer shot photons and essentially have each photon have a flux > 1. This is ok + # as long as the additional noise due to this approximation is "much less than" the + # noise we'll be adding to the image for the sky noise. + # + # Let's still have Ntot photons, but now each with a flux of g. And let's look at the + # noise we get in the brightest pixel that has a nominal total flux of Imax. + # + # The number of photons hitting this pixel will be Imax/flux * Ntot. + # The variance of this number is the same thing (Poisson counting). + # So the noise in that pixel is: + # + # N^2 = Imax/flux * Ntot * g^2 + # + # And the signal in that pixel will be: + # + # S = Imax/flux * (N+ - N-) * g which has to equal Imax, so + # g = flux / Ntot(1-2eta) + # N^2 = Imax/Ntot * flux / (1-2eta)^2 + # + # As expected, we see that lowering Ntot will increase the noise in that (and every + # other) pixel. + # The input max_extra_noise parameter is the maximum value of spurious noise we want + # to allow. + # + # So setting N^2 = Imax + nu, we get + # + # Ntot = flux / (1-2eta)^2 / (1 + nu/Imax) + # g = (1 - 2eta) * (1 + nu/Imax) + # + # Returns the total flux placed inside the image bounds by photon shooting. + # + + flux = self.flux + if flux == 0.0: + return 0, 1.0 + + # The _flux_per_photon property is (1-2eta) + # This factor will already be accounted for by the shoot function, so don't include + # that as part of our scaling here. There may be other adjustments though, so g=1 here. + eta_factor = self._flux_per_photon + mod_flux = flux / (eta_factor * eta_factor) + g = 1. + + # If requested, let the target flux value vary as a Poisson deviate + if poisson_flux: + # If we have both positive and negative photons, then the mix of these + # already gives us some variation in the flux value from the variance + # of how many are positive and how many are negative. + # The number of negative photons varies as a binomial distribution. + # <F-> = eta * Ntot * g + # <F+> = (1-eta) * Ntot * g + # <F+ - F-> = (1-2eta) * Ntot * g = flux + # Var(F-) = eta * (1-eta) * Ntot * g^2 + # F+ = Ntot * g - F- is not an independent variable, so + # Var(F+ - F-) = Var(Ntot*g - 2*F-) + # = 4 * Var(F-) + # = 4 * eta * (1-eta) * Ntot * g^2 + # = 4 * eta * (1-eta) * flux + # We want the variance to be equal to flux, so we need an extra: + # delta Var = (1 - 4*eta + 4*eta^2) * flux + # = (1-2eta)^2 * flux + absflux = abs(flux) + mean = eta_factor*eta_factor * absflux + pd = PoissonDeviate(rng, mean) + pd_val = pd() - mean + absflux + ratio = pd_val / absflux + g *= ratio + mod_flux *= ratio + + if n_photons == 0.: + n_photons = abs(mod_flux) + if max_extra_noise > 0.: + gfactor = 1. + max_extra_noise / abs(self.max_sb) + n_photons /= gfactor + g *= gfactor + + # Make n_photons an integer. + iN = int(n_photons + 0.5) + + return iN, g
+ + +
[docs] def makePhot(self, n_photons=0, rng=None, max_extra_noise=0., poisson_flux=None, + photon_ops=(), local_wcs=None, surface_ops=None): + """ + Make photons for a profile. + + This is equivalent to drawPhot, except that the photons are not placed onto + an image. Instead, it just returns the PhotonArray. + + .. note:: + + The (x,y) positions returned are in the same units as the distance units + of the GSObject being rendered. If you want (x,y) in pixel coordinates, you + should call this function for the profile in image coordinates:: + + >>> photons = image.wcs.toImage(obj).makePhot() + + Or if you just want a simple pixel scale conversion from sky coordinates to image + coordinates, you can instead do + + >>> photons = obj.makePhot() + >>> photons.scaleXY(1./pixel_scale) + + Parameters: + n_photons: If provided, the number of photons to use for photon shooting. + If not provided (i.e. ``n_photons = 0``), use as many photons as + necessary to result in an image with the correct Poisson shot + noise for the object's flux. For positive definite profiles, this + is equivalent to ``n_photons = flux``. However, some profiles need + more than this because some of the shot photons are negative + (usually due to interpolants). [default: 0] + rng: If provided, a random number generator to use for photon shooting, + which may be any kind of `BaseDeviate` object. If ``rng`` is None, one + will be automatically created, using the time as a seed. + [default: None] + max_extra_noise: If provided, the allowed extra noise in each pixel when photon + shooting. This is only relevant if ``n_photons=0``, so the number of + photons is being automatically calculated. In that case, if the image + noise is dominated by the sky background, then you can get away with + using fewer shot photons than the full ``n_photons = flux``. + Essentially each shot photon can have a ``flux > 1``, which increases + the noise in each pixel. The ``max_extra_noise`` parameter specifies + how much extra noise per pixel is allowed because of this approximation. + A typical value for this might be ``max_extra_noise = sky_level / 100`` + where ``sky_level`` is the flux per pixel due to the sky. Note that + this uses a "variance" definition of noise, not a "sigma" definition. + [default: 0.] + poisson_flux: Whether to allow total object flux scaling to vary according to + Poisson statistics for ``n_photons`` samples when photon shooting. + [default: True, unless ``n_photons`` is given, in which case the default + is False] + photon_ops: A list of operators that can modify the photon array that will be + applied in order before accumulating the photons on the sensor. + [default: ()] + local_wcs: The local wcs in the original image. [default: None] + + Returns: + - a `PhotonArray` with the data about the photons. + """ + if surface_ops is not None: + from .deprecated import depr + depr('surface_ops', 2.3, 'photon_ops') + photon_ops = surface_ops + + # Make sure the type of n_photons is correct and has a valid value: + if not n_photons >= 0.: + raise GalSimRangeError("Invalid n_photons < 0.", n_photons, 0., None) + + if poisson_flux is None: + # If n_photons is given, poisson_flux = False + poisson_flux = (n_photons == 0.) + + # Check that either n_photons is set to something or flux is set to something + if n_photons == 0. and self.flux == 1.: + galsim_warn( + "Warning: drawImage for object with flux == 1, area == 1, and " + "exptime == 1, but n_photons == 0. This will only shoot a single photon.") + + Ntot, g = self._calculate_nphotons(n_photons, poisson_flux, max_extra_noise, rng) + + try: + photons = self.shoot(Ntot, rng) + except (GalSimError, NotImplementedError) as e: + raise GalSimNotImplementedError( + "Unable to draw this GSObject with photon shooting. Perhaps it " + "is a Deconvolve or is a compound including one or more " + "Deconvolve objects.\nOriginal error: %r"%(e)) + + if g != 1.: + photons.scaleFlux(g) + + for op in photon_ops: + op.applyTo(photons, local_wcs, rng) + + return photons
+ + +
[docs] def drawPhot(self, image, gain=1., add_to_image=False, + n_photons=0, rng=None, max_extra_noise=0., poisson_flux=None, + sensor=None, photon_ops=(), maxN=None, orig_center=_PositionI(0,0), + local_wcs=None, surface_ops=None): + """ + Draw this profile into an `Image` by shooting photons. + + This is usually called from the `drawImage` function, rather than called directly by the + user. In particular, the input image must be already set up with defined bounds. The + profile will be drawn centered on whatever pixel corresponds to (0,0) with the given + bounds, not the image center (unlike `drawImage`). The image also must have a `PixelScale` + wcs. The profile being drawn should have already been converted to image coordinates via:: + + >>> image_profile = original_wcs.toImage(original_profile) + + Note that the image produced by `drawPhot` represents the profile integrated over the + area of each pixel. This is equivalent to convolving the profile by a square `Pixel` + profile and sampling the value at the center of each pixel, although this happens + automatically by the shooting algorithm, so you do not need to manually convolve by + a `Pixel` as you would for `drawReal` or `drawFFT`. + + Parameters: + image: The `Image` onto which to place the flux. [required] + gain: The number of photons per ADU ("analog to digital units", the units of + the numbers output from a CCD). [default: 1.] + add_to_image: Whether to add to the existing images rather than clear out + anything in the image before drawing. [default: False] + n_photons: If provided, the number of photons to use for photon shooting. + If not provided (i.e. ``n_photons = 0``), use as many photons as + necessary to result in an image with the correct Poisson shot + noise for the object's flux. For positive definite profiles, this + is equivalent to ``n_photons = flux``. However, some profiles need + more than this because some of the shot photons are negative + (usually due to interpolants). [default: 0] + rng: If provided, a random number generator to use for photon shooting, + which may be any kind of `BaseDeviate` object. If ``rng`` is None, one + will be automatically created, using the time as a seed. + [default: None] + max_extra_noise: If provided, the allowed extra noise in each pixel when photon + shooting. This is only relevant if ``n_photons=0``, so the number of + photons is being automatically calculated. In that case, if the image + noise is dominated by the sky background, then you can get away with + using fewer shot photons than the full ``n_photons = flux``. + Essentially each shot photon can have a ``flux > 1``, which increases + the noise in each pixel. The ``max_extra_noise`` parameter specifies + how much extra noise per pixel is allowed because of this approximation. + A typical value for this might be ``max_extra_noise = sky_level / 100`` + where ``sky_level`` is the flux per pixel due to the sky. Note that + this uses a "variance" definition of noise, not a "sigma" definition. + [default: 0.] + poisson_flux: Whether to allow total object flux scaling to vary according to + Poisson statistics for ``n_photons`` samples when photon shooting. + [default: True, unless ``n_photons`` is given, in which case the default + is False] + sensor: An optional `Sensor` instance, which will be used to accumulate the + photons onto the image. [default: None] + photon_ops: A list of operators that can modify the photon array that will be + applied in order before accumulating the photons on the sensor. + [default: ()] + maxN: Sets the maximum number of photons that will be added to the image + at a time. (Memory requirements are proportional to this number.) + [default: None, which means no limit] + orig_center: The position of the image center in the original image coordinates. + [default: (0,0)] + local_wcs: The local wcs in the original image. [default: None] + + Returns: + (added_flux, photons) where: + - added_flux is the total flux of photons that landed inside the image bounds, and + - photons is the `PhotonArray` that was applied to the image. + """ + if surface_ops is not None: + from .deprecated import depr + depr('surface_ops', 2.3, 'photon_ops') + photon_ops = surface_ops + + # Make sure the type of n_photons is correct and has a valid value: + if not n_photons >= 0.: + raise GalSimRangeError("Invalid n_photons < 0.", n_photons, 0., None) + + if poisson_flux is None: + # If n_photons is given, poisson_flux = False + poisson_flux = (n_photons == 0.) + + # Check that either n_photons is set to something or flux is set to something + if n_photons == 0. and self.flux == 1.: + galsim_warn( + "Warning: drawImage for object with flux == 1, area == 1, and " + "exptime == 1, but n_photons == 0. This will only shoot a single photon.") + + # Make sure the image is set up to have unit pixel scale and centered at 0,0. + if image.wcs is None or not image.wcs._isPixelScale: + raise GalSimValueError("drawPhot requires an image with a PixelScale wcs", image) + + if sensor is None: + sensor = Sensor() + elif not isinstance(sensor, Sensor): + raise TypeError("The sensor provided is not a Sensor instance") + + Ntot, g = self._calculate_nphotons(n_photons, poisson_flux, max_extra_noise, rng) + + if gain != 1.: + g /= gain + + # total flux falling inside image bounds, this will be returned on exit. + added_flux = 0. + + if maxN is None: + maxN = Ntot + + if not add_to_image: image.setZero() + + # Nleft is the number of photons remaining to shoot. + Nleft = Ntot + photons = None # Just in case Nleft is already 0. + resume = False + while Nleft > 0: + # Shoot at most maxN at a time + thisN = min(maxN, Nleft) + + try: + photons = self.shoot(thisN, rng) + except (GalSimError, NotImplementedError) as e: + raise GalSimNotImplementedError( + "Unable to draw this GSObject with photon shooting. Perhaps it " + "is a Deconvolve or is a compound including one or more " + "Deconvolve objects.\nOriginal error: %r"%(e)) + + if g != 1. or thisN != Ntot: + photons.scaleFlux(g * thisN / Ntot) + + if image.scale != 1.: + photons.scaleXY(1./image.scale) # Convert x,y to image coords if necessary + + for op in photon_ops: + op.applyTo(photons, local_wcs, rng) + + if image.dtype in (np.float32, np.float64): + added_flux += sensor.accumulate(photons, image, orig_center, resume=resume) + resume = True # Resume from this point if there are any further iterations. + else: + # Need a temporary + im1 = ImageD(bounds=image.bounds) + added_flux += sensor.accumulate(photons, im1, orig_center) + image += im1 + + Nleft -= thisN + + return added_flux, photons
+ + +
[docs] def shoot(self, n_photons, rng=None): + """Shoot photons into a `PhotonArray`. + + Parameters: + n_photons: The number of photons to use for photon shooting. + rng: If provided, a random number generator to use for photon shooting, + which may be any kind of `BaseDeviate` object. If ``rng`` is None, one + will be automatically created, using the time as a seed. + [default: None] + + Returns: + A `PhotonArray`. + """ + photons = pa.PhotonArray(n_photons) + if n_photons == 0: + # It's ok to shoot 0, but downstream can have problems with it, so just stop now. + return photons + if rng is None: + rng = BaseDeviate() + + self._shoot(photons, rng) + return photons
+ +
[docs] def _shoot(self, photons, rng): + """Shoot photons into the given `PhotonArray`. + + This is the backend implementation of `shoot` once the `PhotonArray` has been constructed. + + Parameters: + photons: A `PhotonArray` instance into which the photons should be placed. + rng: A `BaseDeviate` instance to use for the photon shooting, + """ + raise NotImplementedError("%s does not implement shoot"%self.__class__.__name__)
+ +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Apply this surface brightness profile as a convolution to an existing photon array. + + This method allows a GSObject to duck type as a PhotonOp, so one can apply a PSF + in a photon_ops list. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use to effect the convolution. + [default: None] + """ + p1 = pa.PhotonArray(len(photon_array)) + if photon_array.hasAllocatedWavelengths(): + p1._wave = photon_array._wave + if photon_array.hasAllocatedPupil(): + p1._pupil_u = photon_array._pupil_u + p1._pupil_v = photon_array._pupil_v + if photon_array.hasAllocatedTimes(): + p1._time = photon_array._time + obj = local_wcs.toImage(self) if local_wcs is not None else self + obj._shoot(p1, rng) + photon_array.convolve(p1, rng)
+ +
[docs] def drawKImage(self, image=None, nx=None, ny=None, bounds=None, scale=None, + add_to_image=False, recenter=True, bandpass=None, setup_only=False): + """Draws the k-space (complex) `Image` of the object, with bounds optionally set by input + `Image` instance. + + Normalization is always such that image(0,0) = flux. Unlike the real-space `drawImage` + function, the (0,0) point will always be one of the actual pixel values. For even-sized + images, it will be 1/2 pixel above and to the right of the true center of the image. + + Another difference from `drawImage` is that a wcs other than a simple pixel scale is not + allowed. There is no ``wcs`` parameter here, and if the images have a non-trivial wcs (and + you don't override it with the ``scale`` parameter), a TypeError will be raised. + + Also, there is no convolution by a pixel. This is just a direct image of the Fourier + transform of the surface brightness profile. + + Parameters: + image: If provided, this will be the `Image` onto which to draw the k-space + image. If ``image`` is None, then an automatically-sized image will be + created. If ``image`` is given, but its bounds are undefined, then it + will be resized appropriately based on the profile's size. + [default: None] + nx: If provided and ``image`` is None, use to set the x-direction size of + the image. Must be accompanied by ``ny``. + ny: If provided and ``image`` is None, use to set the y-direction size of + the image. Must be accompanied by ``nx``. + bounds: If provided and ``image`` is None, use to set the bounds of the image. + scale: If provided, use this as the pixel scale, dk, for the images. + If ``scale`` is None and ``image`` is given, then take the provided + images' pixel scale (which must be equal). + If ``scale`` is None and ``image`` is None, then use the Nyquist scale. + If ``scale <= 0`` (regardless of ``image``), then use the Nyquist scale. + [default: None] + add_to_image: Whether to add to the existing images rather than clear out + anything in the image before drawing. + Note: This requires that ``image`` be provided and that it has defined + bounds. [default: False] + recenter: Whether to recenter the image to put k = 0 at the center (True) or to + trust the provided bounds (False). [default: True] + bandpass: This parameter is ignored, but is allowed to enable duck typing + eqivalence between this method and the ChromaticObject.drawImage + method. [default: None] + setup_only: Don't actually draw anything on the image. Just make sure the image + is set up correctly. This is used internally by GalSim, but there + may be cases where the user will want the same functionality. + [default: False] + + Returns: + an `Image` instance (created if necessary) + """ + # Make sure provided image is complex + if image is not None: + if not isinstance(image, Image): + raise TypeError("Provided image must be galsim.Image", image) + + if not image.iscomplex: + raise GalSimValueError("Provided image must be complex", image) + + # Possibly get the scale from image. + if image is not None and scale is None: + # Grab the scale to use from the image. + # This will raise a TypeError if image.wcs is not a PixelScale + scale = image.scale + + # The input scale (via scale or image.scale) is really a dk value, so call it that for + # clarity here, since we also need the real-space pixel scale, which we will call dx. + if scale is None or not scale > 0: + dk = self.stepk + else: + dk = float(scale) + if image is not None and image.bounds.isDefined(): + dx = np.pi/( max(image.array.shape) // 2 * dk ) + elif scale is None or not scale > 0: + dx = self.nyquist_scale + else: + # Then dk = scale, which implies that we need to have dx smaller than nyquist_scale + # by a factor of (dk/stepk) + dx = self.nyquist_scale * dk / self.stepk + + # If the profile needs to be constructed from scratch, the _setup_image function will + # do that, but only if the profile is in image coordinates for the real space image. + # So make that profile. + if image is None or not image.bounds.isDefined(): + real_prof = PixelScale(dx).profileToImage(self) + dtype = np.complex128 if image is None else image.dtype + image = real_prof._setup_image(image, nx, ny, bounds, add_to_image, dtype, + center=None, odd=True) + else: + # Do some checks that setup_image would have done for us + if bounds is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide bounds if image is provided", bounds=bounds, image=image) + if nx is not None or ny is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide nx,ny if image is provided", nx=nx, ny=ny, image=image) + + # Can't both recenter a provided image and add to it. + if recenter and image.center != _PositionI(0,0) and add_to_image: + raise GalSimIncompatibleValuesError( + "Cannot use add_to_image=True unless image is centered at (0,0) or recenter=False", + recenter=recenter, image=image, add_to_image=add_to_image) + + # Set the center to 0,0 if appropriate + if recenter and image.center != _PositionI(0,0): + image._shift(-image.center) + + # Set the wcs of the images to use the dk scale size + image.scale = dk + + if setup_only: + return image + + if not add_to_image and image.iscontiguous: + self._drawKImage(image) + else: + im2 = Image(bounds=image.bounds, dtype=image.dtype, scale=image.scale) + self._drawKImage(im2) + image += im2 + return image
+ +
[docs] def _drawKImage(self, image, jac=None): # pragma: no cover (all our classes override this) + """A version of `drawKImage` without the sanity checks or some options. + + Equivalent to ``drawKImage(image, add_to_image=False, recenter=False, add_to_image=False)``, + but without the option to create the image automatically. + + The input image must be provided as a complex `Image` instance (dtype=complex64 or + complex128), and the bounds should be set up appropriately (e.g. with 0,0 in the center if + so desired). This corresponds to recenter=False for the normal `drawKImage`. And, it must + have a c_contiguous array (image.iscontiguous must be True). + + Parameters: + image: The `Image` onto which to draw the k-space image. [required] + """ + raise NotImplementedError("%s does not implement drawKImage"%self.__class__.__name__)
+ + # Derived classes should define the __eq__ function + def __ne__(self, other): return not self.__eq__(other)
+ +# Put these at the bottom to avoid circular import errors. +from . import convolve +from . import sum as _sum +from . import transform +from . import box +from . import photon_array as pa +from . import sed +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/gsparams.html b/docs/_build/html/_modules/galsim/gsparams.html new file mode 100644 index 00000000000..4d1a31f749d --- /dev/null +++ b/docs/_build/html/_modules/galsim/gsparams.html @@ -0,0 +1,383 @@ + + + + + + galsim.gsparams — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.gsparams

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'GSParams' ]
+
+import copy
+
+from . import _galsim
+
+
[docs]class GSParams: + """GSParams stores a set of numbers that govern how a `GSObject` makes various speed/accuracy + tradeoff decisions. + + All `GSObject` classes can take an optional parameter named ``gsparams``, which would be an + instance of this class. e.g.:: + + >>> gsp = galsim.GSParams(folding_threshold=1.e-3) + >>> gal = galsim.Sersic(n=3.4, half_light_radius=3.2, flux=200, gsparams=gsp) + + One can also update the parameters for an existing object using the method + `GSObject.withGSParams`. e.g.:: + + >>> gal = gal.withGSParams(kvalue_accuracy=1.e-8) + + All parameters have reasonable default values. You only need to specify the ones you want + to change. + + Parameters: + minimum_fft_size: The minimum size of any FFT that may need to be performed. + [default: 128] + maximum_fft_size: The maximum allowed size of an image for performing an FFT. This + is more about memory use than accuracy. We have this maximum + value to help prevent the user from accidentally trying to perform + an extremely large FFT that crashes the program. Instead, GalSim + will raise an exception indicating that the image is too large, + which is often a sign of an error in the user's code. However, if + you have the memory to handle it, you can raise this limit to + allow the calculation to happen. [default: 8192] + folding_threshold: This sets a maximum amount of real space folding that is allowed, + an effect caused by the periodic nature of FFTs. FFTs implicitly + use periodic boundary conditions, and a profile specified on a + finite grid in Fourier space corresponds to a real space image + that will have some overlap with the neighboring copies of the real + space profile. As the step size in k increases, the spacing + between neighboring aliases in real space decreases, increasing the + amount of folded, overlapping flux. ``folding_threshold`` is used + to set an appropriate step size in k to allow at most this + fraction of the flux to be folded. + This parameter is also relevant when you let GalSim decide how + large an image to use for your object. The image is made to be + large enough that at most a fraction ``folding_threshold`` of the + total flux is allowed to fall off the edge of the image. + [default: 5.e-3] + stepk_minimum_hlr: In addition to the above constraint for aliasing, also set stepk + such that pi/stepk is at least ``stepk_minimum_hlr`` times the + profile's half-light radius (for profiles that have a well-defined + half-light radius). [default: 5] + maxk_threshold: This sets the maximum amplitude of the high frequency modes in + Fourier space that are excluded by truncating the FFT at some + maximum k value. Lowering this parameter can help minimize the + effect of "ringing" if you see that in your images. + [default: 1.e-3] + kvalue_accuracy: This sets the accuracy of values in Fourier space. Whenever there + is some kind of approximation to be made in the calculation of a + Fourier space value, the error in the approximation is constrained + to be no more than this value times the total flux. + [default: 1.e-5] + xvalue_accuracy: This sets the accuracy of values in real space. Whenever there is + some kind of approximation to be made in the calculation of a + real space value, the error in the approximation is constrained + to be no more than this value times the total flux. + [default: 1.e-5] + table_spacing: Several profiles use lookup tables for either the Hankel transform + (`Sersic`, `Moffat`) or the real space radial function (`Kolmogorov`). + We try to estimate a good spacing between values in the lookup + tables based on either ``xvalue_accuracy`` or ``kvalue_accuracy`` as + appropriate. However, you may change the spacing with this + parameter. Using ``table_spacing < 1`` will use a spacing value that + is that much smaller than the default, which should produce more + accurate interpolations. [default: 1] + realspace_relerr: This sets the relative error tolerance for real-space integration. + [default: 1.e-4] + realspace_abserr: This sets the absolute error tolerance for real-space integration. + [default: 1.e-6] + The estimated integration error for the flux value in each pixel + when using the real-space rendering method (either explicitly with + ``method='real_space'`` or if it is triggered automatically with + ``method='auto'``) is constrained to be no larger than either + ``realspace_relerr`` times the pixel flux or ``realspace_abserr`` + times the object's total flux. + integration_relerr: The relative error tolerance for integrations other than real-space + rendering. [default: 1.e-6] + integration_abserr: The absolute error tolerance for integrations other than real-space + rendering. [default: 1.e-8] + shoot_accuracy: This sets the relative accuracy on the total flux when photon + shooting. The photon shooting algorithm at times needs to make + approximations, such as how high in radius it needs to sample the + radial profile. When such approximations need to be made, it makes + sure that the resulting fractional error in the flux will be at + most this much. [default: 1.e-5] + + After construction, all of the above parameters are available as read-only attributes. + """ + def __init__(self, minimum_fft_size=128, maximum_fft_size=8192, + folding_threshold=5.e-3, stepk_minimum_hlr=5, maxk_threshold=1.e-3, + kvalue_accuracy=1.e-5, xvalue_accuracy=1.e-5, table_spacing=1, + realspace_relerr=1.e-4, realspace_abserr=1.e-6, + integration_relerr=1.e-6, integration_abserr=1.e-8, + shoot_accuracy=1.e-5, allowed_flux_variation=0.81, + range_division_for_extrema=32, small_fraction_of_flux=1.e-4): + self._minimum_fft_size = int(minimum_fft_size) + self._maximum_fft_size = int(maximum_fft_size) + self._folding_threshold = float(folding_threshold) + self._stepk_minimum_hlr = float(stepk_minimum_hlr) + self._maxk_threshold = float(maxk_threshold) + self._kvalue_accuracy = float(kvalue_accuracy) + self._xvalue_accuracy = float(xvalue_accuracy) + self._table_spacing = int(table_spacing) + self._realspace_relerr = float(realspace_relerr) + self._realspace_abserr = float(realspace_abserr) + self._integration_relerr = float(integration_relerr) + self._integration_abserr = float(integration_abserr) + self._shoot_accuracy = float(shoot_accuracy) + + if allowed_flux_variation != 0.81: + from .deprecated import depr + depr('allowed_flux_variation', 2.1, "", "This parameter is no longer used.") + if range_division_for_extrema != 32: + from .deprecated import depr + depr('range_division_for_extrema', 2.1, "", "This parameter is no longer used.") + if small_fraction_of_flux != 1.e-4: + from .deprecated import depr + depr('small_fraction_of_flux', 2.1, "", "This parameter is no longer used.") + + self._gsp = _galsim.GSParams(*self._getinitargs()) + + # Make all the attributes read-only + @property + def minimum_fft_size(self): return self._minimum_fft_size + @property + def maximum_fft_size(self): return self._maximum_fft_size + @property + def folding_threshold(self): return self._folding_threshold + @property + def stepk_minimum_hlr(self): return self._stepk_minimum_hlr + @property + def maxk_threshold(self): return self._maxk_threshold + @property + def kvalue_accuracy(self): return self._kvalue_accuracy + @property + def xvalue_accuracy(self): return self._xvalue_accuracy + @property + def table_spacing(self): return self._table_spacing + @property + def realspace_relerr(self): return self._realspace_relerr + @property + def realspace_abserr(self): return self._realspace_abserr + @property + def integration_relerr(self): return self._integration_relerr + @property + def integration_abserr(self): return self._integration_abserr + @property + def shoot_accuracy(self): return self._shoot_accuracy + +
[docs] @staticmethod + def check(gsparams, default=None, **kwargs): + """Checks that gsparams is either a valid GSParams instance or None. + + In the former case, it returns gsparams, in the latter it returns default + (GSParams.default if no other default specified). + """ + if gsparams is None: + gsparams = default if default is not None else GSParams.default + elif not isinstance(gsparams, GSParams): + raise TypeError("Invalid GSParams: %s"%gsparams) + return gsparams.withParams(**kwargs)
+ +
[docs] def withParams(self, **kwargs): + """Return a `GSParams` that is identical to the current one except for any keyword + arguments given here, which supersede the current value. + """ + if len(kwargs) == 0: + return self + else: + ret = copy.copy(self) + for k in kwargs: + if not hasattr(ret, '_' + k): + raise TypeError('parameter %s is invalid'%k) + setattr(ret, '_' + k, kwargs[k]) + ret._gsp = _galsim.GSParams(*ret._getinitargs()) + return ret
+ +
[docs] @staticmethod + def combine(gsp_list): + """Combine a list of `GSParams` instances using the most restrictive parameter from each. + + Uses the minimum value for most parameters. For the following parameters, it uses the + maximum numerical value: minimum_fft_size, maximum_fft_size, stepk_minimum_hlr. + """ + if len(gsp_list) == 1: + return gsp_list[0] + elif all(g == gsp_list[0] for g in gsp_list[1:]): + return gsp_list[0] + else: + return GSParams( + max([g.minimum_fft_size for g in gsp_list if g is not None]), + max([g.maximum_fft_size for g in gsp_list if g is not None]), + min([g.folding_threshold for g in gsp_list if g is not None]), + max([g.stepk_minimum_hlr for g in gsp_list if g is not None]), + min([g.maxk_threshold for g in gsp_list if g is not None]), + min([g.kvalue_accuracy for g in gsp_list if g is not None]), + min([g.xvalue_accuracy for g in gsp_list if g is not None]), + min([g.table_spacing for g in gsp_list if g is not None]), + min([g.realspace_relerr for g in gsp_list if g is not None]), + min([g.realspace_abserr for g in gsp_list if g is not None]), + min([g.integration_relerr for g in gsp_list if g is not None]), + min([g.integration_abserr for g in gsp_list if g is not None]), + min([g.shoot_accuracy for g in gsp_list if g is not None]))
+ + # Define once the order of args in __init__, since we use it a few times. + def _getinitargs(self): + return (int(self.minimum_fft_size), int(self.maximum_fft_size), + self.folding_threshold, self.stepk_minimum_hlr, self.maxk_threshold, + self.kvalue_accuracy, self.xvalue_accuracy, self.table_spacing, + self.realspace_relerr, self.realspace_abserr, + self.integration_relerr, self.integration_abserr, + self.shoot_accuracy) + + def __getstate__(self): return self._getinitargs() + def __setstate__(self, state): self.__init__(*state) + + def __repr__(self): + return 'galsim.GSParams(%d,%d,%r,%r,%r,%r,%r,%d,%r,%r,%r,%r,%r)'% \ + self._getinitargs() + + def __eq__(self, other): + return (self is other or + (isinstance(other, GSParams) and self._getinitargs() == other._getinitargs())) + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(repr(self))
+ +# We use the default a lot, so make it a class attribute. +GSParams.default = GSParams() +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/hsm.html b/docs/_build/html/_modules/galsim/hsm.html new file mode 100644 index 00000000000..0ecefe9a9c5 --- /dev/null +++ b/docs/_build/html/_modules/galsim/hsm.html @@ -0,0 +1,968 @@ + + + + + + galsim.hsm — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.hsm

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+
+from . import _galsim
+from .position import PositionD
+from .bounds import BoundsI
+from .shear import Shear
+from .image import Image, ImageI, ImageF, ImageD
+from .errors import GalSimValueError, GalSimHSMError, GalSimIncompatibleValuesError
+
+
[docs]class ShapeData: + """A class to contain the outputs of the HSM shape and moments measurement routines. + + The ShapeData class contains the following information about moment measurement (from either + `EstimateShear` or `FindAdaptiveMom`: + + - ``image_bounds``: a `BoundsI` object describing the image. + + - ``moments_status``: the status flag resulting from moments measurement; -1 indicates no + attempt to measure, 0 indicates success. + + - ``observed_shape``: a `Shear` object representing the observed shape based on adaptive + moments. + + - ``observed_e1``, ``observed_e2``: floats representing the e1 and e2 components respectively of the + ``observed_shape``. + + - ``moments_sigma``: size ``sigma=(det M)^(1/4)`` from the adaptive moments, in units of pixels; + -1 if not measured. (If `FindAdaptiveMom` is called with ``use_sky_coords=True``, then + the units will be arcsec.) + + - ``moments_amp``: total image intensity for best-fit elliptical Gaussian from adaptive moments. + Normally, this field is simply equal to the image flux (for objects that follow a Gaussian + light distribution, otherwise it is something approximating the flux). However, if the image + was drawn using ``drawImage(method='sb')`` then moments_amp relates to the flux via + ``flux=(moments_amp)*(pixel scale)^2``. + + - ``moments_centroid``: a `PositionD` object representing the centroid based on adaptive + moments, in units of pixels. The indexing convention is defined with respect to the `BoundsI` + object defining the bounds of the input `Image`, i.e., the center of the lower left pixel is + ``(image.xmin, image.ymin)``. An object drawn at the center of the image should generally + have moments_centroid equal to ``image.true_center``. (If `FindAdaptiveMom` is called with + ``use_sky_coords=True``, then the units will be arcsec, measured in sky coordinates with + respect to the image center.) + + - ``moments_rho4``: the weighted radial fourth moment of the image (dimensionless). + + - ``moments_n_iter``: number of iterations needed to get adaptive moments, or 0 if not measured. + + If `EstimateShear` was used, then the following fields related to PSF-corrected shape will also + be populated: + + - ``correction_status``: the status flag resulting from PSF correction; -1 indicates no attempt + to measure, 0 indicates success. + + - ``corrected_e1``, ``corrected_e2``, ``corrected_g1``, ``corrected_g2``: floats representing + the estimated shear after removing the effects of the PSF. Either e1/e2 or g1/g2 will differ + from the default values of -10, with the choice of shape to use determined by the correction + method (since the correction method determines what quantity is estimated, either the shear or + the distortion). After a measurement is made, the type of shape measurement is stored in the + ShapeData structure as 'meas_type' (a string that equals either 'e' or 'g'). + + - ``corrected_shape_err``: shape measurement uncertainty sigma_gamma per component. The + estimate of the uncertainty will only be non-zero if an estimate of the sky variance was + passed to `EstimateShear`. + + - ``correction_method``: a string indicating the method of PSF correction (will be "None" if + PSF-correction was not carried out). + + - ``resolution_factor``: Resolution factor R_2; 0 indicates object is consistent with a PSF, 1 + indicates perfect resolution. + + - ``psf_sigma``: size ``sigma=(det M)^(1/4)`` of the PSF from the adaptive moments, in units of + pixels; -1 if not measured. + + - ``psf_shape``: a `Shear` object representing the observed PSF shape based on adaptive moments. + + - ``error_message``: a string containing any error messages from the attempt to carry out + PSF-correction. + + The `ShapeData` object can be initialized completely empty, or can be returned from the + routines that measure object moments (`FindAdaptiveMom`) and carry out PSF correction + (`EstimateShear`). + """ + def __init__(self, image_bounds=BoundsI(), moments_status=-1, + observed_shape=Shear(), moments_sigma=-1.0, moments_amp=-1.0, + moments_centroid=PositionD(), moments_rho4=-1.0, moments_n_iter=0, + correction_status=-1, corrected_e1=-10., corrected_e2=-10., + corrected_g1=-10., corrected_g2=-10., meas_type="None", + corrected_shape_err=-1.0, correction_method="None", + resolution_factor=-1.0, psf_sigma=-1.0, + psf_shape=Shear(), error_message=""): + + # Avoid empty string, which can caus problems in C++ layer. + if error_message == "": error_message = "None" + + if not isinstance(image_bounds, BoundsI): + raise TypeError("image_bounds must be a BoundsI instance") + + # The others will raise an appropriate TypeError from the call to _galsim.ShapeData + # when converting to int, float, etc. + self._data = _galsim.ShapeData( + image_bounds._b, int(moments_status), observed_shape.e1, observed_shape.e2, + float(moments_sigma), float(moments_amp), moments_centroid._p, + float(moments_rho4), int(moments_n_iter), int(correction_status), + float(corrected_e1), float(corrected_e2), float(corrected_g1), float(corrected_g2), + str(meas_type), float(corrected_shape_err), str(correction_method), + float(resolution_factor), float(psf_sigma), psf_shape.e1, psf_shape.e2, + str(error_message)) + + @property + def image_bounds(self): return BoundsI(self._data.image_bounds) + @property + def moments_status(self): return self._data.moments_status + + @property + def observed_e1(self): + return self._data.observed_e1 + + @property + def observed_e2(self): + return self._data.observed_e2 + + @property + def observed_shape(self): + return Shear(e1=self.observed_e1, e2=self.observed_e2) + + @property + def moments_sigma(self): return self._data.moments_sigma + @property + def moments_amp(self): return self._data.moments_amp + @property + def moments_centroid(self): return PositionD(self._data.moments_centroid) + @property + def moments_rho4(self): return self._data.moments_rho4 + @property + def moments_n_iter(self): return self._data.moments_n_iter + @property + def correction_status(self): return self._data.correction_status + @property + def corrected_e1(self): return self._data.corrected_e1 + @property + def corrected_e2(self): return self._data.corrected_e2 + @property + def corrected_g1(self): return self._data.corrected_g1 + @property + def corrected_g2(self): return self._data.corrected_g2 + @property + def meas_type(self): return self._data.meas_type + @property + def corrected_shape_err(self): return self._data.corrected_shape_err + @property + def correction_method(self): return self._data.correction_method + @property + def resolution_factor(self): return self._data.resolution_factor + @property + def psf_sigma(self): return self._data.psf_sigma + + @property + def psf_shape(self): + return Shear(e1=self._data.psf_e1, e2=self._data.psf_e2) + + @property + def error_message(self): + # We use "None" in C++ ShapeData to indicate no error messages to avoid problems on + # (some) Macs using zero-length strings. Here, we revert that back to "". + if self._data.error_message == "None": + return "" + else: + return self._data.error_message + + def __repr__(self): + s = 'galsim.hsm.ShapeData(' + if self.image_bounds.isDefined(): s += 'image_bounds=%r, '%self.image_bounds + if self.moments_status != -1: s += 'moments_status=%r, '%self.moments_status + # Always include this one: + s += 'observed_shape=%r'%self.observed_shape + if self.moments_sigma != -1: s += ', moments_sigma=%r'%self.moments_sigma + if self.moments_amp != -1: s += ', moments_amp=%r'%self.moments_amp + if self.moments_centroid != PositionD(): + s += ', moments_centroid=%r'%self.moments_centroid + if self.moments_rho4 != -1: s += ', moments_rho4=%r'%self.moments_rho4 + if self.moments_n_iter != 0: s += ', moments_n_iter=%r'%self.moments_n_iter + if self.correction_status != -1: s += ', correction_status=%r'%self.correction_status + if self.corrected_e1 != -10.: s += ', corrected_e1=%r'%self.corrected_e1 + if self.corrected_e2 != -10.: s += ', corrected_e2=%r'%self.corrected_e2 + if self.corrected_g1 != -10.: s += ', corrected_g1=%r'%self.corrected_g1 + if self.corrected_g2 != -10.: s += ', corrected_g2=%r'%self.corrected_g2 + if self.meas_type != 'None': s += ', meas_type=%r'%self.meas_type + if self.corrected_shape_err != -1.: + s += ', corrected_shape_err=%r'%self.corrected_shape_err + if self.correction_method != 'None': s += ', correction_method=%r'%self.correction_method + if self.resolution_factor != -1.: s += ', resolution_factor=%r'%self.resolution_factor + if self.psf_sigma != -1.: s += ', psf_sigma=%r'%self.psf_sigma + if self.psf_shape != Shear(): s += ', psf_shape=%r'%self.psf_shape + if self.error_message != "": s += ', error_message=%r'%self.error_message + s += ')' + return s + + def __eq__(self, other): + return (self is other or + (isinstance(other,ShapeData) and self._getinitargs() == other._getinitargs())) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(("galsim.hsm.ShapeData", self._getinitargs())) + + def _getinitargs(self): + return (self.image_bounds, self.moments_status, self.observed_shape, + self.moments_sigma, self.moments_amp, self.moments_centroid, self.moments_rho4, + self.moments_n_iter, self.correction_status, self.corrected_e1, self.corrected_e2, + self.corrected_g1, self.corrected_g2, self.meas_type, self.corrected_shape_err, + self.correction_method, self.resolution_factor, self.psf_sigma, + self.psf_shape, self.error_message) + + def __getstate__(self): + return self._getinitargs() + + def __setstate__(self, state): + self.__init__(*state) + +
[docs] def applyWCS(self, wcs, image_pos): + """Convert moments in pixel coordinates to moments in sky coordinates. + + Natively, the HSM algorithm computes second moments in (x,y) coordinates in the + sensor coordinate system. However, many applications of second moments for shape + measurements need the measurements in sky coordinates. This method converts the + moments to (u,v) coordinates, where +v is towards North and +u is towards West. + + The values that are different from the original are: + + * ``moments_sigma`` is changed to be in units of arcsec. + * ``observed_shape`` is changed to be in (u,v) coordinates. + * ``moments_centroid`` is changed to be in (u,v) coordinates relative to image_pos. + + .. note:: + + This currently only works for the measurements from `FindAdaptiveMom`. + If the input ShapeData instance has any values set from `EstimateShear`, they will + not be present in the return value. We would welcome a PR adding the ability to + work on corrected shapes if someone wants it for their science case. + + Parameters: + wcs: The WCS to apply. + image_pos: The position in image coordinates (x,y) of the object whose moments + have been measured. + """ + jac = wcs.jacobian(image_pos=image_pos) + scale, shear, theta, flip = jac.getDecomposition() + + # Fix moments_sigma + moments_sigma = self.moments_sigma * scale + + # Fix observed_shape + shape = self.observed_shape + # First the flip, if any. + if flip: + shape = Shear(g1 = -shape.g1, g2 = shape.g2) + # Next the rotation + shape = Shear(g = shape.g, beta = shape.beta + theta) + # Finally the shear + observed_shape = shear + shape + + # Fix moments_centroid + moments_centroid = jac.toWorld(self.moments_centroid) - jac.toWorld(image_pos) + + return ShapeData(image_bounds=self.image_bounds, + moments_status=self.moments_status, + observed_shape=observed_shape, + moments_sigma=moments_sigma, + moments_amp=self.moments_amp, + moments_centroid=moments_centroid, + moments_rho4=self.moments_rho4, + moments_n_iter=self.moments_n_iter, + error_message=self.error_message)
+ # The other values are reset to the defaults, since they are + # results from EstimateShear. + + +
[docs]class HSMParams: + """A collection of parameters that govern how the HSM functions operate. + + HSMParams stores a set of numbers that determine how the moments/shape estimation + routines make speed/accuracy tradeoff decisions and/or store their results. + + Parameters: + nsig_rg: A parameter used to optimize convolutions by cutting off the galaxy + profile. In the first step of the re-Gaussianization method of PSF + correction, a Gaussian approximation to the pre-seeing galaxy is + calculated. If ``nsig_rg > 0``, then this approximation is cut off + at ``nsig_rg`` sigma to save computation time in convolutions. + [default: 3.0] + + nsig_rg2: A parameter used to optimize convolutions by cutting off the PSF + residual profile. In the re-Gaussianization method of PSF + correction, a 'PSF residual' (the difference between the true PSF + and its best-fit Gaussian approximation) is constructed. If + ``nsig_rg2 > 0``, then this PSF residual is cut off at ``nsig_rg2`` + sigma to save computation time in convolutions. [default: 3.6] + + max_moment_nsig2: No longer used. (Now calculated based on convergence_threshold.) + + regauss_too_small: A parameter for how strictly the re-Gaussianization code treats + small galaxies. If this parameter is 1, then the re-Gaussianization + code does not impose a cut on the apparent resolution before trying + to measure the PSF-corrected shape of the galaxy; if 0, then it is + stricter. Using the default value of 1 prevents the + re-Gaussianization PSF correction from completely failing at the + beginning, before trying to do PSF correction, due to the crudest + possible PSF correction (Gaussian approximation) suggesting that + the galaxy is very small. This could happen for some usable + galaxies particularly when they have very non-Gaussian surface + brightness profiles -- for example, if there's a prominent bulge + that the adaptive moments attempt to fit, ignoring a more + extended disk. Setting a value of 1 is useful for keeping galaxies + that would have failed for that reason. If they later turn out to + be too small to really use, this will be reflected in the final + estimate of the resolution factor, and they can be rejected after + the fact. [default: 1] + + adapt_order: The order to which circular adaptive moments should be calculated + for KSB method. This parameter only affects calculations using the + KSB method of PSF correction. Warning: deviating from default + value of 2 results in code running more slowly, and results have + not been significantly tested. [default: 2] + + convergence_threshold: Accuracy (in x0, y0, and sigma, each as a fraction of sigma) + when calculating adaptive moments. [default: 1.e-6] + + max_mom2_iter: Maximum number of iterations to use when calculating adaptive + moments. This should be sufficient in nearly all situations, with + the possible exception being very flattened profiles. [default: 400] + + num_iter_default: Number of iterations to report in the output ShapeData structure + when code fails to converge within max_mom2_iter iterations. + [default: -1] + + bound_correct_wt: Maximum shift in centroids and sigma between iterations for + adaptive moments. [default: 0.25] + + max_amoment: Maximum value for adaptive second moments before throwing + exception. Very large objects might require this value to be + increased. [default: 8000] + + max_ashift: Maximum allowed x / y centroid shift (units: pixels) between + successive iterations for adaptive moments before throwing + exception. [default: 15] + + ksb_moments_max: Use moments up to ksb_moments_max order for KSB method of PSF + correction. [default: 4] + + ksb_sig_weight: The width of the weight function (in pixels) to use for the KSB + method. Normally, this is derived from the measured moments of the + galaxy image; this keyword overrides this calculation. Can be + combined with ksb_sig_factor. [default: 0.0] + + ksb_sig_factor: Factor by which to multiply the weight function width for the KSB + method (default: 1.0). Can be combined with ksb_sig_weight. + [default: 1.0] + + failed_moments: Value to report for ellipticities and resolution factor if shape + measurement fails. [default: -1000.] + + .. note:: + + Parameters that are given in units of pixels should still be in pixels, even if one is + calling `FindAdaptiveMom` with the option ``use_sky_coords=True``. + + After construction, all of the above are available as read-only attributes. + """ + def __init__(self, nsig_rg=3.0, nsig_rg2=3.6, max_moment_nsig2=0, regauss_too_small=1, + adapt_order=2, convergence_threshold=1.e-6, max_mom2_iter=400, + num_iter_default=-1, bound_correct_wt=0.25, max_amoment=8000., max_ashift=15., + ksb_moments_max=4, ksb_sig_weight=0.0, ksb_sig_factor=1.0, failed_moments=-1000.): + + if max_moment_nsig2 != 0: + from .deprecated import depr + depr('max_moment_nsig2', 2.4, '', 'This parameter is no longer used.') + + self._nsig_rg = float(nsig_rg) + self._nsig_rg2 = float(nsig_rg2) + self._regauss_too_small = int(regauss_too_small) + self._adapt_order = int(adapt_order) + self._convergence_threshold = float(convergence_threshold) + self._max_mom2_iter = int(max_mom2_iter) + self._num_iter_default = int(num_iter_default) + self._bound_correct_wt = float(bound_correct_wt) + self._max_amoment = float(max_amoment) + self._max_ashift = float(max_ashift) + self._ksb_moments_max = int(ksb_moments_max) + self._ksb_sig_weight = float(ksb_sig_weight) + self._ksb_sig_factor = float(ksb_sig_factor) + self._failed_moments = float(failed_moments) + self._make_hsmp() + + def _make_hsmp(self): + self._hsmp = _galsim.HSMParams(*self._getinitargs()) + + def _getinitargs(self): + # TODO: For now, leave 3rd param as unused max_moment_nsig2. + # Remove it at version 3.0 to avoid changing C++ API yet. + return (self.nsig_rg, self.nsig_rg2, 0., self.regauss_too_small, + self.adapt_order, self.convergence_threshold, self.max_mom2_iter, + self.num_iter_default, self.bound_correct_wt, self.max_amoment, self.max_ashift, + self.ksb_moments_max, self.ksb_sig_weight, self.ksb_sig_factor, + self.failed_moments) + + @property + def nsig_rg(self): return self._nsig_rg + @property + def nsig_rg2(self): return self._nsig_rg2 + @property + def max_moment_nsig2(self): return 0. + @property + def regauss_too_small(self): return self._regauss_too_small + @property + def adapt_order(self): return self._adapt_order + @property + def convergence_threshold(self): return self._convergence_threshold + @property + def max_mom2_iter(self): return self._max_mom2_iter + @property + def num_iter_default(self): return self._num_iter_default + @property + def bound_correct_wt(self): return self._bound_correct_wt + @property + def max_amoment(self): return self._max_amoment + @property + def max_ashift(self): return self._max_ashift + @property + def ksb_moments_max(self): return self._ksb_moments_max + @property + def ksb_sig_weight(self): return self._ksb_sig_weight + @property + def ksb_sig_factor(self): return self._ksb_sig_factor + @property + def failed_moments(self): return self._failed_moments + +
[docs] @staticmethod + def check(hsmparams, default=None): + """Checks that hsmparams is either a valid HSMParams instance or None. + + In the former case, it returns hsmparams, in the latter it returns default + (HSMParams.default if no other default specified). + """ + if hsmparams is None: + return default if default is not None else HSMParams.default + elif not isinstance(hsmparams, HSMParams): + raise TypeError("Invalid HSMParams: %s"%hsmparams) + else: + return hsmparams
+ + def __repr__(self): + return ('galsim.hsm.HSMParams(' + 14*'%r,' + '%r)')%self._getinitargs() + + def __eq__(self, other): + return (self is other or + (isinstance(other, HSMParams) and self._getinitargs() == other._getinitargs())) + def __ne__(self, other): + return not self.__eq__(other) + def __hash__(self): + return hash(('galsim.hsm.HSMParams', self._getinitargs())) + + def __getstate__(self): + d = self.__dict__.copy() + del d['_hsmp'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self._make_hsmp()
+ +# We use the default a lot, so make it a class attribute. +HSMParams.default = HSMParams() + + +# A helper function that checks if the weight and the badpix bounds are +# consistent with that of the image, and that the weight is non-negative. +def _checkWeightAndBadpix(image, weight=None, badpix=None): + # Check that the weight and badpix, if given, are sensible and compatible + # with the image. + if weight is not None: + if weight.bounds != image.bounds: + raise GalSimIncompatibleValuesError( + "Weight image does not have same bounds as the input Image.", + weight=weight, image=image) + # also make sure there are no negative values + + if np.any(weight.array < 0): + raise GalSimValueError("Weight image cannot contain negative values.", weight) + + if badpix is not None and badpix.bounds != image.bounds: + raise GalSimIncompatibleValuesError( + "Badpix image does not have the same bounds as the input Image.", + badpix=badpix, image=image) + + +# A helper function for taking input weight and badpix Images, and returning a weight Image in the +# format that the C++ functions want +def _convertMask(image, weight=None, badpix=None): + # Convert from input weight and badpix images to a single mask image needed by C++ functions. + # This is used by EstimateShear() and FindAdaptiveMom(). + + # if no weight image was supplied, make an int array (same size as gal image) filled with 1's + if weight is None: + mask = ImageI(bounds=image.bounds, init_value=1) + else: + # if weight is an ImageI, then we can use it as the mask image: + if weight.dtype == np.int32: + if not badpix: + mask = weight + else: + # If we need to mask bad pixels, we'll need a copy anyway. + mask = ImageI(weight) + + # otherwise, we need to convert it to the right type + else: + mask = ImageI(bounds=image.bounds, init_value=0) + mask.array[weight.array > 0.] = 1 + + # if badpix image was supplied, identify the nonzero (bad) pixels and set them to zero in weight + # image; also check bounds + if badpix is not None: + mask.array[badpix.array != 0] = 0 + + # if no pixels are used, raise an exception + if not np.any(mask.array): + raise GalSimHSMError("No pixels are being used!") + + # finally, return the Image for the weight map + return mask + + +# A simpler helper function to force images to be of type ImageF or ImageD +def _convertImage(image): + # Convert the given image to the correct format needed to pass to the C++ layer. + # This is used by EstimateShear() and FindAdaptiveMom(). + + # if weight is not of type float/double, convert to float/double + if (image.dtype == np.int16 or image.dtype == np.uint16): + image = ImageF(image) + elif (image.dtype == np.int32 or image.dtype == np.uint32): + image = ImageD(image) + + return image + + +
[docs]def EstimateShear(gal_image, PSF_image, weight=None, badpix=None, sky_var=0.0, + shear_est="REGAUSS", recompute_flux="FIT", guess_sig_gal=5.0, + guess_sig_PSF=3.0, precision=1.0e-6, guess_centroid=None, + strict=True, check=True, hsmparams=None): + """Carry out moments-based PSF correction routines. + + Carry out PSF correction using one of the methods of the HSM package (see references in + docstring for file hsm.py) to estimate galaxy shears, correcting for the convolution by the + PSF. + + This method works from `Image` inputs rather than `GSObject` inputs, which provides + additional flexibility (e.g., it is possible to work from an `Image` that was read from file and + corresponds to no particular `GSObject`), but this also means that users who wish to apply it to + compound `GSObject` classes (e.g., `Convolve`) must take the additional step of drawing + their `GSObject` into `Image` instances. + + This routine assumes that (at least locally) the WCS can be approximated as a `PixelScale`, with + no distortion or non-trivial remapping. Any non-trivial WCS gets completely ignored. + + .. note:: + + There is not currently an option to ``use_sky_coords`` as we have for `FindAdaptiveMom`. + We would welcome a PR adding this feature if someone wants it for their science case. + + Note that the method will fail if the PSF or galaxy are too point-like to easily fit an + elliptical Gaussian; when running on batches of many galaxies, it may be preferable to set + ``strict=False`` and catch errors explicitly, as in the second example below. + + This function has a number of keyword parameters, many of which a typical user will not need to + change from the default. + + Example: + + Typical application to a single object:: + + >>> galaxy = galsim.Gaussian(flux=1.0, sigma=1.0) + >>> galaxy = galaxy.shear(g1=0.05, g2=0.0) # shears the Gaussian by (0.05, 0) using the + >>> # |g| = (a-b)/(a+b) definition + >>> psf = galsim.Kolmogorov(flux=1.0, fwhm=0.7) + >>> final = galsim.Convolve(galaxy, psf) + >>> final_image = final.drawImage(scale=0.2) + >>> final_epsf_image = psf.drawImage(scale=0.2) + >>> result = galsim.hsm.EstimateShear(final_image, final_epsf_image) + + After running the above code, ``result.observed_shape`` is a `Shear` object with a value of + ``(0.0438925349133, -2.85747392701e-18)`` and ``result.corrected_e1``, ``result_corrected_e2`` + are ``(0.09934103488922119, -3.746108423463568e-10)``, compared with the expected ``(0.09975, + 0)`` for a perfect PSF correction method. + + The code below gives an example of how one could run this routine on a large batch of galaxies, + explicitly catching and tracking any failures:: + + >>> n_image = 100 + >>> n_fail = 0 + >>> for i=0, range(n_image): + >>> #...some code defining this_image, this_final_epsf_image... + >>> result = galsim.hsm.EstimateShear(this_image, this_final_epsf_image, strict=False) + >>> if result.error_message != "": + >>> n_fail += 1 + >>> print "Number of failures: ", n_fail + + Parameters: + gal_image: The `Image` of the galaxy being measured. + PSF_image: The `Image` for the PSF. + weight: The optional weight image for the galaxy being measured. Can be an int + or a float array. Currently, GalSim does not account for the variation + in non-zero weights, i.e., a weight map is converted to an image with 0 + and 1 for pixels that are not and are used. Full use of spatial + variation in non-zero weights will be included in a future version of + the code. + + badpix: The optional bad pixel mask for the image being used. Zero should be + used for pixels that are good, and any nonzero value indicates a bad + pixel. + + sky_var: The variance of the sky level, used for estimating uncertainty on the + measured shape. [default: 0.] + + shear_est: A string indicating the desired method of PSF correction: 'REGAUSS', + 'LINEAR', 'BJ', or 'KSB'. The first three options return an e-type + distortion, whereas the last option returns a g-type shear. [default: + 'REGAUSS'] + + recompute_flux: A string indicating whether to recompute the object flux, which + should be 'NONE' (for no recomputation), 'SUM' (for recomputation via + an unweighted sum over unmasked pixels), or 'FIT' (for + recomputation using the Gaussian + quartic fit). [default: 'FIT'] + + guess_sig_gal: Optional argument with an initial guess for the Gaussian sigma of the + galaxy (in pixels). [default: 5.] + + guess_sig_PSF: Optional argument with an initial guess for the Gaussian sigma of the + PSF (in pixels). [default: 3.] + + precision: The convergence criterion for the moments. [default: 1e-6] + + guess_centroid: An initial guess for the object centroid (useful in + case it is not located at the center, which is used if this keyword is + not set). The convention for centroids is such that the center of + the lower-left pixel is (image.xmin, image.ymin). + [default: gal_image.true_center] + + strict: Whether to require success. If ``strict=True``, then there will be a + ``GalSimHSMError`` exception if shear estimation fails. If set to + ``False``, then information about failures will be silently stored in + the output ShapeData object. [default: True] + + check: Check if the object_image, weight and badpix are in the correct format and valid. + [default: True] + + hsmparams: The hsmparams keyword can be used to change the settings used by + `EstimateShear` when estimating shears; see `HSMParams` documentation + for more information. [default: None] + + Returns: + a `ShapeData` object containing the results of shape measurement. + """ + # prepare inputs to C++ routines: ImageF or ImageD for galaxy, PSF, and ImageI for weight map + gal_image = _convertImage(gal_image) + PSF_image = _convertImage(PSF_image) + hsmparams = HSMParams.check(hsmparams) + if check: + _checkWeightAndBadpix(gal_image, weight=weight, badpix=badpix) + weight = _convertMask(gal_image, weight=weight, badpix=badpix) + + if guess_centroid is None: + guess_centroid = gal_image.true_center + try: + result = ShapeData() + _galsim.EstimateShearView(result._data, + gal_image._image, PSF_image._image, weight._image, + float(sky_var), shear_est.upper(), recompute_flux.upper(), + float(guess_sig_gal), float(guess_sig_PSF), float(precision), + guess_centroid._p, hsmparams._hsmp) + return result + except RuntimeError as err: + if (strict == True): + raise GalSimHSMError(str(err)) from None + else: + return ShapeData(error_message = str(err))
+ +
[docs]def FindAdaptiveMom(object_image, weight=None, badpix=None, guess_sig=5.0, precision=1.0e-6, + guess_centroid=None, strict=True, check=True, round_moments=False, hsmparams=None, + use_sky_coords=False): + """Measure adaptive moments of an object. + + This method estimates the best-fit elliptical Gaussian to the object (see Hirata & Seljak 2003 + for more discussion of adaptive moments). This elliptical Gaussian is computed iteratively + by initially guessing a circular Gaussian that is used as a weight function, computing the + weighted moments, recomputing the moments using the result of the previous step as the weight + function, and so on until the moments that are measured are the same as those used for the + weight function. `FindAdaptiveMom` can be used either as a free function, or as a method of the + `Image` class. + + By default, this routine computes moments in pixel coordinates, which generally use (x,y) + for the coordinate variables, so the underlying second moments are Ixx, Iyy, and Ixy. + If the WCS is (at least approximately) just a `PixelScale`, then this scale can be applied to + convert the moments' units from pixels to arcsec. The derived shapes are unaffected by + the pixel scale. + + However, there is also an option to apply a non-trivial WCS, which may potentially rotate + and/or shear the (x,y) moments to the local sky coordinates, which generally use (u,v) + for the coordinate variables. These coordinates are measured in arcsec and are oriented + such that +v is towards North and +u is towards West. In this case, the returned values are + all in arcsec, and are based instead on Iuu, Ivv, and Iuv. To enable this feature, use + ``use_sky_coords=True``. See also the method `ShapeData.applyWCS` for more details. + + .. note:: + + The application of the WCS implicitly assumes that the WCS is locally uniform across the + size of the object being measured. This is normally a very good approximation for most + applications of interest. + + Like `EstimateShear`, `FindAdaptiveMom` works on `Image` inputs, and fails if the object is + small compared to the pixel scale. For more details, see `EstimateShear`. + + Example:: + + >>> my_gaussian = galsim.Gaussian(flux=1.0, sigma=1.0) + >>> my_gaussian_image = my_gaussian.drawImage(scale=0.2, method='no_pixel') + >>> my_moments = galsim.hsm.FindAdaptiveMom(my_gaussian_image) + + or:: + + >>> my_moments = my_gaussian_image.FindAdaptiveMom() + + Assuming a successful measurement, the most relevant pieces of information are + ``my_moments.moments_sigma``, which is ``|det(M)|^(1/4)`` (= ``sigma`` for a circular Gaussian) + and ``my_moments.observed_shape``, which is a `Shear`. In this case, + ``my_moments.moments_sigma`` is precisely 5.0 (in units of pixels), and + ``my_moments.observed_shape`` is consistent with zero. + + Methods of the `Shear` class can be used to get the distortion ``e``, the shear ``g``, the + conformal shear ``eta``, and so on. + + As an example of how to use the optional ``hsmparams`` argument, consider cases where the input + images have unusual properties, such as being very large. This could occur when measuring the + properties of a very over-sampled image such as that generated using:: + + >>> my_gaussian = galsim.Gaussian(sigma=5.0) + >>> my_gaussian_image = my_gaussian.drawImage(scale=0.01, method='no_pixel') + + If the user attempts to measure the moments of this very large image using the standard syntax, + :: + + >>> my_moments = my_gaussian_image.FindAdaptiveMom() + + then the result will be a ``GalSimHSMError`` due to moment measurement failing because the + object is so large. While the list of all possible settings that can be changed is accessible + in the docstring of the `HSMParams` class, in this case we need to modify ``max_amoment`` which + is the maximum value of the moments in units of pixel^2. The following measurement, using the + default values for every parameter except for ``max_amoment``, will be + successful:: + + >>> new_params = galsim.hsm.HSMParams(max_amoment=5.0e5) + >>> my_moments = my_gaussian_image.FindAdaptiveMom(hsmparams=new_params) + + Parameters: + object_image: The `Image` for the object being measured. + weight: The optional weight image for the object being measured. Can be an int + or a float array. Currently, GalSim does not account for the variation + in non-zero weights, i.e., a weight map is converted to an image with 0 + and 1 for pixels that are not and are used. Full use of spatial + variation in non-zero weights will be included in a future version of + the code. [default: None] + badpix: The optional bad pixel mask for the image being used. Zero should be + used for pixels that are good, and any nonzero value indicates a bad + pixel. [default: None] + guess_sig: Optional argument with an initial guess for the Gaussian sigma of the + object (in pixels). [default: 5.0] + precision: The convergence criterion for the moments. [default: 1e-6] + guess_centroid: An initial guess for the object centroid (useful in case it is not + located at the center, which is used if this keyword is not set). The + convention for centroids is such that the center of the lower-left pixel + is (image.xmin, image.ymin). + [default: object_image.true_center] + strict: Whether to require success. If ``strict=True``, then there will be a + ``GalSimHSMError`` exception if shear estimation fails. If set to + ``False``, then information about failures will be silently stored in + the output ShapeData object. [default: True] + check: Check if the object_image, weight and badpix are in the correct format and valid. + [default: True] + round_moments: Use a circular weight function instead of elliptical. + [default: False] + hsmparams: The hsmparams keyword can be used to change the settings used by + FindAdaptiveMom when estimating moments; see `HSMParams` documentation + for more information. [default: None] + use_sky_coords: Whether to convert the measured moments to sky_coordinates. + Setting this to true is equivalent to running + ``applyWCS(object_image.wcs, image_pos=object_image.true_center)`` + on the result. [default: False] + + Returns: + a `ShapeData` object containing the results of moment measurement. + """ + # prepare inputs to C++ routines: ImageF or ImageD for galaxy, PSF, and ImageI for weight map + object_image = _convertImage(object_image) + hsmparams = HSMParams.check(hsmparams) + if check: + _checkWeightAndBadpix(object_image, weight=weight, badpix=badpix) + + weight = _convertMask(object_image, weight=weight, badpix=badpix) + + if guess_centroid is None: + guess_centroid = object_image.true_center + + try: + result = ShapeData() + _galsim.FindAdaptiveMomView(result._data, + object_image._image, weight._image, + float(guess_sig), float(precision), guess_centroid._p, + bool(round_moments), hsmparams._hsmp) + + if use_sky_coords: + result = result.applyWCS(object_image.wcs, image_pos=object_image.true_center) + return result + except RuntimeError as err: + if (strict == True): + raise GalSimHSMError(str(err)) from None + else: + return ShapeData(error_message = str(err))
+ +# make FindAdaptiveMom a method of Image class +Image.FindAdaptiveMom = FindAdaptiveMom +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/image.html b/docs/_build/html/_modules/galsim/image.html new file mode 100644 index 00000000000..6f972dc8ebd --- /dev/null +++ b/docs/_build/html/_modules/galsim/image.html @@ -0,0 +1,2162 @@ + + + + + + galsim.image — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.image

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Image', '_Image',
+            'ImageS', 'ImageI', 'ImageF', 'ImageD',
+            'ImageCF', 'ImageCD', 'ImageUS', 'ImageUI', ]
+
+import numpy as np
+
+from . import _galsim
+from .position import PositionI, _PositionD, parse_pos_args
+from .bounds import BoundsI, BoundsD, _BoundsI
+from ._utilities import lazy_property
+from .errors import GalSimError, GalSimBoundsError, GalSimValueError, GalSimImmutableError
+from .errors import GalSimUndefinedBoundsError, GalSimIncompatibleValuesError, convert_cpp_errors
+
+# Sometimes (on 32-bit systems) there are two numpy.int32 types.  This can lead to some confusion
+# when doing arithmetic with images.  So just make sure both of them point to ImageViewI in the
+# _cpp_type dict.  One of them is what you get when you just write numpy.int32.  The other is
+# what numpy decides an int16 + int32 is.
+# For more information regarding this rather unexpected behaviour for numpy.int32 types, see
+# the following (closed, marked "wontfix") ticket on the numpy issue tracker:
+# http://projects.scipy.org/numpy/ticket/1246
+
+alt_int32 = (np.array([0], dtype=np.int16) + np.array([0], dtype=np.int32)).dtype.type
+
+
+
[docs]class Image: + """A class for storing image data along with the pixel scale or WCS information + + The Image class encapsulates all the relevant information about an image including a NumPy array + for the pixel values, a bounding box, and some kind of WCS that converts between pixel + coordinates and world coordinates. The NumPy array may be constructed by the Image class + itself, or an existing array can be provided by the user. + + This class creates shallow copies unless a deep copy is explicitly requested using the `copy` + method. The main reason for this is that it allows users to work directly with and modify + subimages of larger images (for example, to successively draw many galaxies into one large + image). For other implications of this convention, see the description of initialization + instructions below. + + In most applications with images, we will use (x,y) to refer to the coordinates. We adopt + the same meaning for these coordinates as most astronomy applications do: ds9, SAOImage, + SExtractor, etc. all treat x as the column number and y as the row number. However, this + is different from the default convention used by numpy. In numpy, the access is by + [row_num,col_num], which means this is really [y,x] in terms of the normal x,y values. + Users are typically insulated from this concern by the Image API, but if you access the + numpy array directly via the ``array`` attribute, you will need to be careful about this + difference. + + There are 6 data types that the Image can use for the data values. These are ``numpy.uint16``, + ``numpy.uint32``, ``numpy.int16``, ``numpy.int32``, ``numpy.float32``, and ``numpy.float64``. + If you are constructing a new Image from scratch, the default is ``numpy.float32``, but you + can specify one of the other data types. + + There are several ways to construct an Image: + (Optional arguments are shown with their default values after the = sign.) + + ``Image(ncol, nrow, dtype=numpy.float32, init_value=0, xmin=1, ymin=1, ...)`` + + This constructs a new image, allocating memory for the pixel values according to + the number of columns and rows. You can specify the data type as ``dtype`` if you + want. The default is ``numpy.float32`` if you don't specify it. You can also + optionally provide an initial value for the pixels, which defaults to 0. + The optional ``xmin,ymin`` allow you to specify the location of the lower-left + pixel, which defaults to (1,1). Reminder, with our convention for x,y coordinates + described above, ncol is the number of pixels in the x direction, and nrow is the + number of pixels in the y direction. + + ``Image(bounds, dtype=numpy.float32, init_value=0, ...)`` + + This constructs a new image, allocating memory for the pixel values according to a + given `Bounds` object. Particularly, the bounds should be a `BoundsI` instance. + You can specify the data type as ``dtype`` if you want. The default is + ``numpy.float32`` if you don't specify it. You can also optionally provide an + initial value for the pixels, which defaults to 0. + + ``Image(array, xmin=1, ymin=1, make_const=False, copy=False ...)`` + + This views an existing NumPy array as an Image, where updates to either the image + or the original array will affect the other one. The data type is taken from + ``array.dtype``, which must be one of the allowed types listed above. You can also + optionally set the origin ``xmin, ymin`` if you want it to be something other than + (1,1). + + You can also optionally force the Image to be read-only with ``make_const=True``, + though if the original NumPy array is modified then the contents of ``Image.array`` + will change. + + If you want to make a copy of the input array, rather than just view the existing + array, you can force a copy with:: + + >>> image = galsim.Image(array, copy=True) + + ``Image(image, dtype=image.dtype, copy=True)`` + + This creates a copy of an Image, possibly changing the type. e.g.:: + + >>> image_float = galsim.Image(64, 64) # default dtype=numpy.float32 + >>> image_double = galsim.Image(image_float, dtype=numpy.float64) + + You can see a list of valid values for dtype in ``galsim.Image.valid_dtypes``. + Without the ``dtype`` argument, this is equivalent to ``image.copy()``, which makes + a deep copy. If you want a copy that shares data with the original, see + the `view` method. + + If you only want to enforce the image to have a given type and not make a copy + if the array is already the correct type, you can use, e.g.:: + + >>> image_double = galsim.Image(image, dtype=numpy.float64, copy=False) + + You can specify the ``ncol``, ``nrow``, ``bounds``, ``array``, or ``image`` parameters by + keyword argument if you want, or you can pass them as simple arg as shown aboves, and the + constructor will figure out what they are. + + The other keyword arguments (shown as ... above) relate to the conversion between sky + coordinates, which is how all the GalSim objects are defined, and the pixel coordinates. + There are three options for this: + + scale + You can optionally specify a pixel scale to use. This would normally have + units arcsec/pixel, but it doesn't have to be arcsec. If you want to + use different units for the physical scale of your galsim objects, then + the same unit would be used here. + wcs + A WCS object that provides a non-trivial mapping between sky units and + pixel units. The ``scale`` parameter is equivalent to + ``wcs=PixelScale(scale)``. But there are a number of more complicated options. + See the WCS class for more details. + None + If you do not provide either of the above, then the conversion is undefined. + When drawing onto such an image, a suitable pixel scale will be automatically + set according to the Nyquist scale of the object being drawn. + + After construction, you can set or change the scale or wcs with:: + + >>> image.scale = new_scale + >>> image.wcs = new_wcs + + Note that ``image.scale`` will only work if the WCS is a `PixelScale`. Once you set the + wcs to be something non-trivial, then you must interact with it via the ``wcs`` attribute. + The ``image.scale`` syntax will raise an exception. + + There are also two read-only attributes:: + + >>> image.bounds + >>> image.array + + The ``array`` attribute is a NumPy array of the Image's pixels. The individual elements in the + array attribute are accessed as ``image.array[y,x]``, matching the standard NumPy convention, + while the Image class's own accessor uses either ``(x,y)`` or ``[x,y]``. + + That is, the following are equivalent:: + + >>> ixy = image(x,y) + >>> ixy = image[x,y] + >>> ixy = image.array[y,x] + >>> ixy = image.getValue(x,y) + + Similarly, for setting individual pixel values, the following are equivalent:: + + >>> image[x,y] = new_ixy + >>> image.array[y,x] = new_ixy + >>> image.setValue(x,y,new_ixy) + """ + + _cpp_type = { np.uint16 : _galsim.ImageViewUS, + np.uint32 : _galsim.ImageViewUI, + np.int16 : _galsim.ImageViewS, + np.int32 : _galsim.ImageViewI, + np.float32 : _galsim.ImageViewF, + np.float64 : _galsim.ImageViewD, + np.complex64 : _galsim.ImageViewCF, + np.complex128 : _galsim.ImageViewCD, + } + _cpp_valid_dtypes = list(_cpp_type.keys()) + + _alias_dtypes = { + int : np.int32, # So that user gets what they would expect + float : np.float64, # if using dtype=int or float or complex + complex : np.complex128, + np.int64 : np.int32, # Not equivalent, but will convert + } + # Note: Numpy uses int64 for int on 64 bit machines. We don't implement int64 at all, + # so we cannot quite match up to the numpy convention for dtype=int. e.g. via + # int : numpy.zeros(1,dtype=int).dtype.type + # If this becomes too confusing, we might need to add an ImageL class that uses int64. + # Hard to imagine a use case where this would be required though... + + # This one is in the public API. (No leading underscore.) + valid_dtypes = _cpp_valid_dtypes + list(_alias_dtypes.keys()) + + def __init__(self, *args, **kwargs): + # Parse the args, kwargs + ncol = None + nrow = None + bounds = None + array = None + image = None + if len(args) > 2: + raise TypeError("Error, too many unnamed arguments to Image constructor") + elif len(args) == 2: + ncol = args[0] + nrow = args[1] + xmin = kwargs.pop('xmin',1) + ymin = kwargs.pop('ymin',1) + elif len(args) == 1: + if isinstance(args[0], np.ndarray): + array = args[0] + array, xmin, ymin = self._get_xmin_ymin(array, kwargs) + make_const = kwargs.pop('make_const',False) + elif isinstance(args[0], BoundsI): + bounds = args[0] + elif isinstance(args[0], (list, tuple)): + array = np.array(args[0]) + array, xmin, ymin = self._get_xmin_ymin(array, kwargs) + make_const = kwargs.pop('make_const',False) + elif isinstance(args[0], Image): + image = args[0] + else: + raise TypeError("Unable to parse %s as an array, bounds, or image."%args[0]) + else: + if 'array' in kwargs: + array = kwargs.pop('array') + array, xmin, ymin = self._get_xmin_ymin(array, kwargs) + make_const = kwargs.pop('make_const',False) + elif 'bounds' in kwargs: + bounds = kwargs.pop('bounds') + elif 'image' in kwargs: + image = kwargs.pop('image') + else: + ncol = kwargs.pop('ncol',None) + nrow = kwargs.pop('nrow',None) + xmin = kwargs.pop('xmin',1) + ymin = kwargs.pop('ymin',1) + + # Pop off the other valid kwargs: + dtype = kwargs.pop('dtype', None) + init_value = kwargs.pop('init_value', None) + scale = kwargs.pop('scale', None) + wcs = kwargs.pop('wcs', None) + copy = kwargs.pop('copy', None) + + # Check that we got them all + if kwargs: + raise TypeError("Image constructor got unexpected keyword arguments: %s",kwargs) + + # Figure out what dtype we want: + dtype = Image._alias_dtypes.get(dtype,dtype) + if dtype is not None and dtype not in Image.valid_dtypes: + raise GalSimValueError("Invalid dtype.", dtype, Image.valid_dtypes) + if array is not None: + if copy is None: copy = False + if dtype is None: + dtype = array.dtype.type + if dtype in Image._alias_dtypes: + dtype = Image._alias_dtypes[dtype] + array = array.astype(dtype, copy=copy) + elif dtype not in Image._cpp_valid_dtypes: + raise GalSimValueError("Invalid dtype of provided array.", array.dtype, + Image._cpp_valid_dtypes) + elif copy: + array = np.array(array) + else: + array = array.astype(dtype, copy=copy) + # Be careful here: we have to watch out for little-endian / big-endian issues. + # The path of least resistance is to check whether the array.dtype is equal to the + # native one (using the dtype.isnative flag), and if not, make a new array that has a + # type equal to the same one but with the appropriate endian-ness. + if not array.dtype.isnative: + array = array.astype(array.dtype.newbyteorder('=')) + self._dtype = array.dtype.type + elif dtype is not None: + self._dtype = dtype + else: + self._dtype = np.float32 + + # Construct the image attribute + if (ncol is not None or nrow is not None): + if ncol is None or nrow is None: + raise GalSimIncompatibleValuesError( + "Both nrow and ncol must be provided", ncol=ncol, nrow=nrow) + if ncol != int(ncol) or nrow != int(nrow): + raise TypeError("nrow, ncol must be integers") + ncol = int(ncol) + nrow = int(nrow) + self._array = self._make_empty(shape=(nrow,ncol), dtype=self._dtype) + self._bounds = BoundsI(xmin, xmin+ncol-1, ymin, ymin+nrow-1) + if init_value: + self.fill(init_value) + elif bounds is not None: + if not isinstance(bounds, BoundsI): + raise TypeError("bounds must be a galsim.BoundsI instance") + self._array = self._make_empty(bounds.numpyShape(), dtype=self._dtype) + self._bounds = bounds + if init_value: + self.fill(init_value) + elif array is not None: + self._array = array.view() + nrow,ncol = array.shape + self._bounds = BoundsI(xmin, xmin+ncol-1, ymin, ymin+nrow-1) + if make_const or not array.flags.writeable: + self._array.flags.writeable = False + if init_value is not None: + raise GalSimIncompatibleValuesError( + "Cannot specify init_value with array", init_value=init_value, array=array) + elif image is not None: + if not isinstance(image, Image): + raise TypeError("image must be an Image") + if init_value is not None: + raise GalSimIncompatibleValuesError( + "Cannot specify init_value with image", init_value=init_value, image=image) + if wcs is None and scale is None: + wcs = image.wcs + self._bounds = image.bounds + if dtype is None: + self._dtype = image.dtype + else: + # Allow dtype to force a retyping of the provided image + # e.g. im = ImageF(...) + # im2 = ImageD(im) + self._dtype = dtype + if copy is False: + self._array = image.array.astype(self._dtype, copy=False) + else: + self._array = self._make_empty(shape=image.bounds.numpyShape(), dtype=self._dtype) + self._array[:,:] = image.array[:,:] + else: + self._array = np.zeros(shape=(1,1), dtype=self._dtype) + self._bounds = BoundsI() + if init_value is not None: + raise GalSimIncompatibleValuesError( + "Cannot specify init_value without setting an initial size", + init_value=init_value, ncol=ncol, nrow=nrow, bounds=bounds) + + # Construct the wcs attribute + if scale is not None: + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both scale and wcs to Image constructor", wcs=wcs, scale=scale) + self.wcs = PixelScale(float(scale)) + else: + if wcs is not None and not isinstance(wcs, BaseWCS): + raise TypeError("wcs parameters must be a galsim.BaseWCS instance") + self.wcs = wcs + + @staticmethod + def _get_xmin_ymin(array, kwargs): + """A helper function for parsing xmin, ymin, bounds options with a given array + """ + if not isinstance(array, np.ndarray): + raise TypeError("array must be a numpy.ndarray instance") + xmin = kwargs.pop('xmin',1) + ymin = kwargs.pop('ymin',1) + if 'bounds' in kwargs: + b = kwargs.pop('bounds') + if not isinstance(b, BoundsI): + raise TypeError("bounds must be a galsim.BoundsI instance") + if b.xmax-b.xmin+1 != array.shape[1]: + raise GalSimIncompatibleValuesError( + "Shape of array is inconsistent with provided bounds", array=array, bounds=b) + if b.ymax-b.ymin+1 != array.shape[0]: + raise GalSimIncompatibleValuesError( + "Shape of array is inconsistent with provided bounds", array=array, bounds=b) + if b.isDefined(): + xmin = b.xmin + ymin = b.ymin + else: + # Indication that array is formally undefined, even though provided. + if 'dtype' not in kwargs: + kwargs['dtype'] = array.dtype.type + array = None + xmin = None + ymin = None + elif array.shape[1] == 0: + # Another way to indicate that we don't have a defined image. + if 'dtype' not in kwargs: + kwargs['dtype'] = array.dtype.type + array = None + xmin = None + ymin = None + return array, xmin, ymin + + def __repr__(self): + s = 'galsim.Image(bounds=%r' % self.bounds + if self.bounds.isDefined(): + s += ', array=\n%r' % self.array + s += ', wcs=%r' % self.wcs + if self.isconst: + s += ', make_const=True' + s += ')' + return s + + def __str__(self): + # Get the type name without the <type '...'> part. + t = str(self.dtype).split("'")[1] + if self.wcs is not None and self.wcs._isPixelScale: + return 'galsim.Image(bounds=%s, scale=%s, dtype=%s)'%(self.bounds, self.scale, t) + else: + return 'galsim.Image(bounds=%s, wcs=%s, dtype=%s)'%(self.bounds, self.wcs, t) + + # Pickling almost works out of the box, but numpy arrays lose their non-writeable flag + # when pickled, so make sure to set it to preserve const Images. + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_image',None) + return d, self.isconst + + def __setstate__(self, args): + d, isconst = args + self.__dict__ = d + if isconst: + self._array.flags.writeable = False + + # Read-only attributes: + @property + def dtype(self): + """The dtype of the underlying numpy array. + """ + return self._dtype + @property + def bounds(self): + """The bounds of the `Image`. + """ + return self._bounds + @property + def array(self): + """The underlying numpy array. + """ + return self._array + @property + def nrow(self): + """The number of rows in the image + """ + return self._array.shape[0] + @property + def ncol(self): + """The number of columns in the image + """ + return self._array.shape[1] + + @property + def isconst(self): + """Whether the `Image` is constant. I.e. modifying its values is an error. + """ + return self._array.flags.writeable == False + @property + def iscomplex(self): + """Whether the `Image` values are complex. + """ + return self._array.dtype.kind == 'c' + @property + def isinteger(self): + """Whether the `Image` values are integral. + """ + return self._array.dtype.kind in ('i','u') + + @property + def iscontiguous(self): + """Indicates whether each row of the image is contiguous in memory. + + Note: it is ok for the end of one row to not be contiguous with the start of the + next row. This just checks that each individual row has a stride of 1. + """ + return self._array.strides[1]//self._array.itemsize == 1 + + @lazy_property + def _image(self): + cls = self._cpp_type[self.dtype] + _data = self._array.__array_interface__['data'][0] + return cls(_data, + self._array.strides[1]//self._array.itemsize, + self._array.strides[0]//self._array.itemsize, + self._bounds._b) + + # Allow scale to work as a PixelScale wcs. + @property + def scale(self): + """The pixel scale of the `Image`. Only valid if the wcs is a `PixelScale`. + + If the WCS is either not set (i.e. it is ``None``) or it is a `PixelScale`, then + it is permissible to change the scale with:: + + >>> image.scale = new_pixel_scale + """ + try: + return self.wcs.scale + except Exception: + if self.wcs: + raise GalSimError( + "image.wcs is not a simple PixelScale; scale is undefined.") from None + else: + return None + + @scale.setter + def scale(self, value): + if self.wcs is not None and not self.wcs._isPixelScale: + raise GalSimError("image.wcs is not a simple PixelScale; scale is undefined.") + else: + self.wcs = PixelScale(value) + + # Convenience functions + @property + def xmin(self): + """Alias for self.bounds.xmin.""" + return self._bounds.xmin + @property + def xmax(self): + """Alias for self.bounds.xmax.""" + return self._bounds.xmax + @property + def ymin(self): + """Alias for self.bounds.ymin.""" + return self._bounds.ymin + @property + def ymax(self): + """Alias for self.bounds.ymax.""" + return self._bounds.ymax + + @property + def outer_bounds(self): + """The bounds of the outer edge of the pixels. + + Equivalent to galsim.BoundsD(im.xmin-0.5, im.xmax+0.5, im.ymin-0.5, im.ymax+0.5) + """ + return BoundsD(self.xmin-0.5, self.xmax+0.5, self.ymin-0.5, self.ymax+0.5) + + # real, imag for everything, even real images. + @property + def real(self): + """Return the real part of an image. + + This is a property, not a function. So write ``im.real``, not ``im.real()``. + + This works for real or complex. For real images, it acts the same as `view`. + """ + return _Image(self.array.real, self.bounds, self.wcs) + + @property + def imag(self): + """Return the imaginary part of an image. + + This is a property, not a function. So write ``im.imag``, not ``im.imag()``. + + This works for real or complex. For real images, the returned array is read-only and + all elements are 0. + """ + return _Image(self.array.imag, self.bounds, self.wcs) + + @property + def conjugate(self): + """Return the complex conjugate of an image. + + This works for real or complex. For real images, it acts the same as `view`. + + Note that for complex images, this is not a conjugate view into the original image. + So changing the original image does not change the conjugate (or vice versa). + """ + return _Image(self.array.conjugate(), self.bounds, self.wcs) + +
[docs] def copy(self): + """Make a copy of the `Image` + """ + return _Image(self.array.copy(), self.bounds, self.wcs)
+ +
[docs] def get_pixel_centers(self): + """A convenience function to get the x and y values at the centers of the image pixels. + + Returns: + (x, y), each of which is a numpy array the same shape as ``self.array`` + """ + x,y = np.meshgrid(np.arange(self.array.shape[1], dtype=float), + np.arange(self.array.shape[0], dtype=float)) + x += self.bounds.xmin + y += self.bounds.ymin + return x, y
+ + def _make_empty(self, shape, dtype): + """Helper function to make an empty numpy array of the given shape, making sure that + the array is 16-btye aligned so it is usable by FFTW. + """ + # cf. http://stackoverflow.com/questions/9895787/memory-alignment-for-fast-fft-in-python-using-shared-arrrays + nbytes = shape[0] * shape[1] * np.dtype(dtype).itemsize + if nbytes == 0: + # Make degenerate images have 1 element. Otherwise things get weird. + return np.zeros(shape=(1,1), dtype=self._dtype) + buf = np.zeros(nbytes + 16, dtype=np.uint8) + start_index = -buf.__array_interface__['data'][0] % 16 + a = buf[start_index:start_index + nbytes].view(dtype).reshape(shape) + #assert a.ctypes.data % 16 == 0 + return a + +
[docs] def resize(self, bounds, wcs=None): + """Resize the image to have a new bounds (must be a `BoundsI` instance) + + Note that the resized image will have uninitialized data. If you want to preserve + the existing data values, you should either use `subImage` (if you want a smaller + portion of the current `Image`) or make a new `Image` and copy over the current values + into a portion of the new image (if you are resizing to a larger `Image`). + + Parameters: + bounds: The new bounds to resize to. + wcs: If provided, also update the wcs to the given value. [default: None, + which means keep the existing wcs] + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify an immutable Image", self) + if not isinstance(bounds, BoundsI): + raise TypeError("bounds must be a galsim.BoundsI instance") + self._array = self._make_empty(shape=bounds.numpyShape(), dtype=self.dtype) + self._bounds = bounds + if wcs is not None: + self.wcs = wcs
+ +
[docs] def subImage(self, bounds): + """Return a view of a portion of the full image + + This is equivalent to self[bounds] + """ + if not isinstance(bounds, BoundsI): + raise TypeError("bounds must be a galsim.BoundsI instance") + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Attempt to access subImage of undefined image") + if not self.bounds.includes(bounds): + raise GalSimBoundsError("Attempt to access subImage not (fully) in image", + bounds,self.bounds) + i1 = bounds.ymin - self.ymin + i2 = bounds.ymax - self.ymin + 1 + j1 = bounds.xmin - self.xmin + j2 = bounds.xmax - self.xmin + 1 + subarray = self.array[i1:i2, j1:j2] + # NB. The wcs is still accurate, since the sub-image uses the same (x,y) values + # as the original image did for those pixels. It's only once you recenter or + # reorigin that you need to update the wcs. So that's taken care of in im.shift. + return _Image(subarray, bounds, self.wcs)
+ +
[docs] def setSubImage(self, bounds, rhs): + """Set a portion of the full image to the values in another image + + This is equivalent to self[bounds] = rhs + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + self.subImage(bounds).copyFrom(rhs)
+ +
[docs] def __getitem__(self, *args): + """Return either a subimage or a single pixel value. + + For example,:: + + >>> subimage = im[galsim.BoundsI(3,7,3,7)] + >>> value = im[galsim.PositionI(5,5)] + >>> value = im[5,5] + """ + if len(args) == 1: + if isinstance(args[0], BoundsI): + return self.subImage(*args) + elif isinstance(args[0], PositionI): + return self(*args) + elif isinstance(args[0], tuple): + return self.getValue(*args[0]) + else: + raise TypeError("image[index] only accepts BoundsI or PositionI for the index") + elif len(args) == 2: + return self(*args) + else: + raise TypeError("image[..] requires either 1 or 2 args")
+ +
[docs] def __setitem__(self, *args): + """Set either a subimage or a single pixel to new values. + + For example,:: + + >>> im[galsim.BoundsI(3,7,3,7)] = im2 + >>> im[galsim.PositionI(5,5)] = 17. + >>> im[5,5] = 17. + """ + if len(args) == 2: + if isinstance(args[0], BoundsI): + self.setSubImage(*args) + elif isinstance(args[0], PositionI): + self.setValue(*args) + elif isinstance(args[0], tuple): + self.setValue(*args) + else: + raise TypeError("image[index] only accepts BoundsI or PositionI for the index") + elif len(args) == 3: + return self.setValue(*args) + else: + raise TypeError("image[..] requires either 1 or 2 args")
+ +
[docs] def wrap(self, bounds, hermitian=False): + """Wrap the values in a image onto a given subimage and return the subimage. + + This would typically be used on a k-space image where you initially draw a larger image + than you want for the FFT and then wrap it onto a smaller subset. This will cause + aliasing of course, but this is often preferable to just using the smaller image + without wrapping. + + For complex images of FFTs, one often only stores half the image plane with the + implicit understanding that the function is Hermitian, so im(-x,-y) == im(x,y).conjugate(). + In this case, the wrapping needs to work slightly differently, so you can specify + that your image is implicitly Hermitian with the ``hermitian`` argument. Options are: + + hermitian=False + (default) Normal non-Hermitian image. + + hermitian='x' + Only x>=0 values are stored with x<0 values being implicitly Hermitian. + In this case im.bounds.xmin and bounds.xmin must be 0. + + hermitian='y' + Only y>=0 values are stored with y<0 values being implicitly Hermitian. + In this case im.bounds.ymin and bounds.ymin must be 0. + + Also, in the two Hermitian cases, the direction that is not implicitly Hermitian must be + symmetric in the image's bounds. The wrap bounds must be almost symmetric, but missing + the most negative value. For example,:: + + >>> N = 100 + >>> im_full = galsim.ImageCD(bounds=galsim.BoundsI(0,N/2,-N/2,N/2), scale=dk) + >>> # ... fill with im[i,j] = FT(kx=i*dk, ky=j*dk) + >>> N2 = 64 + >>> im_wrap = im_full.wrap(galsim.BoundsI(0,N/2,-N2/2,N2/2-1, hermitian='x') + + This sets up im_wrap to be the properly Hermitian version of the data appropriate for + passing to an FFT. + + Note that this routine modifies the original image (and not just the subimage onto which + it is wrapped), so if you want to keep the original pristine, you should call + ``wrapped_image = image.copy().wrap(bounds)``. + + Parameters: + bounds: The bounds of the subimage onto which to wrap the full image. + hermitian: Whether the image is implicitly Hermitian and if so, whether it is the + x or y values that are not stored. [default: False] + + Returns: + the subimage, image[bounds], after doing the wrapping. + """ + if not isinstance(bounds, BoundsI): + raise TypeError("bounds must be a galsim.BoundsI instance") + # Get this at the start to check for invalid bounds and raise the exception before + # possibly writing data past the edge of the image. + if not hermitian: + return self._wrap(bounds, False, False) + elif hermitian == 'x': + if self.bounds.xmin != 0: + raise GalSimIncompatibleValuesError( + "hermitian == 'x' requires self.bounds.xmin == 0", + hermitian=hermitian, bounds=self.bounds) + if bounds.xmin != 0: + raise GalSimIncompatibleValuesError( + "hermitian == 'x' requires bounds.xmin == 0", + hermitian=hermitian, bounds=bounds) + return self._wrap(bounds, True, False) + elif hermitian == 'y': + if self.bounds.ymin != 0: + raise GalSimIncompatibleValuesError( + "hermitian == 'y' requires self.bounds.ymin == 0", + hermitian=hermitian, bounds=self.bounds) + if bounds.ymin != 0: + raise GalSimIncompatibleValuesError( + "hermitian == 'y' requires bounds.ymin == 0", + hermitian=hermitian, bounds=bounds) + return self._wrap(bounds, False, True) + else: + raise GalSimValueError("Invalid value for hermitian", hermitian, (False, 'x', 'y'))
+ +
[docs] def _wrap(self, bounds, hermx, hermy): + """A version of `wrap` without the sanity checks. + + Equivalent to ``image.wrap(bounds, hermitian='x' if hermx else 'y' if hermy else False)`` + """ + ret = self.subImage(bounds) + _galsim.wrapImage(self._image, bounds._b, hermx, hermy) + return ret
+ +
[docs] def bin(self, nx, ny): + """Bin the image pixels in blocks of nx x ny pixels. + + This returns a new image that is a binned version of the current image. + Adjacent pixel values in nx x ny blocks are added together to produce the flux in each + output pixel. + + If the current number of pixels in each direction is not a multiple of nx, ny, then the + last pixel in each direction will be the sum of fewer than nx or ny pixels as needed. + + See also subsample, which is the opposite of this. + + If the wcs is a Jacobian (or simpler), the output image will have its wcs set properly. + But if the wcs is more complicated, the output wcs would be fairly complicated to figure + out properly, so we leave it as None. The user should set it themselves if required. + + Parameters: + nx: The number of adjacent pixels in the x direction to add together into each + output pixel. + ny: The number of adjacent pixels in the y direction to add together into each + output pixel. + + Returns: + a new `Image` + """ + ncol = self.xmax - self.xmin + 1 + nrow = self.ymax - self.ymin + 1 + nbins_x = (ncol-1) // nx + 1 + nbins_y = (nrow-1) // ny + 1 + nbins = nbins_x * nbins_y + + # target_bins just provides a number from 0..nbins for each target pixel + target_bins = np.arange(nbins).reshape(nbins_y, nbins_x) + # current_bins is the same number for each pixel in the current image. + current_bins = np.repeat(np.repeat(target_bins, ny, axis=0), nx, axis=1) + current_bins = current_bins[0:nrow, 0:ncol] + + # bincount with weights is a tricky way to do the sum over the bins + target_ar = np.bincount(current_bins.ravel(), weights=self.array.ravel()) + target_ar = target_ar.reshape(target_bins.shape) + + if self.wcs is None or not self.wcs._isUniform: + target_wcs = None + else: + if self.wcs._isPixelScale and nx == ny: + target_wcs = PixelScale(self.scale * nx) + else: + dudx, dudy, dvdx, dvdy = self.wcs.jacobian().getMatrix().ravel() + dudx *= nx + dvdx *= nx + dudy *= ny + dvdy *= ny + target_wcs = JacobianWCS(dudx, dudy, dvdx, dvdy) + + # Set the origin so that corresponding image positions correspond to the same world_pos + x0 = (self.wcs.origin.x - self.xmin + 0.5) / nx + 0.5 + y0 = (self.wcs.origin.y - self.ymin + 0.5) / ny + 0.5 + target_wcs = target_wcs.shiftOrigin(_PositionD(x0,y0), self.wcs.world_origin) + + target_bounds = _BoundsI(1, nbins_x, 1, nbins_y) + + return _Image(target_ar, target_bounds, target_wcs)
+ +
[docs] def subsample(self, nx, ny, dtype=None): + """Subdivide the image pixels into nx x ny sub-pixels. + + This returns a new image that is a subsampled version of the current image. + Each pixel's flux is split (uniformly) into nx x ny smaller pixels. + + See also bin, which is the opposite of this. Note that subsample(nx,ny) followed by + bin(nx,ny) is essentially a no op. + + If the wcs is a Jacobian (or simpler), the output image will have its wcs set properly. + But if the wcs is more complicated, the output wcs would be fairly complicated to figure + out properly, so we leave it as None. The user should set it themselves if required. + + Parameters: + nx: The number of sub-pixels in the x direction for each original pixel. + ny: The number of sub-pixels in the y direction for each original pixel. + dtype: Optionally provide a dtype for the return image. [default: None, which + means to use the same dtype as the original image] + + Returns: + a new `Image` + """ + ncol = self.xmax - self.xmin + 1 + nrow = self.ymax - self.ymin + 1 + npix_x = ncol * nx + npix_y = nrow * ny + flux_factor = nx * ny + + target_ar = np.repeat(np.repeat(self.array, ny, axis=0), nx, axis=1) + target_ar = target_ar.astype(dtype, copy=False) # Cute. This is a no op if dtype=None + target_ar /= flux_factor + + if self.wcs is None or not self.wcs._isUniform: + target_wcs = None + else: + if self.wcs._isPixelScale and nx == ny: + target_wcs = PixelScale(self.scale / nx) + else: + dudx, dudy, dvdx, dvdy = self.wcs.jacobian().getMatrix().ravel() + dudx /= nx + dvdx /= nx + dudy /= ny + dvdy /= ny + target_wcs = JacobianWCS(dudx, dudy, dvdx, dvdy) + + # Set the origin so that corresponding image positions correspond to the same world_pos + x0 = (self.wcs.origin.x - self.xmin + 0.5) * nx + 0.5 + y0 = (self.wcs.origin.y - self.ymin + 0.5) * ny + 0.5 + target_wcs = target_wcs.shiftOrigin(_PositionD(x0,y0), self.wcs.world_origin) + + target_bounds = _BoundsI(1, npix_x, 1, npix_y) + + return _Image(target_ar, target_bounds, target_wcs)
+ +
[docs] def calculate_fft(self): + """Performs an FFT of an `Image` in real space to produce a k-space `Image`. + + Note: the image will be padded with zeros as needed to make an image with bounds that + look like ``BoundsI(-N/2, N/2-1, -N/2, N/2-1)``. + + The input image must have a `PixelScale` wcs. The output image will be complex (an + `ImageCF` or `ImageCD` instance) and its scale will be 2pi / (N dx), where dx is the scale + of the input image. + + Returns: + an `Image` instance with the k-space image. + """ + if self.wcs is None: + raise GalSimError("calculate_fft requires that the scale be set.") + if not self.wcs._isPixelScale: + raise GalSimError("calculate_fft requires that the image has a PixelScale wcs.") + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError( + "calculate_fft requires that the image have defined bounds.") + + No2 = max(-self.bounds.xmin, self.bounds.xmax+1, -self.bounds.ymin, self.bounds.ymax+1) + + full_bounds = _BoundsI(-No2, No2-1, -No2, No2-1) + if self.bounds == full_bounds: + # Then the image is already in the shape we need. + ximage = self + else: + # Then we pad out with zeros + ximage = Image(full_bounds, dtype=self.dtype, init_value=0) + ximage[self.bounds] = self[self.bounds] + + dx = self.scale + # dk = 2pi / (N dk) + dk = np.pi / (No2 * dx) + + out = Image(_BoundsI(0,No2,-No2,No2-1), dtype=np.complex128, scale=dk) + with convert_cpp_errors(): + _galsim.rfft(ximage._image, out._image, True, True) + out *= dx*dx + out.setOrigin(0,-No2) + return out
+ +
[docs] def calculate_inverse_fft(self): + """Performs an inverse FFT of an `Image` in k-space to produce a real-space `Image`. + + The starting image is typically an `ImageCD`, although if the Fourier function is real + valued, then you could get away with using an `ImageD` or `ImageF`. + + The image is assumed to be Hermitian. In fact, only the portion with x >= 0 needs to + be defined, with f(-x,-y) taken to be conj(f(x,y)). + + Note: the k-space image will be padded with zeros and/or wrapped as needed to make an + image with bounds that look like ``BoundsI(0, N/2, -N/2, N/2-1)``. If you are building a + larger k-space image and then wrapping, you should wrap directly into an image of + this shape. + + The input image must have a `PixelScale` wcs. The output image will be real (an `ImageD` + instance) and its scale will be 2pi / (N dk), where dk is the scale of the input image. + + Returns: + an `Image` instance with the real-space image. + """ + if self.wcs is None: + raise GalSimError("calculate_inverse_fft requires that the scale be set.") + if not self.wcs._isPixelScale: + raise GalSimError("calculate_inverse_fft requires that the image has a PixelScale wcs.") + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("calculate_inverse_fft requires that the image have " + "defined bounds.") + if not self.bounds.includes(0,0): + raise GalSimBoundsError("calculate_inverse_fft requires that the image includes (0,0)", + PositionI(0,0), self.bounds) + + No2 = max(self.bounds.xmax, -self.bounds.ymin, self.bounds.ymax) + + target_bounds = _BoundsI(0, No2, -No2, No2-1) + if self.bounds == target_bounds: + # Then the image is already in the shape we need. + kimage = self + else: + # Then we can pad out with zeros and wrap to get this in the form we need. + full_bounds = _BoundsI(0, No2, -No2, No2) + kimage = Image(full_bounds, dtype=self.dtype, init_value=0) + posx_bounds = _BoundsI(0, self.bounds.xmax, self.bounds.ymin, self.bounds.ymax) + kimage[posx_bounds] = self[posx_bounds] + kimage = kimage.wrap(target_bounds, hermitian = 'x') + + dk = self.scale + # dx = 2pi / (N dk) + dx = np.pi / (No2 * dk) + + # For the inverse, we need a bit of extra space for the fft. + out_extra = Image(_BoundsI(-No2,No2+1,-No2,No2-1), dtype=float, scale=dx) + with convert_cpp_errors(): + _galsim.irfft(kimage._image, out_extra._image, True, True) + # Now cut off the bit we don't need. + out = out_extra.subImage(_BoundsI(-No2,No2-1,-No2,No2-1)) + out *= (dk * No2 / np.pi)**2 + out.setCenter(0,0) + return out
+ +
[docs] @classmethod + def good_fft_size(cls, input_size): + """Round the given input size up to the next higher power of 2 or 3 times a power of 2. + + This rounds up to the next higher value that is either 2^k or 3*2^k. If you are + going to be performing FFTs on an image, these will tend to be faster at performing + the FFT. + """ + return _galsim.goodFFTSize(int(input_size))
+ +
[docs] def copyFrom(self, rhs): + """Copy the contents of another image + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + if not isinstance(rhs, Image): + raise TypeError("Trying to copyFrom a non-image") + if self.bounds.numpyShape() != rhs.bounds.numpyShape(): + raise GalSimIncompatibleValuesError( + "Trying to copy images that are not the same shape", self_image=self, rhs=rhs) + self._copyFrom(rhs)
+ + def _copyFrom(self, rhs): + """Same as copyFrom, but no sanity checks. + """ + self._array[:,:] = self._safe_cast(rhs.array) + +
[docs] def view(self, scale=None, wcs=None, origin=None, center=None, + make_const=False, dtype=None, contiguous=False): + """Make a view of this image, which lets you change the scale, wcs, origin, etc. + but view the same underlying data as the original image. + + If you do not provide either ``scale`` or ``wcs``, the view will keep the same wcs + as the current `Image` object. + + Parameters: + scale: If provided, use this as the pixel scale for the image. [default: None] + wcs: If provided, use this as the wcs for the image. [default: None] + origin: If provided, use this as the origin position of the view. + [default: None] + center: If provided, use this as the center position of the view. + [default: None] + make_const: Make the view's data array immutable. [default: False] + dtype: If provided, ensure that the output has this dtype. If the original + Image is a different dtype, then a copy will be made. [default: None] + contiguous: If provided, ensure that the output array is contiguous. [default: False] + """ + if origin is not None and center is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both center and origin", center=center, origin=origin) + + if scale is not None: + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both scale and wcs", scale=scale, wcs=wcs) + wcs = PixelScale(scale) + elif wcs is not None: + if not isinstance(wcs, BaseWCS): + raise TypeError("wcs parameters must be a galsim.BaseWCS instance") + else: + wcs = self.wcs + + # Figure out the dtype for the return Image + dtype = dtype if dtype else self.dtype + + # If currently empty, just return a new empty image. + if not self.bounds.isDefined(): + return Image(wcs=wcs, dtype=dtype) + + # Recast the array type if necessary + if dtype != self.array.dtype: + array = self.array.astype(dtype) + elif contiguous: + array = np.ascontiguousarray(self.array) + else: + array = self.array + + # Make the array const if requested + if make_const: + array = array.view() + array.flags.writeable = False + + # Make the return Image + ret = _Image(array, self.bounds, wcs) + + # Update the origin if requested + if origin is not None: + ret.setOrigin(origin) + elif center is not None: + ret.setCenter(center) + + return ret
+ +
[docs] def _view(self): + """Equivalent to `view`, but without some of the sanity checks and extra options. + """ + return _Image(self.array.view(), self.bounds, self.wcs)
+ +
[docs] def shift(self, *args, **kwargs): + """Shift the pixel coordinates by some (integral) dx,dy. + + The arguments here may be either (dx, dy) or a PositionI instance. + Or you can provide dx, dy as named kwargs. + + In terms of columns and rows, dx means a shift in the x value of each column in the + array, and dy means a shift in the y value of each row. In other words, the following + will return the same value for ixy. The shift function just changes the coordinates (x,y) + used for that pixel:: + + >>> ixy = im(x,y) + >>> im.shift(3,9) + >>> ixy = im(x+3, y+9) + """ + delta = parse_pos_args(args, kwargs, 'dx', 'dy', integer=True) + self._shift(delta)
+ +
[docs] def _shift(self, delta): + """Equivalent to `shift`, but without some of the sanity checks and ``delta`` must + be a `PositionI` instance. + + Parameters: + delta: The amount to shift as a `PositionI`. + """ + # The parse_pos_args function is a bit slow, so go directly to this point when we + # call shift from setCenter or setOrigin. + if delta.x != 0 or delta.y != 0: + self._bounds = self._bounds.shift(delta) + if self.wcs is not None: + self.wcs = self.wcs.shiftOrigin(delta)
+ +
[docs] def setCenter(self, *args, **kwargs): + """Set the center of the image to the given (integral) (xcen, ycen) + + The arguments here may be either (xcen, ycen) or a PositionI instance. + Or you can provide xcen, ycen as named kwargs. + + In terms of the rows and columns, xcen is the new x value for the central column, and ycen + is the new y value of the central row. For even-sized arrays, there is no central column + or row, so the convention we adopt in this case is to round up. For example:: + + >>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4))) + >>> im(1,1) + 0.0 + >>> im(4,1) + 3.0 + >>> im(4,4) + 15.0 + >>> im(3,3) + 10.0 + >>> im.setCenter(0,0) + >>> im(0,0) + 10.0 + >>> im(-2,-2) + 0.0 + >>> im(1,-2) + 3.0 + >>> im(1,1) + 15.0 + >>> im.setCenter(234,456) + >>> im(234,456) + 10.0 + >>> im.bounds + galsim.BoundsI(xmin=232, xmax=235, ymin=454, ymax=457) + """ + cen = parse_pos_args(args, kwargs, 'xcen', 'ycen', integer=True) + self._shift(cen - self.center)
+ +
[docs] def setOrigin(self, *args, **kwargs): + """Set the origin of the image to the given (integral) (x0, y0) + + The arguments here may be either (x0, y0) or a PositionI instance. + Or you can provide x0, y0 as named kwargs. + + In terms of the rows and columns, x0 is the new x value for the first column, + and y0 is the new y value of the first row. For example:: + + >>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4))) + >>> im(1,1) + 0.0 + >>> im(4,1) + 3.0 + >>> im(1,4) + 12.0 + >>> im(4,4) + 15.0 + >>> im.setOrigin(0,0) + >>> im(0,0) + 0.0 + >>> im(3,0) + 3.0 + >>> im(0,3) + 12.0 + >>> im(3,3) + 15.0 + >>> im.setOrigin(234,456) + >>> im(234,456) + 0.0 + >>> im.bounds + galsim.BoundsI(xmin=234, xmax=237, ymin=456, ymax=459) + """ + origin = parse_pos_args(args, kwargs, 'x0', 'y0', integer=True) + self._shift(origin - self.origin)
+ + @property + def center(self): + """The current nominal center (xcen,ycen) of the image as a PositionI instance. + + In terms of the rows and columns, xcen is the x value for the central column, and ycen + is the y value of the central row. For even-sized arrays, there is no central column + or row, so the convention we adopt in this case is to round up. For example:: + + >>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4))) + >>> im.center + galsim.PositionI(x=3, y=3) + >>> im(im.center) + 10.0 + >>> im.setCenter(56,72) + >>> im.center + galsim.PositionI(x=56, y=72) + >>> im(im.center) + 10.0 + """ + return self.bounds.center + + @property + def true_center(self): + """The current true center of the image as a PositionD instance. + + Unline the nominal center returned by im.center, this value may be half-way between + two pixels if the image has an even number of rows or columns. It gives the position + (x,y) at the exact center of the image, regardless of whether this is at the center of + a pixel (integer value) or halfway between two (half-integer). For example:: + + >>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4))) + >>> im.center + galsim.PositionI(x=3, y=3) + >>> im.true_center + galsim.PositionI(x=2.5, y=2.5) + >>> im.setCenter(56,72) + >>> im.center + galsim.PositionI(x=56, y=72) + >>> im.true_center + galsim.PositionD(x=55.5, y=71.5) + >>> im.setOrigin(0,0) + >>> im.true_center + galsim.PositionD(x=1.5, y=1.5) + """ + return self.bounds.true_center + + @property + def origin(self): + """Return the origin of the image. i.e. the (x,y) position of the lower-left pixel. + + In terms of the rows and columns, this is the (x,y) coordinate of the first column, and + first row of the array. For example:: + + >>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4))) + >>> im.origin + galsim.PositionI(x=1, y=1) + >>> im(im.origin) + 0.0 + >>> im.setOrigin(23,45) + >>> im.origin + galsim.PositionI(x=23, y=45) + >>> im(im.origin) + 0.0 + >>> im(23,45) + 0.0 + >>> im.bounds + galsim.BoundsI(xmin=23, xmax=26, ymin=45, ymax=48) + """ + return self.bounds.origin + +
[docs] def __call__(self, *args, **kwargs): + """Get the pixel value at given position + + The arguments here may be either (x, y) or a PositionI instance. + Or you can provide x, y as named kwargs. + """ + pos = parse_pos_args(args, kwargs, 'x', 'y', integer=True) + return self.getValue(pos.x,pos.y)
+ +
[docs] def getValue(self, x, y): + """This method is a synonym for im(x,y). It is a bit faster than im(x,y), since GalSim + does not have to parse the different options available for __call__. (i.e. im(x,y) or + im(pos) or im(x=x,y=y)) + + Parameters: + x: The x coordinate of the pixel to get. + y: The y coordinate of the pixel to get. + """ + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Attempt to access values of an undefined image") + if not self.bounds.includes(x,y): + raise GalSimBoundsError("Attempt to access position not in bounds of image.", + PositionI(x,y), self.bounds) + return self._getValue(x,y)
+ +
[docs] def _getValue(self, x, y): + """Equivalent to `getValue`, except there are no checks that the values fall + within the bounds of the image. + """ + return self._array[y-self.ymin, x-self.xmin]
+ +
[docs] def setValue(self, *args, **kwargs): + """Set the pixel value at given (x,y) position + + The arguments here may be either (x, y, value) or (pos, value) where pos is a PositionI. + Or you can provide x, y, value as named kwargs. + + This is equivalent to self[x,y] = rhs + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Attempt to set value of an undefined image") + pos, value = parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) + if not self.bounds.includes(pos): + raise GalSimBoundsError("Attempt to set position not in bounds of image", + pos, self.bounds) + self._setValue(pos.x,pos.y,value)
+ +
[docs] def _setValue(self, x, y, value): + """Equivalent to `setValue` except that there are no checks that the values + fall within the bounds of the image, and the coordinates must be given as ``x``, ``y``. + + Parameters: + x: The x coordinate of the pixel to set. + y: The y coordinate of the pixel to set. + value: The value to set the pixel to. + """ + self._array[y-self.ymin, x-self.xmin] = value
+ +
[docs] def addValue(self, *args, **kwargs): + """Add some amount to the pixel value at given (x,y) position + + The arguments here may be either (x, y, value) or (pos, value) where pos is a PositionI. + Or you can provide x, y, value as named kwargs. + + This is equivalent to self[x,y] += rhs + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Attempt to set value of an undefined image") + pos, value = parse_pos_args(args, kwargs, 'x', 'y', integer=True, others=['value']) + if not self.bounds.includes(pos): + raise GalSimBoundsError("Attempt to set position not in bounds of image", + pos,self.bounds) + self._addValue(pos.x,pos.y,value)
+ +
[docs] def _addValue(self, x, y, value): + """Equivalent to `addValue` except that there are no checks that the values + fall within the bounds of the image, and the coordinates must be given as ``x``, ``y``. + + Parameters: + x: The x coordinate of the pixel to add to. + y: The y coordinate of the pixel to add to. + value: The value to add to this pixel. + """ + self._array[y-self.ymin, x-self.xmin] += value
+ +
[docs] def fill(self, value): + """Set all pixel values to the given ``value`` + + Parameter: + value: The value to set all the pixels to. + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Attempt to set values of an undefined image") + self._fill(value)
+ +
[docs] def _fill(self, value): + """Equivalent to `fill`, except that there are no checks that the bounds are defined. + """ + self._array[:,:] = value
+ +
[docs] def setZero(self): + """Set all pixel values to zero. + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + self._fill(0) # This might be made faster with a C++ call to use memset
+ +
[docs] def invertSelf(self): + """Set all pixel values to their inverse: x -> 1/x. + + Note: any pixels whose value is 0 originally are ignored. They remain equal to 0 + on the output, rather than turning into inf. + """ + if self.isconst: + raise GalSimImmutableError("Cannot modify the values of an immutable Image", self) + if not self.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Attempt to set values of an undefined image") + self._invertSelf()
+ +
[docs] def _invertSelf(self): + """Equivalent to `invertSelf`, except that there are no checks that the bounds are defined. + """ + # C++ version skips 0's to 1/0 -> 0 instead of inf. + _galsim.invertImage(self._image)
+ +
[docs] def replaceNegative(self, replace_value=0): + """Replace any negative values currently in the image with 0 (or some other value). + + Sometimes FFT drawing can result in tiny negative values, which may be undesirable for + some purposes. This method replaces those values with 0 or some other value if desired. + + Parameters: + replace_value: The value with which to replace any negative pixels. [default: 0] + """ + self.array[self.array<0] = replace_value
+ +
[docs] def calculateHLR(self, center=None, flux=None, flux_frac=0.5): + """Returns the half-light radius of a drawn object. + + This method is equivalent to `GSObject.calculateHLR` when the object has already been + been drawn onto an image. Note that the profile should be drawn using a method that + integrates over pixels and does not add noise. (The default method='auto' is acceptable.) + + If the image has a wcs other than a `PixelScale`, an AttributeError will be raised. + + Parameters: + center: The position in pixels to use for the center, r=0. + [default: self.true_center] + flux: The total flux. [default: sum(self.array)] + flux_frac: The fraction of light to be enclosed by the returned radius. + [default: 0.5] + + Returns: + an estimate of the half-light radius in physical units defined by the pixel scale. + """ + if center is None: + center = self.true_center + + if flux is None: + flux = np.sum(self.array, dtype=float) + + # Use radii at centers of pixels as approximation to the radial integral + x,y = self.get_pixel_centers() + x -= center.x + y -= center.y + rsq = x*x + y*y + + # Sort by radius + indx = np.argsort(rsq.ravel()) + rsqf = rsq.ravel()[indx] + data = self.array.ravel()[indx] + cumflux = np.cumsum(data, dtype=float) + + # Find the first value with cumflux > 0.5 * flux + k = np.argmax(cumflux > flux_frac * flux) + flux_k = cumflux[k] / flux # normalize to unit total flux + + # Interpolate (linearly) between this and the previous value. + if k == 0: + hlrsq = rsqf[0] * (flux_frac / flux_k) + else: + fkm1 = cumflux[k-1] / flux + # For brevity in the next formula: + fk = flux_k + f = flux_frac + hlrsq = (rsqf[k-1] * (fk-f) + rsqf[k] * (f-fkm1)) / (fk-fkm1) + + # This has all been done in pixels. So normalize according to the pixel scale. + hlr = np.sqrt(hlrsq) * self.scale + + return hlr
+ + +
[docs] def calculateMomentRadius(self, center=None, flux=None, rtype='det'): + """Returns an estimate of the radius based on unweighted second moments of a drawn object. + + This method is equivalent to `GSObject.calculateMomentRadius` when the object has already + been drawn onto an image. Note that the profile should be drawn using a method that + integrates over pixels and does not add noise. (The default method='auto' is acceptable.) + + If the image has a wcs other than a `PixelScale`, an AttributeError will be raised. + + Parameters: + center: The position in pixels to use for the center, r=0. + [default: self.true_center] + flux: The total flux. [default: sum(self.array)] + rtype: There are three options for this parameter: + + - 'trace' means return sqrt(T/2) + - 'det' means return det(Q)^1/4 + - 'both' means return both: (sqrt(T/2), det(Q)^1/4) + + [default: 'det'] + + Returns: + an estimate of the radius in physical units defined by the pixel scale + (or both estimates if rtype == 'both'). + """ + if rtype not in ('trace', 'det', 'both'): + raise GalSimValueError("Invalid rtype.", rtype, ('trace', 'det', 'both')) + + if center is None: + center = self.true_center + + if flux is None: + flux = np.sum(self.array, dtype=float) + + # Use radii at centers of pixels as approximation to the radial integral + x,y = self.get_pixel_centers() + x -= center.x + y -= center.y + + if rtype in ('trace', 'both'): + # Calculate trace measure: + rsq = x*x + y*y + Irr = np.sum(rsq * self.array, dtype=float) / flux + + # This has all been done in pixels. So normalize according to the pixel scale. + sigma_trace = (Irr/2.)**0.5 * self.scale + + if rtype in ('det', 'both'): + # Calculate det measure: + Ixx = np.sum(x*x * self.array, dtype=float) / flux + Iyy = np.sum(y*y * self.array, dtype=float) / flux + Ixy = np.sum(x*y * self.array, dtype=float) / flux + + # This has all been done in pixels. So normalize according to the pixel scale. + sigma_det = (Ixx*Iyy-Ixy**2)**0.25 * self.scale + + if rtype == 'trace': + return sigma_trace + elif rtype == 'det': + return sigma_det + else: + return sigma_trace, sigma_det
+ + +
[docs] def calculateFWHM(self, center=None, Imax=0.): + """Returns the full-width half-maximum (FWHM) of a drawn object. + + This method is equivalent to `GSObject.calculateFWHM` when the object has already + been drawn onto an image. Note that the profile should be drawn using a method that + does not integrate over pixels, so either 'sb' or 'no_pixel'. Also, if there is a + significant amount of noise in the image, this method may not work well. + + If the image has a wcs other than a `PixelScale`, an AttributeError will be raised. + + Parameters: + center: The position in pixels to use for the center, r=0. + [default: self.true_center] + Imax: The maximum surface brightness. [default: max(self.array)] + Note: If Imax is provided, and the maximum pixel value is larger than + this value, Imax will be updated to use the larger value. + + Returns: + an estimate of the full-width half-maximum in physical units defined by the pixel scale. + """ + if center is None: + center = self.true_center + + # If the full image has a larger maximum, use that. + Imax2 = np.max(self.array) + if Imax2 > Imax: Imax = Imax2 + + # Use radii at centers of pixels. + x,y = self.get_pixel_centers() + x -= center.x + y -= center.y + rsq = x*x + y*y + + # Sort by radius + indx = np.argsort(rsq.ravel()) + rsqf = rsq.ravel()[indx] + data = self.array.ravel()[indx] + + # Find the first value with I < 0.5 * Imax + k = np.argmax(data < 0.5 * Imax) + Ik = data[k] / Imax + + # Interpolate (linearly) between this and the previous value. + if k == 0: + rsqhm = rsqf[0] * (0.5 / Ik) + else: + Ikm1 = data[k-1] / Imax + rsqhm = (rsqf[k-1] * (Ik-0.5) + rsqf[k] * (0.5-Ikm1)) / (Ik-Ikm1) + + # This has all been done in pixels. So normalize according to the pixel scale. + fwhm = 2. * np.sqrt(rsqhm) * self.scale + + return fwhm
+ + # Define a utility function to be used by the arithmetic functions below + def check_image_consistency(self, im2, integer=False): + if integer and not self.isinteger: + raise GalSimValueError("Image must have integer values.",self) + if isinstance(im2, Image): + if self.array.shape != im2.array.shape: + raise GalSimIncompatibleValuesError("Image shapes are inconsistent", + im1=self, im2=im2) + if integer and not im2.isinteger: + raise GalSimValueError("Image must have integer values.",im2) + + def __add__(self, other): + self.check_image_consistency(other) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array + a, self.bounds, self.wcs) + + __radd__ = __add__ + + def _safe_cast(self, array): + # Assign the given array to self.array, safely casting it to the required type. + # Most important is to make sure integer types round first before casting, since + # numpy's astype doesn't do any rounding. + if self.isinteger: + array = np.around(array) + return array.astype(self.array.dtype, copy=False) + + def __iadd__(self, other): + self.check_image_consistency(other) + try: + a = other.array + dt = a.dtype + except AttributeError: + a = other + dt = type(a) + if dt == self.array.dtype: + self.array[:,:] += a + else: + self.array[:,:] = self._safe_cast(self.array + a) + return self + + def __sub__(self, other): + self.check_image_consistency(other) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array - a, self.bounds, self.wcs) + + def __rsub__(self, other): + return _Image(other-self.array, self.bounds, self.wcs) + + def __isub__(self, other): + self.check_image_consistency(other) + try: + a = other.array + dt = a.dtype + except AttributeError: + a = other + dt = type(a) + if dt == self.array.dtype: + self.array[:,:] -= a + else: + self.array[:,:] = self._safe_cast(self.array - a) + return self + + def __mul__(self, other): + self.check_image_consistency(other) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array * a, self.bounds, self.wcs) + + __rmul__ = __mul__ + + def __imul__(self, other): + self.check_image_consistency(other) + try: + a = other.array + dt = a.dtype + except AttributeError: + a = other + dt = type(a) + if dt == self.array.dtype: + self.array[:,:] *= a + else: + self.array[:,:] = self._safe_cast(self.array * a) + return self + + def __div__(self, other): + self.check_image_consistency(other) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array / a, self.bounds, self.wcs) + + __truediv__ = __div__ + + def __rdiv__(self, other): + return _Image(other / self.array, self.bounds, self.wcs) + + __rtruediv__ = __rdiv__ + + def __idiv__(self, other): + self.check_image_consistency(other) + try: + a = other.array + dt = a.dtype + except AttributeError: + a = other + dt = type(a) + if dt == self.array.dtype and not self.isinteger: + # if dtype is an integer type, then numpy doesn't allow true division /= to assign + # back to an integer array. So for integers (or mixed types), don't use /=. + self.array[:,:] /= a + else: + self.array[:,:] = self._safe_cast(self.array / a) + return self + + __itruediv__ = __idiv__ + + def __floordiv__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array // a, self.bounds, self.wcs) + + def __rfloordiv__(self, other): + self.check_image_consistency(other, integer=True) + return _Image(other // self.array, self.bounds, self.wcs) + + def __ifloordiv__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + dt = a.dtype + except AttributeError: + a = other + dt = type(a) + if dt == self.array.dtype: + self.array[:,:] //= a + else: + self.array[:,:] = self._safe_cast(self.array // a) + return self + + def __mod__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array % a, self.bounds, self.wcs) + + def __rmod__(self, other): + self.check_image_consistency(other, integer=True) + return _Image(other % self.array, self.bounds, self.wcs) + + def __imod__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + dt = a.dtype + except AttributeError: + a = other + dt = type(a) + if dt == self.array.dtype: + self.array[:,:] %= a + else: + self.array[:,:] = self._safe_cast(self.array % a) + return self + + def __pow__(self, other): + result = self.copy() + result **= other + return result + + def __ipow__(self, other): + if not isinstance(other, int) and not isinstance(other, float): + raise TypeError("Can only raise an image to a float or int power!") + self.array[:,:] **= other + return self + + def __neg__(self): + result = self.copy() + result *= np.int64(-1) + return result + + # Define &, ^ and | only for integer-type images + def __and__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array & a, self.bounds, self.wcs) + + __rand__ = __and__ + + def __iand__(self, other): + self.check_image_consistency(other, integer=True) + try: + self.array[:,:] &= other.array + except AttributeError: + self.array[:,:] &= other + return self + + def __xor__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array ^ a, self.bounds, self.wcs) + + __rxor__ = __xor__ + + def __ixor__(self, other): + self.check_image_consistency(other, integer=True) + try: + self.array[:,:] ^= other.array + except AttributeError: + self.array[:,:] ^= other + return self + + def __or__(self, other): + self.check_image_consistency(other, integer=True) + try: + a = other.array + except AttributeError: + a = other + return _Image(self.array | a, self.bounds, self.wcs) + + __ror__ = __or__ + + def __ior__(self, other): + self.check_image_consistency(other, integer=True) + try: + self.array[:,:] |= other.array + except AttributeError: + self.array[:,:] |= other + return self + +
[docs] def transpose(self): + """Return the tranpose of the image. + + Note: The returned image will have an undefined wcs. + If you care about the wcs, you will need to set it yourself. + """ + bT = _BoundsI(self.ymin, self.ymax, self.xmin, self.xmax) + return _Image(self.array.T, bT, None)
+ +
[docs] def flip_lr(self): + """Return a version of the image flipped left to right. + + Note: The returned image will have an undefined wcs. + If you care about the wcs, you will need to set it yourself. + """ + return _Image(self.array[:,::-1], self._bounds, None)
+ +
[docs] def flip_ud(self): + """Return a version of the image flipped top to bottom. + + Note: The returned image will have an undefined wcs. + If you care about the wcs, you will need to set it yourself. + """ + return _Image(self.array[::-1,:], self._bounds, None)
+ +
[docs] def rot_cw(self): + """Return a version of the image rotated 90 degrees clockwise. + + Note: The returned image will have an undefined wcs. + If you care about the wcs, you will need to set it yourself. + """ + bT = _BoundsI(self.ymin, self.ymax, self.xmin, self.xmax) + return _Image(self.array.T[::-1,:], bT, None)
+ +
[docs] def rot_ccw(self): + """Return a version of the image rotated 90 degrees counter-clockwise. + + Note: The returned image will have an undefined wcs. + If you care about the wcs, you will need to set it yourself. + """ + bT = _BoundsI(self.ymin, self.ymax, self.xmin, self.xmax) + return _Image(self.array.T[:,::-1], bT, None)
+ +
[docs] def rot_180(self): + """Return a version of the image rotated 180 degrees. + + Note: The returned image will have an undefined wcs. + If you care about the wcs, you will need to set it yourself. + """ + return _Image(self.array[::-1,::-1], self._bounds, None)
+ +
[docs] def depixelize(self, x_interpolant): + """Return a depixelized version of the image. + + Specifically, this function creates an image that could be used with `InterpolatedImage` + with the given x_interpolant, which when drawn with method=auto would produce the + current image. + + >>> alt_image = image.depixelize(x_interpolant) + >>> ii = galsim.InterpolatedImage(alt_image, x_interpolant=x_interpolant) + >>> image2 = ii.drawImage(image.copy(), method='auto') + + image2 will end up approximately equal to the original image. + + .. warning:: + + This function is fairly expensive, both in memory and CPU time, so it should + only be called on fairly small images (~100x100 or smaller typically). + The memory requirement scales as Npix^2, and the execution time scales as Npix^3. + + However, the expensive part of the calculation is independent of the image values. + It only depends on the size of the image and interpolant being used. So this part + of the calculation is cached and reused if possible. If you make repeated calls + to depixelize using the same image size and interpolant, it will be much faster + after the first call. + + If you need to release the cache (since it can be a non-trivial amount of memory), + you may do so using `Image.clear_depixelize_cache`. + + Parameters: + x_interpolant: The `Interpolant` to use in the `InterpolatedImage` to describe + how the profile should be interpolated between the pixel centers. + + Returns: + an `Image` representing the underlying profile without the pixel convolution. + """ + ny, nx = self.array.shape + npix = nx * ny + + # Each kernel is the integral of the interpolant over 1 pixel. + unit_integrals = x_interpolant.unit_integrals(max_len=max(nx,ny)) + + # The rest of the implementation is done in C++. cf. src/Image.cpp + im2 = self.copy() + _unit_integrals = unit_integrals.__array_interface__['data'][0] + _galsim.depixelizeImage(im2._image, _unit_integrals, unit_integrals.size) + return im2
+ +
[docs] @staticmethod + def clear_depixelize_cache(): + """Release the cached solver used by depixelize to make repeated calls more efficient. + """ + _galsim.ClearDepixelizeCache()
+ + def __eq__(self, other): + # Note that numpy.array_equal can return True if the dtypes of the two arrays involved are + # different, as long as the contents of the two arrays are logically the same. For example: + # + # >>> double_array = np.arange(1024).reshape(32, 32)*np.pi + # >>> int_array = np.arange(1024).reshape(32, 32) + # >>> assert galsim.ImageD(int_array) == galsim.ImageF(int_array) # passes + # >>> assert galsim.ImageD(double_array) == galsim.ImageF(double_array) # fails + + return (self is other or + (isinstance(other, Image) and + self.bounds == other.bounds and + self.wcs == other.wcs and + (not self.bounds.isDefined() or np.array_equal(self.array,other.array)) and + self.isconst == other.isconst)) + + def __ne__(self, other): return not self.__eq__(other) + + # Not immutable object. So shouldn't be used as a hash. + __hash__ = None
+ +
[docs]def _Image(array, bounds, wcs): + """Equivalent to ``Image(array, bounds, wcs)``, but without the overhead of sanity checks, + and the other options for how to provide the arguments. + """ + ret = Image.__new__(Image) + ret.wcs = wcs + ret._dtype = array.dtype.type + if ret._dtype in Image._alias_dtypes: + ret._dtype = Image._alias_dtypes[ret._dtype] + array = array.astype(ret._dtype) + ret._array = array + ret._bounds = bounds + return ret
+ +# These are essentially aliases for the regular Image with the correct dtype +
[docs]def ImageUS(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.uint16) + """ + kwargs['dtype'] = np.uint16 + return Image(*args, **kwargs)
+ +
[docs]def ImageUI(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.uint32) + """ + kwargs['dtype'] = np.uint32 + return Image(*args, **kwargs)
+ +
[docs]def ImageS(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.int16) + """ + kwargs['dtype'] = np.int16 + return Image(*args, **kwargs)
+ +
[docs]def ImageI(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.int32) + """ + kwargs['dtype'] = np.int32 + return Image(*args, **kwargs)
+ +
[docs]def ImageF(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.float32) + """ + kwargs['dtype'] = np.float32 + return Image(*args, **kwargs)
+ +
[docs]def ImageD(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.float64) + """ + kwargs['dtype'] = np.float64 + return Image(*args, **kwargs)
+ +
[docs]def ImageCF(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.complex64) + """ + kwargs['dtype'] = np.complex64 + return Image(*args, **kwargs)
+ +
[docs]def ImageCD(*args, **kwargs): + """Alias for galsim.Image(..., dtype=numpy.complex128) + """ + kwargs['dtype'] = np.complex128 + return Image(*args, **kwargs)
+ +# Put this at the end to avoid circular imports +from .wcs import BaseWCS, PixelScale, JacobianWCS +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/inclined.html b/docs/_build/html/_modules/galsim/inclined.html new file mode 100644 index 00000000000..db53ca3cf5a --- /dev/null +++ b/docs/_build/html/_modules/galsim/inclined.html @@ -0,0 +1,597 @@ + + + + + + galsim.inclined — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.inclined

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'InclinedExponential', 'InclinedSersic', ]
+
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .exponential import Exponential
+from .angle import Angle
+from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors
+
+
+
[docs]class InclinedExponential(GSObject): + r"""A class describing an inclined exponential profile. + + The Inclined Exponential surface brightness profile is characterized by three properties: its + inclination angle (where 0 degrees = face-on and 90 degrees = edge-on), its scale radius, and + its scale height. The 3D light distribution function is: + + .. math:: + I(R,z) \sim \mathrm{sech}^2 (z/h_s) \, \exp\left(-R/R_s\right) + + where :math:`z` is the distance along the minor axis, :math:`R` is the radial distance from the + minor axis, :math:`R_s` is the scale radius of the disk, and :math:`h_s` is the scale height of + the disk. The 2D light distribution function is then determined from the scale height and + radius here, along with the inclination angle. + + In this implementation, the profile is inclined along the y-axis. This means that it will likely + need to be rotated in most circumstances. + + At present, this profile is not enabled for photon-shooting. + + A profile can be initialized using one (and only one) of two possible size parameters: + ``scale_radius`` or ``half_light_radius``. Exactly one of these two is required. Similarly, + at most one of ``scale_height`` and ``scale_h_over_r`` is required; if neither is given, the + default of scale_h_over_r = 0.1 will be used. Note that if half_light_radius and + scale_h_over_r are supplied (or the default value of scale_h_over_r is used), + scale_h_over_r will be assumed to refer to the scale radius, not the half-light radius. + + Parameters: + inclination: The inclination angle, which must be a `galsim.Angle` instance + scale_radius: The scale radius of the exponential disk. Typically given in + arcsec. This can be compared to the 'scale_radius' parameter of the + `galsim.Exponential` class, and in the face-on case, the same scale + radius will result in the same 2D light distribution as with that + class. + half_light_radius: The half-light radius of the exponential disk, as an alternative to + the scale radius. + scale_height: The scale height of the exponential disk. Typically given in arcsec. + [default: None] + scale_h_over_r: In lieu of the scale height, you may also specify the ratio of the + scale height to the scale radius. [default: 0.1] + flux: The flux (in photons) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "inclination" : Angle } + _single_params = [ { "scale_radius" : float , "half_light_radius" : float } ] + _opt_params = { "scale_height" : float, "scale_h_over_r" : float, "flux" : float } + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, inclination, half_light_radius=None, scale_radius=None, scale_height=None, + scale_h_over_r=None, flux=1., gsparams=None): + + # Check that the scale/half-light radius is valid + if scale_radius is not None: + if not scale_radius > 0.: + raise GalSimRangeError("scale_radius must be > 0.", scale_radius, 0.) + if half_light_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius and half_light_radius may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) + self._r0 = float(scale_radius) + elif half_light_radius is not None: + if not half_light_radius > 0.: + raise GalSimRangeError("half_light_radius must be > 0.", half_light_radius, 0.) + # Use the factor from the Exponential class + self._r0 = float(half_light_radius) / Exponential._hlr_factor + else: + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) + + # Check that the height specification is valid + if scale_height is not None: + if not scale_height > 0.: + raise GalSimRangeError("scale_height must be > 0.", scale_height, 0.) + if scale_h_over_r is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_height and scale_h_over_r may be specified.", + scale_height=scale_height, scale_h_over_r=scale_h_over_r) + self._h0 = float(scale_height) + else: + if scale_h_over_r is None: + # Use the default scale_h_over_r + scale_h_over_r = 0.1 + elif not scale_h_over_r > 0.: + raise GalSimRangeError("half_light_radius must be > 0.", scale_h_over_r, 0.) + self._h0 = float(self._r0) * float(scale_h_over_r) + + # Explicitly check for angle type, so we can give more informative error if eg. a float is + # passed + if not isinstance(inclination, Angle): + raise TypeError("Input inclination should be an Angle") + + self._inclination = inclination + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _sbp(self): + return _galsim.SBInclinedExponential(self._inclination.rad, self._r0, + self._h0, self._flux, self.gsparams._gsp) + + @property + def inclination(self): + """The inclination angle. + """ + return self._inclination + @property + def scale_radius(self): + """The scale radius of the exponential disk. + """ + return self._r0 + @property + def scale_height(self): + """The scale height of the disk. + """ + return self._h0 + + @property + def disk_half_light_radius(self): + """The half-light radius of the exponential disk. + """ + return self._r0 * Exponential._hlr_factor + @property + def scale_h_over_r(self): + """The ratio scale_height / scale_radius + """ + return self._h0 / self._r0 + + def __eq__(self, other): + return (self is other or + (isinstance(other, InclinedExponential) and + (self.inclination == other.inclination) and + (self.scale_radius == other.scale_radius) and + (self.scale_height == other.scale_height) and + (self.flux == other.flux) and + (self.gsparams == other.gsparams))) + + def __hash__(self): + return hash(("galsim.InclinedExponential", self.inclination, self.scale_radius, + self.scale_height, self.flux, self.gsparams)) + + def __repr__(self): + return ('galsim.InclinedExponential(inclination=%r, scale_radius=%r, scale_height=%r, ' + 'flux=%r, gsparams=%r)')%( + self.inclination, self.scale_radius, self.scale_height, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.InclinedExponential(inclination=%s, scale_radius=%s, scale_height=%s' % ( + self.inclination, self.scale_radius, self.scale_height) + if self.flux != 1.0: + s += ', flux=%s' % self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return InclinedExponential(inclination=self.inclination, scale_radius=self.scale_radius, + scale_height=self.scale_height, flux=flux, + gsparams=self.gsparams)
+ + +
[docs]class InclinedSersic(GSObject): + r"""A class describing an inclined sersic profile. This class is general, and so for certain + special cases, more specialized classes will be more efficient. For the case where n==1 + with no truncation, the `InclinedExponential` class will be much more efficient. For the case + where the inclination angle is zero (face-on), the `Sersic` class will be slightly more + efficient. + + The InclinedSersic surface brightness profile is characterized by four properties: its + Sersic index ``n``, its inclination angle (where 0 degrees = face-on and 90 degrees = edge-on), + its scale radius, and its scale height. The 3D light distribution function is: + + .. math:: + I(R,z) \sim \mathrm{sech}^2 (z/h_s) \, \exp\left(-(R/R_s)^{1/n}\right) + + where :math:`z` is the distance along the minor axis, :math:`R` is the radial distance from the + minor axis, :math:`R_s` is the scale radius of the disk, and :math:`h_s` is the scale height of + the disk. The 2D light distribution function is then determined from the scale height and + radius here, along with the inclination angle. + + In this implementation, the profile is inclined along the y-axis. This means that it will likely + need to be rotated in most circumstances. + + At present, this profile is not enabled for photon-shooting. + + The allowed range of values for the ``n`` parameter is 0.3 <= n <= 6.2. An exception will be + thrown if you provide a value outside that range, matching the range of the `Sersic` profile. + + This class shares the caching of Hankel transformations with the `Sersic` class; see that + class for documentation on efficiency considerations with regards to caching. + + A profile can be initialized using one (and only one) of two possible size parameters: + ``scale_radius`` or ``half_light_radius``. Exactly one of these two is required. Similarly, + at most one of ``scale_height`` and ``scale_h_over_r`` is required; if neither is given, the + default of scale_h_over_r = 0.1 will be used. Note that if half_light_radius and + scale_h_over_r are supplied (or the default value of scale_h_over_r is used), + scale_h_over_r will be assumed to refer to the scale radius, not the half-light radius. + + Parameters: + n: The Sersic index, n. + inclination: The inclination angle, which must be a `galsim.Angle` instance + scale_radius: The scale radius of the disk. Typically given in arcsec. + This can be compared to the 'scale_radius' parameter of the + `galsim.Sersic` class, and in the face-on case, the same scale + radius will result in the same 2D light distribution as with that + class. Exactly one of this and half_light_radius must be provided. + half_light_radius: The half-light radius of disk when seen face-on. Exactly one of this + and scale_radius must be provided. + scale_height: The scale height of the exponential disk. Typically given in arcsec. + [default: None] + scale_h_over_r: In lieu of the scale height, you may specify the ratio of the + scale height to the scale radius. [default: 0.1] + flux: The flux (in photons) of the profile. [default: 1] + trunc: An optional truncation radius at which the profile is made to drop to + zero, in the same units as the size parameter. + [default: 0, indicating no truncation] + flux_untruncated: Should the provided ``flux`` and ``half_light_radius`` refer to the + untruncated profile? See the documentation of the `Sersic` class for + more details. [default: False] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "inclination" : Angle, "n" : float } + _opt_params = { "scale_height" : float, "scale_h_over_r" : float, "flux" : float, + "trunc" : float, "flux_untruncated" : bool } + _single_params = [ { "scale_radius" : float , "half_light_radius" : float }] + _takes_rng = False + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, n, inclination, half_light_radius=None, scale_radius=None, scale_height=None, + scale_h_over_r=None, flux=1., trunc=0., flux_untruncated=False, gsparams=None): + + self._flux = float(flux) + self._n = float(n) + self._inclination = inclination + self._trunc = float(trunc) + self._gsparams = GSParams.check(gsparams) + + # Check that trunc is valid + if trunc < 0.: + raise GalSimRangeError("trunc must be >= 0. (zero implying no truncation).", trunc, 0.) + + # Parse the radius options + if scale_radius is not None: + if not scale_radius > 0.: + raise GalSimRangeError("scale_radius must be > 0.", scale_radius, 0.) + if half_light_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius and half_light_radius may be specified.", + half_light_radius=half_light_radius, scale_radius=scale_radius) + self._r0 = float(scale_radius) + self._hlr = 0. + elif half_light_radius is not None: + if not half_light_radius > 0.: + raise GalSimRangeError("half_light_radius must be > 0.", half_light_radius, 0.) + self._hlr = float(half_light_radius) + if self._trunc == 0. or flux_untruncated: + with convert_cpp_errors(): + self._r0 = self._hlr / _galsim.SersicHLR(self._n, 1.) + else: + if self._trunc <= math.sqrt(2.) * self._hlr: + raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius", + self._trunc, math.sqrt(2.) * self._hlr) + with convert_cpp_errors(): + self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) + else: + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) + + # Parse the height options + if scale_height is not None: + if not scale_height > 0.: + raise GalSimRangeError("scale_height must be > 0.", scale_height, 0.) + if scale_h_over_r is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_height and scale_h_over_r may be specified", + scale_height=scale_height, scale_h_over_r=scale_h_over_r) + self._h0 = float(scale_height) + else: + if scale_h_over_r is None: + scale_h_over_r = 0.1 + elif not scale_h_over_r > 0.: + raise GalSimRangeError("half_light_radius must be > 0.", scale_h_over_r, 0.) + self._h0 = float(scale_h_over_r) * self._r0 + + # Explicitly check for angle type, so we can give more informative error if eg. a float is + # passed + if not isinstance(inclination, Angle): + raise TypeError("Input inclination should be an Angle") + + # If flux_untrunctated, then the above picked the right radius, but the flux needs + # to be updated. + if self._trunc > 0.: + with convert_cpp_errors(): + self._flux_fraction = _galsim.SersicIntegratedFlux(self._n, self._trunc/self._r0) + if flux_untruncated: + self._flux *= self._flux_fraction + self._hlr = 0. # This will be updated by getHalfLightRadius if necessary. + else: + self._flux_fraction = 1. + + @lazy_property + def _sbp(self): + with convert_cpp_errors(): + return _galsim.SBInclinedSersic(self._n, self._inclination.rad, self._r0, self._h0, + self._flux, self._trunc, self.gsparams._gsp) + + @property + def n(self): + """The Sersic parameter n. + """ + return self._n + @property + def inclination(self): + """The inclination angle. + """ + return self._inclination + @property + def scale_radius(self): + """The scale radius of the exponential disk. + """ + return self._r0 + @property + def scale_height(self): + """The scale height of the disk. + """ + return self._h0 + @property + def trunc(self): + """The truncation radius (if any). + """ + return self._trunc + @property + def scale_h_over_r(self): + """The ratio scale_height / scale_radius. + """ + return self._h0 / self._r0 + + @property + def disk_half_light_radius(self): + """The half-light radius of the exponential disk. + """ + if self._hlr == 0.: + with convert_cpp_errors(): + self._hlr = self._r0 * _galsim.SersicHLR(self._n, self._flux_fraction) + return self._hlr + + def __eq__(self, other): + return (self is other or + (isinstance(other, InclinedSersic) and + (self.n == other.n) and + (self.inclination == other.inclination) and + (self.scale_radius == other.scale_radius) and + (self.scale_height == other.scale_height) and + (self.flux == other.flux) and + (self.trunc == other.trunc) and + (self.gsparams == other.gsparams))) + + def __hash__(self): + return hash(("galsim.InclinedSersic", self.n, self.inclination, self.scale_radius, + self.scale_height, self.flux, self.trunc, self.gsparams)) + def __repr__(self): + return ('galsim.InclinedSersic(n=%r, inclination=%r, scale_radius=%r, scale_height=%r, ' + 'flux=%r, trunc=%r, gsparams=%r)')%( + self.n, self.inclination, self.scale_radius, self.scale_height, self.flux, + self.trunc, self.gsparams) + + def __str__(self): + s = 'galsim.InclinedSersic(n=%s, inclination=%s, scale_radius=%s, scale_height=%s' % ( + self.n, self.inclination, self.scale_radius, self.scale_height) + if self.flux != 1.0: + s += ', flux=%s' % self.flux + if self.trunc != 0.: + s += ', trunc=%s' % self.trunc + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return InclinedSersic(n=self.n, inclination=self.inclination, + scale_radius=self.scale_radius, scale_height=self.scale_height, + flux=flux, trunc=self.trunc, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/integ.html b/docs/_build/html/_modules/galsim/integ.html new file mode 100644 index 00000000000..ff527fd541c --- /dev/null +++ b/docs/_build/html/_modules/galsim/integ.html @@ -0,0 +1,542 @@ + + + + + + galsim.integ — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.integ

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+
+from . import _galsim
+from .errors import GalSimError, GalSimValueError, convert_cpp_errors
+from ._utilities import LRU_Cache
+
+
+
[docs]def int1d(func, min, max, rel_err=1.e-6, abs_err=1.e-12): + """Integrate a 1-dimensional function from min to max. + + Example usage:: + + >>> def func(x): return x**2 + >>> galsim.integ.int1d(func, 0, 1) + 0.33333333333333337 + >>> galsim.integ.int1d(func, 0, 2) + 2.666666666666667 + >>> galsim.integ.int1d(func, -1, 1) + 0.66666666666666674 + + .. note:: + + This uses an adaptive Gauss-Kronrod-Patterson method for doing the integration. + + cf. https://www.jstor.org/stable/2004583 + + If one or both endpoints are infinite, it will automatically use an appropriate + transformation to turn it into a finite integral. + + Parameters: + func: The function to be integrated. y = func(x) should be valid. + min: The lower end of the integration bounds (anything < -1.e10 is treated as + negative infinity). + max: The upper end of the integration bounds (anything > 1.e10 is treated as positive + infinity). + rel_err: The desired relative error [default: 1.e-6] + abs_err: The desired absolute error [default: 1.e-12] + + Returns: + the value of the integral. + """ + min = float(min) + max = float(max) + rel_err = float(rel_err) + abs_err = float(abs_err) + with convert_cpp_errors(): + success, result = _galsim.PyInt1d(func,min,max,rel_err,abs_err) + if success: + return result + else: + raise GalSimError(result)
+ +
[docs]def hankel(func, k, nu=0, rmax=None, rel_err=1.e-6, abs_err=1.e-12): + r"""Perform an order nu Hankel transform of the given function f(r) at a specific k value. + + .. math:: + + F(k) = \int_0^\infty f(r) J_\nu(k r) r dr + + .. note:: + + For non-truncated Hankel integrals, this uses the method outlined in Ogata, 2005: + http://www.kurims.kyoto-u.ac.jp/~prims/pdf/41-4/41-4-40.pdf + + For truncated integrals (and k=0), it uses the same adaptive Gauss-Kronrod-Patterson + method used for `int1d`. + + Parameters: + + func: The function f(r) + k: (float or numpy array) The value(s) of k for which to calculate F(k). + nu: The order of the Bessel function to use for the transform. [default: 0] + rmax: An optional truncation radius at which to have f(r) drop to 0. [default: None] + rel_err: The desired relative accuracy [default: 1.e-6] + abs_err: The desired absolute accuracy [default: 1.e-12] + + Returns: + + An estimate of F(k) + """ + rel_err = float(rel_err) + abs_err = float(abs_err) + nu = float(nu) + rmax = float(rmax) if rmax is not None else 0. + + k = np.ascontiguousarray(k, dtype=float) + res = np.empty_like(k, dtype=float) + N = 1 if k.shape == () else len(k) + + if np.any(k < 0): + raise GalSimValueError("k must be >= 0",k) + if nu < 0: + raise GalSimValueError("nu must be >= 0",k) + _k = k.__array_interface__['data'][0] + _res = res.__array_interface__['data'][0] + with convert_cpp_errors(): + _galsim.PyHankel(func, _k, _res, N, nu, rmax, rel_err, abs_err) + return res
+ +
[docs]class IntegrationRule: + """A class that can be used to integrate something more complicated than a normal + scalar function. + + In GalSim, we use it to do the integration of chromatic images over a bandpass. + Typically f is some kind of draw function, xs are the wavelengths, and w is the + bandpass throughput. But this class is abstracted away from all of that and can be used + for anything where the function returns something complicated, but which can be added + together to compute the quadrature. + + Specifically the return value from f must be closed under both addition and multiplication + by a scalar (a float value). + """ + def __init__(self): + pass + + def __call__(self, f, xs, w=None): + """Calculate the integral int(f(x) w(x) dx) using the appropriate Rule. + + Parameters: + f: Function to integrate. + xs: Locations at which to evaluate f. + w: Weight function if desired [default: None] + + Returns: + The approximation to the integral. + """ + gs = self.calculateWeights(xs, w) + return sum(g * f(x) for g,x in zip(gs, xs))
+ +
[docs]class MidptRule(IntegrationRule): + """Midpoint rule for integration. + """ +
[docs] def calculateWeights(self, xs, w): + """Calculate the apporpriate weights for the midpoint rule integration + + Parameters: + xs: Locations at which to evaluate f. + w: Weight function if desired [default: None] + + Returns: + The net weights to use at each location. + """ + if len(xs) < 2: + raise GalSimValueError("Not enough points for midptRule integration", xs) + xs = np.asarray(xs) + gs = np.empty_like(xs) + gs[0] = (xs[1] - xs[0]) + gs[1:-1] = 0.5 * (xs[2:] - xs[:-2]) + gs[-1] = (xs[-1] - xs[-2]) + if w is not None: + gs *= w(xs) + return gs
+ +
[docs]class TrapzRule(IntegrationRule): + """Trapezoidal rule for integration. + """ +
[docs] def calculateWeights(self, xs, w): + """Calculate the apporpriate weights for the trapezoidal rule integration + + Parameters: + xs: Locations at which to evaluate f. + w: Weight function if desired [default: None] + + Returns: + The net weights to use at each location. + """ + if len(xs) < 2: + raise GalSimValueError("Not enough points for trapzRule integration", xs) + xs = np.asarray(xs) + gs = np.empty_like(xs) + gs[0] = 0.5 * (xs[1] - xs[0]) + gs[1:-1] = 0.5 * (xs[2:] - xs[:-2]) + gs[-1] = 0.5 * (xs[-1] - xs[-2]) + if w is not None: + gs *= w(xs) + return gs
+ +
[docs]class QuadRule(IntegrationRule): + """Quadratic rule for integration + + This models both f and w as linear between the evaluation points, so the product is + quadratic. + """ +
[docs] def calculateWeights(self, xs, w): + """Calculate the apporpriate weights for the quadratic rule integration + + Parameters: + xs: Locations at which to evaluate f. + w: Weight function if desired [default: None] + + Returns: + The net weights to use at each location. + """ + if len(xs) < 2: + raise GalSimValueError("Not enough points for quadRule integration", xs) + if w is None: + return TrapzRule().calculateWeights(xs,w) + xs = np.asarray(xs) + ws = w(xs) + gs = np.empty_like(xs) + gs[0] = (xs[1] - xs[0]) * (2*ws[0] + ws[1]) + gs[1:-1] = (xs[1:-1] - xs[:-2]) * (ws[:-2] + 2*ws[1:-1]) + gs[1:-1] += (xs[2:] - xs[1:-1]) * (2*ws[1:-1] + ws[2:]) + gs[-1] = (xs[-1] - xs[-2]) * (ws[-2] + 2*ws[-1]) + gs /= 6. + return gs
+ +# To ease backwards compatibility, these are an instantiated object of the above classes +midptRule = MidptRule() #: For convenience, an instance of `MidptRule` +trapzRule = TrapzRule() #: For convenience, an instance of `TrapzRule` +quadRule = QuadRule() #: For convenience, an instance of `QuadRule` + + +
[docs]class ImageIntegrator: + """A base class for integrators used by `ChromaticObject` to integrate the drawn images + over wavelengthh using a `Bandpass` as a weight function. + """ + def __init__(self): + raise NotImplementedError("Must instantiate subclass of ImageIntegrator") + # subclasses must define + # 1) a method `.calculateWaves(bandpass)` which will return the wavelengths at which to + # evaluate the integrand + # 2) an function attribute `.rule` which takes an integrand function as its first + # argument, and a list of evaluation wavelengths as its second argument, and returns + # an approximation to the integral. (E.g., the function midptRule above) + +
[docs] def __call__(self, evaluateAtWavelength, bandpass, image, drawImageKwargs, doK=False): + """ + Parameters: + evaluateAtWavelength: Function that returns a monochromatic surface brightness + profile as a function of wavelength. + bandpass: `Bandpass` object representing the filter being imaged through. + image: `Image` used to set size and scale of output + drawImageKwargs: dict with other kwargs to send to `ChromaticObject.drawImage` + function. + doK: Integrate up results of `ChromaticObject.drawKImage` instead of + results of `ChromaticObject.drawImage`. [default: False] + + Returns: + the result of integral as an `Image` + """ + waves = self.calculateWaves(bandpass) + self.last_n_eval = len(waves) + drawImageKwargs.pop('add_to_image', None) # Make sure add_to_image isn't in kwargs + + def integrand(w): + prof = evaluateAtWavelength(w) + if not doK: + return prof.drawImage(image=image.copy(), **drawImageKwargs) + else: + return prof.drawKImage(image=image.copy(), **drawImageKwargs) + return self.rule(integrand, waves, bandpass)
+ + +
[docs]class SampleIntegrator(ImageIntegrator): + """Create a chromatic surface brightness profile integrator, which will integrate over + wavelength using a `Bandpass` as a weight function. + + This integrator will evaluate the integrand only at the wavelengths in ``bandpass.wave_list``. + See ContinuousIntegrator for an integrator that evaluates the integrand at a given number of + points equally spaced apart. + + Parameters: + rule: Which integration rule to apply to the wavelength and monochromatic surface + brightness samples. Options include: + + - galsim.integ.midptRule: Use the midpoint integration rule + - galsim.integ.trapzRule: Use the trapezoidal integration rule + - galsim.integ.quadRule: Use the quadratic integration rule + + """ + def __init__(self, rule): + self.rule = rule + + def calculateWaves(self, bandpass): + return bandpass.wave_list
+ + +
[docs]class ContinuousIntegrator(ImageIntegrator): + """Create a chromatic surface brightness profile integrator, which will integrate over + wavelength using a `Bandpass` as a weight function. + + This integrator will evaluate the integrand at a given number ``N`` of equally spaced + wavelengths over the interval defined by bandpass.blue_limit and bandpass.red_limit. See + SampleIntegrator for an integrator that only evaluates the integrand at the predefined set of + wavelengths in ``bandpass.wave_list``. + + Parameters: + rule: Which integration rule to apply to the wavelength and monochromatic + surface brightness samples. Options include: + + - galsim.integ.midptRule: Use the midpoint integration rule + - galsim.integ.trapzRule: Use the trapezoidal integration rule + - galsim.integ.quadRule: Use the quadratic integration rule + + N: Number of equally-wavelength-spaced monochromatic surface brightness + samples to evaluate. [default: 250] + use_endpoints: Whether to sample the endpoints ``bandpass.blue_limit`` and + ``bandpass.red_limit``. This should probably be True for a rule like + numpy.trapz, which explicitly samples the integration limits. For a + rule like the midpoint rule, however, the integration limits are not + generally sampled, (only the midpoint between each integration limit and + its nearest interior point is sampled), thus ``use_endpoints`` should be + set to False in this case. [default: True] + """ + def __init__(self, rule, N=250, use_endpoints=True): + self.rule = rule + self.N = N + self.use_endpoints = use_endpoints + + def calculateWaves(self, bandpass): + h = (bandpass.red_limit*1.0 - bandpass.blue_limit)/self.N + if self.use_endpoints: + return [bandpass.blue_limit + h * i for i in range(self.N+1)] + else: + return [bandpass.blue_limit + h * (i+0.5) for i in range(self.N)]
+ + +_leggauss = LRU_Cache(np.polynomial.legendre.leggauss) + + +def gq_annulus_points(r_outer, r_inner, n_rings, n_spokes): + r""" Generate points and weights for Gaussian quadrature over an annulus. + + Sample points are generated on a grid of rings (constant radius) and spokes + (constant azimuth). The sample points with the weights can be used to + approximate an integral over an annulus as + + .. math:: + \Int_annulus f(x, y) dx dy = \Sum_{x, y, w} f(x, y) * w + + To integrate a nth order 2d polynomial over an annulus exactly requires + n_rings = n//2+1 + n_spokes = n+1 + + References: + - G. W. Forbes, + "Optical system assessment for design: numerical ray tracing in the Gaussian pupil," + J. Opt. Soc. Am. A 5, 1943-1956 (1988) + + - Brian J. Bauman, Hong Xiao, + "Gaussian quadrature for optical design with noncircular pupils and fields, and broad wavelength range," + Proc. SPIE 7652, International Optical Design Conference 2010, 76522S (9 September 2010); https://doi.org/10.1117/12.872773 + + Parameters: + r_outer: Outer radius of annulus + r_inner: Inner radius of annulus + n_rings: Number of sample rings to use in the Gaussian quadrature + n_spokes: Number of sample spokes to use in the Gaussian quadrature + + Returns: + x, y: Cartesian coordinates of the sample points + weights: Weights to use for the Gaussian quadrature + + """ + Li, w = _leggauss(n_rings) + eps = r_inner/r_outer + area = np.pi*(r_outer**2 - r_inner**2) + rings = np.sqrt(eps**2 + (1+Li)*(1-eps**2)/2)*r_outer + spokes = np.linspace(0, 2*np.pi, n_spokes, endpoint=False) + weights = w*area/(2*n_spokes) + rings, spokes = np.meshgrid(rings, spokes) + weights = np.broadcast_to(weights, rings.shape) + rings = rings.ravel() + spokes = spokes.ravel() + weights = weights.ravel() + x = rings*np.cos(spokes) + y = rings*np.sin(spokes) + return x, y, weights + + +def gq_annulus(f, r_outer, r_inner, n_rings, n_spokes): + """ Integrate a function over an annular region, using Gaussian quadrature + over an annulus. + + References: + - G. W. Forbes, + "Optical system assessment for design: numerical ray tracing in the Gaussian pupil," + J. Opt. Soc. Am. A 5, 1943-1956 (1988) + + - Brian J. Bauman, Hong Xiao, + "Gaussian quadrature for optical design with noncircular pupils and fields, and broad wavelength range," + Proc. SPIE 7652, International Optical Design Conference 2010, 76522S (9 September 2010); https://doi.org/10.1117/12.872773 + + The annulus is sampled on a grid of rings and spokes. + To integrate a nth order 2d polynomial exactly, this technique requires + n_rings = n//2+1 + n_spokes = n+1 + + Parameters: + f: Function to integrate + r_inner: Inner radius of annulus + r_outer: Outer radius of annulus + n_rings: Number of sample rings to use in the Gaussian quadrature + n_spokes: Number of sample spokes to use in the Gaussian quadrature + + Returns: + The integral of f over the annulus + """ + x, y, weights = gq_annulus_points(r_outer, r_inner, n_rings, n_spokes) + val = np.array([f(x_, y_) for x_, y_ in zip(x, y)]) + return np.sum(val*weights) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/interpolant.html b/docs/_build/html/_modules/galsim/interpolant.html new file mode 100644 index 00000000000..9f2d1c4cd4f --- /dev/null +++ b/docs/_build/html/_modules/galsim/interpolant.html @@ -0,0 +1,807 @@ + + + + + + galsim.interpolant — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.interpolant

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Interpolant', 'Nearest', 'Linear', 'Cubic', 'Quintic',
+            'Lanczos', 'SincInterpolant', 'Delta', ]
+
+import math
+import numpy as np
+import copy
+
+from . import _galsim
+from .gsparams import GSParams
+from ._utilities import lazy_property
+from .errors import GalSimValueError
+from .integ import int1d
+from .bessel import si
+
+
[docs]class Interpolant: + """A base class that defines how interpolation should be done. + + An Interpolant is needed for an `InterpolatedImage` to define how interpolation should be done + an locations in between the integer pixel centers. + """ + def __init__(self): + raise NotImplementedError( + "The Interpolant base class should not be instantiated directly. " + "Use one of the subclasses instead, or use the `from_name` factory function.") + +
[docs] @staticmethod + def from_name(name, tol=None, gsparams=None): + """A factory function to create an `Interpolant` of the correct type according to + the (string) name of the `Interpolant`. + + This is mostly used to simplify how config files specify the `Interpolant` to use. + + Valid names are: + + - 'delta' = `Delta` + - 'nearest' = `Nearest` + - 'sinc' = `SincInterpolant` + - 'linear' = `Linear` + - 'cubic' = `Cubic` + - 'quintic' = `Quintic` + - 'lanczosN' = `Lanczos` (where N is an integer, given the ``n`` parameter) + + In addition, if you want to specify the ``conserve_dc`` option for `Lanczos`, you can + append either T or F to represent ``conserve_dc = True/False`` (respectively). Otherwise, + the default ``conserve_dc=True`` is used. + + Parameters: + name: The name of the interpolant to create. + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + gsparams = GSParams.check(gsparams) + + # Do these in rough order of likelihood (most to least) + if name.lower() == 'quintic': + return Quintic(gsparams=gsparams) + if name.lower().startswith('lanczos'): + conserve_dc = True + if name[-1].upper() in ('T', 'F'): + conserve_dc = (name[-1].upper() == 'T') + name = name[:-1] + try: + n = int(name[7:]) + except Exception: + raise GalSimValueError("Invalid Lanczos specification. Should look like " + "lanczosN, where N is an integer", name) from None + return Lanczos(n, conserve_dc, gsparams=gsparams) + elif name.lower() == 'linear': + return Linear(gsparams=gsparams) + elif name.lower() == 'cubic' : + return Cubic(gsparams=gsparams) + elif name.lower() == 'nearest': + return Nearest(gsparams=gsparams) + elif name.lower() == 'delta': + return Delta(gsparams=gsparams) + elif name.lower() == 'sinc': + return SincInterpolant(gsparams=gsparams) + else: + raise GalSimValueError("Invalid Interpolant name %s.",name, + ('linear', 'cubic', 'quintic', 'lanczosN', 'nearest', 'delta', + 'sinc'))
+ + @property + def gsparams(self): + """The `GSParams` of the `Interpolant` + """ + return self._gsparams + + @property + def positive_flux(self): + """The positive-flux fraction of the interpolation kernel.""" + return self._i.getPositiveFlux(); + + @property + def negative_flux(self): + """The negative-flux fraction of the interpolation kernel.""" + return self._i.getNegativeFlux(); + + @property + def tol(self): + from .deprecated import depr + depr('interpolant.tol', 2.2, 'interpolant.gsparams.kvalue_accuracy') + return self._gsparams.kvalue_accuracy + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current interpolant with the given gsparams + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + return ret
+ + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_i', None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + def __eq__(self, other): + return (self is other or (isinstance(other, self.__class__) and repr(self) == repr(other))) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash(repr(self)) + +
[docs] def xval(self, x): + """Calculate the value of the interpolant kernel at one or more x values + + Parameters: + x: The value (as a float) or values (as a np.array) at which to compute the + amplitude of the Interpolant kernel. + + Returns: + xval: The value(s) at the x location(s). If x was an array, then this is also + an array. + """ + xx = np.array(x, dtype=float, copy=True) + if xx.shape == (): + return self._i.xval(float(xx)) + else: + dimen = len(x.shape) + if dimen > 1: + raise GalSimValueError("Input x must be 1-dimensional", x) + _xx = xx.__array_interface__['data'][0] + self._i.xvalMany(_xx, len(xx)) + return xx
+ +
[docs] def kval(self, k): + """Calculate the value of the interpolant kernel in Fourier space at one or more k values. + + Parameters: + k: The value (as a float) or values (as a np.array) at which to compute the + amplitude of the Interpolant kernel in Fourier space. + + Returns: + kval: The k-value(s) at the k location(s). If k was an array, then this is also + an array. + """ + # Note: the C++ layer uses u = k/2pi rather than k. + u = np.array(k, dtype=float, copy=True) / (2.*np.pi) + if u.shape == (): + return self._i.uval(float(u)) + else: + dimen = len(k.shape) + if dimen > 1: + raise GalSimValueError("Input k must be 1-dimensional", k) + _u = u.__array_interface__['data'][0] + self._i.uvalMany(_u, len(u)) + return u
+ +
[docs] def unit_integrals(self, max_len=None): + """Compute the unit integrals of the real-space kernel. + + integrals[i] = int(xval(x), i-0.5, i+0.5) + + Parameters: + max_len: The maximum length of the returned array. This is usually only relevant + for SincInterpolant, where xrange = inf. + + Returns: + integrals: An array of unit integrals of length max_len or smaller. + """ + n = self.ixrange//2 + 1 + n = n if max_len is None else min(n, max_len) + if not hasattr(self, '_unit_integrals') or n > len(self._unit_integrals): + # Note: This is pretty fast, but subclasses may choose to override this if there + # is an analytic integral to avoid the int1d call. + self._unit_integrals = np.zeros(n) + for i in range(n): + self._unit_integrals[i] = int1d(self.xval, i-0.5, i+0.5) + return self._unit_integrals[:n]
+ + # Sub-classes should define _i property, repr, and str + + +
[docs]class Delta(Interpolant): + """Delta-function interpolation. + + The interpolant for when you do not want to interpolate between samples. It is not really + intended to be used for any analytic drawing because it is infinite in the x domain at the + location of samples, and it extends to infinity in the u domain. But it could be useful for + photon-shooting, where it is trivially implemented as no displacements. + + Parameters: + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.Delta(self._gsparams._gsp) + + def __repr__(self): + return "galsim.Delta(gsparams=%r)"%(self._gsparams) + + def __str__(self): + return "galsim.Delta()" + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + return 0. + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 0 + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + return 2. * math.pi / self._gsparams.kvalue_accuracy + + _unit_integrals = np.array([1], dtype=float) + +
[docs] def unit_integrals(self, max_len=None): + """Compute the unit integrals of the real-space kernel. + + integrals[i] = int(xval(x), i-0.5, i+0.5) + + Parameters: + max_len: The maximum length of the returned array. (ignored) + + Returns: + integrals: An array of unit integrals of length max_len or smaller. + """ + return self._unit_integrals
+ + +
[docs]class Nearest(Interpolant): + """Nearest-neighbor interpolation (boxcar). + + The nearest-neighbor interpolant performs poorly as a k-space or x-space interpolant for + interpolated images. (See paper by "Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) + The objection to its use in Fourier space does not apply when shooting photons to generate + an image; in that case, the nearest-neighbor interpolant is quite efficient (but not + necessarily the best choice in terms of accuracy). + + Parameters: + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.Nearest(self._gsparams._gsp) + + def __repr__(self): + return "galsim.Nearest(gsparams=%r)"%(self._gsparams) + + def __str__(self): + return "galsim.Nearest()" + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + return 0.5 + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 1 + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + return 2. / self._gsparams.kvalue_accuracy + + _unit_integrals = np.array([1], dtype=float) + +
[docs] def unit_integrals(self, max_len=None): + """Compute the unit integrals of the real-space kernel. + + integrals[i] = int(xval(x), i-0.5, i+0.5) + + Parameters: + max_len: The maximum length of the returned array. (ignored) + + Returns: + integrals: An array of unit integrals of length max_len or smaller. + """ + return self._unit_integrals
+ + +
[docs]class SincInterpolant(Interpolant): + """Sinc interpolation (inverse of nearest-neighbor). + + The Sinc interpolant (K(x) = sin(pi x)/(pi x)) is mathematically perfect for band-limited + data, introducing no spurious frequency content beyond kmax = pi/dx for input data with pixel + scale dx. However, it is formally infinite in extent and, even with reasonable trunction, is + still quite large. It will give exact results in `GSObject.kValue` for `InterpolatedImage` + when it is used as a k-space interpolant, but is extremely slow. The usual compromise between + sinc accuracy vs. speed is the `Lanczos` interpolant (see its documentation for details). + + Parameters: + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.SincInterpolant(self._gsparams._gsp) + + def __repr__(self): + return "galsim.SincInterpolant(gsparams=%r)"%(self._gsparams) + + def __str__(self): + return "galsim.SincInterpolant()" + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + # Technically infinity, but truncated by the tolerance. + return 1./(math.pi * self._gsparams.kvalue_accuracy) + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 2*int(np.ceil(self.xrange)) + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + return math.pi + +
[docs] def unit_integrals(self, max_len=None): + """Compute the unit integrals of the real-space kernel. + + integrals[i] = int(xval(x), i-0.5, i+0.5) + + Parameters: + max_len: The maximum length of the returned array. + + Returns: + integrals: An array of unit integrals of length max_len or smaller. + """ + n = self.ixrange//2 + 1 + n = n if max_len is None else min(n, max_len) + if not hasattr(self, '_unit_integrals') or n > len(self._unit_integrals): + self._unit_integrals = np.zeros(n) + for i in range(n): + self._unit_integrals[i] = (si(np.pi * (i+0.5)) - si(np.pi * (i-0.5))) / np.pi + return self._unit_integrals[:n]
+ + +
[docs]class Linear(Interpolant): + """Linear interpolation + + The linear interpolant is a poor choice for FFT-based operations on interpolated images, as + it rings to high frequencies. (See Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) + This objection does not apply when shooting photons, in which case the linear interpolant is + quite efficient (but not necessarily the best choice in terms of accuracy). + + Parameters: + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.Linear(self._gsparams._gsp) + + def __repr__(self): + return "galsim.Linear(gsparams=%r)"%(self._gsparams) + + def __str__(self): + return "galsim.Linear()" + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + # Reduce range slightly so not including points with zero weight. + return 1. + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 2 + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + return 2. / self._gsparams.kvalue_accuracy**0.5 + + _unit_integrals = np.array([0.75, 0.125], dtype=float) + +
[docs] def unit_integrals(self, max_len=None): + """Compute the unit integrals of the real-space kernel. + + integrals[i] = int(xval(x), i-0.5, i+0.5) + + Parameters: + max_len: The maximum length of the returned array. This is usually only relevant + for SincInterpolant, where xrange = inf. + Returns: + integrals: An array of unit integrals of length max_len or smaller. + """ + # i0 = 2*int(1-x, x=0..0.5) + # = 3/4 + # i1 = int(1-x, x=0.5..1) + # = 1/8 + return self._unit_integrals
+ + +
[docs]class Cubic(Interpolant): + """Cubic interpolation + + The cubic interpolant is exact to 3rd order Taylor expansion (from R. G. Keys, IEEE Trans. + Acoustics, Speech, & Signal Proc 29, p 1153, 1981). It is a reasonable choice for a four-point + interpolant for interpolated images. (See Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) + + Parameters: + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.Cubic(self._gsparams._gsp) + + def __repr__(self): + return "galsim.Cubic(gsparams=%r)"%(self._gsparams) + + def __str__(self): + return "galsim.Cubic()" + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + return 2. + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 4 + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + # kmax = 2 * (3sqrt(3)/8 tol)^1/3 + return 1.7320508075688774 / self._gsparams.kvalue_accuracy**(1./3.) + + _unit_integrals = np.array([161./192, 3./32, -5./384], dtype=float) + +
[docs] def unit_integrals(self, max_len=None): + """Compute the unit integrals of the real-space kernel. + + integrals[i] = int(xval(x), i-0.5, i+0.5) + + Parameters: + max_len: The maximum length of the returned array. This is usually only relevant + for SincInterpolant, where xrange = inf. + Returns: + integrals: An array of unit integrals of length max_len or smaller. + """ + # i0 = 2*int(1 + 1/2 x^2(3x-5), x=0..0.5) + # = 161/192 + # i1 = int(1 + 1/2 x^2(3x-5), x=0.5..1) - int(1/2 (x-1)*(x-2)^2, x=1..1.5) + # = 47/384 - 11/384 = 3/32 + # i2 = -int(1/2 (x-1)*(x-2)^2, x=1.5..2) + # = -5/384 + return self._unit_integrals
+ + +
[docs]class Quintic(Interpolant): + """Fifth order interpolation + + The quintic interpolant is exact to 5th order in the Taylor expansion and was found by + Bernstein & Gruen (http://arxiv.org/abs/1401.2636) to give optimal results as a k-space + interpolant. + + Parameters: + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.Quintic(self._gsparams._gsp) + + def __repr__(self): + return "galsim.Quintic(gsparams=%r)"%(self._gsparams) + + def __str__(self): + return "galsim.Quintic()" + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + return 3. + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 6 + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + # kmax = 2 * (25sqrt(5)/108 tol)^1/3 + return 1.6058208066649935 / self._gsparams.kvalue_accuracy**(1./3.)
+ + +
[docs]class Lanczos(Interpolant): + """The Lanczos interpolation filter, nominally sinc(x)*sinc(x/n) + + The Lanczos filter is an approximation to the band-limiting sinc filter with a smooth cutoff + at high x. Order n Lanczos has a range of +/- n pixels. It typically is a good compromise + between kernel size and accuracy. + + Note that pure Lanczos, when interpolating a set of constant-valued samples, does not return + this constant. Setting ``conserve_dc`` in the constructor tweaks the function so that it + approximately conserves the value of constant (DC) input data (accurate to better than 1.e-5 + when used in two dimensions). + + Parameters: + n: The order of the Lanczos function + conserve_dc: Whether to add the first order correction to flatten out the flux response + to a constant input. [default: True, see above] + tol: [deprecated] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, n, conserve_dc=True, tol=None, gsparams=None): + if tol is not None: + from .deprecated import depr + depr('tol', 2.2, 'gsparams=GSParams(kvalue_accuracy=tol)') + gsparams = GSParams(kvalue_accuracy=tol) + self._n = int(n) + self._conserve_dc = bool(conserve_dc) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _i(self): + return _galsim.Lanczos(self._n, self._conserve_dc, self._gsparams._gsp) + + def __repr__(self): + return "galsim.Lanczos(%r, %r, gsparams=%r)"%(self._n, self._conserve_dc, self._gsparams) + + def __str__(self): + return "galsim.Lanczos(%s)"%(self._n) + + @property + def n(self): + """The order of the Lanczos function. + """ + return self._n + + @property + def conserve_dc(self): + """Whether this interpolant is modified to improve flux conservation. + """ + return self._conserve_dc + + @property + def xrange(self): + """The maximum extent of the interpolant from the origin (in pixels). + """ + return self._n + + @property + def ixrange(self): + """The total integral range of the interpolant. Typically 2 * xrange. + """ + return 2*self._n + + @property + def krange(self): + """The maximum extent of the interpolant in Fourier space (in 1/pixels). + """ + return 2. * math.pi * self._i.urange()
+ +
[docs]def convert_interpolant(interpolant): + """Convert a given interpolant to an `Interpolant` if it is given as a string. + + Parameter: + interpolant: Either an `Interpolant` or a string to convert. + + Returns: + an `Interpolant` + """ + if isinstance(interpolant, Interpolant): + return interpolant + else: + # Will raise an appropriate exception if this is invalid. + return Interpolant.from_name(interpolant)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/interpolatedimage.html b/docs/_build/html/_modules/galsim/interpolatedimage.html new file mode 100644 index 00000000000..105da433d92 --- /dev/null +++ b/docs/_build/html/_modules/galsim/interpolatedimage.html @@ -0,0 +1,1230 @@ + + + + + + galsim.interpolatedimage — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.interpolatedimage

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'InterpolatedImage', '_InterpolatedImage',
+            'InterpolatedKImage', '_InterpolatedKImage' ]
+
+import numpy as np
+import math
+import copy
+
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .image import Image
+from .bounds import _BoundsI
+from .position import PositionD, _PositionD
+from .interpolant import Quintic
+from .utilities import convert_interpolant, lazy_property, doc_inherit, basestring
+from .random import BaseDeviate
+from . import _galsim
+from . import fits
+from .errors import GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError
+from .errors import GalSimIncompatibleValuesError, convert_cpp_errors, galsim_warn
+from .wcs import BaseWCS, PixelScale
+from .noise import GaussianNoise
+
+
+
[docs]class InterpolatedImage(GSObject): + """A class describing non-parametric profiles specified using an `Image`, which can be + interpolated for the purpose of carrying out transformations. + + The InterpolatedImage class is useful if you have a non-parametric description of an object as + an `Image`, that you wish to manipulate / transform using `GSObject` methods such as + `GSObject.shear`, `GSObject.magnify`, `GSObject.shift`, etc. Note that when convolving an + InterpolatedImage, the use of real-space convolution is not recommended, since it is typically + a great deal slower than Fourier-space convolution for this kind of object. + + There are three options for determining the flux of the profile. + + 1. You can simply specify a ``flux`` value explicitly. + 2. If you set ``normalization = 'flux'``, the flux will be taken as the sum of the pixel values + in the input image. This corresponds to an image that was drawn with + ``drawImage(method='no_pixel')``. This is the default if flux is not given. + 3. If you set ``normalization = 'sb'``, the pixel values are treated as samples of the surface + brightness profile at each location. This corresponds to an image drawn with + ``drawImage(method='sb')``. The flux is then the sum of the pixels in the input image + multiplied by the pixel area. + + You can also use images that were drawn with one of the pixel-integrating methods ('auto', + 'phot', 'fft', or 'real_space'), or real images where nature has integrated over the pixel for + you. However, the resulting profile will by default include a convolution by a `Pixel` (this + is equivalent to integration over the pixel). This is often acceptable, and the resulting + `InterpolatedImage` object can be convolved by other profiles as usual. One just needs to + remember to draw the final convolved profile using ``method='no_pixel'`` to avoid including + the pixel convolution a second time. In particular, one should not use it in conjunction + with photon shooting, for the same reason. + + However, if you want to try to remove the effect of the pixel and have the `InterpolatedImage` + model the pre-pixelized profile, then you can set ``depixelize=True``. This will call + `Image.depixelize` on the image automatically to try to remove the effect of the pixelization. + We recommend using a Lanczos interpolant with this option for best results. (Higher order + tends to work better here.) This step can be rather slow and memory-demanding, so use this + with caution. But if used, the resulting profile represents the true underlying profile, + without the pixel convolution. It can therefore be rotated, sheared, etc. And when rendering, + one should use the methods that do involve integration over the pixel: ``auto``, ``phot``, etc. + + .. warning :: + + Input images that are undersampled and/or noisy may not necessarily work well with the + ``depixelize=True`` option. Users should treat this option with some care and validate + that the results are sufficiently accurate for your particular use case. + + If the input `Image` has a ``scale`` or ``wcs`` associated with it, then there is no need to + specify one as a parameter here. But if one is provided, that will override any ``scale`` or + ``wcs`` that is native to the `Image`. + + The user may optionally specify an interpolant, ``x_interpolant``, for real-space manipulations + (e.g., shearing, resampling). If none is specified, then by default, a `Quintic` interpolant is + used. The user may also choose to specify two quantities that can affect the Fourier space + convolution: the k-space interpolant (``k_interpolant``) and the amount of padding to include + around the original images (``pad_factor``). The default values for ``x_interpolant``, + ``k_interpolant``, and ``pad_factor`` were chosen based on the tests of branch #389 to reach + good accuracy without being excessively slow. Users should be particularly wary about changing + ``k_interpolant`` and ``pad_factor`` from the defaults without careful testing. The user is + given complete freedom to choose interpolants and pad factors, and no warnings are raised when + the code is modified to choose some combination that is known to give significant error. More + details can be found in http://arxiv.org/abs/1401.2636, especially table 1, and in comment + https://github.com/GalSim-developers/GalSim/issues/389#issuecomment-26166621 and the following + comments. + + The user can choose to pad the image with a noise profile if desired. To do so, specify + the target size for the noise padding in ``noise_pad_size``, and specify the kind of noise + to use in ``noise_pad``. The ``noise_pad`` option may be a Gaussian random noise of some + variance, or a Gaussian but correlated noise field that is specified either as a + `BaseCorrelatedNoise` instance, an `Image` (from which a correlated noise model is derived), or + a string (interpreted as a filename of an image to use for deriving a `CorrelatedNoise`). + The user can also pass in a random number generator to be used for noise generation. Finally, + the user can pass in a ``pad_image`` for deterministic image padding. + + By default, the InterpolatedImage recalculates the Fourier-space step and number of points to + use for further manipulations, rather than using the most conservative possibility. For typical + objects representing galaxies and PSFs this can easily make the difference between several + seconds (conservative) and 0.04s (recalculated). However, the user can turn off this option, + and may especially wish to do so when using images that do not contain a high S/N object - e.g., + images of noise fields. + + + Example:: + + >>> interpolated_image = galsim.InterpolatedImage( + image, x_interpolant=None, k_interpolant=None, normalization='flux', scale=None, + wcs=None, flux=None, pad_factor=4., noise_pad_size=0, noise_pad=0., use_cache=True, + pad_image=None, rng=None, calculate_stepk=True, calculate_maxk=True, + use_true_center=True, offset=None, hdu=None) + + Initializes ``interpolated_image`` as an InterpolatedImage instance. + + For comparison of the case of padding with noise or zero when the image itself includes noise, + compare ``im1`` and ``im2`` from the following code snippet (which can be executed from the + examples/ directory):: + + >>> image = galsim.fits.read('data/147246.0_150.416558_1.998697_masknoise.fits') + >>> int_im1 = galsim.InterpolatedImage(image) + >>> int_im2 = galsim.InterpolatedImage(image, noise_pad='data/blankimg.fits') + >>> im1 = galsim.ImageF(1000,1000) + >>> im2 = galsim.ImageF(1000,1000) + >>> int_im1.drawImage(im1, method='no_pixel') + >>> int_im2.drawImage(im2, method='no_pixel') + + Examination of these two images clearly shows how padding with a correlated noise field that is + similar to the one in the real data leads to a more reasonable appearance for the result when + re-drawn at a different size. + + Parameters: + image: The `Image` from which to construct the object. + This may be either an `Image` instance or a string indicating a fits + file from which to read the image. In the latter case, the ``hdu`` + kwarg can be used to specify a particular HDU in that file. + x_interpolant: Either an `Interpolant` instance or a string indicating which real-space + interpolant should be used. Options are 'nearest', 'sinc', 'linear', + 'cubic', 'quintic', or 'lanczosN' where N should be the integer order + to use. [default: galsim.Quintic()] + k_interpolant: Either an `Interpolant` instance or a string indicating which k-space + interpolant should be used. Options are 'nearest', 'sinc', 'linear', + 'cubic', 'quintic', or 'lanczosN' where N should be the integer order + to use. We strongly recommend leaving this parameter at its default + value; see text above for details. [default: galsim.Quintic()] + normalization: Two options for specifying the normalization of the input `Image`: + + - "flux" or "f" means that the sum of the pixels is normalized + to be equal to the total flux. + - "surface brightness" or "sb" means that the pixels sample + the surface brightness distribution at each location. + + This is overridden if you specify an explicit flux value. + [default: "flux"] + scale: If provided, use this as the pixel scale for the `Image`; this will + override the pixel scale stored by the provided `Image`, in any. + If ``scale`` is ``None``, then take the provided image's pixel scale. + [default: None] + wcs: If provided, use this as the wcs for the image. At most one of + ``scale`` or ``wcs`` may be provided. [default: None] + flux: Optionally specify a total flux for the object, which overrides the + implied flux normalization from the `Image` itself. [default: None] + pad_factor: Factor by which to pad the `Image` with zeros. We strongly recommend + leaving this parameter at its default value; see text above for + details. [default: 4] + noise_pad_size: If provided, the image will be padded out to this size (in arcsec) with + the noise specified by ``noise_pad``. This is important if you are + planning to whiten the resulting image. You want to make sure that the + noise-padded image is larger than the postage stamp onto which you are + drawing this object. [default: None] + noise_pad: Noise properties to use when padding the original image with + noise. This can be specified in several ways: + + a) as a float, which is interpreted as being a variance to use when + padding with uncorrelated Gaussian noise; + b) as a `galsim.BaseCorrelatedNoise`, which contains information about + the desired noise power spectrum - any random number generator passed + to the ``rng`` keyword will take precedence over that carried in + an input `BaseCorrelatedNoise` instance; + c) as an `Image` of a noise field, which is used to calculate + the desired noise power spectrum; or + d) as a string which is interpreted as a filename containing an + example noise field with the proper noise power spectrum (as an + `Image` in the first HDU). + + It is important to keep in mind that the calculation of the correlation + function that is internally stored within a `BaseCorrelatedNoise` + object is a non-negligible amount of overhead, so the recommended means + of specifying a correlated noise field for padding are (b) or (d). In + the case of (d), if the same file is used repeatedly, then the + ``use_cache`` keyword (see below) can be used to prevent the need for + repeated `CorrelatedNoise` initializations. [default: 0, i.e., pad + with zeros] + use_cache: Specify whether to cache ``noise_pad`` read in from a file to save + having to build a CorrelatedNoise object repeatedly from the same image. + [default: True] + rng: If padding by noise, the user can optionally supply the random noise + generator to use for drawing random numbers as ``rng`` (may be any kind + of `BaseDeviate` object). Such a user-input random number generator + takes precedence over any stored within a user-input + `BaseCorrelatedNoise` instance (see the ``noise_pad`` parameter). + If ``rng=None``, one will be automatically created, using the time as a + seed. [default: None] + pad_image: `Image` to be used for deterministically padding the original image. + This can be specified in two ways: + + - as an `Image`; or + - as a string which is interpreted as a filename containing an + image to use (in the first HDU). + + The ``pad_image`` scale or wcs is ignored. It uses the same scale or + wcs for both the ``image`` and the ``pad_image``. + The user should be careful to ensure that the image used for padding + has roughly zero mean. The purpose of this keyword is to allow for a + more flexible representation of some noise field around an object; if + the user wishes to represent the sky level around an object, they + should do that after they have drawn the final image instead. + [default: None] + calculate_stepk: Specify whether to perform an internal determination of the extent of + the object being represented by the InterpolatedImage; often this is + useful in choosing an optimal value for the stepsize in the Fourier + space lookup table. + If you know a priori an appropriate maximum value for ``stepk``, then + you may also supply that here instead of a bool value, in which case + the ``stepk`` value is still calculated, but will not go above the + provided value. + [default: True] + calculate_maxk: Specify whether to perform an internal determination of the highest + spatial frequency needed to accurately render the object being + represented by the InterpolatedImage; often this is useful in choosing + an optimal value for the extent of the Fourier space lookup table. + If you know a priori an appropriate maximum value for ``maxk``, then + you may also supply that here instead of a bool value, in which case + the ``maxk`` value is still calculated, but will not go above the + provided value. + [default: True] + use_true_center: Similar to the same parameter in the `GSObject.drawImage` function, + this sets whether to use the true center of the provided image as the + center of the profile (if ``use_true_center=True``) or the nominal + center given by image.center (if ``use_true_center=False``) + [default: True] + depixelize: Whether to try to remove the effect of the pixelization. If this is + True, then drawing this profile with method='auto' should render an + image equivalent to the input image. If this is False (the default), + then you would need to draw with method='no_pixel' to get an equivalent + image. See discussion above. [default: False] + offset: The location in the input image to use as the center of the profile. + This should be specified relative to the center of the input image + (either the true center if ``use_true_center=True``, or the nominal + center if ``use_true_center=False``). [default: None] + gsparams: An optional `GSParams` argument. [default: None] + hdu: When reading in an `Image` from a file, this parameter can be used to + select a particular HDU in the file. [default: None] + _force_stepk: Override the normal stepk calculation (using gsparams.folding_threshold) + and force stepk to the given value. [default: 0] + _force_maxk: Override the normal maxk calculation (using gsparams.maxk_threshold) + and force maxk to the given value. This option in particular can help + reduce FFT artifacts in a manner that is currently unobtainable by + lowering maxk_threshold. [default: 0] + """ + _req_params = { 'image' : str } + _opt_params = { + 'x_interpolant': str, + 'k_interpolant': str, + 'normalization': str, + 'scale': float, + 'flux': float, + 'pad_factor': float, + 'noise_pad_size': float, + 'noise_pad': str, + 'pad_image': str, + 'calculate_stepk': bool, + 'calculate_maxk': bool, + 'use_true_center': bool, + 'depixelize': bool, + 'offset': PositionD, + 'hdu': int + } + _takes_rng = True + _cache_noise_pad = {} + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, image, x_interpolant=None, k_interpolant=None, normalization='flux', + scale=None, wcs=None, flux=None, pad_factor=4., noise_pad_size=0, noise_pad=0., + rng=None, pad_image=None, calculate_stepk=True, calculate_maxk=True, + use_cache=True, use_true_center=True, depixelize=False, offset=None, + gsparams=None, _force_stepk=0., _force_maxk=0., hdu=None): + + # If the "image" is not actually an image, try to read the image as a file. + if isinstance(image, str): + image = fits.read(image, hdu=hdu) + elif not isinstance(image, Image): + raise TypeError("Supplied image must be an Image or file name") + + # it must have well-defined bounds, otherwise seg fault in SBInterpolatedImage constructor + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Supplied image does not have bounds defined.") + + # check what normalization was specified for the image: is it an image of surface + # brightness, or flux? + if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): + raise GalSimValueError("Invalid normalization requested.", normalization, + ('flux', 'f', 'surface brightness', 'sb')) + + # Set up the interpolants if none was provided by user, or check that the user-provided ones + # are of a valid type + self._gsparams = GSParams.check(gsparams) + if x_interpolant is None: + self._x_interpolant = Quintic(gsparams=self._gsparams) + else: + self._x_interpolant = convert_interpolant(x_interpolant).withGSParams(self._gsparams) + if k_interpolant is None: + self._k_interpolant = Quintic(gsparams=self._gsparams) + else: + self._k_interpolant = convert_interpolant(k_interpolant).withGSParams(self._gsparams) + + # Store the image as an attribute and make sure we don't change the original image + # in anything we do here. (e.g. set scale, etc.) + if depixelize: + self._image = image.view(dtype=np.float64).depixelize(self._x_interpolant) + else: + self._image = image.view(dtype=np.float64, contiguous=True) + self._image.setCenter(0,0) + + # Set the wcs if necessary + if scale is not None: + if wcs is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both scale and wcs to InterpolatedImage", scale=scale, wcs=wcs) + self._image.wcs = PixelScale(scale) + elif wcs is not None: + if not isinstance(wcs, BaseWCS): + raise TypeError("wcs parameter is not a galsim.BaseWCS instance") + self._image.wcs = wcs + elif self._image.wcs is None: + raise GalSimIncompatibleValuesError( + "No information given with Image or keywords about pixel scale!", + scale=scale, wcs=wcs, image=image) + + # Figure out the offset to apply based on the original image (not the padded one). + # We will apply this below in _sbp. + offset = self._parse_offset(offset) + self._offset = self._adjust_offset(self._image.bounds, offset, None, use_true_center) + + im_cen = image.true_center if use_true_center else image.center + self._wcs = self._image.wcs.local(image_pos=im_cen) + + # Build the fully padded real-space image according to the various pad options. + self._buildRealImage(pad_factor, pad_image, noise_pad_size, noise_pad, rng, use_cache) + self._image_flux = np.sum(self._image.array, dtype=np.float64) + + # I think the only things that will mess up if flux == 0 are the + # calculateStepK and calculateMaxK functions, and rescaling the flux to some value. + if (calculate_stepk or calculate_maxk or flux is not None) and self._image_flux == 0.: + raise GalSimValueError("This input image has zero total flux. It does not define a " + "valid surface brightness profile.", image) + + # Process the different options for flux, stepk, maxk + self._flux = self._getFlux(flux, normalization) + self._calculate_stepk = calculate_stepk + self._calculate_maxk = calculate_maxk + self._stepk = self._getStepK(calculate_stepk, _force_stepk) + self._maxk = self._getMaxK(calculate_maxk, _force_maxk) + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + ret._x_interpolant = self._x_interpolant.withGSParams(ret._gsparams, **kwargs) + ret._k_interpolant = self._k_interpolant.withGSParams(ret._gsparams, **kwargs) + if ret._gsparams.folding_threshold != self._gsparams.folding_threshold: + ret._stepk = ret._getStepK(self._calculate_stepk, 0.) + if ret._gsparams.maxk_threshold != self._gsparams.maxk_threshold: + ret._maxk = ret._getMaxK(self._calculate_maxk, 0.) + return ret
+ + @lazy_property + def _sbp(self): + min_scale = self._wcs._minScale() + max_scale = self._wcs._maxScale() + self._sbii = _galsim.SBInterpolatedImage( + self._xim._image, self._image.bounds._b, self._pad_image.bounds._b, + self._x_interpolant._i, self._k_interpolant._i, + self._stepk*min_scale, + self._maxk*max_scale, + self.gsparams._gsp) + + self._sbp = self._sbii # Temporary. Will overwrite this with the return value. + + # Apply the offset + prof = self + if self._offset != _PositionD(0,0): + # Opposite direction of what drawImage does. + prof = prof._shift(-self._offset.x, -self._offset.y) + + # If the user specified a flux, then set to that flux value. + if self._flux != self._image_flux: + flux_ratio = self._flux / self._image_flux + else: + flux_ratio = 1. + + # Bring the profile from image coordinates into world coordinates + # Note: offset needs to happen first before the transformation, so can't bundle it here. + prof = self._wcs._profileToWorld(prof, flux_ratio, _PositionD(0,0)) + + return prof._sbp + + @property + def x_interpolant(self): + """The real-space `Interpolant` for this profile. + """ + return self._x_interpolant + + @property + def k_interpolant(self): + """The Fourier-space `Interpolant` for this profile. + """ + return self._k_interpolant + + @property + def image(self): + """The underlying `Image` being interpolated. + """ + return self._image + + def _buildRealImage(self, pad_factor, pad_image, noise_pad_size, noise_pad, rng, use_cache): + # Check that given pad_image is valid: + if pad_image is not None: + if isinstance(pad_image, basestring): + pad_image = fits.read(pad_image).view(dtype=np.float64) + elif isinstance(pad_image, Image): + pad_image = pad_image.view(dtype=np.float64, contiguous=True) + else: + raise TypeError("Supplied pad_image must be an Image.", pad_image) + + if pad_factor <= 0.: + raise GalSimRangeError("Invalid pad_factor <= 0 in InterpolatedImage", pad_factor, 0.) + + # Convert noise_pad_size from arcsec to pixels according to the local wcs. + # Use the minimum scale, since we want to make sure noise_pad_size is + # as large as we need in any direction. + if noise_pad_size: + if noise_pad_size < 0: + raise GalSimValueError("noise_pad_size may not be negative", noise_pad_size) + if not noise_pad: + raise GalSimIncompatibleValuesError( + "Must provide noise_pad if noise_pad_size > 0", + noise_pad=noise_pad, noise_pad_size=noise_pad_size) + noise_pad_size = int(math.ceil(noise_pad_size / self._wcs._minScale())) + noise_pad_size = Image.good_fft_size(noise_pad_size) + else: + if noise_pad: + raise GalSimIncompatibleValuesError( + "Must provide noise_pad_size if noise_pad != 0", + noise_pad=noise_pad, noise_pad_size=noise_pad_size) + + # The size of the final padded image is the largest of the various size specifications + pad_size = max(self._image.array.shape) + if pad_factor > 1.: + pad_size = int(math.ceil(pad_factor * pad_size)) + if noise_pad_size: + pad_size = max(pad_size, noise_pad_size) + if pad_image: + pad_image.setCenter(0,0) + pad_size = max(pad_size, *pad_image.array.shape) + # And round up to a good fft size + pad_size = Image.good_fft_size(pad_size) + + self._xim = Image(pad_size, pad_size, dtype=np.float64, wcs=self._wcs) + self._xim.setCenter(0,0) + + # If requested, fill (some of) this image with noise padding. + nz_bounds = self._image.bounds + if noise_pad: + # This is a bit involved, so pass this off to another helper function. + b = self._buildNoisePadImage(noise_pad_size, noise_pad, rng, use_cache) + nz_bounds += b + + # The the user gives us a pad image to use, fill the relevant portion with that. + if pad_image: + #assert self._xim.bounds.includes(pad_image.bounds) + self._xim[pad_image.bounds] = pad_image + nz_bounds += pad_image.bounds + + # Now place the given image in the center of the padding image: + #assert self._xim.bounds.includes(self._image.bounds) + self._xim[self._image.bounds] = self._image + self._xim.wcs = self._wcs + + # And update the _image to be that portion of the full real image rather than the + # input image. + self._image = self._xim[self._image.bounds] + + # These next two allow for easy pickling/repring. We don't need to serialize all the + # zeros around the edge. But we do need to keep any non-zero padding as a pad_image. + self._pad_image = self._xim[nz_bounds] + #self._pad_factor = (max(self._xim.array.shape)-1.e-6) / max(self._image.array.shape) + self._pad_factor = pad_factor + + def _buildNoisePadImage(self, noise_pad_size, noise_pad, rng, use_cache): + """A helper function that builds the ``pad_image`` from the given ``noise_pad`` + specification. + """ + from .correlatednoise import BaseCorrelatedNoise, CorrelatedNoise + # Make sure we make rng a BaseDeviate if rng is None + rng1 = BaseDeviate(rng) + + # Figure out what kind of noise to apply to the image + try: + noise_pad = float(noise_pad) + except (TypeError, ValueError): + if isinstance(noise_pad, BaseCorrelatedNoise): + noise = noise_pad.copy(rng=rng1) + elif isinstance(noise_pad, Image): + noise = CorrelatedNoise(noise_pad, rng1) + elif use_cache and noise_pad in InterpolatedImage._cache_noise_pad: + noise = InterpolatedImage._cache_noise_pad[noise_pad] + if rng: + # Make sure that we are using a specified RNG by resetting that in this cached + # CorrelatedNoise instance, otherwise preserve the cached RNG + noise = noise.copy(rng=rng1) + elif isinstance(noise_pad, basestring): + noise = CorrelatedNoise(fits.read(noise_pad), rng1) + if use_cache: + InterpolatedImage._cache_noise_pad[noise_pad] = noise + else: + raise GalSimValueError( + "Input noise_pad must be a float/int, a CorrelatedNoise, Image, or filename " + "containing an image to use to make a CorrelatedNoise.", noise_pad) + + else: + if noise_pad < 0.: + raise GalSimRangeError("Noise variance may not be negative.", noise_pad, 0.) + noise = GaussianNoise(rng1, sigma = np.sqrt(noise_pad)) + + # Find the portion of xim to fill with noise. + # It's allowed for the noise padding to not cover the whole pad image + half_size = noise_pad_size // 2 + b = _BoundsI(-half_size, -half_size + noise_pad_size-1, + -half_size, -half_size + noise_pad_size-1) + #assert self._xim.bounds.includes(b) + noise_image = self._xim[b] + # Add the noise + noise_image.addNoise(noise) + return b + + def _getFlux(self, flux, normalization): + # If the user specified a surface brightness normalization for the input Image, then + # need to rescale flux by the pixel area to get proper normalization. + if flux is None: + flux = self._image_flux + if normalization.lower() in ('surface brightness','sb'): + flux *= self._wcs.pixelArea() + return flux + + def _getStepK(self, calculate_stepk, _force_stepk): + # GalSim cannot automatically know what stepK and maxK are appropriate for the + # input image. So it is usually worth it to do a manual calculation (below). + # + # However, there is also a hidden option to force it to use specific values of stepK and + # maxK (caveat user!). The values of _force_stepk and _force_maxk should be provided in + # terms of physical scale, e.g., for images that have a scale length of 0.1 arcsec, the + # stepK and maxK should be provided in units of 1/arcsec. Then we convert to the 1/pixel + # units required by the C++ layer below. Also note that profile recentering for even-sized + # images (see the ._adjust_offset step below) leads to automatic reduction of stepK slightly + # below what is provided here, while maxK is preserved. + if _force_stepk > 0.: + return _force_stepk + elif calculate_stepk: + if calculate_stepk is True: + im = self._image + else: + # If not a bool, then value is max_stepk + R = int(math.ceil(math.pi / calculate_stepk)) + b = _BoundsI(-R, R, -R, R) + b = self._image.bounds & b + im = self._image[b] + thresh = (1.-self.gsparams.folding_threshold) * self._image_flux + R = _galsim.CalculateSizeContainingFlux(self._image._image, thresh) + else: + R = np.max(self._image.array.shape) / 2. - 0.5 + return self._getSimpleStepK(R) + + def _getSimpleStepK(self, R): + min_scale = self._wcs._minScale() + # Add xInterp range in quadrature just like convolution: + R2 = self._x_interpolant.xrange + R = math.hypot(R, R2) + stepk = math.pi / (R * min_scale) + return stepk + + def _getMaxK(self, calculate_maxk, _force_maxk): + max_scale = self._wcs._maxScale() + if _force_maxk > 0.: + return _force_maxk + elif calculate_maxk: + self._maxk = 0. + self._sbp + if calculate_maxk is True: + self._sbii.calculateMaxK(0.) + else: + # If not a bool, then value is max_maxk + self._sbii.calculateMaxK(float(calculate_maxk)) + self.__dict__.pop('_sbp') # Need to remake it. + return self._sbii.maxK() / max_scale + else: + return self._x_interpolant.krange / max_scale + + def __eq__(self, other): + return (self is other or + (isinstance(other, InterpolatedImage) and + self._xim == other._xim and + self.x_interpolant == other.x_interpolant and + self.k_interpolant == other.k_interpolant and + self.flux == other.flux and + self._offset == other._offset and + self.gsparams == other.gsparams and + self._stepk == other._stepk and + self._maxk == other._maxk)) + + def __hash__(self): + # Definitely want to cache this, since the size of the image could be large. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.InterpolatedImage", self.x_interpolant, self.k_interpolant)) + self._hash ^= hash((self.flux, self._stepk, self._maxk, self._pad_factor)) + self._hash ^= hash((self._xim.bounds, self._image.bounds, self._pad_image.bounds)) + # A common offset is 0.5,0.5, and *sometimes* this produces the same hash as 0,0 + # (which is also common). I guess because they are only different in 2 bits. + # This mucking of the numbers seems to help make the hash more reliably different for + # these two cases. Note: "sometiems" because of this: + # https://stackoverflow.com/questions/27522626/hash-function-in-python-3-3-returns-different-results-between-sessions + self._hash ^= hash((self._offset.x * 1.234, self._offset.y * 0.23424)) + self._hash ^= hash(self._gsparams) + self._hash ^= hash(self._xim.wcs) + # Just hash the diagonal. Much faster, and usually is unique enough. + # (Let python handle collisions as needed if multiple similar IIs are used as keys.) + self._hash ^= hash(tuple(np.diag(self._pad_image.array))) + return self._hash + + def __repr__(self): + s = 'galsim.InterpolatedImage(%r, %r, %r'%( + self._image, self.x_interpolant, self.k_interpolant) + # Most things we keep even if not required, but the pad_image is large, so skip it + # if it's really just the same as the main image. + if self._pad_image.bounds != self._image.bounds: + s += ', pad_image=%r'%(self._pad_image) + s += ', pad_factor=%f, flux=%r, offset=%r'%(self._pad_factor, self.flux, self._offset) + s += ', use_true_center=False, gsparams=%r, _force_stepk=%r, _force_maxk=%r)'%( + self.gsparams, self._stepk, self._maxk) + return s + + def __str__(self): return 'galsim.InterpolatedImage(image=%s, flux=%s)'%(self.image, self.flux) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbii',None) + d.pop('_sbp',None) + # Only pickle _pad_image. Not _xim or _image + d['_xim_bounds'] = self._xim.bounds + d['_image_bounds'] = self._image.bounds + d.pop('_xim',None) + d.pop('_image',None) + return d + + def __setstate__(self, d): + xim_bounds = d.pop('_xim_bounds') + image_bounds = d.pop('_image_bounds') + self.__dict__ = d + if self._pad_image.bounds == xim_bounds: + self._xim = self._pad_image + else: + self._xim = Image(xim_bounds, wcs=self._wcs, dtype=np.float64) + self._xim[self._pad_image.bounds] = self._pad_image + self._image = self._xim[image_bounds] + + @property + def _centroid(self): + return PositionD(self._sbp.centroid()) + + @property + def _positive_flux(self): + return self._sbp.getPositiveFlux() + + @property + def _negative_flux(self): + return self._sbp.getNegativeFlux() + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _shoot(self, photons, rng): + with convert_cpp_errors(): + self._sbp.shoot(photons._pa, rng._rng) + photons.flux *= self._flux_per_photon + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + dx,dy = offset + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac)
+ + +
[docs]def _InterpolatedImage(image, x_interpolant=Quintic(), k_interpolant=Quintic(), + use_true_center=True, offset=None, gsparams=None, + force_stepk=0., force_maxk=0.): + """Approximately equivalent to `InterpolatedImage`, but with fewer options and no sanity checks. + + Some notable reductions in functionality relative to `InterpolatedImage`: + + 1. There are no padding options. The image must be provided with all padding already applied. + 2. The stepk and maxk values will not be calculated. If you want to use values for these other + than the default, you may provide them as force_stepk and force_maxk. Otherwise + stepk ~= 2pi / image_size and maxk ~= x_interpolant.krange() / pixel_scale. + 3. The flux is just the flux of the image. It cannot be rescaled to a different flux value. + 4. The input image must have a defined wcs. + 5. The image is not recentered to have its center at (0,0). The returned profile will be + centered wherever the (0,0) location is in the image, possibly with an offset governed + by ``offset`` and ``use_true_center``. If you want to mimic the behavior of the regular + `InterpolatedImage` initializer, you can call ``image.setCenter(0,0)`` before calling this + function. + + Parameters: + image: The `Image` from which to construct the object. + x_interpolant: An `Interpolant` instance for real-space interpolation + [default: Quintic] + k_interpolant: An `Interpolant` instance for k-space interpolation [default: Quintic] + use_true_center: Whether to adjust the offset by the difference between the integer + center and the true center. For odd-sized images, this does nothing, + but for even-sized dimensions, it adjusts the offset by -0.5. + [default: True] + offset: The location in the input image to use as the center of the profile + relative to position (0,0). [default: None] + gsparams: An optional `GSParams` argument. [default: None] + force_stepk: A stepk value to use rather than the default value. [default: 0.] + force_maxk: A maxk value to use rather than the default value. [default: 0.] + + Returns: + an `InterpolatedImage` instance + """ + ret = InterpolatedImage.__new__(InterpolatedImage) + + # We need to set all the various attributes that are expected to be in an InterpolatedImage: + ret._image = image.view(dtype=np.float64, contiguous=True) + ret._gsparams = GSParams.check(gsparams) + ret._x_interpolant = x_interpolant.withGSParams(ret._gsparams) + ret._k_interpolant = k_interpolant.withGSParams(ret._gsparams) + + offset = ret._parse_offset(offset) + ret._offset = ret._adjust_offset(ret._image.bounds, offset, None, use_true_center) + im_cen = ret._image.true_center if use_true_center else ret._image.center + ret._wcs = ret._image.wcs.local(image_pos = im_cen) + ret._pad_factor = 1. + ret._image_flux = np.sum(ret._image.array, dtype=np.float64) + ret._flux = ret._image_flux + + # If image isn't a good fft size, we may still need to pad it out. + size = max(ret._image.array.shape) + pad_size = Image.good_fft_size(size) + if size == pad_size: + ret._xim = ret._image + else: + ret._xim = Image(pad_size, pad_size, dtype=np.float64) + ret._xim.setCenter(ret._image.center) + ret._xim[ret._image.bounds] = ret._image + ret._xim.wcs = ret._wcs + ret._image = ret._xim[ret._image.bounds] + ret._pad_image = ret._image + + if force_stepk == 0.: + ret._stepk = ret._getSimpleStepK(np.max(ret._image.array.shape) / 2. - 0.5) + else: + ret._stepk = force_stepk + if force_maxk == 0.: + ret._maxk = ret._x_interpolant.krange / ret._wcs._maxScale() + else: + ret._maxk = force_maxk + return ret
+ + +
[docs]class InterpolatedKImage(GSObject): + """A class describing non-parametric profiles specified by samples of their complex Fourier + transform. + + The InterpolatedKImage class is useful if you have a non-parametric description of the Fourier + transform of the profile (provided as either a complex `Image` or two images giving the real + and imaginary parts) that you wish to manipulate / transform using `GSObject` methods such as + `GSObject.shear`, `GSObject.magnify`, `GSObject.shift`, etc. Note that neither real-space + convolution nor photon-shooting of InterpolatedKImages is currently implemented. Please submit + an issue at http://github.com/GalSim-developers/GalSim/issues if you require either of these + use cases. + + The images required for creating an InterpolatedKImage are precisely those returned by the + `GSObject.drawKImage` method. The ``a`` and ``b`` objects in the following command will + produce essentially equivalent images when drawn with the `GSObject.drawImage` method:: + + >>> a = returns_a_GSObject() + >>> b = galsim.InterpolatedKImage(a.drawKImage()) + + The input ``kimage`` must have dtype=numpy.complex64 or dtype=numpy.complex128, which are also + known as `ImageCF` and `ImageCD` objects respectively. + The only wcs permitted is a simple `PixelScale` (or `OffsetWCS`), in which case ``kimage.scale`` + is used for the ``stepk`` value unless overridden by the ``stepk`` initialization argument. + + Furthermore, the complex-valued Fourier profile given by ``kimage`` must be Hermitian, since it + represents a real-valued real-space profile. (To see an example of valid input to + InterpolatedKImage, you can look at the output of `GSObject.drawKImage`). + + The user may optionally specify an interpolant, ``k_interpolant``, for Fourier-space + manipulations (e.g., shearing, resampling). If none is specified, then by default, a `Quintic` + interpolant is used. The `Quintic` interpolant has been found to be a good compromise between + speed and accuracy for real-and Fourier-space interpolation of objects specified by samples of + their real-space profiles (e.g., in `InterpolatedImage`), though no extensive testing has been + performed for objects specified by samples of their Fourier-space profiles (e.g., this + class). + + Example:: + + >>> interpolated_kimage = galsim.InterpolatedKImage(kimage, k_interpolant=None, stepk=0., + gsparams=None) + + Initializes ``interpolated_kimage`` as an InterpolatedKImage instance. + + Parameters: + kimage: The complex `Image` corresponding to the Fourier-space samples. + k_interpolant: Either an `Interpolant` instance or a string indicating which k-space + interpolant should be used. Options are 'nearest', 'sinc', 'linear', + 'cubic', 'quintic', or 'lanczosN' where N should be the integer order + to use. [default: galsim.Quintic()] + stepk: By default, the stepk value (the sampling frequency in Fourier-space) + of the profile is set by the ``scale`` attribute of the supplied images. + This keyword allows the user to specify a coarser sampling in Fourier- + space, which may increase efficiency at the expense of decreasing the + separation between neighboring copies of the DFT-rendered real-space + profile. (See the `GSParams` docstring for the parameter + ``folding_threshold`` for more information). [default: kimage.scale] + gsparams: An optional `GSParams` argument. [default: None] + real_kimage: Optionally, rather than provide kimage, you may provide the real + and imaginary parts separately. These separate real-valued images + may be strings, in which case they refer to FITS files from which + to read the images. [default: None] + imag_kimage: The imaginary image corresponding to real_kimage. [default: None] + real_hdu: When reading in real_kimage from a file, this parameter can be used to + select a particular HDU in the file. [default: None] + imag_hdu: When reading in imag_kimage from a file, this parameter can be used to + select a particular HDU in the file. [default: None] + """ + _req_params = { 'real_kimage' : str, + 'imag_kimage' : str } + _opt_params = { + 'k_interpolant' : str, 'stepk': float, + 'real_hdu': int, 'imag_hdu': int, + } + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, kimage=None, k_interpolant=None, stepk=None, gsparams=None, + real_kimage=None, imag_kimage=None, real_hdu=None, imag_hdu=None): + if kimage is None: + if real_kimage is None or imag_kimage is None: + raise GalSimIncompatibleValuesError( + "Must provide either kimage or real_kimage/imag_kimage", + kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimage) + + # If the "image" is not actually an image, try to read the image as a file. + if isinstance(real_kimage, str): + real_kimage = fits.read(real_kimage, hdu=real_hdu) + elif not isinstance(real_kimage, Image): + raise TypeError("real_kimage must be either an Image or a file name") + if isinstance(imag_kimage, str): + imag_kimage = fits.read(imag_kimage, hdu=imag_hdu) + elif not isinstance(imag_kimage, Image): + raise TypeError("imag_kimage must be either an Image or a file name") + + # make sure real_kimage, imag_kimage are congruent. + if real_kimage.bounds != imag_kimage.bounds: + raise GalSimIncompatibleValuesError( + "Real and Imag kimages must have same bounds.", + real_kimage=real_kimage, imag_kimage=imag_kimage) + if real_kimage.wcs != imag_kimage.wcs: + raise GalSimIncompatibleValuesError( + "Real and Imag kimages must have same scale/wcs.", + real_kimage=real_kimage, imag_kimage=imag_kimage) + + kimage = real_kimage + 1j*imag_kimage + else: + if real_kimage is not None or imag_kimage is not None: + raise GalSimIncompatibleValuesError( + "Cannot provide both kimage and real_kimage/imag_kimage", + kimage=kimage, real_kimage=real_kimage, imag_kimage=imag_kimage) + if not isinstance(kimage, Image): + raise TypeError("kimage must be a galsim.Image isntance") + if not kimage.iscomplex: + raise GalSimValueError("Supplied kimage is not complex", kimage) + + # Make sure wcs is a PixelScale. + if kimage.wcs is not None and not kimage.wcs._isPixelScale: + raise GalSimValueError("kimage wcs must be PixelScale or None.", kimage.wcs) + + if not kimage.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Supplied image does not have bounds defined.") + + self._gsparams = GSParams.check(gsparams) + + # Check for Hermitian symmetry properties of kimage + shape = kimage.array.shape + # If image is even-sized, ignore first row/column since in this case not every pixel has + # a symmetric partner to which to compare. + bd = _BoundsI(kimage.xmin + (1 if shape[1]%2==0 else 0), + kimage.xmax, + kimage.ymin + (1 if shape[0]%2==0 else 0), + kimage.ymax) + if not (np.allclose(kimage[bd].real.array, + kimage[bd].real.array[::-1,::-1]) and + np.allclose(kimage[bd].imag.array, + -kimage[bd].imag.array[::-1,::-1])): + raise GalSimIncompatibleValuesError( + "Real and Imag kimages must form a Hermitian complex matrix.", kimage=kimage) + + # Make sure the image is complex128 and contiguous + self._kimage = kimage.view(dtype=np.complex128, contiguous=True) + self._kimage.setCenter(0,0) + + if stepk is None: + if self._kimage.scale is None: + # Defaults to 1.0 if no scale is set. + self._kimage.scale = 1. + self._stepk = self._kimage.scale + elif stepk < kimage.scale: + galsim_warn( + "Provided stepk is smaller than kimage.scale; overriding with kimage.scale.") + self._stepk = kimage.scale + else: + self._stepk = stepk + + # set up k_interpolant if none was provided by user, or check that the user-provided one + # is of a valid type + if k_interpolant is None: + self._k_interpolant = Quintic(gsparams=self._gsparams) + else: + self._k_interpolant = convert_interpolant(k_interpolant).withGSParams(self._gsparams) + + @property + def kimage(self): + """The underlying `Image` being interpolated. + """ + return self._kimage + + @property + def k_interpolant(self): + """The Fourier-space `Interpolant` for this profile. + """ + return self._k_interpolant + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + ret._k_interpolant = self._k_interpolant.withGSParams(ret._gsparams, **kwargs) + return ret
+ + @lazy_property + def _sbp(self): + stepk_image = self.stepk / self.kimage.scale # usually 1, but could be larger + + # C++ layer needs Bounds that look like 0, N/2, -N/2, N/2-1 + # So find the biggest N that works like that. + b = self._kimage.bounds + N = min(b.xmax*2, -b.ymin*2, b.ymax*2+1) + b = _BoundsI(0, N//2, -(N//2), N//2-1) + posx_kimage = self._kimage[b] + self._sbiki = _galsim.SBInterpolatedKImage( + posx_kimage._image, stepk_image, self.k_interpolant._i, self.gsparams._gsp) + + scale = self.kimage.scale + jac = np.array((1./scale, 0., 0., 1./scale)) + _jac = jac.__array_interface__['data'][0] + if scale != 1.: + return _galsim.SBTransform(self._sbiki, _jac, 0., 0., scale**2, self.gsparams._gsp) + else: + return self._sbiki + + def __eq__(self, other): + return (self is other or + (isinstance(other, InterpolatedKImage) and + np.array_equal(self.kimage.array, other.kimage.array) and + self.kimage.scale == other.kimage.scale and + self.k_interpolant == other.k_interpolant and + self.stepk == other.stepk and + self.gsparams == other.gsparams)) + + def __hash__(self): + # Definitely want to cache this, since the kimage could be large. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.InterpolatedKImage", self.k_interpolant, self._stepk, + self.gsparams)) + self._hash ^= hash(tuple(self.kimage.array.ravel())) + self._hash ^= hash((self.kimage.bounds, self.kimage.wcs)) + return self._hash + + def __repr__(self): + return ('galsim.InterpolatedKImage(\n%r,\n%r, stepk=%r, gsparams=%r)')%( + self.kimage, self.k_interpolant, self.stepk, self.gsparams) + + def __str__(self): + return 'galsim.InterpolatedKImage(kimage=%s)'%(self.kimage) + + def __getstate__(self): + # The SBInterpolatedKImage is picklable, but that is pretty inefficient, due to the large + # images being written as strings. Better to pickle the intermediate products and then + # call init again on the other side. There's still an image to be pickled, but at least + # it will be through the normal pickling rules, rather than the repr. + d = self.__dict__.copy() + d.pop('_sbiki',None) + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _centroid(self): + with convert_cpp_errors(): + return PositionD(self._sbp.centroid()) + + @property + def _flux(self): + return self._sbp.getFlux() + + @property + def _positive_flux(self): + return self._sbp.getPositiveFlux() + + @property + def _negative_flux(self): + return self._sbp.getNegativeFlux() + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac)
+ + +
[docs]def _InterpolatedKImage(kimage, k_interpolant, gsparams): + """Approximately equivalent to `InterpolatedKImage`, but with fewer options and no sanity + checks. + + Parameters: + kimage: The complex `Image` corresponding to the Fourier-space samples. + k_interpolant: An `Interpolant` instance indicating which k-space interpolant should be + used. + gsparams: An optional `GSParams` argument. [default: None] + """ + ret = InterpolatedKImage.__new__(InterpolatedKImage) + ret._kimage = kimage.view(dtype=np.complex128, contiguous=True) + ret._kimage.shift(-kimage.center) + ret._stepk = kimage.scale + ret._gsparams = GSParams.check(gsparams) + ret._k_interpolant = k_interpolant.withGSParams(gsparams) + return ret
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/knots.html b/docs/_build/html/_modules/galsim/knots.html new file mode 100644 index 00000000000..fb2b06ecf2b --- /dev/null +++ b/docs/_build/html/_modules/galsim/knots.html @@ -0,0 +1,463 @@ + + + + + + galsim.knots — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.knots

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'RandomKnots' ]
+
+import numpy as np
+
+from . import _galsim
+from .gsparams import GSParams
+from .gsobject import GSObject
+from .position import PositionD
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError
+from .gaussian import Gaussian
+from .random import BaseDeviate
+
+
+
[docs]class RandomKnots(GSObject): + """ + A class for generating a set of point sources, following either a `Gaussian` profile or a + specified input profile. + + Uses of this profile include representing an "irregular" galaxy, or + adding this profile to an Exponential to represent knots of star formation. + + RandomKnots profiles have "shape noise" that depends on the number of point + sources used. For example, using the default Gaussian distribution, with + 100 points, the shape noise is g~0.05, and this will decrease as more + points are added. The profile can be sheared to give additional + ellipticity, for example to follow that of an associated disk. + + The requested half light radius (hlr) should be thought of as a rough + value. With a finite number point sources the actual realized hlr will be + noisy. + + .. note:: + + If providing an input ``profile`` object, it must be "shoot-able". Objects that + cannot be drawn with ``method='phot'`` cannot be used as the ``profile`` parameter here. + + Parameters: + npoints: Number of point sources to generate. + half_light_radius: Optional half light radius of the distribution of points. This value + is used for a Gaussian distribution if an explicit profile is not sent. + This is the mean half light radius produced by an infinite number of + points. A single instance will be noisy. [default None] + flux: Optional total flux in all point sources. This value is used for a + Gaussian distribution if an explicit profile is not sent. Defaults to + None if profile is sent, otherwise 1. [default: None] + profile: Optional profile to use for drawing points. If a profile is sent, the + half_light_radius and flux keywords are invalid. [default: None] + rng: Optional random number generator. Can be any `galsim.BaseDeviate`. If + None, the rng is created internally. [default: None] + gsparams: Optional `GSParams` for the objects representing each point source. + [default: None] + + Attributes: + npoints: The number of points to use as knots + input_half_light_radius: The input half_light_radius + flux: The flux + points: The array of x,y offsets used to create the point sources + + .. note:: + + The algorithm was originally a modified version of that presented in + https://arxiv.org/abs/1312.5514v3. However, we now use the GalSim photon shooting + mechanism, which allows the knots to trace any profile, not just a `Gaussian`. + """ + # these allow use in a galsim configuration context + + _req_params = { "npoints" : int } + _opt_params = { + "flux" : float , + "half_light_radius": float, + "profile": GSObject, + } + _takes_rng = True + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, npoints, half_light_radius=None, flux=None, profile=None, rng=None, + gsparams=None): + + self._npoints=npoints + self._half_light_radius = half_light_radius + self._flux = flux + self._profile=profile + + self._verify() + self._gsparams = GSParams.check(gsparams) + + if rng is None: + rng = BaseDeviate(rng) + self._orig_rng=rng.duplicate() + else: + if not isinstance(rng, BaseDeviate): + raise TypeError("rng must be an instance of galsim.BaseDeviate, got %s"%rng) + self._orig_rng=rng.duplicate() + # We won't use the rng yet, but make sure the original advances the right number + # of values. + rng.discard(2*npoints) + + if profile is None: + if self._flux is None: self._flux = 1.0 + self._profile = Gaussian(half_light_radius=self._half_light_radius, flux=self._flux) + + else: + self._flux=profile.flux + try: + # not all GSObjects have this attribute + self._half_light_radius = profile.half_light_radius + except Exception: + self._half_light_radius = None + + + @lazy_property + def _sbp(self): + fluxper = self._flux/self._npoints + deltas = [] + for p in self.points: + d = _galsim.SBDeltaFunction(fluxper, self.gsparams._gsp) + d = _galsim.SBTransform(d, 0, p[0], p[1], 1.0, self.gsparams._gsp) + deltas.append(d) + return _galsim.SBAdd(deltas, self.gsparams._gsp) + + @property + def input_half_light_radius(self): + """ + Get the input half light radius (HLR). + + Note the input HLR is not necessarily the realized HLR, + due to the finite number of points used in the profile. + + If a profile is sent, and that profile is a Transformation object (e.g. + it has been sheared, its flux set, etc), then this value will be None. + + You can get the *calculated* half light radius using the calculateHLR + method. That value will be valid in all cases. + """ + return self._half_light_radius + + @property + def npoints(self): + """The number of point sources. + """ + return self._npoints + + @lazy_property + def points(self): + """A list of the locations (x,y) of the point sources. + + Technically, this is a numpy array of shape (npoints, 2). + """ + rng = self._orig_rng.duplicate() + photons = self._profile.shoot(self._npoints, rng) + return np.column_stack([ photons.x, photons.y ]) + +
[docs] def calculateHLR(self): + """ + calculate the half-light radius of the generated points + """ + pts = self.points + my,mx=pts.mean(axis=0) + + r=np.sqrt( (pts[:,0]-my)**2 + (pts[:,1]-mx)**2) + + hlr=np.median(r) + + return hlr
+ + def _verify(self): + """ + type and range checking on the inputs + """ + try: + self._npoints = int(self._npoints) + except ValueError as err: + raise GalSimValueError("npoints should be a number: %s", str(err)) from None + + if self._npoints <= 0: + raise GalSimRangeError("npoints must be > 0", self._npoints, 1) + + if self._profile is None: + if self._half_light_radius is None: + raise GalSimIncompatibleValuesError( + "half_light_radius required when not providing a profile") + + if self._half_light_radius <= 0.: + raise GalSimRangeError( + "half_light_radius must be positive", self._half_light_radius, 0.) + + else: + if self._flux is not None: + raise GalSimIncompatibleValuesError("flux is invalid when providing a profile") + + if self._half_light_radius is not None: + raise GalSimIncompatibleValuesError( + "half_light_radius is invalid when providing a profile") + + if not isinstance(self._profile, GSObject): + raise GalSimIncompatibleValuesError("profile must be a GSObject") + + def __str__(self): + rep = 'galsim.RandomKnots(%(npoints)d, profile=%(profile)s)' + rep = rep % dict( + npoints=self._npoints, + profile=str(self._profile), + ) + + return rep + + def __repr__(self): + rep = 'galsim.RandomKnots(%r, profile=%r, rng=%r, gsparams=%r)'%( + self._npoints, self._profile, self._orig_rng, self._gsparams) + return rep + + def __eq__(self, other): + return (self is other or + (isinstance(other, RandomKnots) and + self._npoints == other._npoints and + self._profile == other._profile and + self._orig_rng == other._orig_rng and + self._gsparams == other._gsparams)) + + def __hash__(self): + return hash(("galsim.RandomKnots", self._npoints, self._half_light_radius, self._flux, + self.gsparams)) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _centroid(self): + return PositionD(self._sbp.centroid()) + + @property + def _positive_flux(self): + return self._sbp.getPositiveFlux() + + @property + def _negative_flux(self): + return self._sbp.getNegativeFlux() + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + + # For all the transformations methods, apply to the internal profile, and remake points + # in the correct locations. This makes fft drawing much faster than the normal way + # of applying the transformation to the k-space image. +
[docs] @doc_inherit + def withFlux(self, flux): + return RandomKnots(self.npoints, profile=self._profile.withFlux(flux), + rng=self._orig_rng.duplicate(), gsparams=self.gsparams)
+ +
[docs] @doc_inherit + def withScaledFlux(self, flux_ratio): + if hasattr(flux_ratio, '__call__'): + return GSObject.withScaledFlux(self, flux_ratio) + else: + return RandomKnots(self._npoints, profile=self._profile.withScaledFlux(flux_ratio), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ +
[docs] @doc_inherit + def expand(self, scale): + return RandomKnots(self._npoints, profile=self._profile.expand(scale), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ +
[docs] @doc_inherit + def dilate(self, scale): + return RandomKnots(self._npoints, profile=self._profile.dilate(scale), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ +
[docs] @doc_inherit + def shear(self, *args, **kwargs): + return RandomKnots(self._npoints, profile=self._profile.shear(*args, **kwargs), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ + def _shear(self, shear): + return RandomKnots(self._npoints, profile=self._profile._shear(shear), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams) + +
[docs] @doc_inherit + def rotate(self, theta): + return RandomKnots(self._npoints, profile=self._profile.rotate(theta), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ +
[docs] @doc_inherit + def transform(self, dudx, dudy, dvdx, dvdy): + return RandomKnots(self._npoints, profile=self._profile.transform(dudx,dudy,dvdx,dvdy), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ +
[docs] @doc_inherit + def shift(self, *args, **kwargs): + return RandomKnots(self._npoints, profile=self._profile.shift(*args, **kwargs), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+ + def _shift(self, dx, dy): + return RandomKnots(self._npoints, profile=self._profile._shift(dx,dy), + rng=self._orig_rng.duplicate(), gsparams=self._gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/kolmogorov.html b/docs/_build/html/_modules/galsim/kolmogorov.html new file mode 100644 index 00000000000..bc52831d0e4 --- /dev/null +++ b/docs/_build/html/_modules/galsim/kolmogorov.html @@ -0,0 +1,435 @@ + + + + + + galsim.kolmogorov — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.kolmogorov

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Kolmogorov' ]
+
+import numpy as np
+import astropy.units as u
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimIncompatibleValuesError, convert_cpp_errors
+from .angle import arcsec, radians, AngleUnit
+
+
+
[docs]class Kolmogorov(GSObject): + r"""A class describing a Kolmogorov surface brightness profile, which represents a long + exposure atmospheric PSF. + + The Kolmogorov profile is defined in Fourier space. Its transfer function is: + + .. math:: + T(k) \sim \exp(-D(k)/2) + + where + + .. math:: + D(k) = 6.8839 \left(\frac{\lambda k}{2\pi r_0} \right)^{5/3}, + + :math:`\lambda` is the wavelength of the light (say in the middle of the bandpass you are + using), and :math:`r_0` is the Fried parameter. Typical values for the Fried parameter are on + the order of 0.1 m for most observatories and up to 0.2 m for excellent sites. The values are + usually quoted at :math:`\lambda` = 500nm and :math:`r_0` depends on wavelength as + :math:`r_0 \sim \lambda^{6/5}`. + + For more information, refer to + + http://en.wikipedia.org/wiki/Atmospheric_seeing#The_Kolmogorov_model_of_turbulence + + The Kolmogorov profile is normally defined in terms of the ratio :math:`\lambda / r_0`. + The natural units for this ratio is radians, which is not normally a convenient unit to use for + other `GSObject` dimensions. Assuming that the other sky coordinates you are using are all in + arcsec (e.g. the pixel scale when you draw the image, the size of the galaxy, etc.), then you + should convert this to arcsec as well:: + + >>> lam = 700 # nm + >>> r0 = 0.15 * (lam/500)**1.2 # meters + >>> lam_over_r0 = (lam * 1.e-9) / r0 # radians + >>> lam_over_r0 *= 206265 # Convert to arcsec + >>> psf = galsim.Kolmogorov(lam_over_r0) + + To make this process a bit simpler, we recommend instead providing the wavelength and Fried + parameter separately using the parameters ``lam`` (in nm) and either ``r0`` or ``r0_500`` + (in m). GalSim will then convert this to any of the normal kinds of angular units using the + ``scale_unit`` parameter:: + + >>> psf = galsim.Kolmogorov(lam=lam, r0=r0, scale_unit=galsim.arcsec) + + or:: + + >>> psf = galsim.Kolmogorov(lam=lam, r0_500=0.15, scale_unit=galsim.arcsec) + + When drawing images, the scale_unit should match the unit used for the pixel scale or the WCS. + e.g. in this case, a pixel scale of 0.2 arcsec/pixel would be specified as ``pixel_scale=0.2``. + + A Kolmogorov object may also be initialized using ``fwhm`` or ``half_light_radius``. Exactly + one of these four ways to define the size is required. + + The FWHM of the Kolmogorov PSF is :math:`\sim 0.976 \lambda/r_0` arcsec. + (e.g., Racine 1996, PASP 699, 108). + + Parameters: + lam_over_r0: The parameter that governs the scale size of the profile. + See above for details about calculating it. [One of ``lam_over_r0``, + ``fwhm``, ``half_light_radius``, or ``lam`` (along with either ``r0`` or + ``r0_500``) is required.] + fwhm: The full-width-half-max of the profile. Typically given in arcsec. + [One of ``lam_over_r0``, ``fwhm``, ``half_light_radius``, or ``lam`` + (along with either ``r0`` or ``r0_500``) is required.] + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``lam_over_r0``, ``fwhm``, ``half_light_radius``, or ``lam`` + (along with either ``r0`` or ``r0_500``) is required.] + lam: Lambda (wavelength) either as an astropy Quantity or a float in units of + nanometers. Must be supplied with either ``r0`` or ``r0_500``, and in + this case, image scales (``scale``) should be specified in units of + ``scale_unit``. + r0: The Fried parameter, either as an astropy Quantity or a float in units + of meters. Must be supplied with ``lam``, and in this case, image + scales (``scale``) should be specified in units of ``scale_unit``. + r0_500: The Fried parameter at wavelength of 500 nm, either as an astropy + Quantity or a float in units of meters. The Fried parameter at the + given wavelength, ``lam``, will be computed using the standard relation + r0 = r0_500 * (lam/500)**1.2. + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + scale_unit: Units to use for the sky coordinates when calculating lam/r0 if these + are supplied separately. Note that the results of using properties + like `fwhm` will be returned in units of ``scale_unit`` as well. Should + be either a `galsim.AngleUnit` or a string that can be used to construct + one (e.g., 'arcsec', 'radians', etc.). [default: galsim.arcsec] + gsparams: An optional `GSParams` argument. [default: None] + + In addition to the usual `GSObject` methods, Kolmogorov has the following access properties: + + Attributes: + lam_over_r0: The input ratio lambda / r0 + fwhm: The full-width half-max size + half_light_radius: The half-light radius + """ + _opt_params = { + "flux" : float, + "r0" : (float, u.Quantity), + "r0_500" : (float, u.Quantity), + "scale_unit" : str + } + # Note that this is not quite right; it's true that exactly one of these 4 should be supplied, + # but if lam is supplied then r0 is required. Errors in which parameters are used may be + # caught either by config or by the python code itself, depending on the particular error. + _single_params = [ { + "lam_over_r0" : float, + "fwhm" : float, + "half_light_radius" : float, + "lam" : (float, u.Quantity) + } ] + + # The FWHM of the Kolmogorov PSF is ~0.976 lambda/r0 (e.g., Racine 1996, PASP 699, 108). + # In SBKolmogorov.cpp we refine this factor to 0.9758634299 + _fwhm_factor = 0.9758634299 + # Similarly, SBKolmogorov calculates the relation between lambda/r0 and half-light radius + _hlr_factor = 0.5548101137 + + # This constant comes from the standard form of the Kolmogorov spectrum from + # from Racine, 1996 PASP, 108, 699 (who in turn is quoting Fried, 1966, JOSA, 56, 1372): + # T(k) = exp(-1/2 D(k)) + # D(k) = 6.8839 (lambda/r0 k/2Pi)^(5/3) + # + # We convert this into T(k) = exp(-(k/k0)^5/3) for efficiency, + # which implies 1/2 6.8839 (lambda/r0 / 2Pi)^5/3 = (1/k0)^5/3 + # k0 * lambda/r0 = 2Pi * (6.8839 / 2)^-3/5 = 2.992934 + # + # Update: It turns out that 6.8839/2 is actually (24 Gamma(6/5) / 5)^(5/6) + # Which in turn makes the constant factor above + # 2Pi (24 Gamma(6/5) / 5)^(5/6)^-(3/5) + # = 2Pi (24 Gamma(6/5) / 5)^(-1/2) + # = 2.992939911888651 + # (Not that we need this many digits, but hey, why not?) + _k0_factor = 2.992939911888651 + + # The value in real space at (x,y) = (0,0) is analytic: + # int( flux (k/2pi) exp(-(k/k0)**(5/3)), k=0..inf) + # = flux * k0^2 * (3/5) Gamma(6/5) / 2pi + _xzero = 0.08767865636723461 + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, lam_over_r0=None, fwhm=None, half_light_radius=None, lam=None, r0=None, + r0_500=None, flux=1., scale_unit=None, gsparams=None): + + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + + if isinstance(lam, u.Quantity): + lam = lam.to_value(u.nm) + if isinstance(r0, u.Quantity): + r0 = r0.to_value(u.m) + if isinstance(r0_500, u.Quantity): + r0_500 = r0_500.to_value(u.m) + + if fwhm is not None : + if any(item is not None for item in (lam_over_r0, lam, r0, r0_500, half_light_radius)): + raise GalSimIncompatibleValuesError( + "Only one of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or r0_500) " + "may be specified", + fwhm=fwhm, lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500, + half_light_radius=half_light_radius) + self._lor0 = float(fwhm) / Kolmogorov._fwhm_factor + elif half_light_radius is not None: + if any(item is not None for item in (lam_over_r0, lam, r0, r0_500)): + raise GalSimIncompatibleValuesError( + "Only one of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or r0_500) " + "may be specified", + fwhm=fwhm, lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500, + half_light_radius=half_light_radius) + self._lor0 = float(half_light_radius) / Kolmogorov._hlr_factor + elif lam_over_r0 is not None: + if any(item is not None for item in (lam, r0, r0_500)): + raise GalSimIncompatibleValuesError( + "Cannot specify lam, r0 or r0_500 in conjunction with lam_over_r0.", + lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500) + self._lor0 = float(lam_over_r0) + else: + if lam is None or (r0 is None and r0_500 is None): + raise GalSimIncompatibleValuesError( + "One of lam_over_r0, fwhm, half_light_radius, or lam (with r0 or r0_500) " + "must be specified", + fwhm=fwhm, lam_over_r0=lam_over_r0, lam=lam, r0=r0, r0_500=r0_500, + half_light_radius=half_light_radius) + # In this case we're going to use scale_unit, so parse it in case of string input: + if scale_unit is None: + scale_unit = arcsec + elif isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + if r0 is None: + r0 = r0_500 * (lam/500.)**1.2 + self._lor0 = (1.e-9*float(lam)/float(r0))*(radians/scale_unit) + + self._k0 = Kolmogorov._k0_factor / self._lor0 + + @lazy_property + def _sbp(self): + with convert_cpp_errors(): + return _galsim.SBKolmogorov(self._lor0, self._flux, self.gsparams._gsp) + + @property + def lam_over_r0(self): + """The input lambda / r0 for this `Kolmogorov` profile. + """ + return self._lor0 + + @property + def fwhm(self): + """The FWHM of this `Kolmogorov` profile. + """ + return self._lor0 * Kolmogorov._fwhm_factor + + @property + def half_light_radius(self): + """The half light radius of this `Kolmogorov` profile. + """ + return self._lor0 * Kolmogorov._hlr_factor + + def __eq__(self, other): + return (self is other or + (isinstance(other, Kolmogorov) and + self.lam_over_r0 == other.lam_over_r0 and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Kolmogorov", self.lam_over_r0, self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.Kolmogorov(lam_over_r0=%r, flux=%r, gsparams=%r)'%( + self.lam_over_r0, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Kolmogorov(lam_over_r0=%s'%self.lam_over_r0 + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + # exp(-k^(5/3)) = kvalue_accuracy + return (-math.log(self.gsparams.kvalue_accuracy)) ** 0.6 * self._k0 + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _max_sb(self): + return self._flux * self._k0**2 * Kolmogorov._xzero + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Kolmogorov(lam_over_r0=self.lam_over_r0, flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/lensing_ps.html b/docs/_build/html/_modules/galsim/lensing_ps.html new file mode 100644 index 00000000000..e22003c3248 --- /dev/null +++ b/docs/_build/html/_modules/galsim/lensing_ps.html @@ -0,0 +1,1527 @@ + + + + + + galsim.lensing_ps — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.lensing_ps

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'PowerSpectrum' ]
+
+import os
+import numpy as np
+
+from .angle import arcsec, AngleUnit
+from .position import PositionD, PositionI
+from .bounds import BoundsD, BoundsI
+from .interpolant import Quintic, Lanczos
+from .image import Image, ImageD
+from .random import GaussianDeviate
+from .table import LookupTable, LookupTable2D
+from . import utilities
+from . import integ
+from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError
+from .errors import GalSimNotImplementedError, galsim_warn
+from .bessel import j0, jn
+
+
[docs]def theoryToObserved(gamma1, gamma2, kappa): + """Helper function to convert theoretical lensing quantities to observed ones. + + This helper function is used internally by the methods `PowerSpectrum.getShear`, + `PowerSpectrum.getMagnification`, and `PowerSpectrum.getLensing` to convert from theoretical + quantities (shear and convergence) to observable ones (reduced shear and magnification). + Users of `PowerSpectrum.buildGrid` outputs can also apply this method directly to the outputs + in order to get the values of reduced shear and magnification on the output grid. + + Parameters: + gamma1: The first shear component, which must be the NON-reduced shear. This and + all other inputs may be supplied either as individual floating point + numbers or lists/arrays of floats. + gamma2: The second (x) shear component, which must be the NON-reduced shear. + kappa: The convergence. + + Returns: + the reduced shear and magnification as a tuple (g1, g2, mu) + """ + gamma1 = np.asarray(gamma1, dtype=float) + gamma2 = np.asarray(gamma2, dtype=float) + kappa = np.asarray(kappa, dtype=float) + + g1 = gamma1/(1.-kappa) + g2 = gamma2/(1.-kappa) + mu = 1./((1.-kappa)**2 - (gamma1**2 + gamma2**2)) + return g1, g2, mu
+ +
[docs]class PowerSpectrum: + r"""Class to represent a lensing shear field according to some power spectrum :math:`P(k)`. + + **General considerations**: + + A PowerSpectrum represents some (flat-sky) shear power spectrum, either for gridded points or at + arbitary positions. This class is originally initialized with a power spectrum from which we + would like to generate g1 and g2 (and, optionally, convergence kappa) values. It generates + shears on a grid, and if necessary, when `getShear` (or another "get" method) is called, it + will interpolate to the requested positions. For detail on how these processes are carried + out, please see the document in the GalSim repository, ``devel/modules/lensing_engine.pdf``. + + This class generates the shears according to the input power spectrum using a DFT approach, + which means that we implicitly assume our discrete representation of :math:`P(k)` on a grid is + one complete cell in an infinite periodic series. We are making assumptions about what + :math:`P(k)` is doing outside of our minimum and maximum k range, and those must be kept in + mind when comparing with theoretical expectations. Specifically, since the power spectrum is + realized on only a finite grid it has been been effectively bandpass filtered between a + minimum and maximum k value in each of the k1, k2 directions. See the `buildGrid` method for + more information. + + As a result, the shear generation currently does not include sample variance due to coverage of + a finite patch. We explicitly enforce :math:`P(0)=0`, which is true for the full sky in a + reasonable cosmological model, but it ignores the fact that our little patch of sky might + reasonably live in some special region with respect to shear correlations. Our :math:`P(0)=0` + is essentially setting the integrated power below our minimum k value to zero. The + implications of the discrete representation, and the :math:`P(0)=0` choice, are discussed in + more detail in ``devel/modules/lensing_engine.pdf``. + + The effective shear correlation function for the gridded points will be modified both because of + the DFT approach to representing shears according to a power spectrum, and because of the power + cutoff below and above the minimum k values. The latter effect can be particularly important on + large scales, so the `buildGrid` method has some keywords that can be used to reduce the + impact of the minimum k set by the grid extent. The calculateXi() method can be used to + calculate the expected shear correlation functions given the minimum and maximum k for some grid + (but ignoring the discrete vs. continuous Fourier transform effects), for comparison with some + ideal theoretical correlation function given an infinite k range. + + When interpolating the shears to non-gridded points, the shear correlation function and power + spectrum are modified; see the `getShear` and other "get" method docstrings for more details. + + **The power spectra to be used**: + + When creating a PowerSpectrum instance, you must specify at least one of the E or B mode power + spectra, which is normally given as a function :math:`P(k)`. The typical thing is to just use a lambda + function in Python (i.e., a function that is not associated with a name); for example, to define + :math:`P(k)=k^2`, one would use ``lambda k : k**2``. But the power spectra can also be more complicated + user-defined functions that take a single argument ``k`` and return the power at that ``k`` + value, or they can be instances of the `LookupTable` class for power spectra that are known at + particular ``k`` values but for which there is not a simple analytic form. + + Cosmologists often express the power spectra in terms of an expansion in spherical harmonics + (ell), i.e., the :math:`C_\ell` values. In the flat-sky limit, we can replace :math:`\ell` + with :math:`k` and :math:`C_\ell` with :math:`P(k)`. Thus, :math:`k` and :math:`P(k)` have + dimensions of inverse angle and angle^2, respectively. It is quite common for people to plot + :math:`\ell(\ell+1) C_\ell/2\pi`, a dimensionless quantity; the analogous flat-sky + quantity is :math:`\Delta^2 = k^2 P(k)/2\pi`. + + By default, the PowerSpectrum object assumes it is getting :math:`P(k)`, but it is possible to + instead give it :math:`\Delta^2` by setting the optional keyword ``delta2 = True`` in the + constructor. + + The power functions must return a list/array that is the same size as what they are given, e.g., + in the case of no power or constant power, a function that just returns a float would not be + permitted; it would have to return an array of floats all with the same value. + + It is important to note that the power spectra used to initialize the PowerSpectrum object + should use the same units for k and :math:`P(k)`, i.e., if k is in inverse radians then + :math:`P(k)` should be in radians^2 (as is natural for outputs from a cosmological shear power + spectrum calculator). However, when we actually draw images, there is a natural scale that + defines the pitch of the image, which is typically taken to be arcsec. This definition of a + specific length scale means that by default we assume all quantities to the PowerSpectrum are + in arcsec, and those are the units used for internal calculations, but the ``units`` keyword + can be used to specify different input units for :math:`P(k)` (again, within the constraint + that k and :math:`P(k)` must be consistent). If the ``delta2`` keyword is set to specify that + the input is actually the dimensionless power :math:`\Delta^2`, then the input ``units`` are + taken to apply only to the k values. + + Parameters: + e_power_function: A function or other callable that accepts a NumPy array of abs(k) + values, and returns the E-mode power spectrum P_E(abs(k)) in an array of + the same shape. The function should return the power spectrum desired + in the E (gradient) mode of the image. + It may also be a string that can be converted to a function using + ``eval('lambda k : '+e_power_function)``, a `LookupTable`, or + ``file_name`` from which to read in a `LookupTable`. If a ``file_name`` + is given, the resulting `LookupTable` uses the defaults for the + `LookupTable` class, namely spline interpolation in :math:`P(k)`. + Users who wish to deviate from those defaults (for example, to + interpolate in log(P) and log(k), as might be more natural for + power-law functions) should instead read in the file to create a + `LookupTable` using the necessary non-default settings. [default: None, + which means no E-mode power.] + b_power_function: A function or other callable that accepts a NumPy array of abs(k) + values, and returns the B-mode power spectrum P_B(abs(k)) in an array of + the same shape. The function should return the power spectrum desired + in the B (curl) mode of the image. See description of + ``e_power_function`` for input format options. [default: None, which + means no B-mode power.] + delta2: Is the power actually given as dimensionless :math:`\Delta^2`, which + requires us to multiply by :math:`2\pi / k^2` to get the shear power + :math:`P(k)` in units of angle^2? [default: False] + units: The angular units used for the power spectrum (i.e. the units of + k^-1 and sqrt(P)). This should be either an `AngleUnit` instance + (e.g. galsim.radians) or a string (e.g. 'radians'). [default: arcsec] + """ + _opt_params = { 'e_power_function' : str, 'b_power_function' : str, + 'delta2' : bool, 'units' : str } + + def __init__(self, e_power_function=None, b_power_function=None, delta2=False, units=arcsec): + # Check that at least one power function is not None + if e_power_function is None and b_power_function is None: + raise GalSimIncompatibleValuesError( + "At least one of e_power_function or b_power_function must be provided.", + e_power_function=e_power_function, b_power_function=b_power_function) + + self.e_power_function = e_power_function + self.b_power_function = b_power_function + self.delta2 = delta2 + self.units = units + + # Try these conversions, but we don't actually keep the output. This just + # provides a way to test if the arguments are sane. + # Note: we redo this in buildGrid for real rather than keeping the outputs + # (e.g. in self.e_power_function, self.b_power_function) so that PowerSpectrum is + # picklable. It turns out lambda functions are not picklable. + self._convert_power_function(self.e_power_function,'e_power_function') + self._convert_power_function(self.b_power_function,'b_power_function') + + # Check validity of units + if isinstance(units, str): + # if the string is invalid, this raises a reasonable error message. + units = AngleUnit.from_name(units) + if not isinstance(units, AngleUnit): + raise GalSimValueError("units must be either an AngleUnit or a string", units, + ('arcsec', 'arcmin', 'degree', 'hour', 'radian')) + + if units == arcsec: + self.scale = 1 + else: + self.scale = units / arcsec + + def __repr__(self): + s = 'galsim.PowerSpectrum(e_power_function=%r'%self.e_power_function + if self.b_power_function is not None: + s += ', b_power_function=%r'%self.b_power_function + if self.delta2: + s += ', delta2=%r'%self.delta2 + if self.units != arcsec: + s += ', units=%r'%self.units + s += ')' + return s + + def __str__(self): + s = 'galsim.PowerSpectrum(e_power_function=%s'%self.e_power_function + if self.b_power_function is not None: + s += ', b_power_function=%s'%self.b_power_function + s += ')' + return s + + def __eq__(self, other): + return (self is other or + (isinstance(other, PowerSpectrum) and + self.e_power_function == other.e_power_function and + self.b_power_function == other.b_power_function and + self.delta2 == other.delta2 and + self.scale == other.scale)) + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): return hash(repr(self)) + + def _get_scale_fac(self, units): + if isinstance(units, str): + # if the string is invalid, this raises a reasonable error message. + units = AngleUnit.from_name(units) + if not isinstance(units, AngleUnit): + raise GalSimValueError("units must be either an AngleUnit or a string", units, + ('arcsec', 'arcmin', 'degree', 'hour', 'radian')) + return units / arcsec + + def _get_bandlimit_func(self, bandlimit): + if bandlimit == 'hard': + return self._hard_cutoff + elif bandlimit == 'soft': + return self._softening_function + elif bandlimit is None: + return lambda k, kmax: 1.0 + else: + raise GalSimValueError("Unrecognized option for band limit!", bandlimit, + (None, 'soft', 'hard')) + + def _get_pk(self, power_function, k_max, bandlimit_func): + if power_function is None: + return None + elif self.delta2: + # Here we have to go from Delta^2 (dimensionless) to P = 2pi Delta^2 / k^2. We want to + # have P and therefore 1/k^2 in units of arcsec, so we won't rescale the k that goes in + # the denominator. This naturally gives P(k) in arcsec^2. + return lambda k : (2.*np.pi) * power_function(self.scale*k)/(k**2) * \ + bandlimit_func(self.scale*k, self.scale*k_max) + elif self.scale != 1: + # Here, the scale comes in two places: + # The units of k have to be converted from 1/arcsec, which GalSim wants to use, into + # whatever the power spectrum function was defined to use. + # The units of power have to be converted from (input units)^2 as returned by the power + # function, to Galsim's units of arcsec^2. + # Recall that scale is (input units)/arcsec. + return lambda k : power_function(self.scale*k)*(self.scale**2) * \ + bandlimit_func(self.scale*k, self.scale*k_max) + else: + return lambda k : power_function(k) * bandlimit_func(k, k_max) + +
[docs] def buildGrid(self, grid_spacing, ngrid, rng=None, interpolant=None, + center=PositionD(0,0), units=arcsec, get_convergence=False, + kmax_factor=1, kmin_factor=1, bandlimit="hard", variance=None): + """Generate a realization of the current power spectrum on the specified grid. + + **Basic functionality**: + + This function will generate a Gaussian random realization of the specified E and B mode + shear power spectra at a grid of positions, specified by the input parameters + ``grid_spacing`` (distance between grid points) and ``ngrid`` (number of grid points in + each direction.) Units for ``grid_spacing`` and ``center`` can be specified using the + ``units`` keyword; the default is arcsec, which is how all values are stored internally. + It automatically computes and stores grids for the shears and convergence. However, since + many users are primarily concerned with shape distortion due to shear, the default is to + return only the shear components; the ``get_convergence`` keyword can be used to also + return the convergence. + + The quantities that are returned are the theoretical shears and convergences, usually + denoted gamma and kappa, respectively. Users who wish to obtain the more + observationally-relevant reduced shear and magnification (that describe real lensing + distortions) can either use the `getShear`, `getMagnification`, or `getLensing` methods + after `buildGrid`, or can use the convenience function `galsim.lensing_ps.theoryToObserved` + to convert from theoretical to observed quantities. + + **Caveats of the DFT approach**: + + Note that the shears generated using this method correspond to the PowerSpectrum multiplied + by a sharp bandpass filter, set by the dimensions of the grid. + + The filter sets :math:`P(k) = 0` for:: + + abs(k1), abs(k2) < kmin / 2 + + and:: + + abs(k1), abs(k2) > kmax + kmin / 2 + + where:: + + kmin = 2. * pi / (ngrid * grid_spacing) + kmax = pi / grid_spacing + + and where we have adopted the convention that grid points at a given ``k`` represent the + interval between (k - dk/2) and (k + dk/2) (noting that the grid spacing dk in k space + is equivalent to ``kmin``). + + It is worth remembering that this bandpass filter will *not* look like a circular annulus + in 2D ``k`` space, but is rather more like a thick-sided picture frame, having a small + square central cutout of dimensions ``kmin`` by ``kmin``. These properties are visible in + the shears generated by this method. + + If you care about these effects and want to ameliorate their effect, there are two + optional kwargs you can provide: ``kmin_factor`` and ``kmax_factor``, both of which are 1 + by default. These should be integers >= 1 that specify some factor smaller or larger + (for kmin and kmax respectively) you want the code to use for the underlying grid in + fourier space. The final shear grid is returned using the specified ``ngrid`` and + ``grid_spacing`` parameters. But the intermediate grid in Fourier space will be larger + by the specified factors. + + Note: These are really just for convenience, since you could easily get the same effect + by providing different values of ngrid and grid_spacing and then take a subset of them. + The ``kmin_factor`` and ``kmax_factor`` just handle the scalings appropriately for you. + + Use of ``kmin_factor`` and ``kmax_factor`` should depend on the desired application. For + accurate representation of power spectra, one should not change these values from their + defaults of 1. Changing them from one means the E- and B-mode power spectra that are input + will be valid for the larger intermediate grids that get generated in Fourier space, but not + necessarily for the smaller ones that get returned to the user. However, for accurate + representation of cosmological shear correlation functions, use of ``kmin_factor`` larger + than one can be helpful in getting the shear correlations closer to the ideal theoretical + ones (see ``devel/module/lensing_engine.pdf`` for details). + + **Aliasing**: + + If the user provides a power spectrum that does not include a cutoff at kmax, then our + method of generating shears will result in aliasing that will show up in both E- and + B-modes. Thus the `buildGrid` method accepts an optional keyword argument called + ``bandlimit`` that can tell the PowerSpectrum object to cut off power above kmax + automatically, where the relevant kmax is larger than the grid Nyquist frequency by a factor + of ``kmax_factor``. The allowed values for ``bandlimit`` are None (i.e., do nothing), + ``hard`` (set power to zero above the band limit), or ``soft`` (use an arctan-based + softening function to make the power go gradually to zero above the band limit). By + default, ``bandlimit=hard``. Use of this keyword does nothing to the internal + representation of the power spectrum, so if the user calls the `buildGrid` method again, + they will need to set ``bandlimit`` again (and if their grid setup is different in a way + that changes ``kmax``, then that's fine). + + **Interpolation**: + + If the grid is being created for the purpose of later interpolating to random positions, the + following findings should be kept in mind: since the interpolant modifies the effective + shear correlation function on scales comparable to <~3x the grid spacing, the grid spacing + should be chosen to be at least 3 times smaller than the minimum scales on which the user + wishes to reproduce the shear correlation function accurately. Ideally, the grid should be + somewhat larger than the region in which shears at random points are needed, so that edge + effects in the interpolation will not be important. For this purpose, there should be >~5 + grid points outside of the region in which interpolation will take place. Ignoring this + edge effect and using the grid for interpolation out to its edges can suppress shear + correlations on all scales by an amount that depends on the grid size; for a 100x100 grid, + the suppression is ~2-3%. Note that the above numbers came from tests that use a + cosmological shear power spectrum; precise figures for this suppression can also depend on + the shear correlation function itself. + + **Sign conventions and other info**: + + Note also that the convention for axis orientation differs from that for the GREAT10 + challenge, so when using codes that deal with GREAT10 challenge outputs, the sign of our g2 + shear component must be flipped. + + For more information on the effects of finite grid representation of the power spectrum + see ``devel/modules/lensing_engine.pdf``. + + **Examples**: + + 1. Get shears on a grid of points separated by 1 arcsec:: + + >>> my_ps = galsim.PowerSpectrum(lambda k : k**2) + >>> g1, g2 = my_ps.buildGrid(grid_spacing = 1., ngrid = 100) + + The returned g1, g2 are 2-d NumPy arrays of values, corresponding to the values of + g1 and g2 at the locations of the grid points. + + For a given value of ``grid_spacing`` and ``ngrid``, we could get the x and y values on + the grid using:: + + >>> import numpy as np + >>> min = (-ngrid/2 + 0.5) * grid_spacing + >>> max = (ngrid/2 - 0.5) * grid_spacing + >>> x, y = np.meshgrid(np.arange(min,max+grid_spacing,grid_spacing), + ... np.arange(min,max+grid_spacing,grid_spacing)) + + where the center of the grid is taken to be (0,0). + + 2. Rebuild the grid using a particular rng and set the location of the center of the grid + to be something other than the default (0,0):: + + >>> g1, g2 = my_ps.buildGrid(grid_spacing = 8., ngrid = 65, + ... rng = galsim.BaseDeviate(1413231), + ... center = galsim.PositionD(256.5, 256.5) ) + + 3. Make a `PowerSpectrum` from a tabulated :math:`P(k)` that gets interpolated to find the + power at all necessary values of k, then generate shears and convergences on a grid, and + convert to reduced shear and magnification so they can be used to transform galaxy + images. E.g., assuming that k and P_k are NumPy arrays containing k and :math:`P(k)`:: + + >>> tab_pk = galsim.LookupTable(k, P_k) + >>> my_ps = galsim.PowerSpectrum(tab_pk) + >>> g1, g2, kappa = my_ps.buildGrid(grid_spacing = 1., ngrid = 100, + ... get_convergence = True) + >>> g1_r, g2_r, mu = galsim.lensing_ps.theoryToObserved(g1, g2, kappa) + + Parameters: + grid_spacing: Spacing for an evenly spaced grid of points, by default in arcsec + for consistency with the natural length scale of images created + using the `GSObject.drawImage` method. Other units can be + specified using the ``units`` keyword. + ngrid: Number of grid points in each dimension. [Must be an integer] + rng: A `BaseDeviate` object for drawing the random numbers. + [default: None] + interpolant: `Interpolant` that will be used for interpolating the gridded shears + by methods like `getShear`, `getConvergence`, etc. if they are + later called. [default: galsim.Lanczos(5)] + center: If setting up a new grid, define what position you want to consider + the center of that grid. Units must be consistent with those for + ``grid_spacing``. [default: galsim.PositionD(0,0)] + units: The angular units used for the positions. [default: arcsec] + get_convergence: Return the convergence in addition to the shear? Regardless of the + value of ``get_convergence``, the convergence will still be computed + and stored for future use. [default: False] + kmin_factor: Factor by which the grid spacing in fourier space is smaller than + the default. i.e.:: + + kmin = 2. * pi / (ngrid * grid_spacing) / kmin_factor + + [default: 1; must be an integer] + kmax_factor: Factor by which the overall grid in fourier space is larger than + the default. i.e.:: + + kmax = pi / grid_spacing * kmax_factor + + [default: 1; must be an integer] + bandlimit: Keyword determining how to handle power :math:`P(k)` above the + limiting k value, kmax. The options None, 'hard', and 'soft' + correspond to doing nothing (i.e., allow P(>kmax) to be aliased to + lower k values), cutting off all power above kmax, and applying a + softening filter to gradually cut off power above kmax. Use of + this keyword does not modify the internally-stored power spectrum, + just the shears generated for this particular call to `buildGrid`. + [default: "hard"] + variance: Optionally renormalize the variance of the output shears to a + given value. This is useful if you know the functional form of + the power spectrum you want, but not the normalization. This lets + you set the normalization separately. The resulting shears should + have var(g1) + var(g2) ~= variance. If only ``e_power_function`` is + given, then this is also the variance of kappa. Otherwise, the + variance of kappa may be smaller than the specified variance. + [default: None] + + Returns: + the tuple (g1,g2[,kappa]), where each is a 2-d NumPy array and kappa is included + iff ``get_convergence`` is set to True. + """ + # Check for validity of integer values + if not isinstance(ngrid, int): + if ngrid != int(ngrid): + raise GalSimValueError("ngrid must be an integer", ngrid) + ngrid = int(ngrid) + if not isinstance(kmin_factor, int): + if kmin_factor != int(kmin_factor): + raise GalSimValueError("kmin_factor must be an integer", kmin_factor) + kmin_factor = int(kmin_factor) + if not isinstance(kmax_factor, int): + if kmax_factor != int(kmax_factor): + raise GalSimValueError("kmax_factor must be an integer", kmax_factor) + kmax_factor = int(kmax_factor) + + # Check if center is a PositionD + if not isinstance(center, PositionD): + raise GalSimValueError("center argument for buildGrid must be a PositionD instance", + center) + + # Automatically convert units to arcsec at the outset, then forget about it. This is + # because PowerSpectrum by default wants to work in arsec, and all power functions are + # automatically converted to do so, so we'll also do that here. + scale_fac = self._get_scale_fac(units) + center *= scale_fac + grid_spacing *= scale_fac + + # The final grid spacing that will be in the computed images is grid_spacing/kmax_factor. + self.grid_spacing = grid_spacing / kmax_factor + self.center = center + + # It is also convenient to store the bounds within which an input position is allowed. + self.bounds = BoundsD( center.x - (ngrid-1) * grid_spacing / 2. , + center.x + (ngrid-1) * grid_spacing / 2. , + center.y - (ngrid-1) * grid_spacing / 2. , + center.y + (ngrid-1) * grid_spacing / 2. ) + # Expand the bounds slightly to make sure rounding errors don't lead to points on the + # edge being considered off the edge. + self.bounds = self.bounds.expand( 1. + 1.e-15 ) + + self.x_grid = np.linspace(self.bounds.xmin, self.bounds.xmax, ngrid) + self.y_grid = np.linspace(self.bounds.ymin, self.bounds.ymax, ngrid) + + gd = GaussianDeviate(rng) + + # Check that the interpolant is valid. + if interpolant is None: + self.interpolant = Lanczos(5) + else: + self.interpolant = utilities.convert_interpolant(interpolant) + + # Convert power_functions into callables: + e_power_function = self._convert_power_function(self.e_power_function,'e_power_function') + b_power_function = self._convert_power_function(self.b_power_function,'b_power_function') + + # Figure out how to apply band limit if requested. + # Start by calculating kmax in the appropriate units: + # Generally, it should be kmax_factor*pi/(input grid spacing). We have already converted + # the user-input grid spacing to arcsec, the units that the PowerSpectrum class uses + # internally, and divided it by kmax_factor to get self.grid_spacing, so here we just use + # pi/self.grid_spacing. + k_max = np.pi / self.grid_spacing + bandlimit_func = self._get_bandlimit_func(bandlimit) + + # If we actually have dimensionless Delta^2, then we must convert to power + # P(k) = 2pi Delta^2 / k^2, + # which has dimensions of angle^2. + # Also apply the bandlimit and/or scale as appropriate. + p_E = self._get_pk(e_power_function, k_max, bandlimit_func) + p_B = self._get_pk(b_power_function, k_max, bandlimit_func) + + # Build the grid + self.ngrid_tot = ngrid * kmin_factor * kmax_factor + self.pixel_size = grid_spacing/kmax_factor + psr = PowerSpectrumRealizer(self.ngrid_tot, self.pixel_size, p_E, p_B) + self.grid_g1, self.grid_g2, self.grid_kappa = psr(gd, variance) + if kmin_factor != 1 or kmax_factor != 1: + # Need to make sure the rows are contiguous so we can use it in the constructor + # of the ImageD objects below. This requires a copy. + s = slice(0,ngrid*kmax_factor,kmax_factor) + self.grid_g1 = np.array(self.grid_g1[s,s], copy=True, order='C') + self.grid_g2 = np.array(self.grid_g2[s,s], copy=True, order='C') + self.grid_kappa = np.array(self.grid_kappa[s,s], copy=True, order='C') + + # Set up the images to be interpolated. + # Note: We don't make the LookupTable2D's yet, since we don't know if + # the user wants periodic wrapping or not. + # So we wait to create them when we are actually going to use them. + self.im_g1 = ImageD(self.grid_g1, scale=self.grid_spacing) + self.im_g2 = ImageD(self.grid_g2, scale=self.grid_spacing) + self.im_kappa = ImageD(self.grid_kappa, scale=self.grid_spacing) + + if get_convergence: + return self.grid_g1, self.grid_g2, self.grid_kappa + else: + return self.grid_g1, self.grid_g2
+ +
[docs] def nRandCallsForBuildGrid(self): + """Return the number of times the rng() was called the last time `buildGrid` was called. + + This can be useful for keeping rngs in sync if the connection between them is broken + (e.g. when calling the function through a Proxy object). + """ + if not hasattr(self,'ngrid_tot'): + raise GalSimError("BuildGrid has not been called yet.") + ntot = 0 + # cf. PowerSpectrumRealizer._generate_power_array + temp = 2 * np.prod( (self.ngrid_tot, self.ngrid_tot//2 +1 ) ) + if self.e_power_function is not None: + ntot += temp + if self.b_power_function is not None: + ntot += temp + return int(ntot)
+ + def _convert_power_function(self, pf, pf_str): + if pf is None: return None + + # Convert string inputs to either a lambda function or LookupTable + if isinstance(pf,str): + origpf = pf + if os.path.isfile(pf): + pf = LookupTable.from_file(pf) + else: + # Detect at least _some_ forms of malformed string input. Note that this + # test assumes that the eval string completion is defined for k=1.0. + try: + pf = utilities.math_eval('lambda k : ' + pf) + pf(1.0) + except Exception as e: + raise GalSimValueError( + "String {0} must either be a valid filename or something that " + "can eval to a function of k.\n" + "Caught error: {1}".format(pf_str, e), origpf) + + # Check that the function is sane. + # Note: Only try tests below if it's not a LookupTable. + # (If it's a LookupTable, then it could be a valid function that isn't + # defined at k=1.) + if not isinstance(pf, LookupTable): + pf(np.array((0.1,1.))) + return pf + +
[docs] def calculateXi(self, grid_spacing, ngrid, kmax_factor=1, kmin_factor=1, n_theta=100, + units=arcsec, bandlimit="hard"): + r"""Calculate shear correlation functions for the current power spectrum on the specified + grid. + + This function will calculate the theoretical shear correlation functions, :math:`\xi_+` + and :math:`\xi_-`, for this power spectrum and the grid configuration specified using + keyword arguments, taking into account the minimum and maximum k range implied by the grid + parameters, ``kmin_factor`` and ``kmax_factor``. Most theoretical correlation function + calculators assume an infinite k range, so this utility can be used to check how close the + chosen grid parameters (and the implied minimum and maximum k) come to the "ideal" result. + This is particularly useful on large scales, since in practice the finite grid extent + limits the minimum k value and therefore can suppress shear correlations on large scales. + Note that the actual shear correlation function in the generated shears will still differ + from the one calculated here due to differences between the discrete and continuous Fourier + transform. + + The quantities that are returned are three NumPy arrays: separation theta (in the adopted + units), :math:`\xi_+`, and :math:`\xi_-`. These are defined in terms of the E- and B-mode + shear power spectrum as in the document ``devel/modules/lensing_engine.pdf``, equations 2 + and 3. The values that are returned are for a particular theta value, not an average over + a range of theta values in some bin of finite width. + + This method has been tested with cosmological shear power spectra; users should check for + sanity of outputs if attempting to use power spectra that have very different scalings with + k. + + Parameters: + grid_spacing: Spacing for an evenly spaced grid of points, by default in arcsec + for consistency with the natural length scale of images created + using the `GSObject.drawImage` method. Other units can be specified + using the ``units`` keyword. + ngrid: Number of grid points in each dimension. [Must be an integer] + units: The angular units used for the positions. [default = arcsec] + kmin_factor: (Optional) Factor by which the grid spacing in fourier space is + smaller than the default. i.e.:: + + kmin = 2. * pi / (ngrid * grid_spacing) / kmin_factor + + [default ``kmin_factor = 1``; must be an integer] + kmax_factor: (Optional) Factor by which the overall grid in fourier space is + larger than the default. i.e.:: + + kmax = pi / grid_spacing * kmax_factor + + [default ``kmax_factor = 1``; must be an integer] + n_theta: (Optional) Number of logarithmically spaced bins in angular + separation. [default ``n_theta=100``] + bandlimit: (Optional) Keyword determining how to handle power :math:`P(k)` above + the limiting k value, kmax. The options None, 'hard', and 'soft' + correspond to doing nothing (i.e., allow P(>kmax) to be aliased to + lower k values), cutting off all power above kmax, and applying a + softening filter to gradually cut off power above kmax. Use of this + keyword does not modify the internally-stored power spectrum, just + the result generated by this particular call to `calculateXi`. + [default ``bandlimit="hard"``] + + Returns: + the tuple (theta, xi_p, xi_m), 1-d NumPy arrays for the angular separation theta + and the two shear correlation functions. + """ + # Normalize inputs + grid_spacing = float(grid_spacing) + ngrid = int(ngrid) + kmin_factor = int(kmin_factor) + kmax_factor = int(kmax_factor) + n_theta = int(n_theta) + + # Automatically convert units to arcsec at the outset, then forget about it. This is + # because PowerSpectrum by default wants to work in arsec, and all power functions are + # automatically converted to do so, so we'll also do that here. + scale_fac = self._get_scale_fac(units) + grid_spacing *= scale_fac + + # Decide on a grid of separation values. Do this in arcsec, for consistency with the + # internals of the PowerSpectrum class. + min_sep = grid_spacing + max_sep = ngrid*grid_spacing + theta = np.logspace(np.log10(min_sep), np.log10(max_sep), n_theta) + + # Set up the power spectrum to use for the calculations, just as in buildGrid. + # Convert power_functions into callables: + e_power_function = self._convert_power_function(self.e_power_function,'e_power_function') + b_power_function = self._convert_power_function(self.b_power_function,'b_power_function') + + # Apply band limit if requested; see comments in 'buildGrid()' for more details. + k_max = kmax_factor * np.pi / grid_spacing + bandlimit_func = self._get_bandlimit_func(bandlimit) + + # If we actually have dimensionless Delta^2, then we must convert to power + # P(k) = 2pi Delta^2 / k^2, + # which has dimensions of angle^2. + # Also apply the bandlimit and/or scale as appropriate. + p_E = self._get_pk(e_power_function, k_max, bandlimit_func) + p_B = self._get_pk(b_power_function, k_max, bandlimit_func) + + # Get k_min value in arcsec: + k_min = 2.*np.pi / (ngrid * grid_spacing * kmin_factor) + + # Do the actual integration for each of the separation values, now that we have power + # spectrum functions p_E and p_B. + xi_p = np.zeros(n_theta) + xi_m = np.zeros(n_theta) + for i_theta in range(n_theta): + # Usually theory calculations use radians. However, our k and P are already set up to + # use arcsec, so we need theta to be in arcsec (which it already is) in order for the + # units to work out right. + # xi_p = (1/2pi) \int (P_E + P_B) J_0(k theta) k dk + # xi_m = (1/2pi) \int (P_E - P_B) J_4(k theta) k dk + if p_E is not None and p_B is not None: + integrand_p = xip_integrand(lambda k: p_E(k) + p_B(k), theta[i_theta]) + integrand_m = xim_integrand(lambda k: p_E(k) - p_B(k), theta[i_theta]) + elif p_E is not None: + integrand_p = xip_integrand(p_E, theta[i_theta]) + integrand_m = xim_integrand(p_E, theta[i_theta]) + else: + integrand_p = xip_integrand(p_B, theta[i_theta]) + integrand_m = xim_integrand(lambda k: -p_B(k), theta[i_theta]) + xi_p[i_theta] = integ.int1d(integrand_p, k_min, k_max, rel_err=1.e-6, + abs_err=1.e-12) + xi_m[i_theta] = integ.int1d(integrand_m, k_min, k_max, rel_err=1.e-6, + abs_err=1.e-12) + xi_p /= (2.*np.pi) + xi_m /= (2.*np.pi) + + # Now convert the array of separation values back to whatever units were used as inputs to + # this function. + theta /= scale_fac + + # Return arrays with results. + return theta, xi_p, xi_m
+ + @staticmethod + def _softening_function(k, k_max): + # Softening function for the power spectrum band-limiting step, instead of a hard cut in k. + # We use an arctan function to go smoothly from 1 to 0 above k_max. The input k values + # can be in any units, as long as the choice of units for k and k_max is the same. + + # The magic numbers in the code below come from the following: + # We define the function as + # (arctan[A log(k/k_max) + B] + pi/2)/pi + # For our current purposes, we will define A and B by requiring that this function go to + # 0.95 (0.05) for k/k_max = 0.95 (1). This gives two equations: + # 0.95 = (arctan[log(0.95) A + B] + pi/2)/pi + # 0.05 = (arctan[B] + pi/2)/pi. + # We will solve the second equation: + # -0.45 pi = arctan(B), or + # B = tan(-0.45 pi). + b = np.tan(-0.45*np.pi) + + # Then, we get A from the first equation: + # 0.45 pi = arctan[log(0.95) A + B] + # tan(0.45 pi) = log(0.95) A + B + a = (np.tan(0.45*np.pi)-b) / np.log(0.95) + return (np.arctan(a*np.log(k/k_max)+b) + np.pi/2.)/np.pi + + @staticmethod + def _hard_cutoff(k, k_max): + if isinstance(k, float): + return float(k < k_max) + else: + return (k < k_max).astype(float) + +
[docs] def getShear(self, pos, units=arcsec, reduced=True, periodic=False): + """ + This function can interpolate between grid positions to find the shear values for a given + list of input positions (or just a single position). Before calling this function, you must + call `buildGrid` first to define the grid of shears and convergences on which to + interpolate. The docstring for `buildGrid` provides some guidance on appropriate grid + configurations to use when building a grid that is to be later interpolated to random + positions. + + By default, this method returns the reduced shear, which is defined in terms of shear and + convergence as reduced shear ``g=gamma/(1-kappa)``; the ``reduced`` keyword can be set to + False in order to return the non-reduced shear. + + Note that the interpolation (specified when calling `buildGrid`) modifies the effective + shear power spectrum and correlation function somewhat, though the effects can be limited + by careful choice of grid parameters (see buildGrid() docstring for details). Assuming + those guidelines are followed, then the shear correlation function modifications due to use + of the quintic, Lanczos-3, and Lanczos-5 interpolants are below 5% on all scales from the + grid spacing to the total grid extent, typically below 2%. The linear, cubic, and nearest + interpolants perform significantly more poorly, with modifications of the correlation + functions that can reach tens of percent on the scales where the recommended interpolants + perform well. Thus, the default interpolant is Lanczos-5, and users should think carefully + about the acceptability of significant modification of the shear correlation function before + changing to use linear, cubic, or nearest. + + Users who wish to ensure that the shear power spectrum is preserved post-interpolation + should consider using the ``periodic`` interpolation option, which assumes the shear field + is periodic (i.e., the sky is tiled with many copies of the given shear field). Those who + care about the correlation function should not use this option, and for this reason it's + not the default. + + **Examples**: + + 1. Get the shear for a particular point:: + + >>> g1, g2 = my_ps.getShear(pos = galsim.PositionD(12, 412)) + + This time the returned values are just floats and correspond to the shear for the + provided position. + + 2. You can also provide a position as a tuple to save the explicit PositionD construction:: + + >>> g1, g2 = my_ps.getShear(pos = (12, 412)) + + 3. Get the shears for a bunch of points at once:: + + >>> xlist = [ 141, 313, 12, 241, 342 ] + >>> ylist = [ 75, 199, 306, 225, 489 ] + >>> poslist = [ galsim.PositionD(xlist[i],ylist[i]) for i in range(len(xlist)) ] + >>> g1, g2 = my_ps.getShear( poslist ) + >>> g1, g2 = my_ps.getShear( (xlist, ylist) ) + + Both calls do the same thing. The returned g1, g2 this time are numpy arrays of g1, g2 + values. The arrays are the same length as the number of input positions. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + units: The angular units used for the positions. [default: arcsec] + reduced: Whether returned shear(s) should be reduced shears. [default: True] + periodi: Whether the interpolation should treat the positions as being defined + with respect to a periodic grid, which will wrap them around if they + are outside the bounds of the original grid on which shears were + defined. If not, then shears are set to zero for positions outside the + original grid. [default: False] + + Returns: + the shear as a tuple, (g1,g2) + + If the input ``pos`` is given a single position, (g1,g2) are the two shear components. + If the input ``pos`` is given a list/array of positions, they are NumPy arrays. + """ + if not hasattr(self, 'im_g1'): + raise GalSimError("PowerSpectrum.buildGrid must be called before getShear") + + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getShear') + return self._getShear(pos_x, pos_y, reduced, periodic)
+ +
[docs] def _getShear(self, pos_x, pos_y, reduced=True, periodic=False): + """Equivalent to `getShear`, but without some sanity checks and the positions must be + given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + reduced: Whether returned shear(s) should be reduced shears. [default: True] + periodic: Whether the interpolation should treat the positions as being defined + with respect to a periodic grid. [default: False] + + Returns: + the (possibly reduced) shears as a tuple (g1,g2) (either scalars or numpy arrays) + """ + g1_grid = self.im_g1.array + g2_grid = self.im_g2.array + + if reduced: + # get reduced shear (just discard magnification) + g1_grid, g2_grid, _ = theoryToObserved(g1_grid, g2_grid, self.im_kappa.array) + + lut_g1 = LookupTable2D(self.x_grid, self.y_grid, g1_grid, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + lut_g2 = LookupTable2D(self.x_grid, self.y_grid, g2_grid, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + + ret = lut_g1(pos_x, pos_y), lut_g2(pos_x, pos_y) + return ret
+ +
[docs] def getConvergence(self, pos, units=arcsec, periodic=False): + """ + This function can interpolate between grid positions to find the convergence values for a + given list of input positions (or just a single position). Before calling this function, + you must call `buildGrid` first to define the grid of convergences on which to interpolate. + The docstring for `buildGrid` provides some guidance on appropriate grid configurations to + use when building a grid that is to be later interpolated to random positions. + + Note that the interpolation (specified when calling `buildGrid`) modifies the effective + 2-point functions of these quantities. See docstring for `getShear` docstring for caveats + about interpolation. The user is advised to be very careful about deviating from the + default Lanczos-5 interpolant. + + The usage of getConvergence() is the same as for `getShear`, except that it returns only a + single quantity (convergence value or array of convergence values) rather than two + quantities. See documentation for `getShear` for some examples. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list or array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + units: The angular units used for the positions. [default: arcsec] + periodic: Whether the interpolation should treat the positions as being defined + with respect to a periodic grid, which will wrap them around if they + are outside the bounds of the original grid on which shears and + convergences were defined. If not, then convergences are set to zero + for positions outside the original grid. [default: False] + + Returns: + the convergence, kappa (either a scalar or a numpy array) + + If the input ``pos`` is given a single position, kappa is the convergence value. + If the input ``pos`` is given a list/array of positions, kappa is a NumPy array. + """ + if not hasattr(self, 'im_kappa'): + raise GalSimError("PowerSpectrum.buildGrid must be called before getConvergence") + + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getConvergence') + return self._getConvergence(pos_x, pos_y, periodic)
+ +
[docs] def _getConvergence(self, pos_x, pos_y, periodic=False): + """Equivalent to `getConvergence`, but without some sanity checks and the positions must be + given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + periodic: Whether the interpolation should treat the positions as being defined + with respect to a periodic grid. [default: False] + + Returns: + the convergence, kappa (either a scalar or a numpy array) + """ + kappa_grid = self.im_kappa.array + + lut_kappa = LookupTable2D(self.x_grid, self.y_grid, kappa_grid, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + + return lut_kappa(pos_x, pos_y)
+ +
[docs] def getMagnification(self, pos, units=arcsec, periodic=False): + """ + This function can interpolate between grid positions to find the lensing magnification (mu) + values for a given list of input positions (or just a single position). Before calling this + function, you must call `buildGrid` first to define the grid of shears and convergences on + which to interpolate. The docstring for `buildGrid` provides some guidance on appropriate + grid configurations to use when building a grid that is to be later interpolated to random + positions. + + Note that the interpolation (specified when calling `buildGrid`) modifies the effective + 2-point functions of these quantities. See docstring for `getShear` docstring for caveats + about interpolation. The user is advised to be very careful about deviating from the + default Lanczos-5 interpolant. + + The usage of `getMagnification` is the same as for `getShear`, except that it returns only + a single quantity (a magnification value or array of magnification values) rather than a + pair of quantities. See documentation for `getShear` for some examples. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + units: The angular units used for the positions. [default: arcsec] + periodic: Whether the interpolation should treat the positions as being + defined with respect to a periodic grid, which will wrap them around + if they are outside the bounds of the original grid on which shears + and convergences were defined. If not, then magnification is set to + 1 for positions outside the original grid. [default: False] + + Returns: + the magnification, mu (either a scalar or a numpy array) + + If the input ``pos`` is given a single position, mu is the magnification value. + If the input ``pos`` is given a list/array of positions, mu is a NumPy array. + """ + if not hasattr(self, 'im_kappa'): + raise GalSimError("PowerSpectrum.buildGrid must be called before getMagnification") + + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getMagnification') + return self._getMagnification(pos_x, pos_y, periodic)
+ +
[docs] def _getMagnification(self, pos_x, pos_y, periodic=False): + """Equivalent to `getMagnification`, but without some sanity checks and the positions must + be given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + periodic: Whether the interpolation should treat the positions as being defined + with respect to a periodic grid. [default: False] + + Returns: + the magnification, mu (either a scalar or a numpy array) + """ + _, _, mu_grid = theoryToObserved(self.im_g1.array, self.im_g2.array, self.im_kappa.array) + lut_mu = LookupTable2D(self.x_grid, self.y_grid, mu_grid - 1, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + + return lut_mu(pos_x, pos_y) + 1
+ +
[docs] def getLensing(self, pos, units=arcsec, periodic=False): + """ + This function can interpolate between grid positions to find the lensing observable + quantities (reduced shears g1 and g2, and magnification mu) for a given list of input + positions (or just a single position). Before calling this function, you must call + `buildGrid` first to define the grid of shears and convergences on which to interpolate. + The docstring for `buildGrid` provides some guidance on appropriate grid configurations to + use when building a grid that is to be later interpolated to random positions. + + Note that the interpolation (specified when calling `buildGrid`) modifies the effective + 2-point functions of these quantities. See docstring for `getShear` docstring for caveats + about interpolation. The user is advised to be very careful about deviating from the + default Lanczos-5 interpolant. + + The usage of `getLensing` is the same as for `getShear`, except that it returns three + quantities (two reduced shear components and magnification) rather than two. See + documentation for `getShear` for some examples. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + units: The angular units used for the positions. [default: arcsec] + periodic: Whether the interpolation should treat the positions as being + defined with respect to a periodic grid, which will wrap them around + if they are outside the bounds of the original grid on which shears + and convergences were defined. If not, then shear is set to zero + and magnification is set to 1 for positions outside the original + grid. [default: False] + + Returns: + shear and magnification as a tuple (g1,g2,mu). + + If the input ``pos`` is given a single position, the return values are the shear and + magnification values at that position. + If the input ``pos`` is given a list/array of positions, they are NumPy arrays. + """ + if not hasattr(self, 'im_kappa'): + raise GalSimError("PowerSpectrum.buildGrid must be called before getLensing") + + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getLensing') + return self._getLensing(pos_x, pos_y, periodic)
+ +
[docs] def _getLensing(self, pos_x, pos_y, periodic=False): + """Equivalent to `getLensing`, but without some sanity checks and the positions must + be given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + periodic: Whether the interpolation should treat the positions as being defined + with respect to a periodic grid. [default: False] + + Returns: + the reduced shear and magnification as a tuple (g1,g2,mu) (either scalars or + numpy arrays) + """ + g1_grid, g2_grid, mu_grid = theoryToObserved( + self.im_g1.array, self.im_g2.array, self.im_kappa.array) + + lut_g1 = LookupTable2D(self.x_grid, self.y_grid, g1_grid, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + lut_g2 = LookupTable2D(self.x_grid, self.y_grid, g2_grid, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + lut_mu = LookupTable2D(self.x_grid, self.y_grid, mu_grid-1, + edge_mode='wrap' if periodic else 'warn', + interpolant=self.interpolant) + + return lut_g1(pos_x, pos_y), lut_g2(pos_x, pos_y), lut_mu(pos_x, pos_y)+1
+ +
[docs]class PowerSpectrumRealizer: + """Class for generating realizations of power spectra with any area and pixel size. + + This class is not one that end-users should expect to interact with. It is designed to quickly + generate many realizations of the same shear power spectra on a square grid. The initializer + sets up the grids in k-space and computes the power on them. It also computes spin weighting + terms. You can alter any of the setup properties later. It currently only works for square + grids (at least, much of the internals would be incorrect for non-square grids), so while it + nominally contains arrays that could be allowed to be non-square, the constructor itself + enforces squareness. + + Parameters: + ngrid: The size of the grid in one dimension. + pixel_size: The size of the pixel sides, in units consistent with the units expected + by the power spectrum functions. + p_E: Equivalent to ``e_power_function`` in the documentation for the + `PowerSpectrum` class. + p_B: Equivalent to ``b_power_function`` in the documentation for the + `PowerSpectrum` class. + """ + def __init__(self, ngrid, pixel_size, p_E, p_B): + # Set up the k grids in x and y, and the instance variables + self.set_size(ngrid, pixel_size) + self.set_power(p_E, p_B) + + def __repr__(self): + return "galsim.lensing_ps.PowerSpectrumRealizer(ngrid=%r, pixel_size=%r, p_E=%r, p_B=%r)"%( + self.nx, self.pixel_size, self.p_E, self.p_B) + def __str__(self): + return "galsim.lensing_ps.PowerSpectrumRealizer(ngrid=%r, pixel_size=%r, p_E=%s, p_B=%s)"%( + self.nx, self.pixel_size, self.p_E, self.p_B) + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self)) + + def set_size(self, ngrid, pixel_size): + self.nx = ngrid + self.ny = ngrid + self.pixel_size = float(pixel_size) + + # Setup some handy slices for indexing different parts of k space + self.ikx = slice(0,self.nx//2+1) # positive kx values, including 0, nx/2 + self.ikxp = slice(1,(self.nx+1)//2) # limit to only values with a negative value + self.ikxn = slice(-1,self.nx//2,-1) # negative kx values + + # We always call this with nx=ny, so behavior with nx != ny is not tested. + # However, we make a basic attempt to enable such behavior in the future if needed. + self.iky = slice(0,self.ny//2+1) + self.ikyp = slice(1,(self.ny+1)//2) + self.ikyn = slice(-1,self.ny//2,-1) + + # Set up the scalar k grid. Generally, for a box size of L (in one dimension), the grid + # spacing in k_x or k_y is Delta k=2pi/L + self.kx, self.ky = utilities.kxky((self.ny,self.nx)) + self.kx /= self.pixel_size + self.ky /= self.pixel_size + + # Compute the spin weightings + self._generate_exp2ipsi() + + def set_power(self, p_E, p_B): + self.p_E = p_E + self.p_B = p_B + if p_E is None: self.amplitude_E = None + else: self.amplitude_E = np.sqrt(self._generate_power_array(p_E))/self.pixel_size + if p_B is None: self.amplitude_B = None + else: self.amplitude_B = np.sqrt(self._generate_power_array(p_B))/self.pixel_size + +
[docs] def __call__(self, gd, variance=None): + """Generate a realization of the current power spectrum. + + Parameters: + gd: A Gaussian deviate to use when generating the shear fields. + variance: Optionally renormalize the variance of the output shears to a + given value. This is useful if you know the functional form of + the power spectrum you want, but not the normalization. This lets + you set the normalization separately. The resulting shears should + have var(g1) + var(g2) ~= variance. If only ``e_power_function`` is + given, then this is also the variance of kappa. Otherwise, the + variance of kappa may be smaller than the specified variance. + [default: None] + + @return a tuple of NumPy arrays (g1,g2,kappa) for the shear and convergence. + """ + ISQRT2 = np.sqrt(1.0/2.0) + + if not isinstance(gd, GaussianDeviate): + raise TypeError( + "The gd provided to the PowerSpectrumRealizer is not a GaussianDeviate!") + + # Generate a random complex realization for the E-mode, if there is one + if self.amplitude_E is not None: + r1 = utilities.rand_arr(self.amplitude_E.shape, gd) + r2 = utilities.rand_arr(self.amplitude_E.shape, gd) + E_k = np.empty((self.ny,self.nx), dtype=complex) + E_k[:,self.ikx] = self.amplitude_E * (r1 + 1j*r2) * ISQRT2 + # E_k corresponds to real kappa, so E_k[-k] = conj(E_k[k]) + self._make_hermitian(E_k) + else: E_k = 0 + + # Generate a random complex realization for the B-mode, if there is one + if self.amplitude_B is not None: + r1 = utilities.rand_arr(self.amplitude_B.shape, gd) + r2 = utilities.rand_arr(self.amplitude_B.shape, gd) + B_k = np.empty((self.ny,self.nx), dtype=complex) + B_k[:,self.ikx] = self.amplitude_B * (r1 + 1j*r2) * ISQRT2 + # B_k corresponds to imag kappa, so B_k[-k] = -conj(B_k[k]) + # However, we later multiply this by i, so that means here B_k[-k] = conj(B_k[k]) + self._make_hermitian(B_k) + else: + B_k = 0 + + # In terms of kappa, the E mode is the real kappa, and the B mode is imaginary kappa: + # In fourier space, both E_k and B_k are complex, but the same E + i B relation holds. + kappa_k = E_k + 1j * B_k + + # Renormalize the variance if desired + if variance is not None: + current_var = np.sum(np.abs(kappa_k)**2) / (self.nx * self.ny) + factor = np.sqrt(variance / current_var) + kappa_k *= factor + E_k *= factor # Need this for the k return value below. + + # Compute gamma_k as exp(2i psi) kappa_k + # Equation 2.1.12 of Kaiser & Squires (1993, ApJ, 404, 441) is equivalent to: + # gamma_k = -self.exp2ipsi * kappa_k + # But of course, they only considered real (E-mode) kappa. + # However, this equation has a sign error. There should not be a minus in front. + # If you follow their subsequent deviation, you will see that they drop the minus sign + # when they get to 2.1.15 (another - appears from the derivative). 2.1.15 is correct. + # e.g. it correctly produces a positive point mass for tangential shear ~ 1/r^2. + # So this implies that the minus sign in 2.1.12 should not be there. + gamma_k = self.exp2ipsi * kappa_k + + # And go to real space to get the real-space shear and convergence fields. + # Note the multiplication by N is needed because the np.fft.ifft2 implicitly includes a + # 1/N^2, and for proper normalization we need a factor of 1/N. + gamma = self.nx * np.fft.ifft2(gamma_k) + # Make them contiguous, since we need to use them in an Image, which requires it. + g1 = np.ascontiguousarray(np.real(gamma)) + g2 = np.ascontiguousarray(np.imag(gamma)) + + # Could do the same thing with kappa.. + #kappa = self.nx * np.fft.ifft2(kappa_k) + #k = np.ascontiguousarray(np.real(kappa)) + + # But, since we don't care about imag(kappa), this is a bit faster: + if np.all(E_k == 0): + k = np.zeros((self.ny,self.nx)) + else: + k = self.nx * np.fft.irfft2(E_k[:,self.ikx], s=(self.ny,self.nx)) + + return g1, g2, k
+ + def _make_hermitian(self, P_k): + # Make P_k[-k] = conj(P_k[k]) + # First update the kx=0 values to be consistent with this. + P_k[self.ikyn,0] = np.conj(P_k[self.ikyp,0]) + P_k[0,0] = np.real(P_k[0,0]) # Not reall necessary, since P_k[0,0] = 0, but + # I do it anyway for the sake of pedantry... + # Then fill the kx<0 values appropriately + P_k[self.ikyp,self.ikxn] = np.conj(P_k[self.ikyn,self.ikxp]) + P_k[self.ikyn,self.ikxn] = np.conj(P_k[self.ikyp,self.ikxp]) + P_k[0,self.ikxn] = np.conj(P_k[0,self.ikxp]) + # For even nx,ny, there are a few more changes needed. + if self.ny % 2 == 0: + # Note: this is a bit more complicated if you have to separately check whether + # nx and/or ny are even. I ignore this subtlety until we decide it is needed. + P_k[self.ikyn,self.nx//2] = np.conj(P_k[self.ikyp,self.nx//2]) + P_k[self.ny//2,self.ikxn] = np.conj(P_k[self.ny//2,self.ikxp]) + P_k[self.ny//2,0] = np.real(P_k[self.ny//2,0]) + P_k[0,self.nx//2] = np.real(P_k[0,self.nx//2]) + P_k[self.ny//2,self.nx//2] = np.real(P_k[self.ny//2,self.nx//2]) + + def _generate_power_array(self, power_function): + # Internal function to generate the result of a power function evaluated on a grid, + # taking into account the symmetries. + power_array = np.empty((self.ny, self.nx//2+1)) + + # Set up the scalar |k| grid using just the positive kx,ky + k = np.sqrt(self.kx[self.iky,self.ikx]**2 + self.ky[self.iky,self.ikx]**2) + + # Fudge the value at k=0, so we don't have to evaluate power there + k[0,0] = k[1,0] + P_k = np.empty_like(k) + P_k[:,:] = power_function(k) + + # Now fix the k=0 value of power to zero + P_k[0,0] = type(P_k[0,1])(0.) + if np.any(P_k < 0): + raise GalSimError("Negative power found for some values of k!") + + power_array[self.iky, self.ikx] = P_k + power_array[self.ikyn, self.ikx] = P_k[self.ikyp, self.ikx] + return power_array + + def _generate_exp2ipsi(self): + # exp2ipsi = (kx + iky)^2 / |kx + iky|^2 is the phase of the k vector. + kz = self.kx + self.ky*1j + # exp(2i psi) = kz^2 / |kz|^2 + ksq = kz*np.conj(kz) + # Need to adjust denominator for kz=0 to avoid division by 0. + ksq[0,0] = 1. + self.exp2ipsi = kz*kz/ksq
+ # Note: this leaves exp2ipsi[0,0] = 0, but it turns out that's ok, since we only + # ever multiply it by something that is 0 anyway (amplitude[0,0] = 0). + +def kappaKaiserSquires(g1, g2): + """Perform a Kaiser & Squires (1993) inversion to get a convergence map from gridded shears. + + This function takes gridded shears and constructs a convergence map from them. While this is + complicated in reality by the non-gridded galaxy positions, it is a straightforward + implementation using Fourier transforms for the case of gridded galaxy positions. Note that + there are additional complications when dealing with real observational issues like shape noise + that are not handled by this function, and likewise there are known edge effects. + + Note that, like any process that attempts to recover information from discretely sampled data, + the ``kappa_E`` and ``kappa_B`` maps returned by this function are subject to aliasing. + There will be distortions if there are non-zero frequency modes in the lensing field represented + by ``g1`` and ``g2`` at more than half the frequency represented by the ``g1``, ``g2`` grid + spacing. To avoid this issue in practice you can smooth the input ``g1``, ``g2`` to effectively + bandlimit them (the same smoothing kernel will be present in the output ``kappa_E``, + ``kappa_B``). If applying this function to shears drawn randomly according to some power + spectrum, the power spectrum that is used should be modified to go to zero above the relevant + maximum k value for the grid being used. + + Parameters: + g1: Square NumPy array containing the first component of shear. + g2: Square NumPy array containing the second component of shear. + + Returns: + the tuple (kappa_E, kappa_B), as NumPy arrays. + + The returned kappa_E represents the convergence field underlying the input shears. + The returned kappa_B is the convergence field generated were all shears rotated by 45 degrees + prior to input. + """ + # Checks on inputs + if not (isinstance(g1, np.ndarray) and isinstance(g2, np.ndarray)): + raise TypeError("Input g1 and g2 must be NumPy arrays.") + if g1.shape != g2.shape: + raise GalSimIncompatibleValuesError("Input g1 and g2 must be the same shape.", g1=g1, g2=g2) + if g1.shape[0] != g1.shape[1]: + raise GalSimNotImplementedError("Non-square input shear grids not supported.") + + # Then setup the kx, ky grids + kx, ky = utilities.kxky(g1.shape) + kz = kx + ky*1j + + # exp(2i psi) = kz^2 / |kz|^2 + ksq = kz*np.conj(kz) + # Need to adjust denominator for kz=0 to avoid division by 0. + ksq[0,0] = 1. + exp2ipsi = kz*kz/ksq + + # Build complex g = g1 + i g2 + gz = g1 + g2*1j + + # Go to fourier space + gz_k = np.fft.fft2(gz) + + # Equation 2.1.12 of Kaiser & Squires (1993) is equivalent to: + # kz_k = -np.conj(exp2ipsi)*gz_k + # However, this equation has a sign error. There should not be a minus in front. + # If you follow their subsequent deviation, you will see that they drop the minus sign + # when they get to 2.1.15 (another - appears from the derivative). 2.1.15 is correct. + # e.g. it correctly produces a positive point mass for tangential shear ~ 1/r^2. + # So this implies that the minus sign in 2.1.12 should not be there. + kz_k = np.conj(exp2ipsi)*gz_k + + # Come back to real space + kz = np.fft.ifft2(kz_k) + + # kz = kappa_E + i kappa_B + kappaE = np.real(kz) + kappaB = np.imag(kz) + return kappaE, kappaB + +class xip_integrand: + """Utility class to assist in calculating the xi_+ shear correlation function from power + spectra.""" + def __init__(self, pk, r): + self.pk = pk + self.r = r + def __call__(self, k): + return k * self.pk(k) * j0(self.r*k) + +class xim_integrand: + """Utility class to assist in calculating the xi_- shear correlation function from power + spectra.""" + def __init__(self, pk, r): + self.pk = pk + self.r = r + def __call__(self, k): + return k * self.pk(k) * jn(4,self.r*k) +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/moffat.html b/docs/_build/html/_modules/galsim/moffat.html new file mode 100644 index 00000000000..cb380c9d0fa --- /dev/null +++ b/docs/_build/html/_modules/galsim/moffat.html @@ -0,0 +1,353 @@ + + + + + + galsim.moffat — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.moffat

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Moffat' ]
+
+import numpy as np
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors
+
+
+
[docs]class Moffat(GSObject): + r"""A class describing a Moffat surface brightness profile. + + The Moffat surface brightness profile is + + .. math:: + I(R) \sim \left(1 + (r/r_0)^2\right)^{-\beta} + + where :math:`r_0` is ``scale_radius``. + + The GalSim representation of a Moffat profile also includes an optional truncation beyond a + given radius. + + For more information, refer to + + http://home.fnal.gov/~neilsen/notebook/astroPSF/astroPSF.html + + A Moffat can be initialized using one (and only one) of three possible size parameters: + ``scale_radius``, ``fwhm``, or ``half_light_radius``. Exactly one of these three is required. + + Parameters: + beta: The ``beta`` parameter of the profile. + scale_radius: The scale radius of the profile. Typically given in arcsec. + [One of ``scale_radius``, ``fwhm``, or ``half_light_radius`` is + required.] + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``scale_radius``, ``fwhm``, or ``half_light_radius`` is + required.] + fwhm: The full-width-half-max of the profile. Typically given in arcsec. + [One of ``scale_radius``, ``fwhm``, or ``half_light_radius`` is + required.] + trunc: An optional truncation radius at which the profile is made to drop to + zero, in the same units as the size parameter. + [default: 0, indicating no truncation] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "beta" : float } + _opt_params = { "trunc" : float , "flux" : float } + _single_params = [ { "scale_radius" : float, "half_light_radius" : float, "fwhm" : float } ] + + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + # The conversion from hlr or fwhm to scale radius is complicated for Moffat, especially + # since we allow it to be truncated, which matters for hlr. So we do these calculations + # in the C++-layer constructor. + def __init__(self, beta, scale_radius=None, half_light_radius=None, fwhm=None, trunc=0., + flux=1., gsparams=None): + self._beta = float(beta) + self._trunc = float(trunc) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + + if self._trunc == 0. and self._beta <= 1.1: + raise GalSimRangeError("Moffat profiles with beta <= 1.1 must be truncated", + beta, 1.1) + if self._trunc < 0.: + raise GalSimRangeError("Moffat trunc must be >= 0", self._trunc, 0.) + + # Parse the radius options + if half_light_radius is not None: + if scale_radius is not None or fwhm is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius, half_light_radius, or fwhm may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius, fwhm=fwhm) + self._hlr = float(half_light_radius) + if self._trunc > 0. and self._trunc <= math.sqrt(2.) * self._hlr: + raise GalSimRangeError("Moffat trunc must be > sqrt(2) * half_light_radius.", + self._trunc, math.sqrt(2.) * self._hlr) + with convert_cpp_errors(): + self._r0 = _galsim.MoffatCalculateSRFromHLR(self._hlr, self._trunc, self._beta) + self._fwhm = 0. + elif fwhm is not None: + if scale_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius, half_light_radius, or fwhm may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius, fwhm=fwhm) + self._fwhm = float(fwhm) + self._r0 = self._fwhm / (2. * math.sqrt(2.**(1./self._beta) - 1.)) + self._hlr = 0. + elif scale_radius is not None: + self._r0 = float(scale_radius) + self._hlr = 0. + self._fwhm = 0. + else: + raise GalSimIncompatibleValuesError( + "One of scale_radius, half_light_radius, or fwhm must be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius, fwhm=fwhm) + + @lazy_property + def _sbp(self): + with convert_cpp_errors(): + return _galsim.SBMoffat(self._beta, self._r0, self._trunc, self._flux, + self.gsparams._gsp) + + @property + def beta(self): + """The beta parameter of this `Moffat` profile. + """ + return self._beta + @property + def scale_radius(self): + """The scale radius of this `Moffat` profile. + """ + return self._r0 + @property + def trunc(self): + """The truncation radius (if any) of this `Moffat` profile. + """ + return self._trunc + + @property + def half_light_radius(self): + """The half-light radius of this `Moffat` profile. + """ + if self._hlr == 0.: + self._hlr = self._sbp.getHalfLightRadius() + return self._hlr + + @lazy_property + def fwhm(self): + """The FWHM of this `Moffat` profle. + """ + if self._fwhm == 0.: + self._fwhm = self._r0 * (2. * math.sqrt(2.**(1./self._beta) - 1.)) + return self._fwhm + + def __eq__(self, other): + return (self is other or + (isinstance(other, Moffat) and + self.beta == other.beta and + self.scale_radius == other.scale_radius and + self.trunc == other.trunc and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Moffat", self.beta, self.scale_radius, self.trunc, self.flux, + self.gsparams)) + + def __repr__(self): + return 'galsim.Moffat(beta=%r, scale_radius=%r, trunc=%r, flux=%r, gsparams=%r)'%( + self.beta, self.scale_radius, self.trunc, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Moffat(beta=%s, scale_radius=%s'%(self.beta, self.scale_radius) + if self.trunc != 0.: + s += ', trunc=%s'%self.trunc + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _has_hard_edges(self): + return self._trunc != 0. + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Moffat(beta=self.beta, scale_radius=self.scale_radius, trunc=self.trunc, + flux=flux, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/nfw_halo.html b/docs/_build/html/_modules/galsim/nfw_halo.html new file mode 100644 index 00000000000..bf3a6679ba7 --- /dev/null +++ b/docs/_build/html/_modules/galsim/nfw_halo.html @@ -0,0 +1,621 @@ + + + + + + galsim.nfw_halo — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.nfw_halo

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'NFWHalo', 'Cosmology' ]
+
+import numpy as np
+
+from .position import PositionD
+from .angle import arcsec
+from . import integ
+from . import utilities
+from .errors import GalSimRangeError, GalSimIncompatibleValuesError
+
+
[docs]class Cosmology: + """Basic cosmology calculations. + + Cosmology calculates expansion function E(a) and angular diameter distances Da(z) for a + LambdaCDM universe. Radiation is assumed to be zero and Dark Energy constant with w = -1 (no + quintessence), but curvature is arbitrary. + + Based on Matthias Bartelmann's libastro. + + Parameters: + omega_m: Present day energy density of matter relative to critical density. + [default: 0.3] + omega_lam: Present day density of Dark Energy relative to critical density. + [default: 0.7] + """ + def __init__(self, omega_m=0.3, omega_lam=0.7): + # no quintessence, no radiation in this universe! + self.omega_m = omega_m + self.omega_lam = omega_lam + self.omega_c = (1. - omega_m - omega_lam) + #self.omega_r = 0 + + def __repr__(self): + return "galsim.Cosmology(omega_m=%r, omega_lam=%r)"%(self.omega_m, self.omega_lam) + def __str__(self): + return "galsim.Cosmology(%s,%s)"%(self.omega_m, self.omega_lam) + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self)) + +
[docs] def a(self, z): + """Compute scale factor. + + Parameters: + z: Redshift + """ + return 1./(1+z)
+ +
[docs] def E(self, a): + """Evaluates expansion function. + + Parameters: + a: Scale factor. + """ + #return (self.omega_r*a**(-4) + self.omega_m*a**(-3) + self.omega_c*a**(-2) + \ + # self.omega_lam)**0.5 + return (self.omega_m*a**(-3) + self.omega_c*a**(-2) + self.omega_lam)**0.5
+ + def __angKernel(self, x): + """Integration kernel for angular diameter distance computation. + """ + return self.E(x**-1)**-1 + +
[docs] def Da(self, z, z_ref=0): + """Compute angular diameter distance between two redshifts in units of c/H0. + + In order to get the distance in Mpc/h, multiply by c/H0~3000. + + Parameters: + z: Redshift. + z_ref: Reference redshift, with z_ref <= z. [default: 0] + """ + if isinstance(z, np.ndarray): + da = np.zeros_like(z, dtype=float) + for i in range(len(da)): + da[i] = self.Da(z[i], z_ref) + return da + else: + if z < 0: + raise GalSimRangeError("Redshift z must be >= 0", z, 0.) + if z < z_ref: + raise GalSimRangeError("Redshift z must be >= the reference redshift", z, z_ref) + + d = integ.int1d(self.__angKernel, z_ref+1, z+1) + # check for curvature + rk = (abs(self.omega_c))**0.5 + if (rk*d > 0.01): + if self.omega_c > 0: + d = np.sinh(rk*d)/rk + if self.omega_c < 0: + d = np.sin(rk*d)/rk + return d/(1+z)
+ +
[docs]class NFWHalo: + """Class describing NFW halos. + + This class computes the lensing fields shear and convergence of a spherically symmetric NFW + halo of given mass, concentration, redshift, assuming a particular cosmology. + No mass-concentration relation is employed. + + Based on Matthias Bartelmann's libastro. + + The cosmology to use can be set either by providing a `Cosmology` instance as cosmo, + or by providing omega_m and/or omega_lam. + If only one of the latter is provided, the other is taken to be one minus that. + If no cosmology parameters are set, a default `Cosmology` is constructed. + + Parameters: + mass: Mass defined using a spherical overdensity of 200 times the critical density + of the universe, in units of M_solar/h. + conc: Concentration parameter, i.e., ratio of virial radius to NFW scale radius. + redshift: Redshift of the halo. + halo_pos: `Position` of halo center (in arcsec). [default: PositionD(0,0)] + omega_m: Omega_matter to pass to `Cosmology` constructor. [default: None] + omega_lam: Omega_lambda to pass to `Cosmology` constructor. [default: None] + cosmo: A `Cosmology` instance. [default: None] + """ + _req_params = { 'mass' : float , 'conc' : float , 'redshift' : float } + _opt_params = { 'halo_pos' : PositionD , 'omega_m' : float , 'omega_lam' : float } + + def __init__(self, mass, conc, redshift, halo_pos=PositionD(0,0), + omega_m=None, omega_lam=None, cosmo=None): + if omega_m is not None or omega_lam is not None: + if cosmo is not None: + raise GalSimIncompatibleValuesError( + "NFWHalo constructor received both cosmo and omega parameters", + cosmo=cosmo, omega_m=omega_m, omega_lam=omega_lam) + if omega_m is None: omega_m = 1.-omega_lam + if omega_lam is None: omega_lam = 1.-omega_m + cosmo = Cosmology(omega_m=omega_m, omega_lam=omega_lam) + elif cosmo is None: + cosmo = Cosmology() + elif not isinstance(cosmo,Cosmology): + raise TypeError("Invalid cosmo parameter in NFWHalo constructor") + + # Make sure things are the right types. + self.M = float(mass) + self.c = float(conc) + self.z = float(redshift) + self.halo_pos = PositionD(halo_pos) + self.cosmo = cosmo + + # calculate scale radius + a = self.cosmo.a(self.z) + # First we get the virial radius, which is defined for some spherical overdensity as + # 3 M / [4 pi (r_vir)^3] = overdensity + # Here we have overdensity = 200 * rhocrit, to determine R200. The factor of 1.63e-5 comes + # from the following set of prefactors: (3 / (4 pi * 200 * rhocrit))^(1/3) + # where rhocrit = 2.8e11 h^2 M_solar / Mpc^3. The mass in the equation below is in + # M_solar/h, which is how the final units are Mpc/h. + R200 = 1.63e-5/(1+self.z) * (self.M * self.__omega(a)/self.__omega(1))**0.3333 # in Mpc/h + self.rs = R200/self.c + + # convert scale radius in arcsec + dl = self.cosmo.Da(self.z)*3000. # in Mpc/h + scale = self.rs / dl + arcsec2rad = 1./206265 + self.rs_arcsec = scale/arcsec2rad + + def __repr__(self): + s = "galsim.NFWHalo(mass=%r, conc=%r, redshift=%r"%(self.M, self.c, self.z) + if self.halo_pos != PositionD(0,0): + s += ", halo_pos=%r"%self.halo_pos + if self.cosmo != Cosmology(): + s += ", cosmo=%r"%self.cosmo + s += ")" + return s + def __str__(self): + return "galsim.NFWHalo(mass=%s, conc=%s, redshift=%s)"%(self.M, self.c, self.z) + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self)) + + def __omega(self, a): + """Matter density at scale factor a. + """ + return self.cosmo.omega_m/(self.cosmo.E(a)**2 * a**3) + + def __farcth (self, x): + """Numerical implementation of integral functions of a spherical NFW profile. + + All expressions are a function of ``x``, which is the radius r in units of the NFW scale + radius, r_s. For the derivation of these functions, see for example Wright & Brainerd + (2000, ApJ, 534, 34). + """ + out = np.zeros_like(x, dtype=float) + + # 3 cases: x > 1, x < 1, and |x-1| < 0.001 + mask = np.where(x < 0.999)[0] # Equivalent but usually faster than `mask = (x < 0.999)` + a = ((1.-x[mask])/(x[mask]+1.))**0.5 + out[mask] = 0.5*np.log((1.+a)/(1.-a))/(1-x[mask]**2)**0.5 + + mask = np.where(x > 1.001)[0] + a = ((x[mask]-1.)/(x[mask]+1.))**0.5 + out[mask] = np.arctan(a)/(x[mask]**2 - 1)**0.5 + + # the approximation below has a maximum fractional error of 2.3e-7 + mask = np.where((x >= 0.999) & (x <= 1.001))[0] + out[mask] = 5./6. - x[mask]/3. + return out + + def __kappa(self, x, ks): + """Calculate convergence of halo. + + Parameters: + x: Radial coordinate in units of rs (scale radius of halo), i.e., ``x=r/rs``. + ks: Lensing strength prefactor. + """ + # convenience: call with single number + if not isinstance(x, np.ndarray): + return self.__kappa(np.array([x], dtype=float), np.array([ks], dtype=float))[0] + out = np.zeros_like(x, dtype=float) + + # 3 cases: x > 1, x < 1, and |x-1| < 0.001 + mask = np.where(x < 0.999)[0] + a = ((1 - x[mask])/(x[mask] + 1))**0.5 + out[mask] = 2*ks[mask]/(x[mask]**2 - 1) * (1 - np.log((1+a)/(1-a)) / (1-x[mask]**2)**0.5) + + mask = np.where(x > 1.001)[0] + a = ((x[mask] - 1)/(x[mask] + 1))**0.5 + out[mask] = 2*ks[mask]/(x[mask]**2 - 1) * (1 - 2*np.arctan(a)/(x[mask]**2 - 1)**0.5) + + # the approximation below has a maximum fractional error of 7.4e-7 + mask = np.where((x >= 0.999) & (x <= 1.001))[0] + out[mask] = ks[mask]*(22./15. - 0.8*x[mask]) + return out + + def __gamma(self, x, ks): + """Calculate tangential shear of halo. + + Parameters: + x: Radial coordinate in units of rs (scale radius of halo), i.e., ``x=r/rs``. + ks: Lensing strength prefactor. + """ + # convenience: call with single number + if not isinstance(x, np.ndarray): + return self.__gamma(np.array([x], dtype=float), np.array([ks], dtype=float))[0] + out = np.zeros_like(x, dtype=float) + + mask = np.where(x > 0.01)[0] + out[mask] = 4*ks[mask]*(np.log(x[mask]/2) + 2*self.__farcth(x[mask])) * \ + x[mask]**(-2) - self.__kappa(x[mask], ks[mask]) + + # the approximation below has a maximum fractional error of 1.1e-7 + mask = np.where(x <= 0.01)[0] + out[mask] = 4*ks[mask]*(0.25 + 0.125 * x[mask]**2 * (3.25 + 3.0*np.log(x[mask]/2))) + return out + + def __ks(self, z_s): + """Lensing strength of halo as function of source redshift. + """ + # critical density and surface density + rho_c = 2.7722e11 + Sigma_c = 5.5444e14 + # density contrast of halo at redshift z + a = self.cosmo.a(self.z) + ez = self.cosmo.E(a) + d0 = 200./3 * self.c**3/(np.log(1+self.c) - (1.*self.c)/(1+self.c)) + rho_s = rho_c * ez**2 *d0 + + # lensing weights: the only thing that depends on z_s + # this does takes some time... + dl = self.cosmo.Da(z_s, self.z) * self.cosmo.Da(self.z) / self.cosmo.Da(z_s) + k_s = dl * self.rs * rho_s / Sigma_c + return k_s + +
[docs] def getShear(self, pos, z_s, units=arcsec, reduced=True): + """Calculate (reduced) shear of halo at specified positions. + + Parameters: + po: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + z_s: Source redshift(s). + units: Angular units of coordinates. [default: galsim.arcsec] + reduced: Whether returned shear(s) should be reduced shears. [default: True] + + Returns: + the (possibly reduced) shears as a tuple (g1,g2) + + If the input ``pos`` is given a single position, (g1,g2) are the two shear components. + If the input ``pos`` is given a list/array of positions, they are NumPy arrays. + """ + pos_x, pos_y = utilities._convertPositions(pos, units, 'getShear') + return self._getShear(pos_x, pos_y, z_s, reduced)
+ +
[docs] def _getShear(self, pos_x, pos_y, z_s, reduced=True): + """Equivalent to `getShear`, but without some sanity checks and the positions must be + given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + z_s: Source redshift(s). + reduced: Whether returned shear(s) should be reduced shears. [default: True] + + Returns: + the (possibly reduced) shears as a tuple (g1,g2) (either scalars or numpy arrays) + """ + r = ((pos_x - self.halo_pos.x)**2 + (pos_y - self.halo_pos.y)**2)**0.5/self.rs_arcsec + # compute strength of lensing fields + ks = self.__ks(z_s) + if not isinstance(z_s, np.ndarray): + ks = ks*np.ones_like(r) + g = self.__gamma(r, ks) + + # convert to observable = reduced shear + if reduced: + kappa = self.__kappa(r, ks) + g /= 1 - kappa + + # pure tangential shear, no cross component + dx = pos_x - self.halo_pos.x + dy = pos_y - self.halo_pos.y + drsq = dx*dx+dy*dy + # Avoid division by 0 + cos2phi = np.divide(dx*dx-dy*dy, drsq, where=(drsq != 0.)) + sin2phi = np.divide(2*dx*dy, drsq, where=(drsq != 0.)) + g1 = -g*cos2phi + g2 = -g*sin2phi + return g1, g2
+ + +
[docs] def getConvergence(self, pos, z_s, units=arcsec): + """Calculate convergence of halo at specified positions. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + z_s: Source redshift(s). + unit: Angular units of coordinates. [default: galsim.arcsec] + + Returns: + the convergence, kappa + + If the input ``pos`` is given a single position, kappa is the convergence value. + If the input ``pos`` is given a list/array of positions, kappa is a NumPy array. + """ + + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getKappa') + return self._getConvergence(pos_x, pos_y, z_s)
+ +
[docs] def _getConvergence(self, pos_x, pos_y, z_s): + """Equivalent to `getConvergence`, but without some sanity checks and the positions must be + given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + z_s: Source redshift(s). + + Returns: + the convergence as either a scalar or a numpy array + """ + r = ((pos_x - self.halo_pos.x)**2 + (pos_y - self.halo_pos.y)**2)**0.5/self.rs_arcsec + # compute strength of lensing fields + ks = self.__ks(z_s) + if not isinstance(z_s, np.ndarray): + ks = ks*np.ones_like(r) + kappa = self.__kappa(r, ks) + return kappa
+ +
[docs] def getMagnification(self, pos, z_s, units=arcsec): + """Calculate magnification of halo at specified positions. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + z_s: Source redshift(s). + units: Angular units of coordinates. [default: galsim.arcsec] + + Returns: + the magnification mu + + If the input ``pos`` is given a single position, mu is the magnification value. + If the input ``pos`` is given a list/array of positions, mu is a NumPy array. + """ + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getMagnification') + return self._getMagnification(pos_x, pos_y, z_s)
+ +
[docs] def _getMagnification(self, pos_x, pos_y, z_s): + """Equivalent to `getMagnification`, but without some sanity checks and the positions must + be given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + z_s: Source redshift(s). + + Returns: + the magnification as either a scalar or a numpy array + """ + r = ((pos_x - self.halo_pos.x)**2 + (pos_y - self.halo_pos.y)**2)**0.5/self.rs_arcsec + # compute strength of lensing fields + ks = self.__ks(z_s) + if not isinstance(z_s, np.ndarray): + ks = ks*np.ones_like(r) + g = self.__gamma(r, ks) + kappa = self.__kappa(r, ks) + + mu = 1. / ( (1.-kappa)**2 - g**2 ) + return mu
+ +
[docs] def getLensing(self, pos, z_s, units=arcsec): + """Calculate lensing shear and magnification of halo at specified positions. + + Parameters: + pos: Position(s) of the source(s), assumed to be post-lensing! + Valid ways to input this: + + - single `Position` instance + - tuple of floats: (x,y) + - list/array of `Position` instances + - tuple of lists/arrays: ( xlist, ylist ) + + z_s: Source redshift(s). + units: Angular units of coordinates. [default: galsim.arcsec] + + Returns: + the reduced shears and magnifications as a tuple (g1,g2,mu) + + If the input ``pos`` is given a single position, the return values are the shear and + magnification values at that position. + If the input ``pos`` is given a list/array of positions, they are NumPy arrays. + """ + # Convert to numpy arrays for internal usage: + pos_x, pos_y = utilities._convertPositions(pos, units, 'getLensing') + return self._getLensing(pos_x, pos_y, z_s)
+ +
[docs] def _getLensing(self, pos_x, pos_y, z_s): + """Equivalent to `getLensing`, but without some sanity checks and the positions must + be given as ``pos_x``, ``pos_y`` in arcsec. + + Parameters: + pos_x: x position in arcsec (either a scalar or a numpy array) + pos_y: y position in arcsec (either a scalar or a numpy array) + z_s: Source redshift(s). + + Returns: + the reduced shears and magnifications as a tuple (g1,g2,mu) (each being + either a scalar or a numpy array) + """ + r = ((pos_x - self.halo_pos.x)**2 + (pos_y - self.halo_pos.y)**2)**0.5/self.rs_arcsec + # compute strength of lensing fields + ks = self.__ks(z_s) + if not isinstance(z_s, np.ndarray): + ks = ks*np.ones_like(r) + g = self.__gamma(r, ks) + kappa = self.__kappa(r, ks) + + mu = 1. / ( (1.-kappa)**2 - g**2 ) + g /= 1 - kappa + # Get the tangential shear (no x component) + dx = pos_x - self.halo_pos.x + dy = pos_y - self.halo_pos.y + drsq = dx*dx+dy*dy + # Avoid division by 0 + cos2phi = np.divide(dx*dx-dy*dy, drsq, where=(drsq != 0.)) + sin2phi = np.divide(2*dx*dy, drsq, where=(drsq != 0.)) + g1 = -g*cos2phi + g2 = -g*sin2phi + return g1, g2, mu
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/noise.html b/docs/_build/html/_modules/galsim/noise.html new file mode 100644 index 00000000000..2045abff926 --- /dev/null +++ b/docs/_build/html/_modules/galsim/noise.html @@ -0,0 +1,786 @@ + + + + + + galsim.noise — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.noise

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'BaseNoise', 'GaussianNoise', 'PoissonNoise', 'CCDNoise',
+            'DeviateNoise', 'VariableGaussianNoise', ]
+
+import numpy as np
+import math
+
+from .image import Image, ImageD
+from ._utilities import doc_inherit
+from .errors import GalSimError, GalSimIncompatibleValuesError
+from .random import BaseDeviate, GaussianDeviate, PoissonDeviate
+
+
+def addNoise(self, noise):
+    # This will be inserted into the Image class as a method.  So self = image.
+    """Add noise to the image according to a supplied noise model.
+
+    Parameters:
+        noise:      The noise (`BaseNoise`) model to use.
+    """
+    noise.applyTo(self)
+
+# This will be inserted into the Image class as a method.  So self = image.
+def addNoiseSNR(self, noise, snr, preserve_flux=False):
+    r"""Adds noise to the image in a way that achieves the specified signal-to-noise ratio.
+
+    The specified ``snr`` (signal-to-noise ratio, or :math:`S/N`) can be achieved either by scaling
+    the flux of the object while keeping the noise level fixed, or the flux can be preserved and
+    the noise variance changed.  This choice is specified using the parameter ``preserve_flux``,
+    where False means the former and True means the latter.
+
+    The definition of :math:`S/N` is equivalent to the one used by Great08.  We take the signal to
+    be a weighted sum of the pixel values:
+
+    .. math::
+        S &= \frac{\sum W(x,y) I(x,y)}{\sum W(x,y)} \\
+        N^2 = Var(S) &= \frac{\sum W(x,y)^2 Var(I(x,y))}{(\sum W(x,y))^2}
+
+    and assume that :math:`Var(I(x,y))` is constant, :math:`V \equiv Var(I(x,y))`.
+    We then assume that we are using a matched filter for :math:`W`, so :math:`W(x,y) = I(x,y)`.
+    Then a few things cancel and we find that
+
+    .. math::
+        (S/N)^2 = \frac{\sum I(x,y)^2}{V}
+
+    and therefore, for a given :math:`I(x,y)` and :math:`S/N` (aka ``snr``)
+
+    .. math::
+        V = \frac{\sum I(x,y)^2}{(S/N)^2}
+
+    .. note::
+        For noise models such as `PoissonNoise` and `CCDNoise`, the assumption of constant
+        :math:`Var(I(x,y))` is only approximate, since the flux of the object adds to the Poisson
+        noise in those pixels.  Thus, the real :math:`S/N` on the final image will be slightly
+        lower than the target ``snr`` value, and this effect will be larger for brighter objects.
+
+    This function relies on `BaseNoise.getVariance` to determine how much variance the noise model
+    will add.  Thus, it will not work for noise models that do not have a well-defined variance,
+    such as `VariableGaussianNoise`.
+
+    Parameters:
+        noise:          The noise (`BaseNoise`) model to use.
+        snr:            The desired signal-to-noise after the noise is applied.
+        preserve_flux:  Whether to preserve the flux of the object (``True``) or the variance of
+                        the noise model (``False``) to achieve the desired snr. [default: False]
+
+    Returns:
+        the variance of the noise that was applied to the image.
+    """
+    noise_var = noise.getVariance()
+    sumsq = np.sum(self.array**2, dtype=float)
+    if preserve_flux:
+        new_noise_var = sumsq/snr/snr
+        noise = noise.withVariance(new_noise_var)
+        self.addNoise(noise)
+        return new_noise_var
+    else:
+        sn_meas = np.sqrt( sumsq/noise_var )
+        flux = snr/sn_meas
+        self *= flux
+        self.addNoise(noise)
+        return noise_var
+
+Image.addNoise = addNoise
+Image.addNoiseSNR = addNoiseSNR
+
+
+
[docs]class BaseNoise: + """Base class for all noise classes. + + This class should not be constructed directly. Rather, you should instantiate one of the + subclasses: + + * `GaussianNoise` + * `PoissonNoise` + * `CCDNoise` + * `VariableGaussianNoise` + * `DeviateNoise` + + which define what kind of noise you want to implement. This base class mostly just serves as + a way to check if an object is a valid noise object with:: + + >>> isinstance(noise, galsim.BaseNoise) + """ + def __init__(self, rng=None): + if rng is None: + self._rng = BaseDeviate() + else: + if not isinstance(rng, BaseDeviate): + raise TypeError("rng must be a galsim.BaseDeviate instance.") + self._rng = rng + + @property + def rng(self): + """The `BaseDeviate` of this noise object. + """ + return self._rng + +
[docs] def getVariance(self): + """Get variance in current noise model.""" + return self._getVariance()
+ + def _getVariance(self): + raise NotImplementedError("Cannot call getVariance on a pure BaseNoise object") + +
[docs] def withVariance(self, variance): + """Return a new noise object (of the same type as the current one) with the specified + variance. + + Parameters: + variance: The desired variance in the noise. + + Returns: + a new Noise object with the given variance. + """ + return self._withVariance(variance)
+ + def _withVariance(self, variance): + raise NotImplementedError("Cannot call withVariance on a pure BaseNoise object") + +
[docs] def withScaledVariance(self, variance_ratio): + """Return a new noise object with the variance scaled up by the specified factor. + + This is equivalent to noise * variance_ratio. + + Parameters: + variance_ratio: The factor by which to scale the variance of the correlation + function profile. + + Returns: + a new Noise object whose variance has been scaled by the given amount. + """ + return self._withScaledVariance(variance_ratio)
+ + def _withScaledVariance(self, variance_ratio): + raise NotImplementedError("Cannot call withScaledVariance on a pure BaseNoise object") + +
[docs] def __mul__(self, variance_ratio): + """Multiply the variance of the noise by ``variance_ratio``. + + Parameters: + variance_ratio: The factor by which to scale the variance of the correlation + function profile. + + Returns: + a new Noise object whose variance has been scaled by the given amount. + """ + return self.withScaledVariance(variance_ratio)
+ +
[docs] def __div__(self, variance_ratio): + """Equivalent to self * (1/variance_ratio)""" + return self.withScaledVariance(1./variance_ratio)
+ + __rmul__ = __mul__ + __truediv__ = __div__ + +
[docs] def applyTo(self, image): + """Add noise to an input `Image`. + + e.g.:: + + >>> noise.applyTo(image) + + On output the `Image` instance ``image`` will have been given additional noise according + to the current noise model. + + Note: This is equivalent to the alternate syntax:: + + >>> image.addNoise(noise) + + which may be more convenient or clearer. + """ + if not isinstance(image, Image): + raise TypeError("Provided image must be a galsim.Image") + return self._applyTo(image)
+ + def _applyTo(self, image): + raise NotImplementedError("Cannot call applyTo on a pure BaseNoise object") + + def __eq__(self, other): + # Quick and dirty. Just check reprs are equal. + return self is other or repr(self) == repr(other) + + def __ne__(self, other): + return not self.__eq__(other) + + __hash__ = None
+ + +
[docs]class GaussianNoise(BaseNoise): + """Class implementing simple Gaussian noise. + + This is a simple noise model where each pixel in the image gets Gaussian noise with a + given ``sigma``. + + Example: + + The following will add Gaussian noise to every element of an image:: + + >>> gaussian_noise = galsim.GaussianNoise(rng, sigma=1.0) + >>> image.addNoise(gaussian_noise) + + Parameters: + rng: A `BaseDeviate` instance to use for generating the random numbers. + sigma: The rms noise on each pixel. [default: 1.] + + Attributes: + rng: The internal random number generator (read-only) + sigma: The value of the constructor parameter sigma (read-only) + """ + def __init__(self, rng=None, sigma=1.): + BaseNoise.__init__(self, rng) + self._sigma = sigma + self._gd = GaussianDeviate(self.rng, sigma=sigma) + + @property + def sigma(self): + """The input sigma value. + """ + return self._sigma + + def _applyTo(self, image): + self._gd.clearCache() + noise_array = np.empty(np.prod(image.array.shape), dtype=float) + self._gd.generate(noise_array) + image.array[:,:] += noise_array.reshape(image.array.shape).astype(image.dtype) + + def _getVariance(self): + return self.sigma**2 + + def _withVariance(self, variance): + return GaussianNoise(self.rng, math.sqrt(variance)) + + def _withScaledVariance(self, variance_ratio): + return GaussianNoise(self.rng, self.sigma * math.sqrt(variance_ratio)) + +
[docs] def copy(self, rng=None): + """Returns a copy of the Gaussian noise model. + + By default, the copy will share the `BaseDeviate` random number generator with the parent + instance. However, you can provide a new rng to use in the copy if you want with:: + + >>> noise_copy = noise.copy(rng=new_rng) + """ + if rng is None: rng = self.rng + return GaussianNoise(rng, self.sigma)
+ + def __repr__(self): + return 'galsim.GaussianNoise(rng=%r, sigma=%r)'%(self.rng, self.sigma) + + def __str__(self): + return 'galsim.GaussianNoise(sigma=%s)'%(self.sigma)
+ + +
[docs]class PoissonNoise(BaseNoise): + """Class implementing Poisson noise according to the flux (and sky level) present in the image. + + The PoissonNoise class encapsulates a simple version of the noise model of a normal CCD image + where each pixel has Poisson noise corresponding to the number of electrons in each pixel + (including an optional extra sky level). + + Note that if the image to which you are adding noise already has a sky level on it, + then you should not provide the sky level here as well. The sky level here corresponds + to a level that is taken to be already subtracted from the image, but that originally + contributed to the addition of Poisson noise. + + Example: + + The following will add Poisson noise to every element of an image:: + + >>> poisson_noise = galsim.PoissonNoise(rng, sky_level=0.) + >>> image.addNoise(poisson_noise) + + Parameters: + rng: A `BaseDeviate` instance to use for generating the random numbers. + sky_level: The sky level in electrons per pixel that was originally in the input image, + but which is taken to have already been subtracted off. [default: 0.] + + Attributes: + rng: The internal random number generator (read-only) + sky_level: The value of the constructor parameter sky_level (read-only) + """ + def __init__(self, rng=None, sky_level=0.): + BaseNoise.__init__(self, rng) + self._sky_level = sky_level + self._pd = PoissonDeviate(self.rng) + + @property + def sky_level(self): + """The input sky_level. + """ + return self._sky_level + + def _applyTo(self, image): + noise_array = np.empty(np.prod(image.array.shape), dtype=float) + noise_array.reshape(image.array.shape)[:,:] = image.array + + # Minor subtlety for integer images. It's a bit more consistent to convert to an + # integer with the sky still added and then subtract off the sky. But this isn't quite + # right if the sky has a fractional part. So only subtract off the integer part of the + # sky at the end. For float images, you get the same answer either way, so it doesn't + # matter. + frac_sky = self.sky_level - image.dtype(self.sky_level) + int_sky = self.sky_level - frac_sky + + if self.sky_level != 0.: + noise_array += self.sky_level + # Make sure no negative values + noise_array = noise_array.clip(0.) + # The noise_image now has the expectation values for each pixel with the sky added. + self._pd.generate_from_expectation(noise_array) + # Subtract off the sky, since we don't want it in the final image. + if frac_sky != 0.: + noise_array -= frac_sky + # Noise array is now the correct value for each pixel. + np.copyto(image.array, noise_array.reshape(image.array.shape), casting='unsafe') + if int_sky != 0.: + image -= int_sky + + def _getVariance(self): + return self.sky_level + + def _withVariance(self, variance): + return PoissonNoise(self.rng, variance) + + def _withScaledVariance(self, variance_ratio): + return PoissonNoise(self.rng, self.sky_level * variance_ratio) + +
[docs] def copy(self, rng=None): + """Returns a copy of the Poisson noise model. + + By default, the copy will share the `BaseDeviate` random number generator with the parent + instance. However, you can provide a new rng to use in the copy if you want with:: + + >>> noise_copy = noise.copy(rng=new_rng) + """ + if rng is None: rng = self.rng + return PoissonNoise(rng, self.sky_level)
+ + def __repr__(self): + return 'galsim.PoissonNoise(rng=%r, sky_level=%r)'%(self.rng, self.sky_level) + + def __str__(self): + return 'galsim.PoissonNoise(sky_level=%s)'%(self.sky_level)
+ + +
[docs]class CCDNoise(BaseNoise): + """Class implementing a basic CCD noise model. + + The CCDNoise class encapsulates the noise model of a normal CCD image. The noise has two + components: first, Poisson noise corresponding to the number of electrons in each pixel + (including an optional extra sky level); second, Gaussian read noise. + + Note that if the image to which you are adding noise already has a sky level on it, + then you should not provide the sky level here as well. The sky level here corresponds + to a level is taken to be already subtracted from the image, but which was present + for the Poisson noise. + + The units here are slightly confusing. We try to match the most common way that each of + these quantities is usually reported. Note: ADU stands for Analog/Digital Units; they are the + units of the numbers in the final image. Some places use the term "counts" for this. + + - sky_level is normally measured from the image itself, so it is normally quoted in ADU/pixel. + - gain is a property of the detector and is normally measured in the laboratory. The units + are normally e-/ADU. This is backwards what might be more intuitive, ADU/e-, but that's + how astronomers use the term gain, so we follow suit here. + - read_noise is also a property of the detector and is usually quoted in e-/pixel. + + If you are manually applying the quantum efficiency of the detector (e-/photon), then this + would normally be applied before the noise. However, it is also fine to fold in the quantum + efficiency into the gain to give units photons/ADU. Either way is acceptable. Just make sure + you give the read noise in photons as well in this case. + + Example: + + The following will add CCD noise to every element of an image:: + + >>> ccd_noise = galsim.CCDNoise(rng, sky_level=0., gain=1., read_noise=0.) + >>> image.addNoise(ccd_noise) + + Parameters: + rng: A `BaseDeviate` instance to use for generating the random numbers. + sky_level: The sky level in ADU per pixel that was originally in the input image, + but which is taken to have already been subtracted off. [default: 0.] + gain: The gain for each pixel in electrons per ADU; setting ``gain<=0`` will shut + off the Poisson noise, and the Gaussian rms will take the value + ``read_noise`` as being in units of ADU rather than electrons. [default: 1.] + read_noise: The read noise on each pixel in electrons (gain > 0.) or ADU (gain <= 0.). + Setting ``read_noise=0``. will shut off the Gaussian noise. [default: 0.] + + Attributes: + rng: The internal random number generator (read-only) + sky_level: The value of the constructor parameter sky_level (read-only) + gain: The value of the constructor parameter gain (read-only) + read_noise: The value of the constructor parameter read_noise (read-only) + """ + def __init__(self, rng=None, sky_level=0., gain=1., read_noise=0.): + BaseNoise.__init__(self, rng) + self._sky_level = float(sky_level) + self._gain = float(gain) + self._read_noise = float(read_noise) + self._pd = PoissonDeviate(self.rng) + if gain > 0.: + self._gd = GaussianDeviate(self.rng, sigma=self.read_noise / self.gain) + else: + self._gd = GaussianDeviate(self.rng, sigma=self.read_noise) + + @property + def sky_level(self): + """The input sky_level. + """ + return self._sky_level + + @property + def gain(self): + """The input gain. + """ + return self._gain + + @property + def read_noise(self): + """The input read_noise. + """ + return self._read_noise + + def _applyTo(self, image): + noise_array = np.empty(np.prod(image.array.shape), dtype=float) + noise_array.reshape(image.array.shape)[:,:] = image.array + + # cf. PoissonNoise._applyTo function + frac_sky = self.sky_level - image.dtype(self.sky_level) # 0 if dtype = float + int_sky = self.sky_level - frac_sky + + if self.sky_level != 0.: + noise_array += self.sky_level + + # First add the poisson noise from the signal + sky: + if self.gain > 0.: + noise_array *= self.gain # convert to electrons + noise_array = noise_array.clip(0.) + # The noise_image now has the expectation values for each pixel with the sky added. + self._pd.generate_from_expectation(noise_array) + # Subtract off the sky, since we don't want it in the final image. + noise_array /= self.gain + + # Now add the read noise: + if self.read_noise > 0.: + self._gd.clearCache() + self._gd.add_generate(noise_array) + + if frac_sky != 0.: + noise_array -= frac_sky + np.copyto(image.array, noise_array.reshape(image.array.shape), casting='unsafe') + if int_sky != 0.: + image -= int_sky + + def _getVariance(self): + if self.gain > 0.: + return self.sky_level/self.gain + (self.read_noise / self.gain)**2 + else: + return self.read_noise**2 + + def _withVariance(self, variance): + current_var = self._getVariance() + if current_var > 0.: + return self._withScaledVariance(variance / current_var) + else: + return CCDNoise(self.rng, sky_level=variance) + + def _withScaledVariance(self, variance_ratio): + return CCDNoise(self.rng, gain=self.gain, + sky_level = self.sky_level * variance_ratio, + read_noise = self.read_noise * math.sqrt(variance_ratio)) + +
[docs] def copy(self, rng=None): + """Returns a copy of the CCD noise model. + + By default, the copy will share the `BaseDeviate` random number generator with the parent + instance. However, you can provide a new rng to use in the copy if you want with:: + + >>> noise_copy = noise.copy(rng=new_rng) + """ + if rng is None: rng = self.rng + return CCDNoise(rng, self.sky_level, self.gain, self.read_noise)
+ + def __repr__(self): + return 'galsim.CCDNoise(rng=%r, sky_level=%r, gain=%r, read_noise=%r)'%( + self.rng, self.sky_level, self.gain, self.read_noise) + + def __str__(self): + return 'galsim.CCDNoise(sky_level=%r, gain=%r, read_noise=%r)'%( + self.sky_level, self.gain, self.read_noise)
+ + +
[docs]class DeviateNoise(BaseNoise): + """Class implementing noise with an arbitrary `BaseDeviate` object. + + The DeviateNoise class provides a way to treat an arbitrary deviate as the noise model for + each pixel in an image. + + The following will add noise according to a given random deviate to every element of an image:: + + >>> dev_noise = galsim.DeviateNoise(dev) + >>> image.addNoise(dev_noise) + + Parameters: + dev: A `BaseDeviate` subclass to use as the noise deviate for each pixel. + + Attributes: + rng: The internal random number generator (read-only) + """ + def __init__(self, dev): + BaseNoise.__init__(self, dev) + + def _applyTo(self, image): + noise_array = np.empty(np.prod(image.array.shape), dtype=float) + self._rng.generate(noise_array) + image.array[:,:] += noise_array.reshape(image.array.shape).astype(image.dtype) + + def _getVariance(self): + raise GalSimError("No single variance value for DeviateNoise") + + def _withVariance(self, variance): + raise GalSimError("Changing the variance is not allowed for DeviateNoise") + + def _withScaledVariance(self, variance): + raise GalSimError("Changing the variance is not allowed for DeviateNoise") + +
[docs] def copy(self, rng=None): + """Returns a copy of the `DeviateNoise` instance. + + By default, the copy will share the `BaseDeviate` random number generator with the parent + instance. However, you can provide a new rng to use in the copy if you want with:: + + >>> noise_copy = noise.copy(rng=new_rng) + """ + if rng is None: + dev = self.rng + else: + # Slightly different this time, since we want to make sure that we keep the same + # kind of deviate, but just reset it to follow the given rng. + dev = self.rng.duplicate() + dev.reset(rng) + return DeviateNoise(dev)
+ + def __repr__(self): + return 'galsim.DeviateNoise(dev=%r)'%self.rng + + def __str__(self): + return 'galsim.DeviateNoise(dev=%s)'%self.rng
+ + +
[docs]class VariableGaussianNoise(BaseNoise): + """ + Class implementing Gaussian noise that has a different variance in each pixel. + + The following will add variable Gaussian noise to every element of an image:: + + >>> variable_noise = galsim.VariableGaussianNoise(rng, var_image) + >>> image.addNoise(variable_noise) + + Parameters: + rng: A `BaseDeviate` instance to use for generating the random numbers. + var_image: The variance of the noise to apply to each pixel. This image must be the + same shape as the image for which you eventually call addNoise(). + + Attributes: + rng: The internal random number generator (read-only) + var_image: The value of the constructor parameter var_image (read-only) + """ + def __init__(self, rng, var_image): + BaseNoise.__init__(self, rng) + self._gd = GaussianDeviate(rng) + + # Make sure var_image is an ImageD, converting dtype if necessary + self._var_image = ImageD(var_image) + + @property + def var_image(self): + """The input var_image. + """ + return self._var_image + + # Repeat this here, since we want to add an extra sanity check, which should go in the + # non-underscore version. +
[docs] @doc_inherit + def applyTo(self, image): + if not isinstance(image, Image): + raise TypeError("Provided image must be a galsim.Image") + if image.array.shape != self.var_image.array.shape: + raise GalSimIncompatibleValuesError( + "Provided image shape does not match the shape of var_image", + image=image, var_image=self.var_image) + return self._applyTo(image)
+ + def _applyTo(self, image): + noise_array = self.var_image.array.flatten() # NB. Makes a copy! (which is what we want) + self._gd.generate_from_variance(noise_array) + image.array[:,:] += noise_array.reshape(image.array.shape).astype(image.dtype) + +
[docs] def copy(self, rng=None): + """Returns a copy of the variable Gaussian noise model. + + By default, the copy will share the `BaseDeviate` random number generator with the parent + instance. However, you can provide a new rng to use in the copy if you want with:: + + >>> noise_copy = noise.copy(rng=new_rng) + """ + if rng is None: rng = self.rng + return VariableGaussianNoise(rng, self.var_image)
+ + def _getVariance(self): + raise GalSimError("No single variance value for VariableGaussianNoise") + + def _withVariance(self, variance): + raise GalSimError("Changing the variance is not allowed for VariableGaussianNoise") + + def _withScaledVariance(self, variance): + # This one isn't undefined like withVariance, but it's inefficient. Better to + # scale the values in the image before constructing VariableGaussianNoise. + raise GalSimError("Changing the variance is not allowed for VariableGaussianNoise") + + def __repr__(self): + return 'galsim.VariableGaussianNoise(rng=%r, var_image%r)'%(self.rng, self.var_image) + + def __str__(self): + return 'galsim.VariableGaussianNoise(var_image%s)'%(self.var_image)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/phase_psf.html b/docs/_build/html/_modules/galsim/phase_psf.html new file mode 100644 index 00000000000..c0a92f47b48 --- /dev/null +++ b/docs/_build/html/_modules/galsim/phase_psf.html @@ -0,0 +1,2198 @@ + + + + + + galsim.phase_psf — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.phase_psf

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Aperture', 'PhaseScreenList', 'PhaseScreenPSF', 'OpticalPSF' ]
+
+from heapq import heappush, heappop
+import numpy as np
+import astropy.units as u
+import copy
+
+from . import fits
+from . import fft
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .angle import radians, degrees, arcsec, Angle, AngleUnit
+from .image import Image, _Image
+from .bounds import _BoundsI
+from .wcs import PixelScale
+from .interpolatedimage import InterpolatedImage
+from .utilities import doc_inherit, OrderedWeakRef, rotate_xy, lazy_property, basestring
+from .errors import GalSimValueError, GalSimRangeError, GalSimIncompatibleValuesError
+from .errors import GalSimFFTSizeError, galsim_warn
+from .photon_array import TimeSampler, PhotonArray
+from .airy import Airy
+from .second_kick import SecondKick
+from .random import UniformDeviate
+
+
+
[docs]class Aperture: + """Class representing a telescope aperture embedded in a larger pupil plane array -- for use + with the `PhaseScreenPSF` class to create PSFs via Fourier or geometric optics. + + The pupil plane array is completely specified by its size, sampling interval, and pattern of + illuminated pixels. Pupil plane arrays can be specified either geometrically or using an image + to indicate the illuminated pixels. In both cases, various options exist to control the pupil + plane size and sampling interval. + + **Geometric pupil specification**: + + The first way to specify the details of the telescope aperture is through a series of keywords + indicating the diameter, size of the central obscuration, and the nature of the struts + holding up the secondary mirror (or prime focus cage, etc.). The struts are assumed to be + rectangular obscurations extending from the outer edge of the pupil to the outer edge of the + obscuration disk (or to the pupil center if ``obscuration = 0.``). You can specify how many + struts there are (evenly spaced in angle), how thick they are as a fraction of the pupil + diameter, and what angle they start at relative to the positive y direction. + + The size (in meters) and sampling interval (in meters) of the pupil plane array representing the + aperture can be set directly using the the ``pupil_plane_size`` and ``pupil_plane_scale`` + keywords. However, in most situations, it's probably more convenient to let GalSim set these + automatically based on the pupil geometry and the nature of the (potentially time-varying) + phase aberrations from which a PSF is being derived. + + The pupil plane array physical size is by default set to twice the pupil diameter producing a + Nyquist sampled PSF image. While this would always be sufficient if using sinc interpolation + over the PSF image for subsequent operations, GalSim by default uses the much faster (though + approximate) quintic interpolant, which means that in some cases -- in particular, for + significantly aberrated optical PSFs without atmospheric aberrations -- it may be useful to + further increase the size of the pupil plane array, thereby increasing the sampling rate of the + resulting PSF image. This can be done by increasing the ``oversampling`` keyword. + + A caveat to the above occurs when using ``geometric_shooting=True`` to draw using + photon-shooting. In this case, we only need an array just large enough to avoid clipping the + pupil, which we can get by setting ``oversampling=0.5``. + + The pupil plane array physical sampling interval (which is directly related to the resulting PSF + image physical size) is set by default to the same interval as would be used to avoid + significant aliasing (image folding) for an obscured `Airy` profile with matching diameter and + obscuration and for the value of ``folding_threshold`` in the optionally specified gsparams + argument. If the phase aberrations are significant, however, the PSF image size computed this + way may still not be sufficiently large to avoid aliasing. To further increase the pupil plane + sampling rate (and hence the PSF image size), you can increase the value of the ``pad_factor`` + keyword. + + An additional way to set the pupil sampling interval for a particular set of phase screens + (i.e., for a particular `PhaseScreenList`) is to provide the screens in the ``screen_list`` + argument. Each screen in the list computes its own preferred sampling rate and the + `PhaseScreenList` appropriately aggregates these. This last option also requires that a + wavelength ``lam`` be specified, and is particularly helpful for creating PSFs derived from + turbulent atmospheric screens. + + Finally, when specifying the pupil geometrically, Aperture may choose to make a small adjustment + to ``pupil_plane_scale`` in order to produce an array with a good size for FFTs. If your + application depends on knowing the size and scale used with the Fourier optics framework, you + can obtain these from the ``aper.pupil_plane_size`` and ``aper.pupil_plane_scale`` attributes. + + **Pupil image specification**: + + The second way to specify the pupil plane configuration is by passing in an image of it. This + can be useful, for example, if the struts are not evenly spaced or are not radially directed, as + is assumed by the simple model for struts described above. In this case, an exception is raised + if keywords related to struts are also given. On the other hand, the ``obscuration`` keyword is + still used to ensure that the PSF images are not aliased, though it is ignored during the actual + construction of the pupil plane illumination pattern. Note that for complicated pupil + configurations, it may be desireable to increase ``pad_factor`` for more fidelity at the expense + of slower running time. Finally, the ``pupil_plane_im`` that is passed in can be rotated during + internal calculations by specifying a ``pupil_angle`` keyword. + + If you choose to pass in a pupil plane image, it must be a square array in which the image of + the pupil is centered. The areas that are illuminated should have some value >0, and the other + areas should have a value of precisely zero. Based on what the Aperture class determines is a + good PSF sampling interval, the image of the pupil plane that is passed in might be zero-padded + during internal calculations. (The pupil plane array size and scale values can be accessed via + the ``aper.pupil_plane_size`` and ``aper.pupil_plane_scale`` attributes.) The pixel scale of + the pupil plane can be specified in one of three ways. In descending order of priority, these + are: + + 1. The ``pupil_plane_scale`` keyword argument (units are meters). + 2. The ``pupil_plane_im.scale`` attribute (units are meters). + 3. If (1) and (2) are both None, then the scale will be inferred by assuming that the + illuminated pixel farthest from the image center is at a physical distance of self.diam/2. + + The ``pupil_plane_size`` and ``lam`` keywords are both ignored when constructing an Aperture + from an image. + + Parameters: + diam: Aperture diameter in meters. + lam: Wavelength in nanometers. [default: None] + circular_pupil: Adopt a circular pupil? [default: True] + obscuration: Linear dimension of central obscuration as fraction of aperture + linear dimension. [0., 1.). [default: 0.0] + nstruts: Number of radial support struts to add to the central obscuration. + [default: 0] + strut_thick: Thickness of support struts as a fraction of aperture diameter. + [default: 0.05] + strut_angle: `Angle` made between the vertical and the strut starting closest to it, + defined to be positive in the counter-clockwise direction; must be an + `Angle` instance. [default: 0. * galsim.degrees] + oversampling: Optional oversampling factor *in the image plane* for the PSF + eventually constructed using this `Aperture`. Setting + ``oversampling < 1`` will produce aliasing in the PSF (not good). + [default: 1.0] + pad_factor: Additional multiple by which to extend the PSF image to avoid + folding. [default: 1.0] + screen_list: An optional `PhaseScreenList` object. If present, then get a good + pupil sampling interval using this object. [default: None] + pupil_plane_im: The GalSim.Image, NumPy array, or name of file containing the pupil + plane image, to be used instead of generating one based on the + obscuration and strut parameters. [default: None] + pupil_angle: If ``pupil_plane_im`` is not None, rotation angle for the pupil plane + (positive in the counter-clockwise direction). Must be an `Angle` + instance. [default: 0. * galsim.degrees] + pupil_plane_scale: Sampling interval in meters to use for the pupil plane array. In + most cases, it's a good idea to leave this as None, in which case + GalSim will attempt to find a good value automatically. The + exception is when specifying the pupil arrangement via an image, in + which case this keyword can be used to indicate the sampling of that + image. See also ``pad_factor`` for adjusting the pupil sampling scale. + [default: None] + pupil_plane_size: Size in meters to use for the pupil plane array. In most cases, it's + a good idea to leave this as None, in which case GalSim will attempt + to find a good value automatically. See also ``oversampling`` for + adjusting the pupil size. [default: None] + gsparams: An optional `GSParams` argument. [default: None] + """ + def __init__(self, diam, lam=None, circular_pupil=True, obscuration=0.0, + nstruts=0, strut_thick=0.05, strut_angle=0.0*radians, + oversampling=1.0, pad_factor=1.0, screen_list=None, + pupil_plane_im=None, pupil_angle=0.0*radians, + pupil_plane_scale=None, pupil_plane_size=None, + gsparams=None): + + self._diam = diam # Always need to explicitly specify an aperture diameter. + self._lam = lam + self._circular_pupil = circular_pupil + self._obscuration = obscuration + self._nstruts = nstruts + self._strut_thick = strut_thick + self._strut_angle = strut_angle + self._oversampling = oversampling + self._pad_factor = pad_factor + self._screen_list = screen_list + self._pupil_plane_im = pupil_plane_im + self._pupil_angle = pupil_angle + self._input_pupil_plane_scale = pupil_plane_scale + self._input_pupil_plane_size = pupil_plane_size + self._gsparams = GSParams.check(gsparams) + + if diam <= 0.: + raise GalSimRangeError("Invalid diam.", diam, 0.) + if obscuration < 0. or obscuration >= 1.: + raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) + if not isinstance(strut_angle, Angle): + raise TypeError("strut_angle must be a galsim.Angle instance.") + if not isinstance(pupil_angle, Angle): + raise TypeError("pupil_angle must be a galsim.Angle instance.") + + # You can either set geometric properties, or use a pupil image, but not both, so check for + # that here. One caveat is that we allow sanity checking the sampling of a pupil_image by + # comparing it to the sampling GalSim would have used for an (obscured) Airy profile. So + # it's okay to specify an obscuration and a pupil_plane_im together, for example, but not + # a pupil_plane_im and struts. + is_default_geom = (circular_pupil and + nstruts == 0 and + strut_thick == 0.05 and + strut_angle == 0.0*radians) + if not is_default_geom and pupil_plane_im is not None: + raise GalSimIncompatibleValuesError( + "Can't specify both geometric parameters and pupil_plane_im.", + circular_pupil=circular_pupil, nstruts=nstruts, strut_thick=strut_thick, + strut_angle=strut_angle, pupil_plane_im=pupil_plane_im) + + if screen_list is not None and lam is None: + raise GalSimIncompatibleValuesError( + "Wavelength ``lam`` must be specified with ``screen_list``.", + screen_list=screen_list, lam=lam) + + # For each of these, the actual value is defined during the construction of the _illuminated + # array, so access that (lazy) property first. + @property + def pupil_plane_scale(self): + """The scale_size of the pupil-plane image. + """ + self._illuminated + return self._pupil_plane_scale + @property + def pupil_plane_size(self): + """The size of the pupil-plane image. + """ + self._illuminated + return self._pupil_plane_size + @property + def npix(self): + """The number of pixels in each direction of the pupil-plane image. + """ + self._illuminated + return self._npix + + @lazy_property + def good_pupil_size(self): + """An estimate of a good pupil-plane image size. + """ + # Although the user can set the pupil plane size and scale directly if desired, in most + # cases it's nicer to have GalSim try to pick good values for these. + + # For the pupil plane size, we'll achieve Nyquist sampling in the focal plane if we sample + # out to twice the diameter of the actual aperture in the pupil plane (completely + # independent of wavelength, struts, obscurations, GSparams, and so on!). This corresponds + # to oversampling=1.0. In fact, if we were willing to always use sinc interpolation, there + # would never be any reason to go beyond this. In practice, we usually use a faster, but + # less accurate, quintic interpolant, which means we can benefit from improved sampling + # (oversampling > 1.0) in some cases, especially when we're *not* modeling an atmosphere + # which would otherwise tend to damp contributions at large k. + return 2 * self.diam * self._oversampling + + @lazy_property + def good_pupil_scale(self): + """An estimate of a good pupil-plane image scale. + """ + # For the pupil plane sampling interval, details like the obscuration and GSParams *are* + # important as they affect the amount of aliasing encountered. (An Airy profile has an + # infinite extent in real space, so it *always* aliases at some level, more so with an + # obscuration than without. The GSParams settings indicate how much aliasing we're + # willing to tolerate, so it's required here.) To pick a good sampling interval, we start + # with the interval that would be used for an obscured Airy GSObject profile. If the + # `screen_list` argument was supplied, then we also check its .stepk propertry, which + # aggregates a good sampling interval from all of the wrapped PhaseScreens, and keep the + # smaller stepk. + if self._lam is None: + # For Airy, pupil_plane_scale is independent of wavelength. We could build an Airy with + # lam_over_diam=1.0 and then alter the `good_pupil_scale = ...` line below + # appropriately, but it's easier to just arbitrarily set `lam=500` if it wasn't set. + lam = 500.0 + else: + lam = self._lam + airy = Airy(diam=self.diam, lam=lam, obscuration=self.obscuration, gsparams=self.gsparams) + stepk = airy.stepk + if self._screen_list is not None: + screen_list = PhaseScreenList(self._screen_list) + stepk = min(stepk, + screen_list._getStepK(lam=lam, diam=self.diam, obscuration=self.obscuration, + gsparams=self.gsparams)) + return stepk * lam * 1.e-9 * (radians / arcsec) / (2 * np.pi * self._pad_factor) + + @lazy_property + def _illuminated(self): + # Now that we have good candidate sizes and scales, we load or generate the pupil plane + # array. + if self._pupil_plane_im is not None: # Use image of pupil plane + return self._load_pupil_plane() + else: # Use geometric parameters. + if self._input_pupil_plane_scale is not None: + self._pupil_plane_scale = self._input_pupil_plane_scale + # Check input scale and warn if looks suspicious. + if self._pupil_plane_scale > self.good_pupil_scale: + ratio = self.good_pupil_scale / self._pupil_plane_scale + galsim_warn("Input pupil_plane_scale may be too large for good sampling.\n" + "Consider decreasing pupil_plane_scale by a factor %f, and/or " + "check PhaseScreenPSF outputs for signs of folding in real " + "space."%(1./ratio)) + else: + self._pupil_plane_scale = self.good_pupil_scale + if self._input_pupil_plane_size is not None: + self._pupil_plane_size = self._input_pupil_plane_size + # Check input size and warn if looks suspicious + if self._pupil_plane_size < self.good_pupil_size: + ratio = self.good_pupil_size / self._pupil_plane_size + galsim_warn("Input pupil_plane_size may be too small for good focal-plane" + "sampling.\n" + "Consider increasing pupil_plane_size by a factor %f, and/or " + "check PhaseScreenPSF outputs for signs of undersampling."%ratio) + else: + self._pupil_plane_size = self.good_pupil_size + return self._generate_pupil_plane() + + def _generate_pupil_plane(self): + """ Create an array of illuminated pixels parameterically. + """ + ratio = self._pupil_plane_size/self._pupil_plane_scale + # Fudge a little to prevent good_fft_size() from turning 512.0001 into 768. + ratio *= (1.0 - 1.0/2**14) + self._npix = Image.good_fft_size(int(np.ceil(ratio))) + + # Check FFT size + if self._npix > self.gsparams.maximum_fft_size: + raise GalSimFFTSizeError("Created pupil plane array that is too large.",self._npix) + + # Shrink scale such that size = scale * npix exactly. + self._pupil_plane_scale = self._pupil_plane_size / self._npix + + radius = 0.5*self.diam + if self._circular_pupil: + illuminated = (self.rsqr < radius**2) + if self.obscuration > 0.: + illuminated *= self.rsqr >= (radius*self.obscuration)**2 + else: + illuminated = (np.abs(self.u) < radius) & (np.abs(self.v) < radius) + if self.obscuration > 0.: + illuminated *= ((np.abs(self.u) >= radius*self.obscuration) * + (np.abs(self.v) >= radius*self.obscuration)) + + if self._nstruts > 0: + # Add the initial rotation if requested, converting to radians. + rot_u, rot_v = self.u, self.v + if self._strut_angle.rad != 0.: + rot_u, rot_v = rotate_xy(rot_u, rot_v, -self._strut_angle) + rotang = 360. * degrees / self._nstruts + # Then loop through struts setting to zero the regions which lie under the strut + for istrut in range(self._nstruts): + rot_u, rot_v = rotate_xy(rot_u, rot_v, -rotang) + illuminated *= ((np.abs(rot_u) >= radius * self._strut_thick) + (rot_v < 0.0)) + return illuminated + + def _load_pupil_plane(self): + """ Create an array of illuminated pixels with appropriate size and scale from an input + image of the pupil. The basic strategy is: + + 1. Read in array. + 2. Determine the scale. + 3. Pad the input array with zeros to meet the requested pupil size. + 4. Check that the pupil plane sampling interval is at least as small as requested. + 5. Optionally rotate pupil plane. + """ + # Handle multiple types of input: NumPy array, galsim.Image, or string for filename with + # image. + if isinstance(self._pupil_plane_im, np.ndarray): + # Make it into an image. + self._pupil_plane_im = Image(self._pupil_plane_im) + elif isinstance(self._pupil_plane_im, Image): + # Make sure not to overwrite input image. + self._pupil_plane_im = self._pupil_plane_im.copy() + else: + # Read in image of pupil plane from file. + self._pupil_plane_im = fits.read(self._pupil_plane_im) + # scale = pupil_plane_im.scale # Interpret as either the pixel scale in meters, or None. + pp_arr = self._pupil_plane_im.array + self._npix = pp_arr.shape[0] + + # Check FFT size + if self._npix > self.gsparams.maximum_fft_size: + raise GalSimFFTSizeError("Loaded pupil plane array that is too large.", self._npix) + + # Sanity checks + if self._pupil_plane_im.array.shape[0] != self._pupil_plane_im.array.shape[1]: + raise GalSimValueError("Input pupil_plane_im must be square.", + self._pupil_plane_im.array.shape) + if self._pupil_plane_im.array.shape[0] % 2 == 1: + raise GalSimValueError("Input pupil_plane_im must have even sizes.", + self._pupil_plane_im.array.shape) + + # Set the scale, priority is: + # 1. pupil_plane_scale kwarg + # 2. image.scale if not None + # 3. Use diameter and farthest illuminated pixel. + if self._input_pupil_plane_scale is not None: + self._pupil_plane_scale = self._input_pupil_plane_scale + elif self._pupil_plane_im.scale is not None: + self._pupil_plane_scale = self._pupil_plane_im.scale + else: + # If self._pupil_plane_scale is not set yet, then figure it out from the distance + # of the farthest illuminated pixel from the image center and the aperture diameter. + # below is essentially np.linspace(-0.5, 0.5, self._npix) + u = np.fft.fftshift(np.fft.fftfreq(self._npix)) + u, v = np.meshgrid(u, u) + r = np.hypot(u, v) + rmax_illum = np.max(r*(self._pupil_plane_im.array > 0)) + self._pupil_plane_scale = self.diam / (2.0 * rmax_illum * self._npix) + self._pupil_plane_size = self._pupil_plane_scale * self._npix + + # Check the pupil plane size here and bump it up if necessary. + if self._pupil_plane_size < self.good_pupil_size: + new_npix = Image.good_fft_size(int(np.ceil( + self.good_pupil_size/self._pupil_plane_scale))) + pad_width = (new_npix-self._npix)//2 + pp_arr = np.pad(pp_arr, [(pad_width, pad_width)]*2, mode='constant') + self._npix = new_npix + self._pupil_plane_size = self._pupil_plane_scale * self._npix + + # Check sampling interval and warn if it's not good enough. + if self._pupil_plane_scale > self.good_pupil_scale: + ratio = self._pupil_plane_scale / self.good_pupil_scale + galsim_warn("Input pupil plane image may not be sampled well enough!\n" + "Consider increasing sampling by a factor %f, and/or check " + "PhaseScreenPSF outputs for signs of folding in real space."%ratio) + + if self._pupil_angle.rad == 0.: + return pp_arr.astype(bool) + else: + # Rotate the pupil plane image as required based on the `pupil_angle`, being careful to + # ensure that the image is one of the allowed types. We ignore the scale. + b = _BoundsI(1,self._npix,1,self._npix) + im = _Image(pp_arr, b, PixelScale(1.)) + int_im = InterpolatedImage(im, x_interpolant='linear', + calculate_stepk=False, calculate_maxk=False) + int_im = int_im.rotate(self._pupil_angle) + new_im = Image(pp_arr.shape[1], pp_arr.shape[0]) + new_im = int_im.drawImage(image=new_im, scale=1., method='no_pixel') + pp_arr = new_im.array + # Restore hard edges that might have been lost during the interpolation. To do this, we + # check the maximum value of the entries. Values after interpolation that are >half + # that maximum value are kept as nonzero (True), but those that are <half the maximum + # value are set to zero (False). + max_pp_val = np.max(pp_arr) + pp_arr[pp_arr < 0.5*max_pp_val] = 0. + return pp_arr.astype(bool) + + @property + def gsparams(self): + """The `GSParams` of this object. + """ + return self._gsparams + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current aperture with the given gsparams + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + return ret
+ + # Used in Aperture.__str__ and OpticalPSF.__str__ + def _geometry_str(self): + s = "" + if not self._circular_pupil: + s += ", circular_pupil=False" + if self.obscuration != 0.0: + s += ", obscuration=%s"%self.obscuration + if self._nstruts != 0: + s += ", nstruts=%s"%self._nstruts + if self._strut_thick != 0.05: + s += ", strut_thick=%s"%self._strut_thick + if self._strut_angle != 0*radians: + s += ", strut_angle=%s"%self._strut_angle + return s + + def __str__(self): + s = "galsim.Aperture(diam=%r"%self.diam + if self._pupil_plane_im is None: + # Pupil was created geometrically, so use that here. + s += self._geometry_str() + s += ")" + return s + + def _geometry_repr(self): + s = "" + if not self._circular_pupil: + s += ", circular_pupil=False" + if self.obscuration != 0.0: + s += ", obscuration=%r"%self.obscuration + if self._nstruts != 0: + s += ", nstruts=%r"%self._nstruts + if self._strut_thick != 0.05: + s += ", strut_thick=%r"%self._strut_thick + if self._strut_angle != 0*radians: + s += ", strut_angle=%r"%self._strut_angle + return s + + def __repr__(self): + s = "galsim.Aperture(diam=%r"%self.diam + if self._pupil_plane_im is None: + # Pupil was created geometrically, so use that here. + s += self._geometry_repr() + s += ", pupil_plane_scale=%r"%self._input_pupil_plane_scale + s += ", pupil_plane_size=%r"%self._input_pupil_plane_size + s += ", oversampling=%r"%self._oversampling + s += ", pad_factor=%r"%self._pad_factor + else: + # Pupil was created from image, so use that instead. + # It's slightly less annoying to see an enormous stream of zeros fly by than an enormous + # stream of Falses, so convert to int16. + tmp = self.illuminated.astype(np.int16).tolist() + s += ", pupil_plane_im=array(%r"%tmp+", dtype='int16')" + s += ", pupil_plane_scale=%r"%self._pupil_plane_scale + if self.gsparams != GSParams(): + s += ", gsparams=%r"%self.gsparams + s += ")" + return s + + def __eq__(self, other): + if self is other: return True + if not (isinstance(other, Aperture) and + self.diam == other.diam and + self._gsparams == other._gsparams): + return False + if self._pupil_plane_im is not None: + return (self.pupil_plane_scale == other.pupil_plane_scale and + np.array_equal(self.illuminated, other.illuminated)) + else: + return (other._pupil_plane_im is None and + self._circular_pupil == other._circular_pupil and + self._obscuration == other._obscuration and + self._nstruts == other._nstruts and + self._strut_thick == other._strut_thick and + self._strut_angle == other._strut_angle and + self._input_pupil_plane_scale == other._input_pupil_plane_scale and + self._input_pupil_plane_size == other._input_pupil_plane_size and + self._oversampling == other._oversampling and + self._pad_factor == other._pad_factor) + + def __hash__(self): + # Cache since self.illuminated may be large. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.Aperture", self.diam, self.pupil_plane_scale)) + self._hash ^= hash(tuple(self.illuminated.ravel())) + return self._hash + + # Properties show up nicely in the interactive terminal for + # >>>help(Aperture) + # So we make a thin wrapper here. + @property + def illuminated(self): + """A boolean array indicating which positions in the pupil plane are exposed to the sky. + """ + return self._illuminated + + @lazy_property + def rho(self): + """Unit-disk normalized pupil plane coordinate as a complex number: + (x, y) => x + 1j * y. + """ + self._illuminated + u = np.fft.fftshift(np.fft.fftfreq(self._npix, self.diam/self._pupil_plane_size/2.0)) + u, v = np.meshgrid(u, u) + return u + 1j * v + + @lazy_property + def _uv(self): + if not hasattr(self, '_npix'): + # Need this check, since `_uv` is used by `_illuminated`, so need to make sure we + # don't have an infinite loop. + self._illuminated + u = np.fft.fftshift(np.fft.fftfreq(self._npix, 1./self._pupil_plane_size)) + u, v = np.meshgrid(u, u) + return u, v + + @property + def u(self): + """Pupil horizontal coordinate array in meters.""" + return self._uv[0] + + @property + def v(self): + """Pupil vertical coordinate array in meters.""" + return self._uv[1] + + @lazy_property + def u_illuminated(self): + """The u values for only the `illuminated` pixels. + """ + return self.u[self.illuminated] + + @lazy_property + def v_illuminated(self): + """The v values for only the `illuminated` pixels. + """ + return self.v[self.illuminated] + + @lazy_property + def rsqr(self): + """Pupil radius squared array in meters squared.""" + return self.u**2 + self.v**2 + + @property + def diam(self): + """Aperture diameter in meters""" + return self._diam + + @property + def obscuration(self): + """Fraction linear obscuration of pupil.""" + return self._obscuration + + def __getstate__(self): + # Let unpickled object reconstruct cached values on-the-fly instead of including them in the + # pickle. + d = self.__dict__.copy() + for k in ('rho', '_uv', 'rsqr', 'u_illuminated', 'v_illuminated'): + d.pop(k, None) + # Only reconstruct _illuminated if we made it from geometry. If loaded, it's probably + # faster to serialize the array. + if self._pupil_plane_im is None: + d.pop('_illuminated', None) + return d + +
[docs] def samplePupil(self, photons, rng): + """Set the pupil_u and pupil_v values in the PhotonArray by sampling the current aperture. + """ + n_photons = len(photons) + u = self.u_illuminated + v = self.v_illuminated + gen = rng.as_numpy_generator() + pick = gen.choice(len(u), size=n_photons).astype(int) + photons.pupil_u = u[pick] + photons.pupil_v = v[pick] + # Make continuous by adding +/- 0.5 pixels shifts. + uscale = self.u[0, 1] - self.u[0, 0] + vscale = self.v[1, 0] - self.v[0, 0] + photons.pupil_u += gen.uniform(-uscale/2.,uscale/2.,size=n_photons) + photons.pupil_v += gen.uniform(-vscale/2.,vscale/2.,size=n_photons)
+ + # Some quick notes for Josh: + # - Relation between real-space grid with size theta and pitch dtheta (dimensions of angle) + # and corresponding (fast) Fourier grid with size 2*maxk and pitch stepk (dimensions of + # inverse angle): + # stepk = 2*pi/theta + # maxk = pi/dtheta + # - Relation between aperture of size L and pitch dL (dimensions of length, not angle!) and + # (fast) Fourier grid: + # dL = stepk * lambda / (2 * pi) + # L = maxk * lambda / pi + # - Implies relation between aperture grid and real-space grid: + # dL = lambda/theta + # L = lambda/dtheta + # + # MJ: Of these four, only _sky_scale is still used. The rest are left here for informational + # purposes, but nothing actually calls them. + def _getStepK(self, lam, scale_unit=arcsec): + """Return the Fourier grid spacing for this aperture at given wavelength. + + Parameters: + lam: Wavelength in nanometers. + scale_unit: Inverse units in which to return result [default: galsim.arcsec] + + Returns: + Fourier grid spacing. + """ + return 2*np.pi*self.pupil_plane_scale/(lam*1e-9) * scale_unit/radians + + def _getMaxK(self, lam, scale_unit=arcsec): + """Return the Fourier grid half-size for this aperture at given wavelength. + + Parameters: + lam: Wavelength in nanometers. + scale_unit: Inverse units in which to return result [default: galsim.arcsec] + + Returns: + Fourier grid half-size. + """ + return np.pi*self.pupil_plane_size/(lam*1e-9) * scale_unit/radians + + def _sky_scale(self, lam, scale_unit=arcsec): + """Return the image scale for this aperture at given wavelength. + + Parameters: + lam: Wavelength in nanometers. + scale_unit: Units in which to return result [default: galsim.arcsec] + + Returns: + Image scale. + """ + return (lam*1e-9) / self.pupil_plane_size * radians/scale_unit + + def _sky_size(self, lam, scale_unit=arcsec): + """Return the image size for this aperture at given wavelength. + + Parameters: + lam: Wavelength in nanometers. + scale_unit: Units in which to return result [default: galsim.arcsec] + + Returns: + Image size. + """ + return (lam*1e-9) / self.pupil_plane_scale * radians/scale_unit
+ + +
[docs]class PhaseScreenList: + """List of phase screens that can be turned into a PSF. Screens can be either atmospheric + layers or optical phase screens. Generally, one would assemble a PhaseScreenList object using + the function `Atmosphere`. Layers can be added, removed, appended, etc. just like items can + be manipulated in a python list. For example:: + + # Create an atmosphere with three layers. + >>> screens = galsim.PhaseScreenList([galsim.AtmosphericScreen(...), + galsim.AtmosphericScreen(...), + galsim.AtmosphericScreen(...)]) + # Add another layer + >>> screens.append(galsim.AtmosphericScreen(...)) + # Remove the second layer + >>> del screens[1] + # Switch the first and second layer. Silly, but works... + >>> screens[0], screens[1] = screens[1], screens[0] + + Parameters: + layers: Sequence of phase screens. + """ + def __init__(self, *layers): + if len(layers) == 1: + # First check if layers[0] is a PhaseScreenList, so we avoid nesting. + if isinstance(layers[0], PhaseScreenList): + self._layers = layers[0]._layers + else: + # Next, see if layers[0] is iterable. E.g., to catch generator expressions. + try: + self._layers = list(layers[0]) + except TypeError: + self._layers = list(layers) + else: + self._layers = list(layers) + self._update_attrs() + self._pending = [] # Pending PSFs to calculate upon first drawImage. + + def __len__(self): + return len(self._layers) + + def __getitem__(self, index): + try: + items = self._layers[index] + except TypeError: + msg = "{cls.__name__} indices must be integers or slices" + raise TypeError(msg.format(cls=self.__class__)) + try: + index + 1 # Regular in indices are the norm, so try something that works for it, + # but not for slices, where we need different handling. + except TypeError: + # index is a slice, so items is a list. + return PhaseScreenList(items) + else: + # index is an int, so items is just one screen. + return items + + def __setitem__(self, index, layer): + self._layers[index] = layer + self._update_attrs() + + def __delitem__(self, index): + del self._layers[index] + self._update_attrs() + + def append(self, layer): + self._layers.append(layer) + self._update_attrs() + + def extend(self, layers): + self._layers.extend(layers) + self._update_attrs() + + def __str__(self): + return "galsim.PhaseScreenList([%s])" % ",".join(str(l) for l in self._layers) + + def __repr__(self): + return "galsim.PhaseScreenList(%r)" % self._layers + + def __eq__(self, other): + return (self is other or + (isinstance(other,PhaseScreenList) and self._layers == other._layers)) + + def __ne__(self, other): return not self == other + + __hash__ = None # Mutable means not hashable. + + def _update_attrs(self): + # If any of the wrapped PhaseScreens have an rng, then eval(repr(screen_list)) will run, but + # fail to round-trip to the original object. So we search for that here and set/delete a + # dummy rng sentinel attribute so do_pickle() will know to skip the obj == eval(repr(obj)) + # test. + self.__dict__.pop('rng', None) + self.dynamic = any(l.dynamic for l in self) + self.reversible = all(l.reversible for l in self) + self.__dict__.pop('r0_500_effective', None) + + def _seek(self, t): + """Set all layers' internal clocks to time t.""" + for layer in self: + try: + layer._seek(t) + except AttributeError: + # Time indep phase screen + pass + self._update_attrs() + + def _reset(self): + """Reset phase screens back to time=0.""" + for layer in self: + try: + layer._reset() + except AttributeError: + # Time indep phase screen + pass + self._update_attrs() + +
[docs] def instantiate(self, pool=None, _bar=None, **kwargs): + """Instantiate the screens in this `PhaseScreenList`. + + Parameters: + pool: A multiprocessing.Pool object to use to instantiate screens in parallel. + **kwargs: Keyword arguments to forward to screen.instantiate(). + """ + _bar = _bar if _bar else dict() # with dict() _bar.update() is a trivial no op. + if pool is not None: + results = [] + for layer in self: + try: + results.append(pool.apply_async(layer.instantiate, kwds=kwargs)) + except AttributeError: # OpticalScreen has no instantiate method + pass + _bar.update() + for r in results: + r.wait() + else: + for layer in self: + try: + layer.instantiate(**kwargs) + except AttributeError: + pass + _bar.update()
+ + def _delayCalculation(self, psf): + """Add psf to delayed calculation list.""" + heappush(self._pending, (psf.t0, OrderedWeakRef(psf))) + + def _prepareDraw(self): + """Calculate previously delayed PSFs.""" + if not self._pending: + return + # See if we have any dynamic screens. If not, then we can immediately compute each PSF + # in a simple loop. + if not self.dynamic: + for _, psfref in self._pending: + psf = psfref() + if psf is not None: + psf._step() + psf._finalize() + self._pending = [] + self._update_time_heap = [] + return + + # If we do have time-evolving screens, then iteratively increment the time while being + # careful to always stop at multiples of each PSF's time_step attribute to update that PSF. + # Use a heap (in _pending list) to track the next time to stop at. + while(self._pending): + # Get and seek to next time that has a PSF update. + t, psfref = heappop(self._pending) + # Check if this PSF weakref is still alive + psf = psfref() + if psf is not None: + # If it's alive, update this PSF + self._seek(t) + psf._step() + # If that PSF's next possible update time doesn't extend past its exptime, then + # push it back on the heap. + t += psf.time_step + if t < psf.t0 + psf.exptime: + heappush(self._pending, (t, OrderedWeakRef(psf))) + else: + psf._finalize() + self._pending = [] + +
[docs] def wavefront(self, u, v, t, theta=(0.0*radians, 0.0*radians)): + """ Compute cumulative wavefront due to all phase screens in `PhaseScreenList`. + + Wavefront here indicates the distance by which the physical wavefront lags or leads the + ideal plane wave (pre-optics) or spherical wave (post-optics). + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Times (in seconds) at which to evaluate wavefront. Can be None, a scalar or an + iterable. If None, then the internal time of the phase screens will be used + for all u, v. If scalar, then the size will be broadcast up to match that of + u and v. If iterable, then the shape must match the shapes of u and v. + theta: Field angle at which to evaluate wavefront, as a 2-tuple of `galsim.Angle` + instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] + Only a single theta is permitted. + + Returns: + Array of wavefront lag or lead in nanometers. + """ + if len(self._layers) > 1: + return np.sum([layer.wavefront(u, v, t, theta) for layer in self], axis=0) + else: + return self._layers[0].wavefront(u, v, t, theta)
+ +
[docs] def wavefront_gradient(self, u, v, t, theta=(0.0*radians, 0.0*radians)): + """ Compute cumulative wavefront gradient due to all phase screens in `PhaseScreenList`. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Times (in seconds) at which to evaluate wavefront gradient. Can be None, a + scalar or an iterable. If None, then the internal time of the phase screens + will be used for all u, v. If scalar, then the size will be broadcast up to + match that of u and v. If iterable, then the shape must match the shapes of + u and v. + theta: Field angle at which to evaluate wavefront, as a 2-tuple of `galsim.Angle` + instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] + Only a single theta is permitted. + + Returns: + Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m. + """ + if len(self._layers) > 1: + return np.sum([layer.wavefront_gradient(u, v, t, theta) for layer in self], axis=0) + else: + return self._layers[0].wavefront_gradient(u, v, t, theta)
+ + def _wavefront(self, u, v, t, theta): + if len(self._layers) > 1: + return np.sum([layer._wavefront(u, v, t, theta) for layer in self], axis=0) + else: + return self._layers[0]._wavefront(u, v, t, theta) + + def _wavefront_gradient(self, u, v, t, theta): + gradx, grady = self._layers[0]._wavefront_gradient(u, v, t, theta) + for layer in self._layers[1:]: + gx, gy = layer._wavefront_gradient(u, v, t, theta) + gradx += gx + grady += gy + return gradx, grady + +
[docs] def makePSF(self, lam, **kwargs): + """Create a PSF from the current `PhaseScreenList`. + + Parameters: + lam: Wavelength in nanometers at which to compute PSF. + t0: Time at which to start exposure in seconds. [default: 0.0] + exptime: Time in seconds over which to accumulate evolving instantaneous + PSF. [default: 0.0] + time_step: Time interval in seconds with which to sample phase screens when + drawing using real-space or Fourier methods, or when using + photon-shooting without the geometric optics approximation. Note + that the default value of 0.025 is fairly arbitrary. For careful + studies, we recommend checking that results are stable when + decreasing time_step. Also note that when drawing using + photon-shooting with the geometric optics approximation this + keyword is ignored, as the phase screen can be sampled + continuously in this case instead of at discrete intervals. + [default: 0.025] + flux: Flux of output PSF. [default: 1.0] + theta: Field angle of PSF as a 2-tuple of `Angle` instances. + [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] + interpolant: Either an Interpolant instance or a string indicating which + interpolant should be used. Options are 'nearest', 'sinc', + 'linear', 'cubic', 'quintic', or 'lanczosN' where N should be the + integer order to use. [default: galsim.Quintic()] + scale_unit: Units to use for the sky coordinates of the output profile. + [default: galsim.arcsec] + ii_pad_factor: Zero-padding factor by which to extend the image of the PSF when + creating the ``InterpolatedImage``. See the + ``InterpolatedImage`` docstring for more details. [default: 1.5] + suppress_warning: If ``pad_factor`` is too small, the code will emit a warning + telling you its best guess about how high you might want to raise + it. However, you can suppress this warning by using + ``suppress_warning=True``. [default: False] + geometric_shooting: If True, then when drawing using photon shooting, use geometric + optics approximation where the photon angles are derived from the + phase screen gradient. If False, then first draw using Fourier + optics and then shoot from the derived InterpolatedImage. + [default: True] + aper: `Aperture` to use to compute PSF(s). [default: None] + second_kick: An optional second kick to also convolve by when using geometric + photon-shooting. (This can technically be any `GSObject`, though + usually it should probably be a SecondKick object). If None, then a + good second kick will be chosen automatically based on + ``screen_list``. If False, then a second kick won't be applied. + [default: None] + kcrit: Critical Fourier scale (in units of 1/r0) at which to separate low-k + and high-k turbulence. The default value was chosen based on + comparisons between Fourier optics and geometric optics with a + second kick correction. While most values of kcrit smaller than the + default produce similar results, we caution the user to compare the + affected geometric PSFs against Fourier optics PSFs carefully before + changing this value. [default: 0.2] + fft_sign: The sign (+/-) to use in the exponent of the Fourier kernel when + evaluating the Fourier optics PSF. As of version 2.3, GalSim uses a + plus sign by default, which we believe to be consistent with, for + example, how Zemax computes a Fourier optics PSF on DECam. Before + version 2.3, the default was a negative sign. Input should be + either the string '+' or the string '-'. [default: '+'] + gsparams: An optional `GSParams` argument. [default: None] + + The following are optional keywords to use to setup the aperture if ``aper`` is not + provided. + + Parameters: + diam: Aperture diameter in meters. + circular_pupil: Adopt a circular pupil? [default: True] + obscuration: Linear dimension of central obscuration as fraction of aperture + linear dimension. [0., 1.). [default: 0.0] + nstruts: Number of radial support struts to add to the central + obscuration. [default: 0] + strut_thick: Thickness of support struts as a fraction of aperture diameter. + [default: 0.05] + strut_angle: `Angle` made between the vertical and the strut starting closest to + it, defined to be positive in the counter-clockwise direction; + must be an `Angle` instance. [default: 0. * galsim.degrees] + oversampling: Optional oversampling factor *in the image plane* for the PSF + eventually constructed using this `Aperture`. Setting + ``oversampling < 1`` will produce aliasing in the PSF (not good). + [default: 1.0] + pad_factor: Additional multiple by which to extend the PSF image to avoid + folding. [default: 1.0] + pupil_plane_im: The GalSim.Image, NumPy array, or name of file containing the + pupil plane image, to be used instead of generating one based on + the obscuration and strut parameters. [default: None] + pupil_angle: If ``pupil_plane_im`` is not None, rotation angle for the pupil + plane (positive in the counter-clockwise direction). Must be an + `Angle` instance. [default: 0. * galsim.degrees] + pupil_plane_scale: Sampling interval in meters to use for the pupil plane array. In + most cases, it's a good idea to leave this as None, in which case + GalSim will attempt to find a good value automatically. The + exception is when specifying the pupil arrangement via an image, + in which case this keyword can be used to indicate the sampling + of that image. See also ``pad_factor`` for adjusting the pupil + sampling scale. [default: None] + pupil_plane_size: Size in meters to use for the pupil plane array. In most cases, + it's a good idea to leave this as None, in which case GalSim will + attempt to find a good value automatically. See also + ``oversampling`` for adjusting the pupil size. [default: None] + """ + return PhaseScreenPSF(self, lam, **kwargs)
+ + @lazy_property + def r0_500_effective(self): + """Effective r0_500 for set of screens in list that define an r0_500 attribute.""" + r0_500s = np.array([l.r0_500 for l in self if hasattr(l, 'r0_500')]) + if len(r0_500s) == 0: + return None + else: + return np.sum(r0_500s**(-5./3))**(-3./5) + + def _getStepK(self, **kwargs): + """Return an appropriate stepk for this list of phase screens. + + The required set of parameters depends on the types of the individual `PhaseScreen` + instances in the `PhaseScreenList`. See the documentation for the individual + `PhaseScreen.pupil_plane_scale` methods for more details. + + Returns: + stepk. + """ + # Generically, GalSim propagates stepk for convolutions using + # stepk = sum(s**-2 for s in stepks)**(-0.5) + # We're not actually doing convolution between screens here, though. In fact, the right + # relation for Kolmogorov screens uses exponents -5./3 and -3./5: + # stepk = sum(s**(-5./3) for s in stepks)**(-3./5) + # Since most of the layers in a PhaseScreenList are likely to be (nearly) Kolmogorov + # screens, we'll use that relation. + return np.sum([layer._getStepK(**kwargs)**(-5./3) for layer in self])**(-3./5) + + def __getstate__(self): + d = self.__dict__.copy() + d['_pending'] = [] + return d
+ + +
[docs]class PhaseScreenPSF(GSObject): + """A PSF surface brightness profile constructed by integrating over time the instantaneous PSF + derived from a set of phase screens and an aperture. + + There are two equivalent ways to construct a PhaseScreenPSF given a `PhaseScreenList`:: + + >>> psf = screen_list.makePSF(...) + >>> psf = PhaseScreenPSF(screen_list, ...) + + Computing a PSF from a phase screen also requires an `Aperture` be specified. This can be done + either directly via the ``aper`` keyword, or by setting a number of keywords that will be passed + to the `Aperture` constructor. The ``aper`` keyword always takes precedence. + + There are effectively three ways to draw a PhaseScreenPSF (or `GSObject` that includes a + PhaseScreenPSF): + + 1) Fourier optics + + This is the default, and is performed for all drawImage methods except method='phot'. This + is generally the most accurate option. For a `PhaseScreenList` that includes an + `AtmosphericScreen`, however, this can be prohibitively slow. For `OpticalPSF`, though, + this can sometimes be a good option. + + 2) Photon-shooting from an image produced using Fourier optics. + + This is done if geometric_shooting=False when creating the PhaseScreenPSF, and method='phot' + when calling drawImage. This actually performs the same calculations as the Fourier optics + option above, but then proceeds by shooting photons from that result. This can sometimes be + a good option for OpticalPSFs, especially if the same OpticalPSF can be reused for may + objects, since the Fourier part of the process would only be performed once in this case. + + 3) Photon-shooting using the "geometric approximation". + + This is done if geometric_shooting=True when creating the PhaseScreenPSF, and method='phot' + when calling drawImage. In this case, a completely different algorithm is used make an + image. Photons are uniformly generated in the `Aperture` pupil, and then the phase gradient + at that location is used to deflect each photon in the image plane. This method, which + corresponds to geometric optics, is broadly accurate for phase screens that vary slowly + across the aperture, and is usually several orders of magnitude or more faster than Fourier + optics (depending on the flux of the object, of course, but much faster even for rather + bright flux levels). + + One short-coming of this method is that it neglects interference effects, i.e. diffraction. + For `PhaseScreenList` that include at least one `AtmosphericScreen`, a correction, dubbed + the "second kick", will automatically be applied to handle both the quickly varying modes + of the screens and the diffraction pattern of the `Aperture`. For PhaseScreenLists without + an `AtmosphericScreen`, the correction is simply an Airy function. Note that this + correction can be overridden using the second_kick keyword argument, and also tuned to some + extent using the kcrit keyword argument. + + Note also that calling drawImage on a PhaseScreenPSF that uses a `PhaseScreenList` with any + uninstantiated `AtmosphericScreen` will perform that instantiation, and that the details of the + instantiation depend on the drawing method used, and also the kcrit keyword argument to + PhaseScreenPSF. See the `AtmosphericScreen` docstring for more details. + + Parameters: + screen_list: `PhaseScreenList` object from which to create PSF. + lam: Wavelength in nanometers at which to compute PSF. + t0: Time at which to start exposure in seconds. [default: 0.0] + exptime: Time in seconds over which to accumulate evolving instantaneous PSF. + [default: 0.0] + time_step: Time interval in seconds with which to sample phase screens when + drawing using real-space or Fourier methods, or when using + photon-shooting without the geometric optics approximation. Note + that the default value of 0.025 is fairly arbitrary. For careful + studies, we recommend checking that results are stable when + decreasing time_step. Also note that when drawing using + photon-shooting with the geometric optics approximation this + keyword is ignored, as the phase screen can be sampled + continuously in this case instead of at discrete intervals. + [default: 0.025] + flux: Flux of output PSF [default: 1.0] + theta: Field angle of PSF as a 2-tuple of `Angle` instances. + [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] + interpolant: Either an Interpolant instance or a string indicating which + interpolant should be used. Options are 'nearest', 'sinc', 'linear', + 'cubic', 'quintic', or 'lanczosN' where N should be the integer order + to use. [default: galsim.Quintic()] + scale_unit: Units to use for the sky coordinates of the output profile. + [default: galsim.arcsec] + ii_pad_factor: Zero-padding factor by which to extend the image of the PSF when + creating the ``InterpolatedImage``. See the ``InterpolatedImage`` + docstring for more details. [default: 1.5] + suppress_warning: If ``pad_factor`` is too small, the code will emit a warning telling + you its best guess about how high you might want to raise it. + However, you can suppress this warning by using + ``suppress_warning=True``. [default: False] + geometric_shooting: If True, then when drawing using photon shooting, use geometric + optics approximation where the photon angles are derived from the + phase screen gradient. If False, then first draw using Fourier + optics and then shoot from the derived InterpolatedImage. + [default: True] + aper: `Aperture` to use to compute PSF(s). [default: None] + second_kick: An optional second kick to also convolve by when using geometric + photon-shooting. (This can technically be any `GSObject`, though + usually it should probably be a SecondKick object). If None, then a + good second kick will be chosen automatically based on + ``screen_list``. If False, then a second kick won't be applied. + [default: None] + kcrit: Critical Fourier scale (in units of 1/r0) at which to separate low-k + and high-k turbulence. The default value was chosen based on + comparisons between Fourier optics and geometric optics with a second + kick correction. While most values of kcrit smaller than the default + produce similar results, we caution the user to compare the affected + geometric PSFs against Fourier optics PSFs carefully before changing + this value. [default: 0.2] + fft_sign: The sign (+/-) to use in the exponent of the Fourier kernel when + evaluating the Fourier optics PSF. As of version 2.3, GalSim uses a + plus sign by default, which we believe to be consistent with, for + example, how Zemax computes a Fourier optics PSF on DECam. Before + version 2.3, the default was a negative sign. Input should be either + the string '+' or the string '-'. [default: '+'] + gsparams: An optional `GSParams` argument. [default: None] + + The following are optional keywords to use to setup the aperture if ``aper`` is not provided: + + Parameters: + diam: Aperture diameter in meters. [default: None] + circular_pupil: Adopt a circular pupil? [default: True] + obscuration: Linear dimension of central obscuration as fraction of aperture + linear dimension. [0., 1.). [default: 0.0] + nstruts: Number of radial support struts to add to the central obscuration. + [default: 0] + strut_thick: Thickness of support struts as a fraction of aperture diameter. + [default: 0.05] + strut_angle: `Angle` made between the vertical and the strut starting closest to it, + defined to be positive in the counter-clockwise direction; must be an + `Angle` instance. [default: 0. * galsim.degrees] + oversampling: Optional oversampling factor *in the image plane* for the PSF + eventually constructed using this `Aperture`. Setting + ``oversampling < 1`` will produce aliasing in the PSF (not good). + [default: 1.0] + pad_factor: Additional multiple by which to extend the PSF image to avoid + folding. [default: 1.0] + pupil_plane_im: The GalSim.Image, NumPy array, or name of file containing the pupil + plane image, to be used instead of generating one based on the + obscuration and strut parameters. [default: None] + pupil_angle: If ``pupil_plane_im`` is not None, rotation angle for the pupil plane + (positive in the counter-clockwise direction). Must be an `Angle` + instance. [default: 0. * galsim.degrees] + pupil_plane_scale: Sampling interval in meters to use for the pupil plane array. In + most cases, it's a good idea to leave this as None, in which case + GalSim will attempt to find a good value automatically. The + exception is when specifying the pupil arrangement via an image, in + which case this keyword can be used to indicate the sampling of that + image. See also ``pad_factor`` for adjusting the pupil sampling + scale. [default: None] + pupil_plane_size: Size in meters to use for the pupil plane array. In most cases, it's + a good idea to leave this as None, in which case GalSim will attempt + to find a good value automatically. See also ``oversampling`` for + adjusting the pupil size. [default: None] + """ + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = True + _is_analytic_k = True + + _default_iipf = 1.5 + + def __init__(self, screen_list, lam, t0=0.0, exptime=0.0, time_step=0.025, flux=1.0, + theta=(0.0*arcsec, 0.0*arcsec), interpolant=None, scale_unit=arcsec, + ii_pad_factor=None, suppress_warning=False, + geometric_shooting=True, aper=None, second_kick=None, kcrit=0.2, fft_sign='+', + gsparams=None, _force_stepk=0., _force_maxk=0., _bar=None, **kwargs): + # Hidden `_bar` kwarg can be used with astropy.console.utils.ProgressBar to print out a + # progress bar during long calculations. + + if not isinstance(screen_list, PhaseScreenList): + screen_list = PhaseScreenList(screen_list) + + if fft_sign not in ['+', '-']: + raise GalSimValueError("Invalid fft_sign", fft_sign, allowed_values=['+','-']) + + self._screen_list = screen_list + self.t0 = float(t0) + self.lam = float(lam) + self.exptime = float(exptime) + self.time_step = float(time_step) + if aper is None: + # Check here for diameter. + if 'diam' not in kwargs: + raise GalSimIncompatibleValuesError( + "Diameter required if aperture not specified directly.", diam=None, aper=aper) + aper = Aperture(lam=lam, screen_list=self._screen_list, gsparams=gsparams, **kwargs) + elif gsparams is None: + gsparams = aper.gsparams + else: + aper = aper.withGSParams(gsparams) + self.aper = aper + + if not isinstance(theta[0], Angle) or not isinstance(theta[1], Angle): + raise TypeError("theta must be 2-tuple of galsim.Angle's.") + self.theta = theta + self.interpolant = interpolant + if isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + self.scale_unit = scale_unit + self._gsparams = GSParams.check(gsparams) + self.scale = aper._sky_scale(self.lam, self.scale_unit) + + self._force_stepk = _force_stepk + self._force_maxk = _force_maxk + + self._img = None + + if self.exptime < 0: + raise GalSimRangeError("Cannot integrate PSF for negative time.", self.exptime, 0.) + + self._ii_pad_factor = ii_pad_factor if ii_pad_factor is not None else self._default_iipf + + self._bar = _bar if _bar else dict() # with dict() _bar.update() is a trivial no op. + self._flux = float(flux) + self._suppress_warning = suppress_warning + self._geometric_shooting = geometric_shooting + self._kcrit = kcrit + self._fft_sign = fft_sign + # We'll set these more intelligently as needed below + self._second_kick = second_kick + self._screen_list._delayCalculation(self) + self._finalized = False + + @lazy_property + def _real_ii(self): + ii = InterpolatedImage( + self._img, x_interpolant=self.interpolant, + _force_stepk=self._force_stepk, _force_maxk=self._force_maxk, + pad_factor=self._ii_pad_factor, + use_true_center=False, gsparams=self._gsparams) + + if not self._suppress_warning: + specified_stepk = 2*np.pi/(self._img.array.shape[0]*self.scale) + observed_stepk = ii.stepk + + if observed_stepk < specified_stepk: + galsim_warn( + "The calculated stepk (%g) for PhaseScreenPSF is smaller than what was used " + "to build the wavefront (%g). This could lead to aliasing problems. " + "Increasing pad_factor is recommended."%(observed_stepk, specified_stepk)) + return ii + + @lazy_property + def _dummy_ii(self): + # If we need self._ii before we've done _prepareDraw, then build a placeholder that has + # roughly the right properties. All we really need is for the stepk and maxk to be + # correct, so use the force_ options to set them how we want. + if self._force_stepk > 0.: + stepk = self._force_stepk + else: + stepk = self._screen_list._getStepK(lam=self.lam, diam=self.aper.diam, + obscuration=self.aper.obscuration, + gsparams=self._gsparams) + if self._force_maxk > 0.: + maxk = self._force_maxk + else: + maxk = self.aper._getMaxK(self.lam, self.scale_unit) + image = _Image(np.array([[self._flux]], dtype=float), + _BoundsI(1, 1, 1, 1), PixelScale(1.)) + interpolant = 'delta' # Use delta so it doesn't contribute to stepk + return InterpolatedImage( + image, pad_factor=1.0, x_interpolant=interpolant, + _force_stepk=stepk, _force_maxk=maxk) + + @property + def _ii(self): + if self._finalized: + return self._real_ii + else: + return self._dummy_ii + + @property + def kcrit(self): + """The critical Fourier scale being used for this object. + """ + return self._kcrit + + @property + def fft_sign(self): + """The sign (+/-) to use in the exponent of the Fourier kernel when evaluating the Fourier + optics PSF. + """ + return self._fft_sign + + @lazy_property + def screen_kmax(self): + """The maximum k value to use in the screen. Typically `kcrit`/r0. + """ + r0_500 = self._screen_list.r0_500_effective + if r0_500 is None: + return np.inf + else: + r0 = r0_500 * (self.lam/500)**(6./5) + return self.kcrit / r0 + + @lazy_property + def second_kick(self): + """Make a SecondKick object based on contents of screen_list and aper. + """ + if self._second_kick is None: + r0_500 = self._screen_list.r0_500_effective + if r0_500 is None: # No AtmosphericScreens in list + return Airy(lam=self.lam, diam=self.aper.diam, + obscuration=self.aper.obscuration, gsparams=self._gsparams) + else: + r0 = r0_500 * (self.lam/500.)**(6./5) + return SecondKick( + self.lam, r0, self.aper.diam, self.aper.obscuration, + kcrit=self.kcrit, scale_unit=self.scale_unit, + gsparams=self._gsparams) + else: + return self._second_kick + + @property + def flux(self): + """The flux of the profile. + """ + return self._flux + + @property + def screen_list(self): + """The `PhaseScreenList` being used for this object. + """ + return self._screen_list + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + aper = self.aper.withGSParams(gsparams) + ret = self.__class__.__new__(self.__class__) + ret.__dict__.update(self.__dict__) + # Make sure we generate fresh versions of any attrs that depend on gsparams + for attr in ['second_kick', '_real_ii', '_dummy_ii']: + ret.__dict__.pop(attr, None) + ret._gsparams = gsparams + ret.aper = aper + # Make sure we mark that we need to recalculate any previously finalized InterpolatedImage + ret._finalized = False + ret._screen_list._delayCalculation(ret) + ret._img = None + return ret
+ + def __str__(self): + return ("galsim.PhaseScreenPSF(%s, lam=%s, exptime=%s)" % + (self._screen_list, self.lam, self.exptime)) + + def __repr__(self): + outstr = ("galsim.PhaseScreenPSF(%r, lam=%r, exptime=%r, flux=%r, aper=%r, theta=%r, " + "interpolant=%r, scale_unit=%r, fft_sign=%r, gsparams=%r)") + return outstr % (self._screen_list, self.lam, self.exptime, self.flux, self.aper, + self.theta, self.interpolant, self.scale_unit, + self._fft_sign, self.gsparams) + + def __eq__(self, other): + # Even if two PSFs were generated with different sets of parameters, they will act + # identically if their img, interpolant, stepk, maxk, pad_factor, fft_sign and gsparams + # match. + return (self is other or + (isinstance(other, PhaseScreenPSF) and + self._screen_list == other._screen_list and + self.lam == other.lam and + self.aper == other.aper and + self.t0 == other.t0 and + self.exptime == other.exptime and + self.time_step == other.time_step and + self._flux == other._flux and + self.interpolant == other.interpolant and + self._force_stepk == other._force_stepk and + self._force_maxk == other._force_maxk and + self._ii_pad_factor == other._ii_pad_factor and + self._fft_sign == other._fft_sign and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.PhaseScreenPSF", tuple(self._screen_list), self.lam, self.aper, + self.t0, self.exptime, self.time_step, self._flux, self.interpolant, + self._force_stepk, self._force_maxk, self._ii_pad_factor, self._fft_sign, + self.gsparams)) + + def _prepareDraw(self): + # Trigger delayed computation of all pending PSFs. + self._screen_list._prepareDraw() + + def _step(self): + """Compute the current instantaneous PSF and add it to the developing integrated PSF.""" + u = self.aper.u_illuminated + v = self.aper.v_illuminated + # This is where I need to make sure the screens are instantiated for FFT. + self._screen_list.instantiate(check='FFT') + wf = self._screen_list._wavefront(u, v, None, self.theta) + expwf = np.exp((2j*np.pi/self.lam) * wf) + expwf_grid = np.zeros_like(self.aper.illuminated, dtype=np.complex128) + expwf_grid[self.aper.illuminated] = expwf + # Note fft is '-' and ifft is '+' below + if self._fft_sign == '+': + ftexpwf = fft.ifft2(expwf_grid, shift_in=True, shift_out=True) + else: + ftexpwf = fft.fft2(expwf_grid, shift_in=True, shift_out=True) + if self._img is None: + self._img = np.zeros(self.aper.illuminated.shape, dtype=np.float64) + self._img += np.abs(ftexpwf)**2 + self._bar.update() + + def _finalize(self): + """Take accumulated integrated PSF image and turn it into a proper GSObject.""" + self._img *= self._flux / self._img.sum(dtype=float) + b = _BoundsI(1,self.aper.npix,1,self.aper.npix) + self._img = _Image(self._img, b, PixelScale(self.scale)) + + self._finalized = True + + def __getstate__(self): + d = self.__dict__.copy() + # The SBProfile is picklable, but it is pretty inefficient, due to the large images being + # written as a string. Better to pickle the image and remake the InterpolatedImage. + d.pop('_dummy_ii',None) + d.pop('_real_ii',None) + d.pop('second_kick',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + if not self._finalized: + self._screen_list._delayCalculation(self) + + @property + def _maxk(self): + return self._ii.maxk + + @property + def _stepk(self): + return self._ii.stepk + + @property + def _centroid(self): + self._prepareDraw() + return self._ii.centroid + + @property + def _positive_flux(self): + if self._geometric_shooting: + return self._flux + else: + return self._ii.positive_flux + + @property + def _negative_flux(self): + if self._geometric_shooting: + return 0. + else: + return self._ii.negative_flux + + @property + def _flux_per_photon(self): + if self._geometric_shooting: + return 1. + else: + return self._calculate_flux_per_photon() + + @property + def _max_sb(self): + return self._ii.max_sb + + def _xValue(self, pos): + self._prepareDraw() + return self._ii._xValue(pos) + + def _kValue(self, kpos): + self._prepareDraw() + return self._ii._kValue(kpos) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + self._ii._drawReal(image, jac, offset, flux_scaling) + + def _shoot(self, photons, rng): + if not self._geometric_shooting: + self._prepareDraw() + return self._ii._shoot(photons, rng) + + if not photons.hasAllocatedPupil(): + self.aper.samplePupil(photons, rng) + if not photons.hasAllocatedTimes(): + TimeSampler(self.t0, self.exptime).applyTo(photons, rng=rng) + u = photons.pupil_u + v = photons.pupil_v + t = photons.time + + n_photons = len(photons) + + # This is where the screens need to be instantiated for drawing with geometric photon + # shooting. + self._screen_list.instantiate(kmax=self.screen_kmax, check='phot') + nm_to_arcsec = 1.e-9 * radians / arcsec + if self._fft_sign == '+': + nm_to_arcsec *= -1 + photons.x, photons.y = self._screen_list._wavefront_gradient(u, v, t, self.theta) + photons.x *= nm_to_arcsec + photons.y *= nm_to_arcsec + photons.flux = self._flux / n_photons + + if self.second_kick: + p2 = PhotonArray(len(photons)) + self.second_kick._shoot(p2, rng) + photons.convolve(p2, rng) + + def _drawKImage(self, image, jac=None): + self._ii._drawKImage(image, jac) + + @property + def img(self): + from .deprecated import depr + depr('img', 2.1, '', "This functionality has been removed.") + return self._img + + @property + def finalized(self): + from .deprecated import depr + depr('finalized', 2.1, "This functionality has been removed.") + return self._finalized + +
[docs] @doc_inherit + def withFlux(self, flux): + if self._finalized: + # Then it's probably not faster to rebuild with a different flux. + return self.withScaledFlux(flux / self.flux) + else: + return PhaseScreenPSF(self._screen_list, lam=self.lam, exptime=self.exptime, flux=flux, + aper=self.aper, theta=self.theta, interpolant=self.interpolant, + scale_unit=self.scale_unit, gsparams=self.gsparams)
+ + +
[docs]class OpticalPSF(GSObject): + """A class describing aberrated PSFs due to telescope optics. Its underlying implementation + uses an InterpolatedImage to characterize the profile. + + The diffraction effects are characterized by the diffraction angle, which is a function of the + ratio lambda / D, where lambda is the wavelength of the light and D is the diameter of the + telescope. The natural unit for this value is radians, which is not normally a convenient + unit to use for other `GSObject` dimensions. Assuming that the other sky coordinates you are + using are all in arcsec (e.g. the pixel scale when you draw the image, the size of the galaxy, + etc.), then you should convert this to arcsec as well:: + + >>> lam = 700 # nm + >>> diam = 4.0 # meters + >>> lam_over_diam = (lam * 1.e-9) / diam # radians + >>> lam_over_diam *= 206265 # Convert to arcsec + >>> psf = galsim.OpticalPSF(lam_over_diam, ...) + + To make this process a bit simpler, we recommend instead providing the wavelength and diameter + separately using the parameters ``lam`` (in nm) and ``diam`` (in m). GalSim will then convert + this to any of the normal kinds of angular units using the ``scale_unit`` parameter:: + + >>> psf = galsim.OpticalPSF(lam=lam, diam=diam, scale_unit=galsim.arcsec, ...) + + When drawing images, the scale_unit should match the unit used for the pixel scale or the WCS. + e.g. in this case, a pixel scale of 0.2 arcsec/pixel would be specified as ``pixel_scale=0.2``. + + Input aberration coefficients are assumed to be supplied in units of wavelength, and correspond + to the Zernike polynomials in the Noll convention defined in + Noll, J. Opt. Soc. Am. 66, 207-211(1976). For a brief summary of the polynomials, refer to + http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials. By default, the + aberration coefficients indicate the amplitudes of _circular_ Zernike polynomials, which are + orthogonal over a circle. If you would like the aberration coefficients to instead be + interpretted as the amplitudes of _annular_ Zernike polynomials, which are orthogonal over an + annulus (see Mahajan, J. Opt. Soc. Am. 71, 1 (1981)), set the ``annular_zernike`` keyword + argument to True. + + There are two ways to specify the geometry of the pupil plane, i.e., the obscuration disk size + and the areas that will be illuminated outside of it. The first way is to use keywords that + specify the size of the obscuration, and the nature of the support struts holding up the + secondary mirror (or prime focus cage, etc.). These are taken to be rectangular obscurations + extending from the outer edge of the pupil to the outer edge of the obscuration disk (or the + pupil center if ``obscuration = 0.``). You can specify how many struts there are (evenly spaced + in angle), how thick they are as a fraction of the pupil diameter, and what angle they start at + relative to the positive y direction. + + The second way to specify the pupil plane configuration is by passing in an image of it. This + can be useful for example if the struts are not evenly spaced or are not radially directed, as + is assumed by the simple model for struts described above. In this case, keywords related to + struts are ignored; moreover, the ``obscuration`` keyword is used to ensure that the images are + properly sampled (so it is still needed), but the keyword is then ignored when using the + supplied image of the pupil plane. Note that for complicated pupil configurations, it may be + desireable to increase ``pad_factor`` for more fidelity at the expense of slower running time. + The ``pupil_plane_im`` that is passed in can be rotated during internal calculations by + specifying a ``pupil_angle`` keyword. + + If you choose to pass in a pupil plane image, it must be a square array in which the image of + the pupil is centered. The areas that are illuminated should have some value >0, and the other + areas should have a value of precisely zero. Based on what the OpticalPSF class thinks is the + required sampling to make the PSF image, the image that is passed in of the pupil plane might be + zero-padded during internal calculations. The pixel scale of the pupil plane can be specified + in one of three ways. In descending order of priority, these are: + + 1. The ``pupil_plane_scale`` keyword argument (units are meters). + 2. The ``pupil_plane_im.scale`` attribute (units are meters). + 3. If (1) and (2) are both None, then the scale will be inferred by assuming that the + illuminated pixel farthest from the image center is at a physical distance of self.diam/2. + + Note that if the scale is specified by either (1) or (2) above (which always includes specifying + the pupil_plane_im as a filename, since the default scale then will be 1.0), then the + lam_over_diam keyword must not be used, but rather the lam and diam keywords are required + separately. Finally, to ensure accuracy of calculations using a pupil plane image, we recommend + sampling it as finely as possible. + + As described above, either specify the lam/diam ratio directly in arbitrary units:: + + >>> optical_psf = galsim.OpticalPSF(lam_over_diam=lam_over_diam, defocus=0., ...) + + or, use separate keywords for the telescope diameter and wavelength in meters and nanometers, + respectively:: + + >>> optical_psf = galsim.OpticalPSF(lam=lam, diam=diam, defocus=0., ...) + + Either of these options initializes ``optical_psf`` as an OpticalPSF instance. + + Parameters: + lam_over_diam: Lambda / telescope diameter in the physical units adopted for ``scale`` + (user responsible for consistency). Either ``lam_over_diam``, or + ``lam`` and ``diam``, must be supplied. + lam: Lambda (wavelength), either as an astropy Quantity or a float in units + of nanometers. Must be supplied with ``diam``, and in this case, image + scales (``scale``) should be specified in units of ``scale_unit``. + diam : Telescope diameter, either as an astropy Quantity or a float in units of + meters. Must be supplied with ``lam``, and in this case, image scales + (``scale``) should be specified in units of ``scale_unit``. + tip: Tip in units of incident light wavelength. [default: 0] + tilt: Tilt in units of incident light wavelength. [default: 0] + defocus: Defocus in units of incident light wavelength. [default: 0] + astig1: Astigmatism (like e2) in units of incident light wavelength. + [default: 0] + astig2: Astigmatism (like e1) in units of incident light wavelength. + [default: 0] + coma1: Coma along y in units of incident light wavelength. [default: 0] + coma2: Coma along x in units of incident light wavelength. [default: 0] + trefoil1: Trefoil (one of the arrows along y) in units of incident light + wavelength. [default: 0] + trefoil2: Trefoil (one of the arrows along x) in units of incident light + wavelength. [default: 0] + spher: Spherical aberration in units of incident light wavelength. + [default: 0] + aberrations: Optional keyword, to pass in a list, tuple, or NumPy array of + aberrations in units of reference wavelength (ordered according to + the Noll convention), rather than passing in individual values for each + individual aberration. Note that aberrations[1] is piston (and not + aberrations[0], which is unused.) This list can be arbitrarily long to + handle Zernike polynomial aberrations of arbitrary order. + annular_zernike: Boolean indicating that aberrations specify the amplitudes of annular + Zernike polynomials instead of circular Zernike polynomials. + [default: False] + aper: `Aperture` object to use when creating PSF. [default: None] + circular_pupil: Adopt a circular pupil? [default: True] + obscuration: Linear dimension of central obscuration as fraction of pupil linear + dimension, [0., 1.). This should be specified even if you are providing + a ``pupil_plane_im``, since we need an initial value of obscuration to + use to figure out the necessary image sampling. [default: 0] + interpolant: Either an Interpolant instance or a string indicating which interpolant + should be used. Options are 'nearest', 'sinc', 'linear', 'cubic', + 'quintic', or 'lanczosN' where N should be the integer order to use. + [default: galsim.Quintic()] + oversampling: Optional oversampling factor for the InterpolatedImage. Setting + ``oversampling < 1`` will produce aliasing in the PSF (not good). + Usually ``oversampling`` should be somewhat larger than 1. 1.5 is + usually a safe choice. [default: 1.5] + pad_factor: Additional multiple by which to zero-pad the PSF image to avoid folding + compared to what would be employed for a simple `Airy`. Note that + ``pad_factor`` may need to be increased for stronger aberrations, i.e. + those larger than order unity. [default: 1.5] + ii_pad_factor: Zero-padding factor by which to extend the image of the PSF when + creating the ``InterpolatedImage``. See the ``InterpolatedImage`` + docstring for more details. [default: 1.5] + suppress_warning: If ``pad_factor`` is too small, the code will emit a warning telling you + its best guess about how high you might want to raise it. However, + you can suppress this warning by using ``suppress_warning=True``. + [default: False] + geometric_shooting: If True, then when drawing using photon shooting, use geometric + optics approximation where the photon angles are derived from the + phase screen gradient. If False, then first draw using Fourier + optics and then shoot from the derived InterpolatedImage. + [default: False] + flux: Total flux of the profile. [default: 1.] + nstruts: Number of radial support struts to add to the central obscuration. + [default: 0] + strut_thick: Thickness of support struts as a fraction of pupil diameter. + [default: 0.05] + strut_angle: `Angle` made between the vertical and the strut starting closest to it, + defined to be positive in the counter-clockwise direction; must be an + `Angle` instance. [default: 0. * galsim.degrees] + pupil_plane_im: The GalSim.Image, NumPy array, or name of file containing the pupil + plane image, to be used instead of generating one based on the + obscuration and strut parameters. [default: None] + pupil_angle: If ``pupil_plane_im`` is not None, rotation angle for the pupil plane + (positive in the counter-clockwise direction). Must be an `Angle` + instance. [default: 0. * galsim.degrees] + pupil_plane_scale: Sampling interval in meters to use for the pupil plane array. In + most cases, it's a good idea to leave this as None, in which case + GalSim will attempt to find a good value automatically. The + exception is when specifying the pupil arrangement via an image, in + which case this keyword can be used to indicate the sampling of that + image. See also ``pad_factor`` for adjusting the pupil sampling scale. + [default: None] + pupil_plane_size: Size in meters to use for the pupil plane array. In most cases, it's + a good idea to leave this as None, in which case GalSim will attempt + to find a good value automatically. See also ``oversampling`` for + adjusting the pupil size. [default: None] + scale_unit: Units to use for the sky coordinates when calculating lam/diam if these + are supplied separately. Should be either a `galsim.AngleUnit` or a + string that can be used to construct one (e.g., 'arcsec', 'radians', + etc.). [default: galsim.arcsec] + fft_sign: The sign (+/-) to use in the exponent of the Fourier kernel when + evaluating the Fourier optics PSF. As of version 2.3, GalSim uses a + plus sign by default, which we believe to be consistent with, for + example, how Zemax computes a Fourier optics PSF on DECam. Before + version 2.3, the default was a negative sign. Input should be either + the string '+' or the string '-'. [default: '+'] + gsparams: An optional `GSParams` argument. [default: None] + """ + _opt_params = { + "diam": (float, u.Quantity), + "defocus": float, + "astig1": float, + "astig2": float, + "coma1": float, + "coma2": float, + "trefoil1": float, + "trefoil2": float, + "spher": float, + "annular_zernike": bool, + "circular_pupil": bool, + "obscuration": float, + "oversampling": float, + "pad_factor": float, + "suppress_warning": bool, + "interpolant": str, + "flux": float, + "nstruts": int, + "strut_thick": float, + "strut_angle": Angle, + "pupil_plane_im": str, + "pupil_angle": Angle, + "pupil_plane_scale": float, + "pupil_plane_size": float, + "scale_unit": str, + "fft_sign": str} + _single_params = [{"lam_over_diam": float, "lam": (float, u.Quantity)}] + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = True + _is_analytic_k = True + + _default_iipf = 1.5 # The default ii_pad_factor, since we need to check it for the repr + + def __init__(self, lam_over_diam=None, lam=None, diam=None, tip=0., tilt=0., defocus=0., + astig1=0., astig2=0., coma1=0., coma2=0., trefoil1=0., trefoil2=0., spher=0., + aberrations=None, annular_zernike=False, + aper=None, circular_pupil=True, obscuration=0., interpolant=None, + oversampling=1.5, pad_factor=1.5, ii_pad_factor=None, flux=1., + nstruts=0, strut_thick=0.05, strut_angle=0.*radians, + pupil_plane_im=None, pupil_plane_scale=None, pupil_plane_size=None, + pupil_angle=0.*radians, scale_unit=arcsec, fft_sign='+', gsparams=None, + _force_stepk=0., _force_maxk=0., + suppress_warning=False, geometric_shooting=False): + if isinstance(lam, u.Quantity): + lam = lam.to_value(u.nm) + if isinstance(diam, u.Quantity): + diam = diam.to_value(u.m) + if fft_sign not in ['+', '-']: + raise GalSimValueError("Invalid fft_sign", fft_sign, allowed_values=['+','-']) + if isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + # Need to handle lam/diam vs. lam_over_diam here since lam by itself is needed for + # OpticalScreen. + if lam_over_diam is not None: + if lam is not None or diam is not None: + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify lam or diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) + # For combination of lam_over_diam and pupil_plane_im with a specified scale, it's + # tricky to determine the actual diameter of the pupil needed by Aperture. So for now, + # we just disallow this combination. Please feel free to raise an issue at + # https://github.com/GalSim-developers/GalSim/issues if you need this functionality. + if pupil_plane_im is not None: + if isinstance(pupil_plane_im, basestring): + # Filename, therefore specific scale exists. + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify pupil_plane_im as " + "as a filename.", + lam_over_diam=lam_over_diam, pupil_plane_im=pupil_plane_im) + elif isinstance(pupil_plane_im, Image) and pupil_plane_im.scale is not None: + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify pupil_plane_im " + "with definite scale attribute.", + lam_over_diam=lam_over_diam, pupil_plane_im=pupil_plane_im) + elif pupil_plane_scale is not None: + raise GalSimIncompatibleValuesError( + "If specifying lam_over_diam, then do not specify pupil_plane_scale. ", + lam_over_diam=lam_over_diam, pupil_plane_scale=pupil_plane_scale) + lam = 500. # Arbitrary + diam = lam*1.e-9 / lam_over_diam * radians / scale_unit + else: + if lam is None or diam is None: + raise GalSimIncompatibleValuesError( + "If not specifying lam_over_diam, then specify lam AND diam", + lam_over_diam=lam_over_diam, lam=lam, diam=diam) + + # Make the optical screen. + self._screen = OpticalScreen( + diam=diam, defocus=defocus, astig1=astig1, astig2=astig2, coma1=coma1, coma2=coma2, + trefoil1=trefoil1, trefoil2=trefoil2, spher=spher, aberrations=aberrations, + obscuration=obscuration, annular_zernike=annular_zernike, lam_0=lam) + + # Make the aperture. + if aper is None: + aper = Aperture( + diam, lam=lam, circular_pupil=circular_pupil, obscuration=obscuration, + nstruts=nstruts, strut_thick=strut_thick, strut_angle=strut_angle, + oversampling=oversampling, pad_factor=pad_factor, + pupil_plane_im=pupil_plane_im, pupil_angle=pupil_angle, + pupil_plane_scale=pupil_plane_scale, pupil_plane_size=pupil_plane_size, + gsparams=gsparams) + self.obscuration = obscuration + else: + self.obscuration = aper.obscuration + + # Save for pickling + self._lam = float(lam) + self._flux = float(flux) + self._interpolant = interpolant + self._scale_unit = scale_unit + self._gsparams = GSParams.check(gsparams) + self._suppress_warning = suppress_warning + self._geometric_shooting = geometric_shooting + self._aper = aper + self._force_stepk = _force_stepk + self._force_maxk = _force_maxk + self._ii_pad_factor = ii_pad_factor if ii_pad_factor is not None else self._default_iipf + self._fft_sign = fft_sign + + @lazy_property + def _psf(self): + psf = PhaseScreenPSF(PhaseScreenList(self._screen), lam=self._lam, flux=self._flux, + aper=self._aper, interpolant=self._interpolant, + scale_unit=self._scale_unit, fft_sign=self._fft_sign, + gsparams=self._gsparams, + suppress_warning=self._suppress_warning, + geometric_shooting=self._geometric_shooting, + _force_stepk=self._force_stepk, _force_maxk=self._force_maxk, + ii_pad_factor=self._ii_pad_factor) + psf._prepareDraw() # No need to delay an OpticalPSF. + return psf + + def __str__(self): + screen = self._screen + s = "galsim.OpticalPSF(lam=%s, diam=%s" % (screen.lam_0, self._aper.diam) + if any(screen.aberrations): + s += ", aberrations=[" + ",".join(str(ab) for ab in screen.aberrations) + "]" + if self._aper._pupil_plane_im is None: + s += self._aper._geometry_str() + if screen.annular_zernike: + s += ", annular_zernike=True" + s += ", obscuration=%r"%self.obscuration + if self._flux != 1.0: + s += ", flux=%s" % self._flux + s += ")" + return s + + def __repr__(self): + screen = self._screen + s = "galsim.OpticalPSF(lam=%r, diam=%r" % (self._lam, self._aper.diam) + s += ", aper=%r"%self._aper + if any(screen.aberrations): + s += ", aberrations=[" + ",".join(repr(ab) for ab in screen.aberrations) + "]" + if screen.annular_zernike: + s += ", annular_zernike=True" + s += ", obscuration=%r"%self.obscuration + if self._interpolant != None: + s += ", interpolant=%r"%self._interpolant + if self._scale_unit != arcsec: + s += ", scale_unit=%r"%self._scale_unit + if self._fft_sign != '+': + s += ", fft_sign='-'" + if self._gsparams != GSParams(): + s += ", gsparams=%r"%self._gsparams + if self._flux != 1.0: + s += ", flux=%r" % self._flux + if self._force_stepk != 0.: + s += ", _force_stepk=%r" % self._force_stepk + if self._force_maxk != 0.: + s += ", _force_maxk=%r" % self._force_maxk + if self._ii_pad_factor != OpticalPSF._default_iipf: + s += ", ii_pad_factor=%r" % self._ii_pad_factor + s += ")" + return s + + def __eq__(self, other): + return (self is other or + (isinstance(other, OpticalPSF) and + self._lam == other._lam and + self._aper == other._aper and + self._screen == other._screen and + self._flux == other._flux and + self._interpolant == other._interpolant and + self._scale_unit == other._scale_unit and + self._force_stepk == other._force_stepk and + self._force_maxk == other._force_maxk and + self._ii_pad_factor == other._ii_pad_factor and + self._fft_sign == other._fft_sign and + self._gsparams == other._gsparams)) + + def __hash__(self): + return hash(("galsim.OpticalPSF", self._lam, self._aper, self._screen, + self._flux, self._interpolant, self._scale_unit, self._force_stepk, + self._force_maxk, self._ii_pad_factor, self._fft_sign, self._gsparams)) + + def __getstate__(self): + # The SBProfile is picklable, but it is pretty inefficient, due to the large images being + # written as a string. Better to pickle the psf and remake the PhaseScreenPSF. + d = self.__dict__.copy() + d.pop('_psf', None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._psf.maxk + + @property + def _stepk(self): + return self._psf.stepk + + @property + def _centroid(self): + return self._psf.centroid + + @property + def _positive_flux(self): + return self._psf.positive_flux + + @property + def _negative_flux(self): + return self._psf.negative_flux + + @property + def _flux_per_photon(self): + return self._psf._flux_per_photon + + @property + def _max_sb(self): + return self._psf.max_sb + + @property + def fft_sign(self): + return self._fft_sign + + def _xValue(self, pos): + return self._psf._xValue(pos) + + def _kValue(self, kpos): + return self._psf._kValue(kpos) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + self._psf._drawReal(image, jac, offset, flux_scaling) + + def _shoot(self, photons, rng): + self._psf._shoot(photons, rng) + + def _drawKImage(self, image, jac=None): + self._psf._drawKImage(image, jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + screen = self._screen + return OpticalPSF( + lam=self._lam, diam=self._aper.diam, aper=self._aper, + aberrations=screen.aberrations, annular_zernike=screen.annular_zernike, + flux=flux, _force_stepk=self._force_stepk, _force_maxk=self._force_maxk, + ii_pad_factor=self._ii_pad_factor, fft_sign=self._fft_sign, + gsparams=self._gsparams)
+ +# Put this at the bottom to avoid circular import errors. +from .phase_screens import AtmosphericScreen, OpticalScreen +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/phase_screens.html b/docs/_build/html/_modules/galsim/phase_screens.html new file mode 100644 index 00000000000..03101b71337 --- /dev/null +++ b/docs/_build/html/_modules/galsim/phase_screens.html @@ -0,0 +1,1390 @@ + + + + + + galsim.phase_screens — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.phase_screens

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'AtmosphericScreen', 'Atmosphere', 'OpticalScreen', 'UserScreen', ]
+
+import numpy as np
+import multiprocessing
+from contextlib import contextmanager
+from numbers import Real
+
+from .random import BaseDeviate, GaussianDeviate
+from .image import Image
+from .angle import radians
+from .table import LookupTable2D, _LookupTable2D
+from . import utilities
+from . import fft
+from . import zernike
+from .utilities import LRU_Cache, lazy_property
+from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError
+from .errors import galsim_warn, GalSimNotImplementedError, GalSimError
+from .kolmogorov import Kolmogorov
+from .airy import Airy
+
+
+# Two helper functions to cache the calculation required for _getStepK
+def __calcAtmStepK(lam, r0_500, gsparams):
+    # Use a Kolmogorov to get appropriate stepk.
+    obj = Kolmogorov(lam=lam, r0_500=r0_500, gsparams=gsparams)
+    return obj.stepk
+_calcAtmStepK = LRU_Cache(__calcAtmStepK)
+
+def __calcOptStepK(lam, diam, obscuration, gsparams):
+    # Use an Airy to get appropriate stepk.
+    obj = Airy(lam=lam, diam=diam, obscuration=obscuration, gsparams=gsparams)
+    return obj.stepk
+_calcOptStepK = LRU_Cache(__calcOptStepK)
+
+
+# Global dict of "pointers" (roughly) to shared memory.
+_GSScreenShare = {}
+
+
+
[docs]def initWorkerArgs(): + """Function used to generate worker arguments to pass to multiprocessing.Pool initializer. + + See `AtmosphericScreen` docstring for more information. + """ + for v in _GSScreenShare.values(): + if v['alpha'].value != 1.0: + raise GalSimNotImplementedError( + "Shared memory use is only supported for frozen-flow screens") + return (_GSScreenShare,)
+ + +
[docs]def initWorker(share): + """Worker initialization function to pass to multiprocessing.Pool initializer. + + See `AtmosphericScreen` docstring for more information. + """ + _GSScreenShare.update(share) # pragma: no cover (covered, but in a fork)
+ +
[docs]def reset_shared_screens(): + """Reset the global dict that contains screens being shared across multiprocessing processes. + + This is almost never necessary. However, if you first use one multiprocessing context with + `initWorker` and `initWorkerArgs`, and then switch to a different context for another + multiprocessing action, the dict we use for storing the shared memory will not be usable + in the new context. In this case, you should reset the global dict before starting + the second multiprocessing action by running:: + + galsim.phase_screens.reset_shared_screens() + + If you only ever use one multiprocessing context in your program, you should never need + to call this. + """ + global _GSScreenShare + _GSScreenShare = {}
+ +# Helper to let the with Lock() context in the __del__ below timeout if things get borked. +# From https://stackoverflow.com/questions/71282555/acquire-a-multiprocessing-lock-in-a-with-statement-if-non-blocking-or-with-timeo +@contextmanager +def acquire_lock(lock, block=True, timeout=None): + held = lock.acquire(block=block, timeout=timeout) + try: + yield held + finally: + if held: # pragma: no branch + lock.release() + + +
[docs]class AtmosphericScreen: + """ An atmospheric phase screen that can drift in the wind and evolves ("boils") over time. The + initial phases and fractional phase updates are drawn from a von Karman power spectrum, which is + defined by a Fried parameter that effectively sets the amplitude of the turbulence, and an outer + scale beyond which the turbulence power flattens. + + AtmosphericScreen delays the actual instantiation of the phase screen array in memory until it + is used for either drawing a PSF or querying the wavefront or wavefront gradient. This is to + facilitate automatic truncation of the screen power spectrum depending on the use case. For + example, when drawing a `PhaseScreenPSF` using Fourier methods, the entire power spectrum should + generally be used. On the other hand, when drawing using photon-shooting and the geometric + approximation, it's better to truncate the high-k modes of the power spectrum here so + that they can be handled instead by a SecondKick object (which also happens automatically; see + the `PhaseScreenPSF` docstring). (See Peterson et al. 2015 for more details about the second + kick). Querying the wavefront or wavefront gradient will instantiate the screen using the full + power spectrum. + + This class will normally attempt to sanity check that the screen has been appropriately + instantiated depending on the use case, i.e., depending on whether it's being used to draw with + Fourier optics or geometric optics. If you want to turn this warning off, however, you can + use the ``suppress_warning`` keyword argument. + + If you wish to override the automatic truncation determination, then you can directly + instantiate the phase screen array using the `AtmosphericScreen.instantiate` method. + + Note that once a screen has been instantiated with a particular set of truncation parameters, it + cannot be re-instantiated with another set of parameters. + + **Shared memory**: + + Instantiated AtmosphericScreen objects can consume a significant amount of memory. For example, + an atmosphere with 6 screens, each extending 819.2 m and with resolution of 10 cm will consume + 3 GB of memory. In contexts where both a realistic atmospheric PSF and high throughput via + multiprocessing are required, allocating this 3 GB of memory once in a shared memory space + accessible to each subprocess (as opposed to once per subprocess) is highly desireable. We + provide a few functions here to enable such usage: + + - The mp_context keyword argument to AtmosphericScreen. + This is used to indicate which multiprocessing process launching context will be used. + This is important for setting up the shared memory correctly. + - The `initWorker` and `initWorkerArgs` functions. + These should be used in a call to multiprocessing.Pool to correctly inform the worker + process where to find AtmosphericScreen shared memory. + + A template example might look something like:: + + import galsim + import multiprocessing as mp + + def work(i, atm): + args, moreArgs = fn(i) + psf = atm.makePSF(*args) + return psf.drawImage(*moreArgs) + + ctx = mp.get_context("spawn") # "spawn" is generally the safest context available + + atm = galsim.Atmosphere(..., mp_context=ctx) # internally calls AtmosphericScreen ctor + nProc = 4 # Note, can set this to None to get a good default + + # Note: Especially if you are using "fork" context, then you want to make sure to run + # your Pool in a single_threaded context. Even if not, it's probably a good idea + # so each process isn't spawning lots of OpenMP (or other) threads. + with galsim.utilities.single_threaded(): + with ctx.Pool( + nProc, + initializer=galsim.phase_screens.initWorker, + initargs=galsim.phase_screens.initWorkerArgs() + ) as pool: + results = [] + # First submit + for i in range(10): + results.append(pool.apply_async(work, (i, atm))) + # Then wait to finish + for r in results: + r.wait() + # Turn future objects into actual returned images. + results = [r.get() for r in results] + + It is also possible to manually instantiate each of the AtmosphericScreen objects in a + `PhaseScreenList` in parallel using a process pool. This requires knowing what k-scale to + truncate the screen at:: + + atm = galsim.Atmosphere(..., mp_context=ctx) + with galsim.utilities.single_threaded(): + with ctx.Pool( + nProc, + initializer=galsim.phase_screens.initWorker, + initargs=galsim.phase_screens.initWorkerArgs() + ) as pool: + dummyPSF = atm.makePSF(...) + kmax = dummyPSF.screen_kmax + atm.instantiate(pool=pool, kmax=kmax) + + Finally, the above multiprocessing shared memory tricks are only currently supported for + non-time-evolving screens (alpha=1). + + **Pickling**: + + The shared memory portion of an atmospheric screen is not included by default in the pickle of + an `AtmosphericScreen` instance. This means that while it is possible to pickle/unpickle an + `AtmosphericScreen` as normal within a single launch of a potentially-multiprocess program, (as + long as the shared memory is persistent), a different path is needed to say, create an + `AtmosphericScreen` pickle, quit python, restart python, and unpickle the screen. The same + holds for any object that wraps an `AtmosphericScreen` as an attribute as well, which could + include, e.g., `PhaseScreenList` or `PhaseScreenPSF`. + + To get around this limitation, the context manager `galsim.utilities.pickle_shared` can be + used. For example:: + + screen = galsim.AtmosphericScreen(...) + with galsim.utilities.pickle_shared(): + pickle.dump(screen, open('myScreen.pkl', 'wb')) + + will pickle both the screen object and any required shared memory used in its definition. + Unpickling then proceeds exactly as normal:: + + screen = pickle.load(open('myScreen.pkl', 'rb')) + + Parameters: + screen_size: Physical extent of square phase screen in meters. This should be large + enough to accommodate the desired field-of-view of the telescope as + well as the meta-pupil defined by the wind speed and exposure time. + Note that the screen will have periodic boundary conditions, so while + the code will still run with a small screen, this may introduce + artifacts into PSFs or PSF correlation functions. Also note that + screen_size may be tweaked by the initializer to ensure ``screen_size`` + is a multiple of ``screen_scale``. + screen_scale: Physical pixel scale of phase screen in meters. An order unity multiple + of the Fried parameter is usually sufficiently small, but users should + test the effects of varying this parameter to ensure robust results. + [default: r0_500] + altitude: Altitude of phase screen in km. This is with respect to the telescope, + not sea-level. [default: 0.0] + r0_500: Fried parameter setting the amplitude of turbulence; contributes to + "size" of the resulting atmospheric PSF. Specified at wavelength 500 + nm, in units of meters. [default: 0.2] + L0: Outer scale in meters. The turbulence power spectrum will smoothly + approach a constant at scales larger than L0. Set to ``None`` or + ``np.inf`` for a power spectrum without an outer scale. [default: 25.0] + vx: x-component wind velocity in meters/second. [default: 0.] + vy: y-component wind velocity in meters/second. [default: 0.] + alpha: Square root of fraction of phase that is "remembered" between time_steps + (i.e., alpha**2 is the fraction remembered). The fraction + sqrt(1-alpha**2) is then the amount of turbulence freshly generated in + each step. Setting alpha=1.0 results in a frozen-flow atmosphere. + Note that computing PSFs from frozen-flow atmospheres may be + significantly faster than computing PSFs with non-frozen-flow + atmospheres. If ``alpha`` != 1.0, then it is required that a + ``time_step`` is also specified. [default: 1.0] + time_step: Time interval between phase boiling updates. Note that this is distinct + from the time interval used to integrate the PSF over time, which is set + by the ``time_step`` keyword argument to `PhaseScreenPSF` or + `PhaseScreenList.makePSF`. If ``time_step`` is not None, then + it is required that ``alpha`` is set to something other than 1.0. + [default: None] + rng: Random number generator as a `BaseDeviate`. If None, then use + the clock time or system entropy to seed a new generator. + [default: None] + suppress_warning: Turn off instantiation sanity checking. (See above) [default: False] + mp_context GalSim uses shared memory for phase screen allocation to better enable + multiprocessing. Use this keyword to set the launch context for + multiprocessing. Usually it will be sufficient to leave this at its + default. [default: None] + + Relevant SPIE paper: + "Remembrance of phases past: An autoregressive method for generating realistic atmospheres in + simulations" + Srikar Srinath, Univ. of California, Santa Cruz; + Lisa A. Poyneer, Lawrence Livermore National Lab.; + Alexander R. Rudy, UCSC; S. Mark Ammons, LLNL + Published in Proceedings Volume 9148: Adaptive Optics Systems IV + September 2014 + """ + def __init__(self, screen_size, screen_scale=None, altitude=0.0, r0_500=0.2, L0=25.0, + vx=0.0, vy=0.0, alpha=1.0, time_step=None, rng=None, suppress_warning=False, + mp_context=None): + if (alpha != 1.0 and time_step is None): + raise GalSimIncompatibleValuesError( + "No time_step provided when alpha != 1.0", alpha=alpha, time_step=time_step) + if (alpha == 1.0 and time_step is not None): + raise GalSimIncompatibleValuesError( + "Setting AtmosphericScreen time_step prohibited when alpha == 1.0. " + "Did you mean to set time_step in makePSF or PhaseScreenPSF?", + alpha=alpha, time_step=time_step) + if (alpha != 1.0 and mp_context is not None): + raise GalSimNotImplementedError( + "Shared memory use is only supported for frozen-flow screens") + if screen_scale is None: + # We copy Jee+Tyson(2011) and (arbitrarily) set the screen scale equal to r0 by default. + screen_scale = r0_500 + self.npix = Image.good_fft_size(int(np.ceil(screen_size/screen_scale))) + self.screen_scale = screen_scale + self.screen_size = screen_scale * self.npix + self._altitude = altitude * 1000. # meters + self.time_step = time_step + self.r0_500 = r0_500 + if L0 == np.inf: # Allow np.inf as synonym for None. + L0 = None + self.L0 = L0 + self.vx = vx + self.vy = vy + self.alpha = alpha + + if rng is None: + rng = BaseDeviate() + self._suppress_warning = suppress_warning + + self._orig_rng = rng.duplicate() + self.dynamic = True + self.reversible = self.alpha == 1.0 + + # Use shared memory for screens. Allocate it here; fill it in on demand. + if not isinstance(mp_context, multiprocessing.context.BaseContext): + mp_context = multiprocessing.get_context(mp_context) + self.mp_context = mp_context + + # A unique id for this screen, created in the parent process, that can be used to find the + # correct shared memory object in child processes. + self._shareKey = id(self) + self._objDict = AtmosphericScreen._initObjDict(self.mp_context, self.npix, alpha=self.alpha) + _GSScreenShare[self._shareKey] = self._objDict + + @staticmethod + def _initObjDict( + ctx, npix, + alpha=1.0, time=0.0, kmin=-1.0, kmax=-1.0, + x0=0.0, y0=0.0, xperiod=0.0, yperiod=0.0, + instantiated=False, refcount=1, + f=None, x=None, y=None, **kwargs + ): + RawArray = ctx.RawArray + RawValue = ctx.RawValue + Lock = ctx.Lock + + _objDict = { + 'f':RawArray('d', (npix+1)*(npix+1)), + 'x':RawArray('d', npix+1), + 'y':RawArray('d', npix+1), + 'alpha':RawValue('d', alpha), + 'time':RawValue('d', time), + 'kmin':RawValue('d', kmin), + 'kmax':RawValue('d', kmax), + 'x0':RawValue('d', x0), + 'y0':RawValue('d', y0), + 'xperiod':RawValue('d', xperiod), + 'yperiod':RawValue('d', yperiod), + 'instantiated':RawValue('b', instantiated), + 'refcount':RawValue('i', refcount), + 'lock':Lock(), + } + if f is not None: + np.frombuffer(_objDict['f'], dtype=np.float64)[:] = f + np.frombuffer(_objDict['x'], dtype=np.float64)[:] = x + np.frombuffer(_objDict['y'], dtype=np.float64)[:] = y + return _objDict + + def __setstate__(self, state): + screenShare = state.pop('screenShare', None) + self.__dict__.update(state) + shareKey = self._shareKey + if screenShare and shareKey not in _GSScreenShare: + _GSScreenShare[shareKey] = AtmosphericScreen._initObjDict( + self.mp_context, self.npix, + refcount=0, **screenShare + ) + self._objDict = _GSScreenShare[shareKey] + with self._objDict['lock']: + self._objDict['refcount'].value += 1 + + def __getstate__(self): + state = self.__dict__.copy() + state.pop('_objDict') + state.pop('_tab2d', None) + if utilities._pickle_shared: + state['screenShare'] = self._getScreenShare() + return state + + def __del__(self): + if not hasattr(self, '_objDict'): # for if __init__ raised exception + return + with acquire_lock(self._objDict['lock'], timeout=3) as acquired: + # If this can't acquire the lock, just timeout and return -- don't hang. + # (This seems to happen occasionally, but apparently only here in __del__.) + if not acquired: return + + self._objDict['refcount'].value -= 1 + # Normally, shareKey is present, but on final cleanup, we have no control over + # the order than things are deleted, so it might already be gone, in which + # case there can be a KeyError here. + if self._objDict['refcount'].value == 0 and self._shareKey in _GSScreenShare: + del _GSScreenShare[self._shareKey] + + def _getScreenShare(self): + screenShare = {} + with self._objDict['lock']: + screenShare['f'] = np.frombuffer(self._objDict['f'], dtype=np.float64) + screenShare['x'] = np.frombuffer(self._objDict['x'], dtype=np.float64) + screenShare['y'] = np.frombuffer(self._objDict['y'], dtype=np.float64) + screenShare['alpha'] = self._objDict['alpha'].value + screenShare['time'] = self._objDict['time'].value + screenShare['kmin'] = self._objDict['kmin'].value + screenShare['kmax'] = self._objDict['kmax'].value + screenShare['x0'] = self._objDict['x0'].value + screenShare['y0'] = self._objDict['y0'].value + screenShare['xperiod'] = self._objDict['xperiod'].value + screenShare['yperiod'] = self._objDict['yperiod'].value + screenShare['instantiated'] = self._objDict['instantiated'].value + return screenShare + + @property + def altitude(self): + """The altitude of the screen in km. + """ + return self._altitude / 1000. # convert back to km + + @lazy_property + def _xs(self): + return np.linspace(-0.5, 0.5, self.npix, endpoint=False)*self.screen_size + + @lazy_property + def _ys(self): + return self._xs + + def __str__(self): + return "galsim.AtmosphericScreen(altitude=%s)" % self.altitude + + def __repr__(self): + return ("galsim.AtmosphericScreen(%r, %r, altitude=%r, r0_500=%r, L0=%r, " + "vx=%r, vy=%r, alpha=%r, time_step=%r, rng=%r)") % ( + self.screen_size, self.screen_scale, self.altitude, self.r0_500, self.L0, + self.vx, self.vy, self.alpha, self.time_step, self._orig_rng) + + # While AtmosphericScreen does have mutable internal state, it's still possible to treat the + # object as hashable under the python data model. The requirements for hashability are that + # the hash value never changes during the lifetime of the object, __eq__ is defined, and a == b + # implies hash(a) == hash(b). We also require that if a == b, then f(a) == f(b) for any public + # function on an AtmosphericScreen, such as producing a PSF. Generally, it's a good idea to + # try for hash(a) == hash(b) to imply that it's very likely that a == b, too. This is mostly + # True for AtmosphericScreen (and derived objects, like PSFs), but note that while we don't + # use the object's mutable internal state for the hash value, we do use it for the __eq__ test. + # In particular, the hash value doesn't change after the screen is instantiated from its value + # before instantiation. Equality, on the other hand, does change. An instantiated screen is + # not equal to an otherwise identical uninstantiated screen. + + def __eq__(self, other): + return (self is other or + (isinstance(other, AtmosphericScreen) and + self.screen_size == other.screen_size and + self.screen_scale == other.screen_scale and + self._altitude == other._altitude and + self.r0_500 == other.r0_500 and + self.L0 == other.L0 and + self.vx == other.vx and + self.vy == other.vy and + self.alpha == other.alpha and + self.time_step == other.time_step and + self._orig_rng == other._orig_rng and + self.kmin == other.kmin and + self.kmax == other.kmax)) + + def __hash__(self): + if not hasattr(self, '_hash'): + self._hash = hash(( + "galsim.AtmosphericScreen", self.screen_size, self.screen_scale, self.altitude, + self.r0_500, self.L0, self.vx, self.vy, self.alpha, self.time_step, + repr(self._orig_rng.serialize()))) + return self._hash + + def __ne__(self, other): return not self == other + +
[docs] def instantiate(self, kmin=0., kmax=np.inf, check=None): + """ + Parameters: + kmin: Minimum k-mode to include when generating phase screens. Generally this will + only be used when testing the geometric approximation for atmospheric PSFs. + [default: 0] + kmax: Maximum k-mode to include when generating phase screens. This may be used in + conjunction with SecondKick to complete the geometric approximation for + atmospheric PSFs. [default: np.inf] + check: Sanity check indicator. If equal to 'FFT', then check that phase screen + Fourier modes are not being truncated, which is appropriate for full Fourier + optics. If equal to 'phot', then check that phase screen Fourier modes *are* + being truncated, which is appropriate for the geometric optics approximation. + If ``None``, then don't perform a check. Also, don't perform a check if + self.suppress_warning is True. + """ + if not self._objDict['instantiated'].value: + with self._objDict['lock']: + # Check that another process didn't finish instantiating + # while this process was waiting for the lock + # Since this is a race, both branches are only covered probabilistically + if not self._objDict['instantiated'].value: # pragma: no branch + self._objDict['kmin'].value = kmin + self._objDict['kmax'].value = kmax + self._init_psi() + self._reset() + if self.reversible: + del self._psi, self._screen, self._xs, self._ys + self._objDict['instantiated'].value = True + + if check is not None and not self._suppress_warning: + if check == 'FFT': + if self.kmax != np.inf: + galsim_warn("AtmosphericScreen was instantiated for photon shooting. " + "Drawing now with FFT may yield surprising results.") + if check == 'phot': + if self.kmax == np.inf: + galsim_warn("AtmosphericScreen was instantiated for FFT drawing. " + "Drawing now with photon shooting may yield surprising results.")
+ + @property + def kmin(self): + """The minimum k value being used. + """ + return self._objDict['kmin'].value + + @property + def kmax(self): + """The maximum k value being used. + """ + return self._objDict['kmax'].value + + @property + def _time(self): + # Should only be set in serial context, so no lock + return self._objDict['time'].value + + @_time.setter + def _time(self, value): + self._objDict['time'].value = value + + # Note the magic number 0.00058 is actually ... wait for it ... + # (5 * (24/5 * gamma(6/5))**(5/6) * gamma(11/6)) / (6 * pi**(8/3) * gamma(1/6)) / (2 pi)**2 + # It's nearly impossible to figure this out from a single source, but it can be derived from a + # combination of Roddier (1981), Sasiela (1994), and Noll (1976). (These atmosphere people + # sure like to work alone... ) + _kolmogorov_constant = np.sqrt(0.00058) + + def _init_psi(self): + """Assemble 2D von Karman sqrt power spectrum. + """ + fx = np.fft.fftfreq(self.npix, self.screen_scale) + fx, fy = np.meshgrid(fx, fx) + # Faster to avoid as many temporary arrays as possible. This is just ksq = fx**2 + fy**2. + ksq = fx + ksq[:,:] *= fx + ksq[:,:] += fy*fy + + # We'll use ksq as our array for psi too. So save this mask for later. + m = (ksq < self.kmin**2) | (ksq > self.kmax**2) + + old_settings = np.seterr(all='ignore') + self._psi = ksq + if self.L0 is not None: + L0_inv = 1./self.L0 + self._psi[:,:] += L0_inv*L0_inv + self._psi[:,:] **= -11./12. + # Note the multiplication by 500 here so we can divide by arbitrary lam later. + self._psi[:,:] *= (self._kolmogorov_constant * self.r0_500**(-5.0/6.0) * self.npix * + 500. / self.screen_size) + self._psi[0, 0] = 0.0 + self._psi[m] = 0.0 + np.seterr(**old_settings) + + def _random_screen(self): + """Generate a random phase screen with power spectrum given by self._psi**2""" + gd = GaussianDeviate(self.rng) + noise = utilities.rand_arr(self._psi.shape, gd) + return fft.ifft2(fft.fft2(noise)*self._psi).real + + def _setShare(self): + tab2d = LookupTable2D(self._xs, self._ys, self._screen, edge_mode='wrap') + + xshare = np.frombuffer(self._objDict['x'], dtype=np.float64) + yshare = np.frombuffer(self._objDict['y'], dtype=np.float64) + fshare = np.frombuffer(self._objDict['f'], dtype=np.float64) + fshare = fshare.reshape((self.npix+1, self.npix+1)) + xshare[:] = tab2d.x + yshare[:] = tab2d.y + fshare[:] = tab2d.f + self._objDict['x0'].value = tab2d.x0 + self._objDict['y0'].value = tab2d.y0 + self._objDict['xperiod'].value = tab2d.xperiod + self._objDict['yperiod'].value = tab2d.yperiod + self.__dict__.pop('_tab2d', None) + + @lazy_property + def _tab2d(self): + # If _tab2d hasn't been overwritten in this process, but it's use is requested, then + # set it up from shared memory. + xshare = np.frombuffer(self._objDict['x'], dtype=np.float64) + yshare = np.frombuffer(self._objDict['y'], dtype=np.float64) + fshare = np.frombuffer(self._objDict['f'], dtype=np.float64) + fshare = fshare.reshape((self.npix+1, self.npix+1)) + return _LookupTable2D( + xshare, yshare, fshare, 'linear', 'wrap', 0.0, + x0=self._objDict['x0'].value, y0=self._objDict['y0'].value, + xperiod=self._objDict['xperiod'].value, yperiod=self._objDict['yperiod'].value + ) + + def _seek(self, t): + """Set layer's internal clock to time t.""" + if t == self._time: + return + if not self.reversible: + # Can't reverse, so reset and move forward. + if t < self._time: + if t < 0.0: + raise GalSimRangeError("Can't rewind irreversible screen to t < 0.0", t, 0.) + self._reset() + # Find number of boiling updates we need to perform. + previous_update_number = int(self._time // self.time_step) + final_update_number = int(t // self.time_step) + n_updates = final_update_number - previous_update_number + if n_updates > 0: + for _ in range(n_updates): + self._screen *= self.alpha + self._screen += np.sqrt(1.-self.alpha**2) * self._random_screen() + # Make a table, copy the x,y,f arrays to shared memory, make a new table that + # points to those locations. + self._setShare() + self._time = float(t) + + def _reset(self): + """Reset phase screen back to time=0.""" + self.rng = self._orig_rng.duplicate() + self._time = 0.0 + + # Only need to reset/create tab2d if not frozen or doesn't already exist + if not self.reversible or not self._objDict['instantiated'].value: + self._screen = self._random_screen() + self._setShare() + + # Note -- use **kwargs here so that AtmosphericScreen.stepk and OpticalScreen.stepk + # can use the same signature, even though they depend on different parameters. + def _getStepK(self, **kwargs): + """Return an appropriate stepk for this atmospheric layer. + + Parameters: + lam: Wavelength in nanometers. + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + Good pupil scale size in meters. + """ + lam = kwargs['lam'] + gsparams = kwargs.pop('gsparams', None) + return _calcAtmStepK(lam, self.r0_500, gsparams) + +
[docs] def wavefront(self, u, v, t=None, theta=(0.0*radians, 0.0*radians)): + """ Compute wavefront due to atmospheric phase screen. + + Wavefront here indicates the distance by which the physical wavefront lags or leads the + ideal plane wave. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Times (in seconds) at which to evaluate wavefront. Can be None, a scalar or + an iterable. If None, then the internal time of the phase screens will be + used for all u, v. If scalar, then the size will be broadcast up to match + that of u and v. If iterable, then the shape must match the shapes of u and + v. [default: None] + theta: Field angle at which to evaluate wavefront, as a 2-tuple of `galsim.Angle` + instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] Only a single + theta is permitted. + + Returns: + Array of wavefront lag or lead in nanometers. + """ + u = np.asarray(u, dtype=float) + v = np.asarray(v, dtype=float) + if u.shape != v.shape: + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape",u=u,v=v) + + if t is None: + t = self._time + + if isinstance(t, Real): + tmp = np.empty_like(u) + tmp.fill(t) + t = tmp + else: + t = np.asarray(t, dtype=float) + if t.shape != u.shape: + raise GalSimIncompatibleValuesError( + "t.shape must match u.shape if t is not a scalar", t=t, u=u) + + self.instantiate() # noop if already instantiated + + if self.reversible: + return self._wavefront(u, v, t, theta) + else: + out = np.empty_like(u, dtype=float) + tmin = np.min(t) + tmax = np.max(t) + tt = (tmin // self.time_step) * self.time_step + while tt <= tmax: + self._seek(tt) + here = ((tt <= t) & (t < tt+self.time_step)) + out[here] = self._wavefront(u[here], v[here], t[here], theta) + tt += self.time_step + return out
+ + def _wavefront(self, u, v, t, theta): + # Same as wavefront(), but no argument checking, no boiling updates, no + # screen instantiation checking + if t is None: + t = self._time + u = u - t*self.vx + if theta[0].rad != 0.: + u += self._altitude*theta[0].tan() + v = v - t*self.vy + if theta[1].rad != 0.: + v += self._altitude*theta[1].tan() + return self._tab2d._call_wrap(u.ravel(), v.ravel()).reshape(u.shape) + +
[docs] def wavefront_gradient(self, u, v, t=None, theta=(0.0*radians, 0.0*radians)): + """ Compute gradient of wavefront due to atmospheric phase screen. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Times (in seconds) at which to evaluate wavefront gradient. Can be None, a + scalar or an iterable. If None, then the internal time of the phase screens + will be used for all u, v. If scalar, then the size will be broadcast up to + match that of u and v. If iterable, then the shape must match the shapes of + u and v. [default: None] + theta: Field angle at which to evaluate wavefront, as a 2-tuple of `galsim.Angle` + instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] Only a single + theta is permitted. + + Returns: + Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m. + """ + u = np.asarray(u, dtype=float) + v = np.asarray(v, dtype=float) + if u.shape != v.shape: + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape", u=u, v=v) + + if isinstance(t, Real): + tmp = np.empty_like(u) + tmp.fill(t) + t = tmp + else: + t = np.asarray(t, dtype=float) + if t.shape != u.shape: + raise GalSimIncompatibleValuesError( + "t.shape must match u.shape if t is not a scalar", t=t, u=u) + + self.instantiate() # noop if already instantiated + + if self.reversible: + return self._wavefront_gradient(u, v, t, theta) + else: + dwdu = np.empty_like(u, dtype=np.float64) + dwdv = np.empty_like(u, dtype=np.float64) + tmin = np.min(t) + tmax = np.max(t) + tt = (tmin // self.time_step) * self.time_step + while tt <= tmax: + self._seek(tt) + here = ((tt <= t) & (t < tt+self.time_step)) + dwdu[here], dwdv[here] = self._wavefront_gradient(u[here], v[here], t[here], theta) + tt += self.time_step + return dwdu, dwdv
+ + def _wavefront_gradient(self, u, v, t, theta): + # Same as wavefront(), but no argument checking and no boiling updates. + u = u - t*self.vx + if theta[0].rad != 0: + u += self._altitude*theta[0].tan() + v = v - t*self.vy + if theta[1].rad != 0: + v += self._altitude*theta[1].tan() + dfdx, dfdy = self._tab2d._gradient_wrap(u.ravel(), v.ravel()) + return dfdx.reshape(u.shape), dfdy.reshape(u.shape)
+ + +
[docs]def Atmosphere(screen_size, rng=None, _bar=None, **kwargs): + r"""Create an atmosphere as a list of turbulent phase screens at different altitudes. The + atmosphere model can then be used to simulate atmospheric PSFs. + + Simulating an atmospheric PSF is typically accomplished by first representing the 3-dimensional + turbulence in the atmosphere as a series of discrete 2-dimensional phase screens. These screens + may blow around in the wind, and may or may not also evolve in time. This function allows one + to quickly assemble a list of atmospheric phase screens into a `galsim.PhaseScreenList` object, + which can then be used to evaluate PSFs through various columns of atmosphere at different field + angles. + + The atmospheric screens currently available represent turbulence following a von Karman power + spectrum. Specifically, the phase power spectrum in each screen can be written + + .. math:: + \psi(\nu) = 0.023 r_0^{-5/3} \left(\nu^2 + \frac{1}{L_0^2}\right)^{11/6} + + where :math:`\psi(\nu)` is the power spectral density at spatial frequency :math:`\nu`, + :math:`r_0` is the Fried parameter (which has dimensions of length) and sets the amplitude of + the turbulence, and :math:`L_0` is the outer scale (also dimensions of length) beyond which the + power asymptotically flattens. + + Typical values for :math:`r_0` are ~0.1 to 0.2 meters, which corresponds roughly to PSF FWHMs + of ~0.5 to 1.0 arcsec for optical wavelengths. Note that :math:`r_0` is a function of + wavelength, scaling like :math:`r_0 \sim \lambda^{6/5}`. To reduce confusion, the input + parameter here is named ``r0_500`` and refers explicitly to the Fried parameter at a wavelength + of 500 nm. The outer scale is typically in the 10s of meters and does not vary with wavelength. + + To create multiple layers, simply specify keyword arguments as length-N lists instead of scalars + (works for all arguments except ``rng``). If, for any of these keyword arguments, you want to + use the same value for each layer, then you can just specify the argument as a scalar and the + function will automatically broadcast it into a list with length equal to the longest found + keyword argument list. Note that it is an error to specify keywords with lists of different + lengths (unless only one of them has length > 1). + + The one exception to the above is the keyword ``r0_500``. The effective Fried parameter for a + set of atmospheric layers is:: + + r0_500_effective = (sum(r**(-5./3) for r in r0_500s))**(-3./5) + + Providing ``r0_500`` as a scalar or single-element list will result in broadcasting such that + the effective Fried parameter for the whole set of layers equals the input argument. You can + weight the contribution of each layer with the ``r0_weights`` keyword. + + As an example, the following code approximately creates the atmosphere used by Jee+Tyson(2011) + for their study of atmospheric PSFs for LSST. Note this code takes about ~2 minutes to run on + a fast laptop, and will consume about (8192**2 pixels) * (8 bytes) * (6 screens) ~ 3 GB of + RAM in its final state, and more at intermediate states.:: + + >>> altitude = [0, 2.58, 5.16, 7.73, 12.89, 15.46] # km + >>> r0_500 = 0.16 # m + >>> weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022] + >>> speed = np.random.uniform(0, 20, size=6) # m/s + >>> direction = [np.random.uniform(0, 360)*galsim.degrees for i in range(6)] + >>> npix = 8192 + >>> screen_scale = r0_500 + >>> atm = galsim.Atmosphere(r0_500=r0_500, r0_weights=weights, + screen_size=screen_scale*npix, + altitude=altitude, L0=25.0, speed=speed, + direction=direction, screen_scale=screen_scale) + + Once the atmosphere is constructed, a 15-sec exposure length, 5ms time step, monochromatic PSF + at 700nm (using an 8.4 meter aperture, 0.6 fractional obscuration and otherwise default + settings) takes about 7 minutes to draw on a fast laptop.:: + + >>> psf = atm.makePSF(lam=700.0, exptime=15.0, time_step=0.005, diam=8.4, obscuration=0.6) + >>> img1 = psf.drawImage() # ~7 min + + The same psf, if drawn using photon-shooting on the same laptop, will generate photons at a rate + of about 1 million per second.:: + + >>> img2 = psf.drawImage(nx=32, ny=32, scale=0.2, method='phot', n_photons=1e6) # ~1 sec. + + Note that the Fourier-based calculation compute time will scale linearly with exposure time, + while the photon-shooting calculation compute time will scale linearly with the number of + photons being shot. + + Many factors will affect the timing of results, of course, including aperture diameter, gsparams + settings, pad_factor and oversampling options to makePSF, time_step and exposure time, frozen + vs. non-frozen atmospheric layers, and so on. We recommend that users try varying these + settings to find a balance of speed and fidelity. + + Parameters: + r0_500: Fried parameter setting the amplitude of turbulence; contributes to "size" + of the resulting atmospheric PSF. Specified at wavelength 500 nm, in units + of meters. [default: 0.2] + r0_weights: Weights for splitting up the contribution of r0_500 between different + layers. Note that this keyword is only allowed if r0_500 is either a + scalar or a single-element list. [default: None] + screen_size: Physical extent of square phase screen in meters. This should be large + enough to accommodate the desired field-of-view of the telescope as well as + the meta-pupil defined by the wind speed and exposure time. Note that + the screen will have periodic boundary conditions, so the code will run + with a smaller sized screen, though this may introduce artifacts into PSFs + or PSF correlation functions. Note that screen_size may be tweaked by the + initializer to ensure screen_size is a multiple of screen_scale. + screen_scale: Physical pixel scale of phase screen in meters. A fraction of the Fried + parameter is usually sufficiently small, but users should test the effects + of this parameter to ensure robust results. + [default: same as each screen's r0_500] + altitude: Altitude of phase screen in km. This is with respect to the telescope, not + sea-level. [default: 0.0] + L0: Outer scale in meters. The turbulence power spectrum will smoothly + approach a constant at scales larger than L0. Set to ``None`` or ``np.inf`` + for a power spectrum without an outer scale. [default: 25.0] + speed: Wind speed in meters/second. [default: 0.0] + direction: Wind direction as `galsim.Angle` [default: 0.0 * galsim.degrees] + alpha: Square root of fraction of phase that is "remembered" between time_steps + (i.e., alpha**2 is the fraction remembered). The fraction sqrt(1-alpha**2) + is then the amount of turbulence freshly generated in each step. Setting + alpha=1.0 results in a frozen-flow atmosphere. Note that computing PSFs + from frozen-flow atmospheres may be significantly faster than computing + PSFs with non-frozen-flow atmospheres. [default: 1.0] + time_step: Time interval between phase boiling updates. Note that this is distinct + from the time interval used when integrating the PSF over time, which is + set by the ``time_step`` keyword argument to `PhaseScreenPSF` or + `PhaseScreenList.makePSF`. If ``time_step`` is not None, then it is + required that ``alpha`` is set to something other than 1.0. [default: None] + rng: Random number generator as a `BaseDeviate`. If None, then use the + clock time or system entropy to seed a new generator. [default: None] + """ + # Fill in screen_size here, since there isn't a default in AtmosphericScreen + kwargs['screen_size'] = utilities.listify(screen_size) + + # Set default r0_500 here; it will get broadcasted below such that the _total_ r0_500 from _all_ + # screens is 0.2 m. + if 'r0_500' not in kwargs: + kwargs['r0_500'] = [0.2] + kwargs['r0_500'] = utilities.listify(kwargs['r0_500']) + + # Turn speed, direction into vx, vy + if 'speed' in kwargs: + kwargs['speed'] = utilities.listify(kwargs['speed']) + if 'direction' not in kwargs: + kwargs['direction'] = [0*radians]*len(kwargs['speed']) + kwargs['vx'], kwargs['vy'] = zip(*[v * np.array(d.sincos()) + for v, d in zip(kwargs['speed'], + kwargs['direction'])]) + del kwargs['speed'] + del kwargs['direction'] + + # Determine broadcast size. + # Note: treat string as a single scalar value, not a vector of characters + nmax = max(len(v) for v in kwargs.values() if hasattr(v, '__len__') and not isinstance(v, str)) + + # Broadcast r0_500 here, since logical combination of indiv layers' r0s is complex: + if len(kwargs['r0_500']) == 1: + r0_weights = np.array(kwargs.pop('r0_weights', [1.]*nmax), dtype=float) + r0_weights /= np.sum(r0_weights) + r0_500 = kwargs['r0_500'][0] + kwargs['r0_500'] = [r0_500 * w**(-3./5) for w in r0_weights] + # kwargs['r0_500'] = [nmax**(3./5) * kwargs['r0_500'][0]] * nmax + elif 'r0_weights' in kwargs: + raise GalSimIncompatibleValuesError( + "Cannot use r0_weights if r0_500 is specified as a list.", + r0_weights=kwargs['r0_weights'], r0_500=kwargs['r0_500']) + + if rng is None: + rng = BaseDeviate() + kwargs['rng'] = [BaseDeviate(rng.raw()) for i in range(nmax)] + return PhaseScreenList([AtmosphericScreen(**kw) for kw in utilities.dol_to_lod(kwargs, nmax)])
+ + +
[docs]class OpticalScreen: + """ + Class to describe optical aberrations in terms of Zernike polynomial coefficients. + + Input aberration coefficients are assumed to be supplied in units of wavelength, and correspond + to the Zernike polynomials in the Noll convention defined in + Noll, J. Opt. Soc. Am. 66, 207-211(1976). For a brief summary of the polynomials, refer to + http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials. + + Parameters: + diam: Diameter of pupil in meters. + tip: Tip aberration in units of reference wavelength. [default: 0] + tilt: Tilt aberration in units of reference wavelength. [default: 0] + defocus: Defocus in units of reference wavelength. [default: 0] + astig1: Astigmatism (like e2) in units of reference wavelength. + [default: 0] + astig2: Astigmatism (like e1) in units of reference wavelength. + [default: 0] + coma1: Coma along y in units of reference wavelength. [default: 0] + coma2: Coma along x in units of reference wavelength. [default: 0] + trefoil1: Trefoil (one of the arrows along y) in units of reference wavelength. + [default: 0] + trefoil2: Trefoil (one of the arrows along x) in units of reference wavelength. + [default: 0] + spher: Spherical aberration in units of reference wavelength. + [default: 0] + aberrations: Optional keyword, to pass in a list, tuple, or NumPy array of + aberrations in units of reference wavelength (ordered according to + the Noll convention), rather than passing in individual values for each + individual aberration. Note that aberrations[1] is piston (and not + aberrations[0], which is unused.) This list can be arbitrarily long to + handle Zernike polynomial aberrations of arbitrary order. + annular_zernike: Boolean indicating that aberrations specify the amplitudes of annular + Zernike polynomials instead of circular Zernike polynomials. + [default: False] + obscuration: Linear dimension of central obscuration as fraction of aperture linear + dimension. [0., 1.). Note it is the user's responsibility to ensure + consistency of `OpticalScreen` obscuration and `Aperture` obscuration. + [default: 0.0] + lam_0: Reference wavelength in nanometers at which Zernike aberrations are + being specified. [default: 500] + """ + dynamic = False + reversible = True + + def __init__(self, diam, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, coma1=0.0, + coma2=0.0, trefoil1=0.0, trefoil2=0.0, spher=0.0, aberrations=None, + annular_zernike=False, obscuration=0.0, lam_0=500.0): + self.diam = diam + if aberrations is None: + aberrations = np.zeros(12) + aberrations[2] = tip + aberrations[3] = tilt + aberrations[4] = defocus + aberrations[5] = astig1 + aberrations[6] = astig2 + aberrations[7] = coma1 + aberrations[8] = coma2 + aberrations[9] = trefoil1 + aberrations[10] = trefoil2 + aberrations[11] = spher + else: + # Make sure no individual aberrations were passed in, since they will be ignored. + if any([tip, tilt, defocus, astig1, astig2, coma1, coma2, trefoil1, trefoil2, spher]): + raise GalSimIncompatibleValuesError( + "Cannot pass in individual aberrations and array.", + tip=tip, tilt=tilt, defocus=defocus, astig1=astig1, astig2=astig2, + coma1=coma1, coma2=coma2, trefoil1=trefoil1, trefoil2=trefoil2, + spher=spher, aberrations=aberrations) + # Aberrations were passed in, so check for right number of entries. + if len(aberrations) < 2: + raise GalSimValueError("Aberrations keyword must have length >= 2", aberrations) + # Check for non-zero value in first two places. Probably a mistake. + if aberrations[0] != 0.0: + galsim_warn("Detected non-zero value in aberrations[0] -- this value is ignored!") + aberrations = np.array(aberrations) + self.aberrations = aberrations + + # strip any trailing zeros. + if self.aberrations[-1] == 0: + self.aberrations = np.trim_zeros(self.aberrations, trim='b') + if len(self.aberrations) <= 1: # Don't let it be length zero or one though. + self.aberrations = np.array([0, 0]) + self.annular_zernike = annular_zernike + self.obscuration = obscuration + self.lam_0 = lam_0 + + R_outer = self.diam/2 + if self.annular_zernike and self.obscuration != 0: + self._zernike = zernike.Zernike(self.aberrations, R_outer=R_outer, + R_inner=R_outer*self.obscuration) + else: + self._zernike = zernike.Zernike(self.aberrations, R_outer=R_outer) + + def __str__(self): + return "galsim.OpticalScreen(diam=%s, lam_0=%s)" % (self.diam, self.lam_0) + + def __repr__(self): + s = "galsim.OpticalScreen(diam=%r, lam_0=%r" % (self.diam, self.lam_0) + if any(self.aberrations): + s += ", aberrations=%r"%self.aberrations + if self.annular_zernike: + s += ", annular_zernike=True" + s += ", obscuration=%r"%self.obscuration + s += ")" + return s + + def __eq__(self, other): + return (self is other or + (isinstance(other, OpticalScreen) + and self.diam == other.diam + and np.array_equal(self.aberrations*self.lam_0, other.aberrations*other.lam_0) + and self.annular_zernike == other.annular_zernike)) + + def __ne__(self, other): return not self == other + + # This screen is immutable, so make a hash for it. + def __hash__(self): + return hash(("galsim.OpticalScreen", self.diam, self.obscuration, self.annular_zernike, + tuple((self.aberrations*self.lam_0).ravel()))) + + # Note -- use **kwargs here so that AtmosphericScreen.stepk and OpticalScreen.stepk + # can use the same signature, even though they depend on different parameters. + def _getStepK(self, **kwargs): + """Return an appropriate stepk for this phase screen. + + Parameters: + lam: Wavelength in nanometers. + diam: Aperture diameter in meters. + obscuration: Fractional linear aperture obscuration. [default: 0.0] + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + stepk in inverse arcsec. + """ + lam = kwargs['lam'] + diam = kwargs['diam'] + obscuration = kwargs.get('obscuration', 0.0) + gsparams = kwargs.get('gsparams', None) + return _calcOptStepK(lam, diam, obscuration, gsparams) + +
[docs] def wavefront(self, u, v, t=None, theta=None): + """ Compute wavefront due to optical phase screen. + + Wavefront here indicates the distance by which the physical wavefront lags or leads the + ideal plane wave. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Ignored for `OpticalScreen`. + theta: Ignored for `OpticalScreen`. + + Returns: + Array of wavefront lag or lead in nanometers. + """ + u = np.asarray(u, dtype=float) + v = np.asarray(v, dtype=float) + if u.shape != v.shape: + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape", u=u, v=v) + return self._wavefront(u, v, t, theta)
+ + def _wavefront(self, u, v, t, theta): + # Same as wavefront(), but no argument checking. + # Note, this phase screen is actually independent of time and theta. + return self._zernike.evalCartesian(u, v) * self.lam_0 + +
[docs] def wavefront_gradient(self, u, v, t=None, theta=None): + """ Compute gradient of wavefront due to optical phase screen. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Ignored for `OpticalScreen`. + theta: Ignored for `OpticalScreen`. + + Returns: + Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m. + """ + u = np.asarray(u, dtype=float) + v = np.asarray(v, dtype=float) + if u.shape != v.shape: + raise GalSimIncompatibleValuesError("u.shape not equal to v.shape", u=u, v=v) + return self._wavefront_gradient(u, v, t, theta)
+ + + def _wavefront_gradient(self, u, v, t, theta): + # Same as wavefront(), but no argument checking. + # Note, this phase screen is actually independent of time and theta. + gradx, grady = self._zernike.evalCartesianGrad(u, v) + gradx *= self.lam_0 + grady *= self.lam_0 + return gradx, grady
+ + +# Used only for testing +class _DummyScreen(OpticalScreen): + def _wavefront(self, *args): + raise GalSimError("Shouldn't reach this") + + +
[docs]class UserScreen: + """ Create a (static) user-defined phase screen. + + Parameters: + table: LookupTable2D instance representing the wavefront as a function on the + entrance pupil. Units are (meters, meters) -> nanometers. + diam: Diameter of entrance pupil in meters. If None, then use the length of the + larger side of the LookupTable2D rectangle in ``table``. This keyword is only + used to compute a value for stepk, and thus has no effect on the + ``wavefront()`` or ``wavefront_gradient()`` methods. + obscuration: Optional fractional circular obscuration of pupil. Like ``diam``, only used + for computing a value for stepk. + """ + dynamic = False + reversible = True + def __init__(self, table, diam=None, obscuration=0.0): + self.table = table + self.diam = diam + self.obscuration = obscuration + + def __str__(self): + out = "galsim.UserScreen({}".format(self.table) + if self.diam: + out += ", diam={}".format(self.diam) + if self.obscuration != 0: + out += ", obscuration={}".format(self.obscuration) + out += ")" + return out + + def __repr__(self): + out = "galsim.UserScreen({!r}".format(self.table) + if self.diam: + out += ", diam={}".format(self.diam) + if self.obscuration != 0: + out += ", obscuration={}".format(self.obscuration) + out += ")" + return out + + def __eq__(self, rhs): + if isinstance(rhs, UserScreen): + return ( + self.table == rhs.table + and self.diam == rhs.diam + and self.obscuration == rhs.obscuration + ) + return False + + def __ne__(self, rhs): + return not self == rhs + + def __hash__(self): + return hash(("UserScreen", self.table, self.diam, self.obscuration)) + + def _getStepK(self, **kwargs): + """Return an appropriate stepk for this phase screen. + + Parameters: + lam: Wavelength in nanometers. + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + stepk in inverse arcsec. + """ + lam = kwargs['lam'] + gsparams = kwargs.get('gsparams', None) + diam = self.diam if self.diam else max(np.ptp(self.table.x), np.ptp(self.table.y)) + return _calcOptStepK(lam, diam, self.obscuration, gsparams) + +
[docs] def wavefront(self, u, v, t=None, theta=None): + """ Evaluate wavefront from lookup table. + + Wavefront here indicates the distance by which the physical wavefront lags or leads the + ideal plane wave. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Ignored for `UserScreen`. + theta: Ignored for `UserScreen`. + + Returns: + Array of wavefront lag or lead in nanometers. + """ + return self.table(u, v)
+ + def _wavefront(self, u, v, t, theta): + return self.table(u, v) + +
[docs] def wavefront_gradient(self, u, v, t=None, theta=None): + """ Evaluate gradient of wavefront from lookup table. + + Parameters: + u: Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + v: Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can + be a scalar or an iterable. The shapes of u and v must match. + t: Ignored for `UserScreen`. + theta: Ignored for `UserScreen`. + + Returns: + Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m. + """ + return self.table.gradient(u, v)
+ + def _wavefront_gradient(self, u, v, t, theta): + return self.table.gradient(u, v)
+ +# Put this at the end to avoid circular imports +from .phase_psf import PhaseScreenList + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/photon_array.html b/docs/_build/html/_modules/galsim/photon_array.html new file mode 100644 index 00000000000..a543f3c80aa --- /dev/null +++ b/docs/_build/html/_modules/galsim/photon_array.html @@ -0,0 +1,1543 @@ + + + + + + galsim.photon_array — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.photon_array

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'PhotonArray', 'PhotonOp', 'WavelengthSampler', 'FRatioAngles',
+            'PhotonDCR', 'Refraction', 'FocusDepth',
+            'PupilImageSampler', 'PupilAnnulusSampler', 'TimeSampler',
+            'ScaleFlux', 'ScaleWavelength' ]
+
+import numpy as np
+import astropy.units as u
+
+from . import _galsim
+from .random import BaseDeviate
+from .celestial import CelestialCoord
+from ._utilities import lazy_property
+from .angle import radians, arcsec, Angle, AngleUnit
+from .errors import GalSimError, GalSimRangeError, GalSimValueError, GalSimUndefinedBoundsError
+from .errors import GalSimIncompatibleValuesError, galsim_warn
+from ._pyfits import pyfits
+from . import dcr
+
+# Add on more methods in the python layer
+
+
[docs]class PhotonArray: + """The PhotonArray class encapsulates the concept of a collection of photons incident on + a detector. + + A PhotonArray object is not typically constructed directly by the user. Rather, it is + typically constructed as the return value of the `GSObject.shoot` method. + At this point, the photons only have x,y,flux values. + + Then there are a number of `Photon Operators`, which perform various modifications to the + photons such as giving them wavelengths (`WavelengthSampler` or inclination angles + (`FRatioAngles`) or move them around according to the effect of differential chromatic + refraction (DCR; `PhotonDCR`). + + One could also add functionality to remove some photons due to fringing or vignetting, + but these are not yet implemented. + + A PhotonArray instance has the following attributes, each of which is a numpy array: + + Attributes: + x: The incidence x position of the photons in image coordinates (pixels), + typically measured at the top of the detector. + y: The incidence y position of the photons in image coordinates (pixels), + typically measured at the top of the detector. + flux: The flux of the photons in units of photons. Typically, these are all 1, + but see the note below for reasons some photons might have flux != 1. + dxdz: The tangent of the inclination angles in the x direction. Note that we define + the +z direction as towards towards the dielectric medium of the detector and + -z as towards vacuum; consequently, a photon with increasing x in time has + positive dxdz. + dydz: The tangent of the inclination angles in the y direction. Note that we define + the +z direction as towards towards the dielectric medium of the detector and + -z as towards vacuum; consequently, a photon with increasing y in time has + positive dydz. + wavelength The wavelength of the photons (in nm) + pupil_u: Horizontal location of photon as it intersected the entrance pupil plane + (meters). + pupil_v: Vertical location of photon as it intersected the entrance pupil plane + (meters). + time: Time stamp for photon impacting the pupil plane (seconds). + + Unlike most GalSim objects (but like `Image`), PhotonArrays are mutable. It is permissible + to write values to the above attributes with code like:: + + >>> photon_array.x += numpy.random.random(1000) * 0.01 + >>> photon_array.flux *= 20. + >>> photon_array.wavelength = sed.sampleWavelength(photonarray.size(), bandpass) + etc. + + All of these will update the existing numpy arrays being used by the photon_array instance. + + .. note:: + + Normal photons have flux=1, but we allow for "fat" photons that combine the effect of + several photons at once for efficiency. Also, some profiles need to use negative flux + photons to properly implement photon shooting (e.g. `InterpolatedImage`, which uses negative + flux photons to get the interpolation correct). Finally, when we "remove" photons, for + better efficiency, we actually just set the flux to 0 rather than recreate new numpy arrays. + + The initialization constructs a PhotonArray to hold N photons, but does not set the values of + anything yet. The constructor allocates space for the x,y,flux arrays, since those are always + needed. The other arrays are only allocated on demand if the user accesses these attributes. + + Parameters: + N: The number of photons to store in this PhotonArray. This value cannot be + changed. + x: Optionally, the initial x values. [default: None] + y: Optionally, the initial y values. [default: None] + flux: Optionally, the initial flux values. [default: None] + dxdz: Optionally, the initial dxdz values. [default: None] + dydz: Optionally, the initial dydz values. [default: None] + wavelength: Optionally, the initial wavelength values (in nm). [default: None] + pupil_u: Optionally, the initial pupil_u values. [default: None] + pupil_v: Optionally, the initial pupil_v values. [default: None] + time: Optionally, the initial time values. [default: None] + """ + def __init__( + self, N, x=None, y=None, flux=None, dxdz=None, dydz=None, wavelength=None, + pupil_u=None, pupil_v=None, time=None + ): + # Only x, y, flux are built by default, since these are always required. + # The others we leave as None unless/until they are needed. + self._x = np.zeros(N, dtype=float) + self._y = np.zeros(N, dtype=float) + self._flux = np.zeros(N, dtype=float) + self._dxdz = None + self._dydz = None + self._wave = None + self._pupil_u = None + self._pupil_v = None + self._time = None + self._is_corr = False + + # These give reasonable errors if x,y,flux are the wrong size/type + if x is not None: self.x = x + if y is not None: self.y = y + if flux is not None: self.flux = flux + if dxdz is not None: self.dxdz = dxdz + if dydz is not None: self.dydz = dydz + if wavelength is not None: self.wavelength = wavelength + if pupil_u is not None: self.pupil_u = pupil_u + if pupil_v is not None: self.pupil_v = pupil_v + if time is not None: self.time = time + +
[docs] @classmethod + def fromArrays( + cls, x, y, flux, dxdz=None, dydz=None, wavelength=None, pupil_u=None, pupil_v=None, + time=None, is_corr=False, + ): + """Create a PhotonArray from pre-allocated numpy arrays without any copying. + + The normal PhotonArray constructor always allocates new arrays and copies any provided + initial values into those new arrays. This class method, by constrast, constructs a + PhotonArray that references existing numpy arrays, so that any PhotonOps or photon shooting + of GSObjects applied to the resulting PhotonArray will also be reflected in the original + arrays. + + Note that the input arrays must all be the same length, have dtype float64 and be + c_contiguous. + + Parameters: + x: X values. + y: X values. + flux: Flux values. + dxdz: Optionally, the initial dxdz values. [default: None] + dydz: Optionally, the initial dydz values. [default: None] + wavelength: Optionally, the initial wavelength values (in nm). [default: None] + pupil_u: Optionally, the initial pupil_u values (in m). [default: None] + pupil_v: Optionally, the initial pupil_v values (in m). [default: None] + time: Optionally, the initial time values (in s). [default: None] + is_corr: Whether or not the photons are correlated. [default: False] + """ + args = [x, y, flux] + argnames = ['x', 'y', 'flux'] + for a, aname in zip( + [dxdz, dydz, wavelength, pupil_u, pupil_v, time], + ['dxdz', 'dydz', 'wavelength', 'pupil_u', 'pupil_v', 'time'] + ): + if a is not None: # don't check optional args that are None + args.append(a) + argnames.append(aname) + + N = len(x) + for a, aname in zip(args, argnames): + if not isinstance(a, np.ndarray): + raise TypeError("Argument {} must be an ndarray".format(aname)) + if not a.dtype == np.float64: + raise TypeError("Array {} dtype must be np.float64".format(aname)) + if not len(a) == N: + raise ValueError("Arrays must all be the same length") + if not a.flags.c_contiguous: + raise ValueError("Array {} must be c_contiguous".format(aname)) + + return cls._fromArrays(x, y, flux, dxdz, dydz, wavelength, pupil_u, pupil_v, time, is_corr)
+ + @classmethod + def _fromArrays( + cls, x, y, flux, dxdz=None, dydz=None, wavelength=None, pupil_u=None, pupil_v=None, + time=None, is_corr=False + ): + """Same as `fromArrays`, but no sanity checking of inputs. + """ + ret = PhotonArray.__new__(PhotonArray) + ret._x = x + ret._y = y + ret._flux = flux + ret._dxdz = dxdz + ret._dydz = dydz + ret._wave = wavelength + ret._pupil_u = pupil_u + ret._pupil_v = pupil_v + ret._time = time + ret._is_corr = False + if is_corr: + from .deprecated import depr + depr('is_corr=True', 2.5, '', + "We don't think this is necessary anymore. If you have a use case that " + "requires it, please open an issue.") + ret._is_corr = is_corr + return ret + +
[docs] @classmethod + def concatenate(cls, photon_arrays): + """Create a single PhotonArray from a list of multiple PhotonArrays. + + The size of the created PhotonArray will be the sum of the sizes of the given arrays, + and the values will be concatenations of the values in each. + + .. note:: + The optional value arrays (e.g. dxdz, wavelength, etc.) must be given in + all the given photon_arrays or in none of them. This is not checked. + + Parameters: + photon_arrays: A list of PhotonArray objects to be concatenated. + """ + p1 = photon_arrays[0] + kwargs = { + 'x': np.concatenate([p.x for p in photon_arrays]), + 'y': np.concatenate([p.y for p in photon_arrays]), + 'flux': np.concatenate([p.flux for p in photon_arrays]), + } + + if p1.hasAllocatedAngles(): + kwargs['dxdz'] = np.concatenate([p.dxdz for p in photon_arrays]) + kwargs['dydz'] = np.concatenate([p.dydz for p in photon_arrays]) + if p1.hasAllocatedWavelengths(): + kwargs['wavelength'] = np.concatenate([p.wavelength for p in photon_arrays]) + if p1.hasAllocatedPupil(): + kwargs['pupil_u'] = np.concatenate([p.pupil_u for p in photon_arrays]) + kwargs['pupil_v'] = np.concatenate([p.pupil_v for p in photon_arrays]) + if p1.hasAllocatedTimes(): + kwargs['time'] = np.concatenate([p.time for p in photon_arrays]) + + return cls._fromArrays(**kwargs)
+ +
[docs] def size(self): + """Return the size of the photon array. Equivalent to ``len(self)``. + """ + return len(self._x)
+ + def __len__(self): + return len(self._x) + + @property + def x(self): + """The incidence x position in image coordinates (pixels), typically at the top of + the detector. + """ + return self._x + @x.setter + def x(self, value): + self._x[:] = value + + @property + def y(self): + """The incidence y position in image coordinates (pixels), typically at the top of + the detector. + """ + return self._y + @y.setter + def y(self, value): + self._y[:] = value + + @property + def flux(self): + """The flux of the photons. + """ + return self._flux + @flux.setter + def flux(self, value): + self._flux[:] = value + + @property + def dxdz(self): + """The tangent of the inclination angles in the x direction: dx/dz. + """ + if not self.hasAllocatedAngles(): + from .deprecated import depr + depr('dxdz accessed before being set.', 2.5, + 'Angle arrays should be set or explicitly allocated before being accessed.', + 'For now, accessing dxdz allocates an array with all zeros. ' + 'This will become an error in a future version (probably 3.0).') + self.allocateAngles() + return self._dxdz + @dxdz.setter + def dxdz(self, value): + self.allocateAngles() + self._dxdz[:] = value + + @property + def dydz(self): + """The tangent of the inclination angles in the y direction: dy/dz. + """ + if not self.hasAllocatedAngles(): + from .deprecated import depr + depr('dydz accessed before being set.', 2.5, + 'Angle arrays should be set or explicitly allocated before being accessed.', + 'For now, accessing dydz allocates an array with all zeros. ' + 'This will become an error in a future version (probably 3.0).') + self.allocateAngles() + return self._dydz + @dydz.setter + def dydz(self, value): + self.allocateAngles() + self._dydz[:] = value + + @property + def wavelength(self): + """The wavelength of the photons (in nm). + """ + if not self.hasAllocatedWavelengths(): + from .deprecated import depr + depr('wavelength accessed before being set.', 2.5, + 'Wavelength array should be set or explicitly allocated before being accessed.', + 'For now, accessing wavelength allocates arrays with all zeros. ' + 'This will become an error in a future version (probably 3.0).') + self.allocateWavelengths() + return self._wave + @wavelength.setter + def wavelength(self, value): + self.allocateWavelengths() + self._wave[:] = value + + @property + def pupil_u(self): + """Horizontal location of photon as it intersected the entrance pupil plane. + """ + if not self.hasAllocatedPupil(): + from .deprecated import depr + depr('pupil_u accessed before being set.', 2.5, + 'Pupil arrays should be set or explicitly allocated before being accessed.', + 'For now, accessing pupil_u allocates arrays with all zeros. ' + 'This will become an error in a future version (probably 3.0).') + self.allocatePupil() + return self._pupil_u + @pupil_u.setter + def pupil_u(self, value): + self.allocatePupil() + self._pupil_u[:] = value + + @property + def pupil_v(self): + """Vertical location of photon as it intersected the entrance pupil plane. + """ + if not self.hasAllocatedPupil(): + from .deprecated import depr + depr('pupil_v accessed before being set.', 2.5, + 'Pupil arrays should be set or explicitly allocated before being accessed.', + 'For now, accessing pupil_v allocates arrays with all zeros. ' + 'This will become an error in a future version (probably 3.0).') + self.allocatePupil() + return self._pupil_v + @pupil_v.setter + def pupil_v(self, value): + self.allocatePupil() + self._pupil_v[:] = value + + @property + def time(self): + """Time stamp of when photon encounters the pupil plane. + """ + if not self.hasAllocatedTimes(): + from .deprecated import depr + depr('time accessed before being set.', 2.5, + 'Time array should be set or explicitly allocated before being accessed.', + 'For now, accessing time allocates arrays with all zeros. ' + 'This will become an error in a future version (probably 3.0).') + self.allocateTimes() + return self._time + @time.setter + def time(self, value): + self.allocateTimes() + self._time[:] = value + +
[docs] def hasAllocatedAngles(self): + """Returns whether the arrays for the incidence angles `dxdz` and `dydz` have been + allocated. + """ + return self._dxdz is not None and self._dydz is not None
+ +
[docs] def allocateAngles(self): + """Allocate memory for the incidence angles, `dxdz` and `dydz`. + """ + if self._dxdz is None: + self._dxdz = np.zeros_like(self._x) + self._dydz = np.zeros_like(self._x) + self.__dict__.pop('_pa', None)
+ +
[docs] def hasAllocatedWavelengths(self): + """Returns whether the `wavelength` array has been allocated. + """ + return self._wave is not None
+ +
[docs] def allocateWavelengths(self): + """Allocate the memory for the `wavelength` array. + """ + if self._wave is None: + self._wave = np.zeros_like(self._x) + self.__dict__.pop('_pa', None)
+ +
[docs] def hasAllocatedPupil(self): + """Returns whether the arrays for the pupil coordinates `pupil_u` and `pupil_v` have been + allocated. + """ + return self._pupil_u is not None and self._pupil_v is not None
+ +
[docs] def allocatePupil(self): + """Allocate the memory for the pupil coordinates, `pupil_u` and `pupil_v`. + """ + if self._pupil_u is None: + self._pupil_u = np.zeros_like(self._x) + self._pupil_v = np.zeros_like(self._x)
+ +
[docs] def hasAllocatedTimes(self): + """Returns whether the array for the time stamps `time` has been allocated. + """ + return self._time is not None
+ +
[docs] def allocateTimes(self): + """Allocate the memory for the time stamps, `time`. + """ + if self._time is None: + self._time = np.zeros_like(self._x)
+ +
[docs] def isCorrelated(self): + """Returns whether the photons are correlated + """ + from .deprecated import depr + depr('isCorrelated', 2.5, '', + "We don't think this is necessary anymore. If you have a use case that " + "requires it, please open an issue.") + return self._is_corr
+ +
[docs] def setCorrelated(self, is_corr=True): + """Set whether the photons are correlated + """ + from .deprecated import depr + depr('setCorrelated', 2.5, '', + "We don't think this is necessary anymore. If you have a use case that " + "requires it, please open an issue.") + self._is_corr = is_corr + self.__dict__.pop('_pa', None)
+ +
[docs] def getTotalFlux(self): + """Return the total flux of all the photons. + """ + return self.flux.sum()
+ +
[docs] def setTotalFlux(self, flux): + """Rescale the photon fluxes to achieve the given total flux. + + Parameter: + flux: The target flux + """ + self.scaleFlux(flux / self.getTotalFlux())
+ +
[docs] def scaleFlux(self, scale): + """Rescale the photon fluxes by the given factor. + + Parameter: + scale: The factor by which to scale the fluxes. + """ + self._flux *= scale
+ +
[docs] def scaleXY(self, scale): + """Scale the photon positions (`x` and `y`) by the given factor. + + Parameter: + scale: The factor by which to scale the positions. + """ + self._x *= scale + self._y *= scale
+ +
[docs] def assignAt(self, istart, rhs): + """Assign the contents of another `PhotonArray` to this one starting at istart. + + Parameters: + istart: The first index at which to insert new values. + rhs: The other `PhotonArray` from which to get the values. + """ + from .deprecated import depr + depr("PhotonArray.assignAt", 2.5, "copyFrom(rhs, slice(istart, istart+rhs.size()))") + if istart + rhs.size() > self.size(): + raise GalSimValueError( + "The given rhs does not fit into this array starting at %d"%istart, rhs) + s = slice(istart, istart + rhs.size()) + self._copyFrom(rhs, s, slice(None))
+ +
[docs] def copyFrom(self, rhs, target_indices=slice(None), source_indices=slice(None), + do_xy=True, do_flux=True, do_other=True): + """Copy some contents of another `PhotonArray` to some elements of this one. + + Specifically each element of rhs[source_indices] is mapped to self[target_indices]. + The values s1 and s2 may be slices, list of indices, or anything else that is a valid + key for a numpy array. + + Parameters: + rhs: The `PhotonArray` from which to get values. + target_indices: The indices at which to assign values in the current PhotonArray (self). + [default: slice(None)] + source_indices: The indices from which to get values from the PhotonArray, ``rhs``. + [default: slice(None)] + do_xy: Whether to include copying the x and y arrays. [default: True] + do_flux: Whether to include copying the flux array. [default: True] + do_other: Whether to include copying the other arrays (angles, wavelength, + pupil positions, time). [default: True] + """ + try: + a1 = self.flux[target_indices] + # Numpy is flexible about allowing slices outside the range of the array. + # Rather than try to check all possible ways the indices can be invalid, we + # just make sure that at least some elements come back from the numpy call. + n1 = len(np.atleast_1d(a1)) + assert n1 > 0 + except (IndexError, AssertionError): + raise GalSimValueError("target_indices is invalid for the target PhotonArray", + target_indices) + try: + a2 = rhs.flux[source_indices] + n2 = len(np.atleast_1d(a2)) + assert n2 > 0 + except (IndexError, AssertionError) as e: + raise GalSimValueError("source_indices is invalid for the source PhotonArray", + source_indices) + if n1 != n2: + raise GalSimIncompatibleValuesError( + "target_indices and source_indices do not reference the same number of elements" + "in their respective PhotonArrays ({} and {} respectively)".format(n1, n2), + dict(target_indices=target_indices, source_indices=source_indices)) + + self._copyFrom(rhs, target_indices, source_indices, do_xy, do_flux, do_other)
+ + def _copyFrom(self, rhs, target_indices, source_indices, do_xy=True, do_flux=True, + do_other=True): + """Equivalent to self.copyFrom(rhs, target_indices, source_indices), but without any + checks that the indices are valid. + """ + # Aliases for notational convenience. + s1 = target_indices + s2 = source_indices + + if do_xy: + self.x[s1] = rhs.x[s2] + self.y[s1] = rhs.y[s2] + if do_flux: + self.flux[s1] = rhs.flux[s2] + if do_other and rhs.hasAllocatedAngles(): + self.allocateAngles() + self.dxdz[s1] = rhs.dxdz[s2] + self.dydz[s1] = rhs.dydz[s2] + if do_other and rhs.hasAllocatedWavelengths(): + self.allocateWavelengths() + self.wavelength[s1] = rhs.wavelength[s2] + if do_other and rhs.hasAllocatedPupil(): + self.allocatePupil() + self.pupil_u[s1] = rhs.pupil_u[s2] + self.pupil_v[s1] = rhs.pupil_v[s2] + if do_other and rhs.hasAllocatedTimes(): + self.allocateTimes() + self.time[s1] = rhs.time[s2] + +
[docs] def convolve(self, rhs, rng=None): + """Convolve this `PhotonArray` with another. + + ..note:: + + If both self and rhs have wavelengths, angles, pupil coordinates or times assigned, + then the values from the first array (i.e. self) take precedence. + """ + if rhs.size() != self.size(): + raise GalSimIncompatibleValuesError("PhotonArray.convolve with unequal size arrays", + self_pa=self, rhs=rhs) + if rhs.hasAllocatedAngles() and not self.hasAllocatedAngles(): + self.dxdz = rhs.dxdz + self.dydz = rhs.dydz + + if rhs.hasAllocatedWavelengths() and not self.hasAllocatedWavelengths(): + self.wavelength = rhs.wavelength + + if rhs.hasAllocatedPupil() and not self.hasAllocatedPupil(): + self.pupil_u = rhs.pupil_u + self.pupil_v = rhs.pupil_v + + if rhs.hasAllocatedTimes() and not self.hasAllocatedTimes(): + self.time = rhs.time + + rng = BaseDeviate(rng) + self._pa.convolve(rhs._pa, rng._rng)
+ + def __repr__(self): + s = "galsim.PhotonArray(%d, x=array(%r), y=array(%r), flux=array(%r)"%( + self.size(), self.x.tolist(), self.y.tolist(), self.flux.tolist()) + if self.hasAllocatedAngles(): + s += ", dxdz=array(%r), dydz=array(%r)"%(self.dxdz.tolist(), self.dydz.tolist()) + if self.hasAllocatedWavelengths(): + s += ", wavelength=array(%r)"%(self.wavelength.tolist()) + if self.hasAllocatedPupil(): + s += ", pupil_u=array(%r), pupil_v=array(%r)"%(self.pupil_u.tolist(), self.pupil_v.tolist()) + if self.hasAllocatedTimes(): + s += ", time=array(%r)"%(self.time.tolist()) + s += ")" + return s + + def __str__(self): + return "galsim.PhotonArray(%d)"%self.size() + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_pa',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + __hash__ = None + + def __eq__(self, other): + return (self is other or + (isinstance(other, PhotonArray) and + np.array_equal(self.x,other.x) and + np.array_equal(self.y,other.y) and + np.array_equal(self.flux,other.flux) and + self.hasAllocatedAngles() == other.hasAllocatedAngles() and + self.hasAllocatedWavelengths() == other.hasAllocatedWavelengths() and + self.hasAllocatedPupil() == other.hasAllocatedPupil() and + self.hasAllocatedTimes() == other.hasAllocatedTimes() and + (np.array_equal(self.dxdz,other.dxdz) if self.hasAllocatedAngles() else True) and + (np.array_equal(self.dydz,other.dydz) if self.hasAllocatedAngles() else True) and + (np.array_equal(self.wavelength,other.wavelength) + if self.hasAllocatedWavelengths() else True) and + (np.array_equal(self.pupil_u,other.pupil_u) + if self.hasAllocatedPupil() else True) and + (np.array_equal(self.pupil_v,other.pupil_v) + if self.hasAllocatedPupil() else True) and + (np.array_equal(self.time,other.time) + if self.hasAllocatedTimes() else True) + )) + + def __ne__(self, other): + return not self == other + + @lazy_property + def _pa(self): + #assert(self._x.strides[0] == self._x.itemsize) + #assert(self._y.strides[0] == self._y.itemsize) + #assert(self._flux.strides[0] == self._flux.itemsize) + _x = self._x.__array_interface__['data'][0] + _y = self._y.__array_interface__['data'][0] + _flux = self._flux.__array_interface__['data'][0] + _dxdz = _dydz = _wave = 0 + if self.hasAllocatedAngles(): + #assert(self._dxdz.strides[0] == self._dxdz.itemsize) + #assert(self._dydz.strides[0] == self._dydz.itemsize) + _dxdz = self._dxdz.__array_interface__['data'][0] + _dydz = self._dydz.__array_interface__['data'][0] + if self.hasAllocatedWavelengths(): + #assert(self._wave.strides[0] == self._wave.itemsize) + _wave = self._wave.__array_interface__['data'][0] + return _galsim.PhotonArray(int(self.size()), _x, _y, _flux, _dxdz, _dydz, _wave, + self._is_corr) + +
[docs] def addTo(self, image): + """Add flux of photons to an image by binning into pixels. + + Photons in this `PhotonArray` are binned into the pixels of the input + `Image` and their flux summed into the pixels. The `Image` is assumed to represent + surface brightness, so photons' fluxes are divided by image pixel area. + Photons past the edges of the image are discarded. + + Parameters: + image: The `Image` to which the photons' flux will be added. + + Returns: + the total flux of photons the landed inside the image bounds. + """ + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError( + "Attempting to PhotonArray::addTo an Image with undefined Bounds") + return self._pa.addTo(image._image)
+ +
[docs] @classmethod + def makeFromImage(cls, image, max_flux=1., rng=None): + """Turn an existing `Image` into a `PhotonArray` that would accumulate into this image. + + The flux in each non-zero pixel will be turned into 1 or more photons with random positions + within the pixel bounds. The ``max_flux`` parameter (which defaults to 1) sets an upper + limit for the absolute value of the flux of any photon. Pixels with abs values > maxFlux + will spawn multiple photons. + + Parameters: + image: The image to turn into a `PhotonArray` + max_flux: The maximum flux value to use for any output photon [default: 1] + rng: A `BaseDeviate` to use for the random number generation [default: None] + + Returns: + a `PhotonArray` + """ + # TODO: This corresponds to the Nearest interpolant. It would be worth figuring out how + # to implement other (presumably better) interpolation options here. + + max_flux = float(max_flux) + if (max_flux <= 0): + raise GalSimRangeError("max_flux must be positive", max_flux, 0.) + total_flux = np.abs(image.array).sum(dtype=float) + + # This goes a bit over what we actually need, but not by much. Worth it to not have to + # worry about array reallocations. + N = int(np.prod(image.array.shape) + total_flux / max_flux) + photons = cls(N) + + rng = BaseDeviate(rng) + N = photons._pa.setFrom(image._image, max_flux, rng._rng) + photons._x = photons.x[:N] + photons._y = photons.y[:N] + photons._flux = photons.flux[:N] + + if image.scale != 1. and image.scale is not None: + photons.scaleXY(image.scale) + return photons
+ +
[docs] def write(self, file_name): + """Write a `PhotonArray` to a FITS file. + + The output file will be a FITS binary table with a row for each photon in the `PhotonArray`. + Columns will include 'id' (sequential from 1 to nphotons), 'x', 'y', and 'flux'. + Additionally, the columns 'dxdz', 'dydz', and 'wavelength' will be included if they are + set for this `PhotonArray` object. + + The file can be read back in with the classmethod `PhotonArray.read`:: + + >>> photons.write('photons.fits') + >>> photons2 = galsim.PhotonArray.read('photons.fits') + + Parameters: + file_name: The file name of the output FITS file. + """ + cols = [] + cols.append(pyfits.Column(name='id', format='J', array=range(self.size()))) + cols.append(pyfits.Column(name='x', format='D', array=self.x)) + cols.append(pyfits.Column(name='y', format='D', array=self.y)) + cols.append(pyfits.Column(name='flux', format='D', array=self.flux)) + + if self.hasAllocatedAngles(): + cols.append(pyfits.Column(name='dxdz', format='D', array=self.dxdz)) + cols.append(pyfits.Column(name='dydz', format='D', array=self.dydz)) + + if self.hasAllocatedWavelengths(): + cols.append(pyfits.Column(name='wavelength', format='D', array=self.wavelength)) + + if self.hasAllocatedPupil(): + cols.append(pyfits.Column(name='pupil_u', format='D', array=self.pupil_u)) + cols.append(pyfits.Column(name='pupil_v', format='D', array=self.pupil_v)) + + if self.hasAllocatedTimes(): + cols.append(pyfits.Column(name='time', format='D', array=self.time)) + + cols = pyfits.ColDefs(cols) + table = pyfits.BinTableHDU.from_columns(cols) + fits.writeFile(file_name, table)
+ +
[docs] @classmethod + def read(cls, file_name): + """Create a `PhotonArray`, reading the photon data from a FITS file. + + The file being read in is not arbitrary. It is expected to be a file that was written + out with the `PhotonArray.write` method.:: + + >>> photons.write('photons.fits') + >>> photons2 = galsim.PhotonArray.read('photons.fits') + + Parameters: + file_name: The file name of the input FITS file. + """ + with pyfits.open(file_name) as fits: + data = fits[1].data + N = len(data) + names = data.columns.names + + photons = cls(N, x=data['x'], y=data['y'], flux=data['flux']) + if 'dxdz' in names: + photons.dxdz = data['dxdz'] + photons.dydz = data['dydz'] + if 'wavelength' in names: + photons.wavelength = data['wavelength'] + if 'pupil_u' in names: + photons.pupil_u = data['pupil_u'] + photons.pupil_v = data['pupil_v'] + if 'time' in names: + photons.time = data['time'] + return photons
+ + +
[docs]class PhotonOp: + """A base class for photon operators, which just defines the interface. + + Photon operators are designed to apply some physical effect to a bundle of photons. They + may adjust the fluxes in some way, or the positions, maybe in a wavelength-dependent way, etc. + They are typically applied via a ``photon_ops`` argument to the `GSObject.drawImage` method. + The order typically matters, so the operators are applied in the order they appear in the list. + """ +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Apply the photon operator to a PhotonArray. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + raise NotImplementedError("Cannot call applyTo on a pure PhotonOp object")
+ + # These simpler versions of == and hash are fine. + def __eq__(self, other): + return repr(self) == repr(other) + + def __hash__(self): + return hash(repr(self))
+ + +
[docs]class WavelengthSampler(PhotonOp): + """A photon operator that uses sed.sampleWavelength to set the wavelengths array of a + `PhotonArray`. + + Parameters: + sed: The `SED` to use for the objects spectral energy distribution. + bandpass: A `Bandpass` object representing a filter, or None to sample over the full + `SED` wavelength range. + rng: If provided, a random number generator that is any kind of `BaseDeviate` + object. If ``rng`` is None, one will be automatically created, using the + time as a seed. [default: None] + npoints: Number of points `DistDeviate` should use for its internal interpolation + tables. [default: None, which uses the `DistDeviate` default] + """ + _opt_params = { 'npoints' : int } + + def __init__(self, sed, bandpass=None, rng=None, npoints=None): + if rng is not None: + from .deprecated import depr + depr('WavelengthSampler(..., rng)', 2.3, '', + 'Instead provide rng when calling applyTo, drawImage, etc.') + self.sed = sed + self.bandpass = bandpass + self.rng = rng + self.npoints = npoints + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Assign wavelengths to the photons sampled from the SED * Bandpass. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + rng = rng if rng is not None else self.rng + if photon_array.hasAllocatedWavelengths(): + galsim_warn("Wavelengths already set before applying WavelengthSampler. " + "This is most likely an error.") + photon_array.wavelength = self.sed.sampleWavelength( + photon_array.size(), self.bandpass, rng=rng, npoints=self.npoints)
+ + def __str__(self): + return "galsim.WavelengthSampler(sed=%s, bandpass=%s, rng=%s, npoints=%s)"%( + self.sed, self.bandpass, self.rng, self.npoints) + + def __repr__(self): + return "galsim.WavelengthSampler(sed=%r, bandpass=%r, rng=%r, npoints=%r)"%( + self.sed, self.bandpass, self.rng, self.npoints)
+ + +
[docs]class FRatioAngles(PhotonOp): + """A photon operator that assigns photon directions based on the f/ratio and obscuration. + + Assigns arrival directions at the focal plane for photons, drawing from a uniform + brightness distribution between the obscuration angle and the edge of the pupil defined + by the f/ratio of the telescope. The angles are expressed in terms of slopes dx/dz + and dy/dz. + + Parameters: + fratio: The f/ratio of the telescope (e.g. 1.2 for LSST) + obscuration: Linear dimension of central obscuration as fraction of aperture + linear dimension. [0., 1.). [default: 0.0] + rng: A random number generator to use or None, in which case an rng + will be automatically constructed for you. [default: None] + """ + _req_params = { 'fratio' : float } + _opt_params = { 'obscuration' : float } + + def __init__(self, fratio, obscuration=0.0, rng=None): + if fratio < 0: + raise GalSimRangeError("The f-ratio must be positive.", fratio, 0.) + if obscuration < 0 or obscuration >= 1: + raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) + if rng is not None: + from .deprecated import depr + depr('FRatioAngles(..., rng)', 2.3, '', + 'Instead provide rng when calling applyTo, drawImage, etc.') + + self.fratio = fratio + self.obscuration = obscuration + self.rng = rng + + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Assign directions to the photons in photon_array. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + gen = BaseDeviate(rng).as_numpy_generator() + + n_photons = len(photon_array) + + # The f/ratio is the ratio of the focal length to the diameter of the aperture of + # the telescope. The angular radius of the field of view is defined by the + # ratio of the radius of the aperture to the focal length + pupil_angle = np.arctan(0.5 / self.fratio) # radians + obscuration_angle = np.arctan(0.5 * self.obscuration / self.fratio) + + # Generate azimuthal angles for the photons + phi = gen.uniform(0, 2*np.pi, size=n_photons) + + # Generate inclination angles for the photons, which are uniform in sin(theta) between + # the sine of the obscuration angle and the sine of the pupil radius + sintheta = gen.uniform(np.sin(obscuration_angle), np.sin(pupil_angle), size=n_photons) + + # Assign the directions to the arrays. In this class the convention for the + # zero of phi does not matter but it would if the obscuration is dependent on + # phi + tantheta = np.sqrt(np.square(sintheta) / (1. - np.square(sintheta))) + photon_array.dxdz = tantheta * np.sin(phi) + photon_array.dydz = tantheta * np.cos(phi)
+ + def __str__(self): + return "galsim.FRatioAngles(fratio=%s, obscration=%s, rng=%s)"%( + self.fratio, self.obscuration, self.rng) + + def __repr__(self): + return "galsim.FRatioAngles(fratio=%r, obscration=%r, rng=%r)"%( + self.fratio, self.obscuration, self.rng)
+ + +
[docs]class PhotonDCR(PhotonOp): + r"""A photon operator that applies the effect of differential chromatic refraction (DCR) + and optionally the chromatic dilation due to atmospheric seeing. + + Due to DCR, blue photons land closer to the zenith than red photons. Kolmogorov turbulence + also predicts that blue photons get spread out more by the atmosphere than red photons, + specifically FWHM is proportional to :math:`\lambda^{-0.2}`. Both of these effects can be + implemented by wavelength-dependent shifts of the photons. + + Since DCR depends on the zenith angle and the parallactic angle (which is the position angle of + the zenith measured from North through East) of the object being drawn, these must be specified + via keywords. There are four ways to specify these values: + + 1) explicitly provide ``zenith_angle`` as a keyword of type `Angle`, and ``parallactic_angle`` + will be assumed to be 0 by default. + 2) explicitly provide both ``zenith_angle`` and ``parallactic_angle`` as keywords of type + `Angle`. + 3) provide the coordinates of the object ``obj_coord`` and the coordinates of the zenith + ``zenith_coord`` as keywords of type `CelestialCoord`. + 4) provide the coordinates of the object ``obj_coord`` as a `CelestialCoord`, the hour angle + of the object ``HA`` as an `Angle`, and the latitude of the observer ``latitude`` as an + `Angle`. + + DCR also depends on temperature, pressure and water vapor pressure of the atmosphere. The + default values for these are expected to be appropriate for LSST at Cerro Pachon, Chile, but + they are broadly reasonable for most observatories. + + This photon op is intended to match the functionality of `ChromaticAtmosphere`, but acting + on the photon array rather than as a `ChromaticObject`. The photons will need to have + wavelengths defined in order to work. + + .. warning:: + The alpha parameter is only appropriate for stars. This photon op will act on + all of the photons, so applying a chromatic dilation according to the chromatic + seeing is the wrong thing to do when the surface brightness being rendered is + not a pure PSF. As such, the default is alpha=0, not -0.2, which would be + appropriate for Kolmogorov turbulence. + + Parameters: + base_wavelength: Wavelength (in nm) represented by the fiducial photon positions + scale_unit: Units used for the positions of the photons. [default: galsim.arcsec] + alpha: Power law index for wavelength-dependent seeing. This should only + be used if doing a star-only simulation. It is not correct when + drawing galaxies. [default: 0.] + zenith_angle: `Angle` from object to zenith, expressed as an `Angle`. [default: 0] + parallactic_angle: Parallactic angle, i.e. the position angle of the zenith, measured + from North through East. [default: 0] + obj_coord: Celestial coordinates of the object being drawn as a `CelestialCoord`. + [default: None] + zenith_coord: Celestial coordinates of the zenith as a `CelestialCoord`. + [default: None] + HA: Hour angle of the object as an `Angle`. [default: None] + latitude: Latitude of the observer as an `Angle`. [default: None] + pressure: Air pressure, either as an astropy Quantity or a float in units of + kiloPascals. [default: 69.328 kPa] + temperature: Temperature, either as an astropy Quantity or a float in units of + Kelvin. [default: 293.15 K] + H2O_pressure: Water vapor pressure, either as an astropy Quantity or a float in units + of kiloPascals. [default: 1.067 kPa] + """ + _req_params = { 'base_wavelength' : float } + _opt_params = { 'scale_unit' : str, + 'alpha' : float, + 'parallactic_angle' : Angle, + 'latitude' : Angle, + 'pressure' : (float, u.Quantity), + 'temperature' : (float, u.Quantity), + 'H2O_pressure' : (float, u.Quantity) + } + _single_params = [ { 'zenith_angle' : Angle, 'HA' : Angle, 'zenith_coord' : CelestialCoord } ] + + def __init__(self, base_wavelength, scale_unit=arcsec, **kwargs): + # This matches the code in ChromaticAtmosphere. + self.base_wavelength = base_wavelength + + if isinstance(scale_unit, str): + scale_unit = AngleUnit.from_name(scale_unit) + self.scale_unit = scale_unit + self.alpha = kwargs.pop('alpha', 0.) + + self.zenith_angle, self.parallactic_angle, self.kw = dcr.parse_dcr_angles(**kwargs) + # Convert any weather data to the appropriate units + p = self.kw.get('pressure', None) + if p is not None and isinstance(p, u.Quantity): + self.kw['pressure'] = p.to_value(u.kPa) + t = self.kw.get('temperature', None) + if t is not None and isinstance(t, u.Quantity): + self.kw['temperature'] = t.to_value(u.K) + h = self.kw.get('H2O_pressure', None) + if h is not None and isinstance(h, u.Quantity): + self.kw['H2O_pressure'] = h.to_value(u.kPa) + + # Any remaining kwargs will get forwarded to galsim.dcr.get_refraction + # Check that they're valid + for kw in self.kw: + if kw not in ('temperature', 'pressure', 'H2O_pressure'): + raise TypeError("Got unexpected keyword: {0}".format(kw)) + + self.base_refraction = dcr.get_refraction(self.base_wavelength, self.zenith_angle, + **self.kw) + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Apply the DCR effect to the photons + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + if not photon_array.hasAllocatedWavelengths(): + raise GalSimError("PhotonDCR requires that wavelengths be set") + if local_wcs is None: + raise TypeError("PhotonDCR requires a local_wcs to be provided to applyTo") + + w = photon_array.wavelength + cenx = local_wcs.origin.x + ceny = local_wcs.origin.y + + # Apply the wavelength-dependent scaling + if self.alpha != 0.: + scale = (w/self.base_wavelength)**self.alpha + photon_array.x = scale * (photon_array.x - cenx) + cenx + photon_array.y = scale * (photon_array.y - ceny) + ceny + + # Apply DCR + shift_magnitude = dcr.get_refraction(w, self.zenith_angle, **self.kw) + shift_magnitude -= self.base_refraction + shift_magnitude *= radians / self.scale_unit + sinp, cosp = self.parallactic_angle.sincos() + + du = -shift_magnitude * sinp + dv = shift_magnitude * cosp + + dx = local_wcs._x(du, dv) + dy = local_wcs._y(du, dv) + photon_array.x += dx + photon_array.y += dy
+ + def __repr__(self): + s = "galsim.PhotonDCR(base_wavelength=%r, scale_unit=%r, alpha=%r, "%( + self.base_wavelength, self.scale_unit, self.alpha) + s += "zenith_angle=%r, parallactic_angle=%r"%(self.zenith_angle, self.parallactic_angle) + for k in sorted(self.kw): + s += ", %s=%r"%(k, self.kw[k]) + s += ")" + return s
+ + +
[docs]class Refraction(PhotonOp): + """A photon operator that refracts photons (manipulating their dxdz and dydz values) at an + interface, commonly the interface between vacuum and silicon at the surface of a CCD. + + Assumes that the surface normal is along the z-axis. If the refraction would result in total + internal reflection, then those photon's dxdz and dydz values are set to NaN, and flux values + set to 0.0. + + Parameters: + index_ratio: The ratio of the refractive index on the far side of the interface to the + near side. Can be given as a number or a callable function. In the + latter case, the function should accept a numpy array of vacuum wavelengths + as input and return a numpy array of refractive index ratios. + """ + _req_params = { 'index_ratio' : float } + + def __init__(self, index_ratio): + self.index_ratio = index_ratio + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Refract photons + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + if hasattr(self.index_ratio, '__call__'): + index_ratio = self.index_ratio(photon_array.wavelength) + else: + index_ratio = self.index_ratio + # Here's the math avoiding any actual trig function calls: + # + # x1 = dr/dz + # ------+ + # \ | + # \ | + # \ | dz/dz = 1 + # \ | + # \| n1 + # ------------------------ + # |\ n2 + # |\ + # dz'/dz' = 1 | \ + # | \ + # | \ + # +--- + # x2 = dr'/dz' + # + # Solve Snell's law for x2 as fn of x1: + # n1 sin(th1) = n2 sin(th2) + # n1 x1 / sqrt(1 + x1^2) = n2 x2 / sqrt(1 + x2^2) + # n1^2 x1^2 (1 + x2^2) = n2^2 x2^2 (1 + x1^2) + # n1^2 x1^2 = x2^2 (n2^2 (1 + x1^2) - n1^2 x1^2) + # x1^2 = x2^2 ((n2/n1)^2 (1 + x1^2) - x1^2) + # x1 = x2 sqrt( (n2/n1)^2 (1 + x1^2) - x1^2 ) + # = x2 sqrt( (n2/n1)^2 (1 + x1^2) - (1 + x1^2) + 1 ) + # = x2 sqrt( 1 - (1 + x1^2) (1 - (n2/n1)^2) ) + normsqr = 1 + photon_array.dxdz**2 + photon_array.dydz**2 # (1 + x1^2) + with np.errstate(invalid='ignore'): + # NaN below <=> total internal reflection + factor = np.sqrt(1 - normsqr*(1-index_ratio**2)) + photon_array.dxdz /= factor + photon_array.dydz /= factor + photon_array.flux = np.where(np.isnan(factor), 0.0, photon_array.flux)
+ + def __repr__(self): + return "galsim.Refraction(index_ratio=%r)"%self.index_ratio
+ + +
[docs]class FocusDepth(PhotonOp): + """A photon operator that focuses/defocuses photons by changing the height of the focal + surface with respect to the conical beam. + + Parameters: + depth: The z-distance by which to displace the focal surface, in units of pixels. A + positive (negative) number here indicates an extra- (intra-) focal sensor height. + I.e., depth > 0 means the sensor surface intersects the beam after it has + converged, and depth < 0 means the sensor surface intersects the beam before it + has converged. + """ + _req_params = { 'depth' : float } + + def __init__(self, depth): + self.depth = depth + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Adjust a photon bundle to account for the change in focal depth. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + if not photon_array.hasAllocatedAngles(): + raise GalSimError("FocusDepth requires that angles be set") + photon_array.x += self.depth * photon_array.dxdz + photon_array.y += self.depth * photon_array.dydz
+ + def __repr__(self): + return "galsim.FocusDepth(depth=%r)"%self.depth
+ + +
[docs]class PupilImageSampler(PhotonOp): + """A photon operator that samples the pupil-plane positions given a pupil-plane image. + Samples are drawn discretely from pupil plane image pixels marked as illuminated. + + Parameters: + diam: Aperture diameter in meters. + lam: Wavelength in nanometers. [default: None] + circular_pupil: Adopt a circular pupil? [default: True] + obscuration: Linear dimension of central obscuration as fraction of aperture + linear dimension. [0., 1.). [default: 0.0] + nstruts: Number of radial support struts to add to the central obscuration. + [default: 0] + strut_thick: Thickness of support struts as a fraction of aperture diameter. + [default: 0.05] + strut_angle: `Angle` made between the vertical and the strut starting closest to it, + defined to be positive in the counter-clockwise direction; must be an + `Angle` instance. [default: 0. * galsim.degrees] + oversampling: Optional oversampling factor *in the image plane* for the PSF + eventually constructed using this `Aperture`. Setting + ``oversampling < 1`` will produce aliasing in the PSF (not good). + [default: 1.0] + pad_factor: Additional multiple by which to extend the PSF image to avoid + folding. [default: 1.0] + pupil_plane_im: The GalSim.Image, NumPy array, or name of file containing the pupil + plane image, to be used instead of generating one based on the + obscuration and strut parameters. [default: None] + pupil_angle: If ``pupil_plane_im`` is not None, rotation angle for the pupil plane + (positive in the counter-clockwise direction). Must be an `Angle` + instance. [default: 0. * galsim.degrees] + pupil_plane_scale: Sampling interval in meters to use for the pupil plane array. In + most cases, it's a good idea to leave this as None, in which case + GalSim will attempt to find a good value automatically. The + exception is when specifying the pupil arrangement via an image, in + which case this keyword can be used to indicate the sampling of that + image. See also ``pad_factor`` for adjusting the pupil sampling scale. + [default: None] + pupil_plane_size: Size in meters to use for the pupil plane array. In most cases, it's + a good idea to leave this as None, in which case GalSim will attempt + to find a good value automatically. See also ``oversampling`` for + adjusting the pupil size. [default: None] + """ + _req_params = { + "diam": float, + } + _opt_params = { + "lam": float, + "circular_pupil": bool, + "obscuration": float, + "nstruts": int, + "strut_thick": float, + "strut_angle": Angle, + "oversampling": float, + "pad_factor": float, + "pupil_plane_im": str, + "pupil_angle": Angle, + "pupil_plane_scale": float, + "pupil_plane_size": float, + } + def __init__(self, diam, **kwargs): + from .phase_psf import Aperture + self.aper = Aperture(diam, **kwargs) + # Save these for the repr + self.diam = diam + self.kwargs = kwargs + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Sample the pupil plane u,v positions for each photon. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + self.aper.samplePupil(photon_array, rng)
+ + def __repr__(self): + s = "galsim.PupilImageSampler(diam=%s"%self.diam + for k,v in self.kwargs.items(): + s += ', %s=%r'%(k,v) + s += ')' + return s
+ + +
[docs]class PupilAnnulusSampler(PhotonOp): + """A photon operator that uniformly samples an annular entrance pupil. + + Parameters: + R_outer: Annulus outer radius in meters. + R_inner: Annulus inner radius in meters. [default: 0.0] + """ + _req_params = { + "R_outer": float, + } + _opt_params = { + "R_inner": float, + } + def __init__(self, R_outer, R_inner=0.0): + self.R_outer = R_outer + self.R_inner = R_inner + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Sample the pupil plane u,v positions for each photon. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + gen = BaseDeviate(rng).as_numpy_generator() + r = gen.uniform(self.R_inner**2, self.R_outer**2, size=len(photon_array)) + np.sqrt(r, out=r) + phi = gen.uniform(0, 2*np.pi, size=len(photon_array)) + photon_array.pupil_u = r * np.cos(phi) + photon_array.pupil_v = r * np.sin(phi)
+ + def __repr__(self): + s = "galsim.PupilAnnulusSampler(R_outer=%r"%self.R_outer + if self.R_inner != 0.0: + s += ", R_inner=%r"%self.R_inner + s += ")" + return s
+ + +
[docs]class TimeSampler(PhotonOp): + """A photon operator that uniformly samples photon time stamps within some interval. + + Parameters: + t0: The nominal start time of the observation in seconds. [default: 0] + exptime: The exposure time in seconds. [default: 0] + """ + _opt_params = { + "t0": float, + "exptime": float + } + def __init__(self, t0=0.0, exptime=0.0): + self.t0 = t0 + self.exptime = exptime + +
[docs] def applyTo(self, photon_array, local_wcs=None, rng=None): + """Add time stamps to photons. + + Parameters: + photon_array: A `PhotonArray` to apply the operator to. + local_wcs: A `LocalWCS` instance defining the local WCS for the current photon + bundle in case the operator needs this information. [default: None] + rng: A random number generator to use if needed. [default: None] + """ + gen = BaseDeviate(rng).as_numpy_generator() + photon_array.time = gen.uniform(self.t0, self.t0+self.exptime, size=len(photon_array))
+ + def __repr__(self): + s = "galsim.TimeSampler(" + if self.t0 != 0.0: + s += "t0=%r"%self.t0 + if self.exptime != 0.0: + s += ", exptime=%r"%self.exptime + else: + if self.exptime != 0.0: + s += "exptime=%r"%self.exptime + s += ")" + return s
+ + +class ScaleFlux(PhotonOp): + """A simple photon operator that multiplies all flux values by a constant. + + Parameters: + x: The constant by which to multiply all flux values. + """ + _req_params = { "x": float } + def __init__(self, x): + self.x = x + + def applyTo(self, photon_array, local_wcs=None, rng=None): + """Apply the scaling. + """ + photon_array.flux *= self.x + + def __repr__(self): + return f"galsim.ScaleFlux({self.x})" + + +class ScaleWavelength(PhotonOp): + """A simple photon operator that multiplies all wavelength values by a constant. + + Parameters: + x: The constant by which to multiply all wavelength values. + """ + _req_params = { "x": float } + def __init__(self, x): + self.x = x + + def applyTo(self, photon_array, local_wcs=None, rng=None): + """Apply the scaling. + """ + photon_array.wavelength *= self.x + + def __repr__(self): + return f"galsim.ScaleWavelength({self.x})" + + +# Put these at the end to avoid circular imports +from . import fits +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/position.html b/docs/_build/html/_modules/galsim/position.html new file mode 100644 index 00000000000..4594e67ecd3 --- /dev/null +++ b/docs/_build/html/_modules/galsim/position.html @@ -0,0 +1,463 @@ + + + + + + galsim.position — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.position

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Position', 'PositionI', 'PositionD', '_PositionI', '_PositionD', ]
+
+from . import _galsim
+
+
[docs]class Position: + """A class for representing 2D positions on the plane. + + Position is a base class for two slightly different kinds of positions: + PositionD describes positions with floating point values in ``x`` and ``y``. + PositionI described positions with integer values in ``x`` and ``y``. + + In the C++ layer, these are templates, but of course no such thing exists in Python, + so the trailing D or I indicate the type. + + Initialization: + + For the float-valued position class, example initializations include:: + + >>> pos = galsim.PositionD(x=0.5, y=-0.5) + >>> pos = galsim.PositionD(0.5, -0.5) + >>> pos = galsim.PositionD( (0.5, -0.5) ) + + And for the integer-valued position class, example initializations include:: + + >>> pos = galsim.PositionI(x=45, y=13) + >>> pos = galsim.PositionI(45, 13) + >>> pos = galsim.PositionD( (45, 15) ) + + Attributes: + x: The x component of the position + y: The y component of the position + + Arithmetic: + + Most arithmetic that makes sense for a position is allowed:: + + >>> pos1 + pos2 + >>> pos1 - pos2 + >>> pos * x + >>> pos / x + >>> -pos + >>> pos1 += pos2 + >>> pos1 -= pos2 + >>> pos *= x + >>> pos -= x + + Note though that the types generally need to match. For example, you cannot multiply + a PositionI by a float add a PositionD to a PositionI in place. + + Position instances can be sheared by a `galsim.Shear`:: + + >>> pos = galsim.PositionD(x=0.5, y=-0.5) + >>> shear = galsim.Shear(g1=0.1, g2=-0.1) + >>> sheared_pos = pos.shear(shear) + + Note that this operation will always return a PositionD even if + an integer position is being sheared. + """ + def __init__(self): + raise NotImplementedError("Cannot instantiate the base class. " + "Use either PositionD or PositionI.") + + def _parse_args(self, *args, **kwargs): + if len(kwargs) == 0: + if len(args) == 2: + self.x, self.y = args + elif len(args) == 0: + self.x = self.y = 0 + elif len(args) == 1: + if isinstance(args[0], (Position, _galsim.PositionD, _galsim.PositionI)): + self.x = args[0].x + self.y = args[0].y + else: + try: + self.x, self.y = args[0] + except (TypeError, ValueError): + raise TypeError("Single argument to %s must be either a Position " + "or a tuple."%self.__class__) from None + else: + raise TypeError("%s takes at most 2 arguments (%d given)"%( + self.__class__, len(args))) + elif len(args) != 0: + raise TypeError("%s takes x and y as either named or unnamed arguments (given %s, %s)"%( + self.__class__, args, kwargs)) + else: + try: + self.x = kwargs.pop('x') + self.y = kwargs.pop('y') + except KeyError: + raise TypeError( + "Keyword arguments x,y are required for %s"%self.__class__) from None + if kwargs: + raise TypeError("Got unexpected keyword arguments %s"%kwargs.keys()) + + def __mul__(self, other): + self._check_scalar(other, 'multiply') + return self.__class__(self.x * other, self.y * other) + + def __rmul__(self, other): + return self.__mul__(other) + + def __div__(self, other): + self._check_scalar(other, 'divide') + return self.__class__(self.x / other, self.y / other) + + __truediv__ = __div__ + + def __neg__(self): + return self.__class__(-self.x, -self.y) + + def __add__(self, other): + if isinstance(other, Bounds): + return other + self + elif not isinstance(other,Position): + raise TypeError("Can only add a Position to a %s"%self.__class__.__name__) + elif isinstance(other, self.__class__): + return self.__class__(self.x + other.x, self.y + other.y) + else: + return PositionD(self.x + other.x, self.y + other.y) + + def __sub__(self, other): + if not isinstance(other,Position): + raise TypeError("Can only subtract a Position from a %s"%self.__class__.__name__) + elif isinstance(other, self.__class__): + return self.__class__(self.x - other.x, self.y - other.y) + else: + return _PositionD(self.x - other.x, self.y - other.y) + + def __repr__(self): + return "galsim.%s(x=%r, y=%r)"%(self.__class__.__name__, self.x, self.y) + + def __str__(self): + return "galsim.%s(%s,%s)"%(self.__class__.__name__, self.x, self.y) + + def __eq__(self, other): + return (self is other or + (isinstance(other, self.__class__) and self.x == other.x and self.y == other.y)) + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + return hash((self.__class__.__name__, self.x, self.y)) + +
[docs] def shear(self, shear): + """Shear the position. + + See the doc string of `galsim.Shear.getMatrix` for more details. + + Parameters: + shear: a `galsim.Shear` instance + + Returns: + a `galsim.PositionD` instance. + """ + shear_mat = shear.getMatrix() + return PositionD( + self.x * shear_mat[0, 0] + self.y * shear_mat[0, 1], + self.x * shear_mat[1, 0] + self.y * shear_mat[1, 1], + )
+ + +
[docs]class PositionD(Position): + """A Position that takes floating point values. + + See the Position doc string for more details. + """ + def __init__(self, *args, **kwargs): + self._parse_args(*args, **kwargs) + self.x = float(self.x) + self.y = float(self.y) + + @property + def _p(self): + return _galsim.PositionD(self.x, self.y) + +
[docs] def round(self): + """Return the rounded-off PositionI version of this position. + """ + return _PositionI(int(round(self.x)), int(round(self.y)))
+ + def _check_scalar(self, other, op): + try: + if other == float(other): return + except (TypeError, ValueError): + pass + raise TypeError("Can only %s a PositionD by float values"%op)
+ + +
[docs]class PositionI(Position): + """A Position that takes only integer values. + + Typically used for coordinate positions on an image. + + See the Position doc string for more details. + """ + def __init__(self, *args, **kwargs): + self._parse_args(*args, **kwargs) + if self.x != int(self.x) or self.y != int(self.y): + raise TypeError("PositionI must be initialized with integer values") + self.x = int(self.x) + self.y = int(self.y) + + @property + def _p(self): + return _galsim.PositionI(self.x, self.y) + + def round(self): + # Just for consistency between PositionD and PositionI + return self + + def _check_scalar(self, other, op): + try: + if other == int(other): return + except (TypeError, ValueError): + pass + raise TypeError("Can only %s a PositionI by integer values"%op)
+ +def _PositionD(x, y): + """Equivalent to `PositionD` constructor, but skips some sanity checks and argument parsing. + This requires that x,y are floats. + """ + ret = PositionD.__new__(PositionD) + ret.x = float(x) + ret.y = float(y) + return ret + + +def _PositionI(x, y): + """Equivalent to `PositionI` constructor, but skips some sanity checks and argument parsing. + This requires that x,y are ints. + """ + ret = PositionI.__new__(PositionI) + ret.x = int(x) + ret.y = int(y) + return ret + +
[docs]def parse_pos_args(args, kwargs, name1, name2, integer=False, others=[]): + """Parse the args and kwargs of a function call to be some kind of position. + + We allow four options: + + f(x,y) + f(galsim.PositionD(x,y)) or f(galsim.PositionI(x,y)) + f( (x,y) ) (or any indexable thing) + f(name1=x, name2=y) + + If the inputs must be integers, set ``integer=True``. + If there are other args/kwargs to parse after these, then their names should be + be given as the parameter ``others``, which are passed back in a tuple after the position. + + Parameters: + args: The args of the original function. + kwargs: The kwargs of the original function. + name1: The allowed kwarg for the first coordinate. + name2: The allowed kwarg for the second coordinate. + integer: Whether to return a `PositionI` rather than a `PositionD`. [default: False] + others: If given, other values to also parse and return from the kwargs. [default: []] + + Returns: + a `Position` instance, possibly also with other values if ``others`` is given. + """ + def canindex(arg): + try: arg[0], arg[1] + except (TypeError, IndexError): return False + else: return True + + other_vals = [] + if len(args) == 0: + # Then name1,name2 need to be kwargs + try: + x = kwargs.pop(name1) + y = kwargs.pop(name2) + except KeyError: + raise TypeError( + 'Expecting kwargs %s, %s. Got %s'%(name1, name2, kwargs.keys())) from None + elif ( ( isinstance(args[0], PositionI) or + (not integer and isinstance(args[0], PositionD)) ) and + len(args) <= 1+len(others) ): + x = args[0].x + y = args[0].y + for arg in args[1:]: + other_vals.append(arg) + others.pop(0) + elif canindex(args[0]) and len(args) <= 1+len(others): + x = args[0][0] + y = args[0][1] + for arg in args[1:]: + other_vals.append(arg) + others.pop(0) + elif len(args) == 1: + if integer: + raise TypeError("Cannot parse argument %s as a PositionI"%(args[0])) + else: + raise TypeError("Cannot parse argument %s as a PositionD"%(args[0])) + elif len(args) <= 2 + len(others): + x = args[0] + y = args[1] + for arg in args[2:]: + other_vals.append(arg) + others.pop(0) + else: + raise TypeError("Too many arguments supplied") + # Read any remaining other kwargs + if others: + for name in others: + val = kwargs.pop(name) + other_vals.append(val) + if kwargs: + raise TypeError("Received unexpected keyword arguments: %s",kwargs) + + if integer: + pos = _PositionI(int(x),int(y)) + else: + pos = _PositionD(float(x),float(y)) + if other_vals: + return (pos,) + tuple(other_vals) + else: + return pos
+ + +# Put this at the bottom to avoid circular import error +from .bounds import Bounds +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/pse.html b/docs/_build/html/_modules/galsim/pse.html new file mode 100644 index 00000000000..6c54e7e5b85 --- /dev/null +++ b/docs/_build/html/_modules/galsim/pse.html @@ -0,0 +1,383 @@ + + + + + + galsim.pse — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.pse

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+"""
+Module containing code for estimating shear power spectra from shears at gridded positions.
+
+The code below was developed largely by Joe Zuntz and tweaked by assorted GalSim
+developers.  This development and testing took place in a separate (private) repository before the
+code was moved into the GalSim repository, but there are some demonstrations of the performance of
+this code in devel/modules/lensing_engine.pdf
+"""
+import numpy as np
+import os
+import sys
+
+from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError
+from .table import LookupTable
+
+
+
[docs]class PowerSpectrumEstimator: + """Class for estimating the shear power spectrum from gridded shears. + + This class stores all the data used in power spectrum estimation that is fixed with the geometry + of the problem - the binning and spin weighting factors. + + The only public method is estimate(), which is called with 2D ``g1`` and ``g2`` arrays on a + square grid. It assumes the flat sky approximation (where ``ell`` and ``k`` are + interchangeable), and rebins the observed ell modes into a user-defined number of logarithimic + bins in ell. Given that the grid parameters are precomputed and stored when the + `PowerSpectrumEstimator` is initialized, computation of the PS for multiple sets of shears + corresponding to the same grid setup can proceed more rapidly than if everything had to be + recomputed each time. + + Below is an example of how to use this code (relying on GalSim to provide the arrays of g1 and + g2, though that is by no means required, and assuming that the user is sitting in the examples/ + directory):: + + >>> grid_size = 10. # Define the total grid extent, in degrees + >>> ngrid = 100 # Define the number of grid points in each dimension: (ngrid x ngrid) + >>> n_ell = 15 # Choose the number of logarithmic bins in ell or k for outputs + >>> + >>> # Define a lookup-table for the power spectrum as a function of k based on the outputs + >>> # of iCosmo (see demo11.py for more description of how this was generated). + >>> my_tab = galsim.LookupTable(file='data/cosmo-fid.zmed1.00.out') + >>> + >>> # Generate a galsim.PowerSpectrum with this P(k), noting the units. + >>> my_ps = galsim.PowerSpectrum(my_tab, units=galsim.radians) + >>> + >>> # Build a grid of shear values with the desired parameters. + >>> g1, g2 = my_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid, + ... units=galsim.degrees) + >>> + >>> # Initialize a PowerSpectrumEstimator with the chosen grid geometry and number of ell + >>> # bins. Note that these values are actually the default, so we didn't technically have + >>> # to specifythem. + >>> my_pse = galsim.pse.PowerSpectrumEstimator(ngrid, grid_size, n_ell) + >>> + >>> # Estimate the power based on this set of g1, g2. If we get another set of shears for + >>> # the same grid geometry, we can reuse the same PowerSpectrumEstimator object. + >>> ell, P_e, P_b, P_eb = my_pse.estimate(g1, g2) + + The output NumPy arrays ``ell``, ``P_e``, ``P_b``, and ``P_eb`` contain the effective ell + value, the E-mode auto-power spectrum, the B-mode auto-power spectrum, and the EB cross-power + spectrum. The units are inverse radians for ell, and radians^2 for the output power spectra. + + Some important notes: + + 1) Power spectrum estimation requires a weight function which decides how the averaging is done + across ell within each bin. By default that weighting is flat in ell using an analytic + calculation of the area in ell space, but this is easy to change with the ``_bin_power`` + function. (Note this area averaged bin weighting is only approximate for the higher + frequency bins in which the lower ``ell`` edge is greater than ``pi * ngrid / grid_size``, + due to the annular ``ell`` region being cut off by the square grid edges beyond this value.) + A keyword allows for weighting by the power itself. + 2) This is the power spectrum of the gridded *data*, not the underlying field - we do not + account for the effects of the finite grid (basically, ignoring all the reasons why power + spectrum estimation is hard - see devel/modules/lensing_engine.pdf in the GalSim repository). + Users must account for the contribution of noise in ``g1``, ``g2`` and any masking. + 3) The binning is currently fixed as uniform in log(ell). + 4) The code for this class uses the notation of the GREAT10 handbook (Kitching et al. 2011, + http://dx.doi.org/10.1214/11-AOAS484), equations 17-21. + """ + def __init__(self, N=100, sky_size_deg=10., nbin=15): + """Create a PowerSpectrumEstimator object given some grid parameters. + + This constructor precomputes some numbers related to the grid geometry, so the same + PowerSpectrumEstimator can be used to estimate the power spectrum quickly for many sets of + shears at gridded positions. + + Parameters: + N: The number of pixels along each side of the grid. [default: 100] + sky_size_deg: The total grid width (in one dimension) in degrees. [default: 10] + nbin: The number of evenly-spaced logarithmic ``ell`` bins to use for + estimating the power spectrum. [default: 15] + """ + # Set up the scales of the sky and pixels + self.N = N + self.sky_size_deg = sky_size_deg + self.nbin = nbin + self.sky_size = np.radians(sky_size_deg) + self.dx = self.sky_size / N + + # Define the possible ell range, the bin edges and effective ell values. + # This is necessary for binning the power spectrum in ell. + lmin = 2*np.pi / self.sky_size + lmax = np.sqrt(2.)*np.pi / self.dx # in 2 dimensions + self.bin_edges = np.logspace(np.log10(lmin), np.log10(lmax), nbin+1) + # By default, report an area-averaged value of ell, which should be fine if there is + # no weighting (in which case it's recomputed) and if there are many ell modes in + # each bin. The latter assumption is most likely to break down at low ell. Note also that + # at high ell when the lower ell edge is greater than pi * ngrid / grid_size, due to the + # annular ell region being cut off by the square grid edges beyond this value, this annular + # average is only approximate. + self.ell = (2./3.)*(self.bin_edges[1:]**3-self.bin_edges[:-1]**3) \ + / (self.bin_edges[1:]**2-self.bin_edges[:-1]**2) + + # Precompute and store two useful factors, both in the form of 2D grids in Fourier space. + # These are the lengths of the wavevector |ell| for each point in the space, and the complex + # valued spin-weighting that takes the complex shear fields -> E,B + self.l_abs, self.eb_rot = self._generate_eb_rotation() + + def __repr__(self): + return "galsim.pse.PowerSpectrumEstimator(N=%r, sky_size_deg=%r, nbin=%r)"%( + self.N, self.sky_size_deg, self.nbin) + def __eq__(self, other): return self is other or repr(self) == repr(other) + def __ne__(self, other): return not self.__eq__(other) + def __hash__(self): return hash(repr(self)) + + def _generate_eb_rotation(self): + # Set up the Fourier space grid lx, ly. + ell = 2*np.pi*np.fft.fftfreq(self.N, self.dx) + lx, ly = np.meshgrid(ell,ell) + + # Now compute the lengths and angles of the ell vectors. + l_sq = lx**2 + ly**2 + + # Compute exp(-2i psi) where psi = atan2(ly,lx) + l_sq[0,0] = 1 # Avoid division by 0 + expm2ipsi = (lx - 1j * ly)**2 / l_sq + l_abs = np.sqrt(l_sq) + l_abs[0,0] = 0 # Go back to correct value at 0,0. + + self.lx = lx + self.ly = ly + + return l_abs, expm2ipsi + + def _bin_power(self, C, ell_weight=None): + # This little utility function bins a 2D C^{E/B, E/B}_{ell} based on |ell|. The use of + # histogram is a little hack, but is quite convenient since it means everything is done in C + # so it is very fast. The first call to `histogram` just returns an array over the + # logarithmic ell bins of + # sum_{|ell| in bin} weight(|ell|)*C_{ell_x,ell_y} + # and the second call returns + # sum_{|ell| in bin} weight(|ell|). + # Thus, the ratio is just the mean power in the bin. If `ell_weight` is None, then weight=1 + # for all ell, corresponding to a simple averaging process. If `ell_weight` is not None, + # then some non-flat weighting scheme is used for averaging over the ell values within a + # bin. + if ell_weight is not None: + ell_weight = np.abs(ell_weight) + P,_ = np.histogram(self.l_abs, self.bin_edges, weights=C*ell_weight) + count,_ = np.histogram(self.l_abs, self.bin_edges, weights=ell_weight) + else: + P,_ = np.histogram(self.l_abs, self.bin_edges, weights=C) + count,_ = np.histogram(self.l_abs, self.bin_edges) + if (count == 0).any(): + raise GalSimError("Logarithmic bin definition resulted in >=1 empty bin!") + return P/count + +
[docs] def estimate(self, g1, g2, weight_EE=False, weight_BB=False, theory_func=None): + """Compute the EE, BB, and EB power spectra of two 2D arrays ``g1`` and ``g2``. + + For example usage, see the docstring for the `PowerSpectrumEstimator` class. + + Parameters: + g1: The shear component g1 as a square 2D NumPy array. + g2: The shear component g2 as a square 2D NumPy array. + weight_EE: If True, then the E auto-power spectrum is re-computed weighting by + the power within each logarithmically-spaced ell bin. [default: False] + weight_BB: If True, then the B auto-power spectrum is re-computed weighting by + the power within each logarithmically-spaced ell bin. [default: False] + theory_func: An optional callable function that can be used to get an idealized + value of power at each point on the grid, and then see what results + it gives for our chosen ell binning. [default: None] + """ + # Check for the expected square geometry consistent with the previously-defined grid size. + if g1.shape != g2.shape: + raise GalSimIncompatibleValuesError( + "g1 and g2 grids do not have the same shape.", g1=g1, g2=g2) + if g1.shape[0] != g1.shape[1]: + raise GalSimValueError("Input shear arrays must be square.", g1.shape) + if g1.shape[0] != self.N: + raise GalSimValueError("Input shear array size is not correct!", g1.shape) + + if not isinstance(weight_EE, bool) or not isinstance(weight_BB, bool): + raise TypeError("Input weight flags must be bools!") + + # Transform g1+j*g2 into Fourier space and rotate into E-B, then separate into E and B. + EB = np.fft.ifft2(self.eb_rot * np.fft.fft2(g1 + 1j*g2)) + E = np.fft.fft2(EB.real) + B = np.fft.fft2(EB.imag) + + # Use the internal function above to bin, and account for the normalization of the FFT. + # Recall that power has units of angle^2, which is the reason why we need a self.dx^2 in the + # equations below in addition to the standard 1/N^2 coming from the FFTs. + C_EE = self._bin_power(E*np.conjugate(E))*(self.dx/self.N)**2 + C_BB = self._bin_power(B*np.conjugate(B))*(self.dx/self.N)**2 + C_EB = self._bin_power(E*np.conjugate(B))*(self.dx/self.N)**2 + + if theory_func is not None: + # theory_func needs to be a callable function + C_theory_ell = np.zeros_like(self.l_abs) + C_theory_ell[self.l_abs>0] = theory_func(self.l_abs[self.l_abs>0]) + C_theory = self._bin_power(C_theory_ell) + + if weight_EE or weight_BB: + # Need to interpolate C_EE to values of self.l_abs. A bit of kludginess as we go off + # the end of our final ell grid... + new_ell = np.zeros(len(self.ell)+2) + new_ell[1:len(self.ell)+1] = self.ell + new_ell[len(self.ell)+1] = 10.*max(self.ell) + if theory_func is not None: + C_theory = self._bin_power(C_theory_ell, ell_weight=C_theory_ell) + + if weight_EE: + new_CEE = np.zeros_like(new_ell) + new_CEE[1:len(self.ell)+1] = np.real(C_EE) + new_CEE[len(self.ell)+1] = new_CEE[len(self.ell)] + EE_table = LookupTable(new_ell, new_CEE) + ell_weight = EE_table(self.l_abs) + C_EE = self._bin_power(E*np.conjugate(E), ell_weight=ell_weight)*(self.dx/self.N)**2 + + if weight_BB: + new_CBB = np.zeros_like(new_ell) + new_CBB[1:len(self.ell)+1] = np.real(C_BB) + new_CBB[len(self.ell)+1] = new_CBB[len(self.ell)] + BB_table = LookupTable(new_ell, new_CBB) + ell_weight = BB_table(self.l_abs) + C_BB = self._bin_power(B*np.conjugate(B), ell_weight=ell_weight)*(self.dx/self.N)**2 + + # For convenience, return ell (copied in case the user messes with it) and the three power + # spectra. If the user requested a binned theoretical spectrum, return that as well. + if theory_func is None: + return self.ell.copy(), np.real(C_EE), np.real(C_BB), np.real(C_EB) + else: + return self.ell.copy(), np.real(C_EE), np.real(C_BB), np.real(C_EB), np.real(C_theory)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/random.html b/docs/_build/html/_modules/galsim/random.html new file mode 100644 index 00000000000..dca356a6eb3 --- /dev/null +++ b/docs/_build/html/_modules/galsim/random.html @@ -0,0 +1,1097 @@ + + + + + + galsim.random — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.random

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'BaseDeviate', 'UniformDeviate', 'GaussianDeviate', 'PoissonDeviate', 'DistDeviate',
+            'BinomialDeviate', 'Chi2Deviate', 'GammaDeviate', 'WeibullDeviate', ]
+
+import numpy as np
+import os
+
+from . import _galsim
+from .errors import GalSimRangeError, GalSimValueError, GalSimIncompatibleValuesError
+from .errors import galsim_warn
+from ._utilities import isinteger, math_eval
+from .table import LookupTable
+from . import integ
+
+
[docs]class BaseDeviate: + """Base class for all the various random deviates. + + This holds the essential random number generator that all the other classes use. + + All deviates take an initial ``seed`` argument that is used to seed the underlying random number + generator. It has three different kinds of behavior. + + 1. An integer value can be provided to explicitly seed the random number generator with a + particular value. This is useful to have deterministic behavior. If you seed with an + integer value, the subsequent series of "random" values will be the same each time you + run the program. + + 2. A seed of 0 or None means to pick some arbitrary value that will be different each time + you run the program. Currently, this tries to get a seed from /dev/urandom if possible. + If that doesn't work, then it creates a seed from the current time. You can also get this + behavior by omitting the seed argument entirely. (i.e. the default is None.) + + 3. Providing another BaseDeviate object as the seed will make the new BaseDeviate share the + same underlying random number generator as the other BaseDeviate. So you can make one + BaseDeviate (of any type), and seed it with a particular deterministic value. Then if you + pass that BaseDeviate to any other one you make, they will both be using the same RNG and + the series of "random" values will be deterministic. + + **Usage**: + + The only kind of random number you can obtain from a pure BaseDeviate (i.e. one that is + not actually one of the variosu subclasses) is a "raw" value. This is an unsigned 32-bit + integer that behind the scenes is used by all sub-classes to generate floating point values + with various distributions. + + >>> rng = galsim.BaseDeviate(215324) + >>> rng.raw() + 3559052779 + + Most other usage is effected by creating sub-classes corresponding to particular random + deviates with various distributions. E.g. `UniformDeviate` generates random values following + a uniform distribution between 0 and 1. + + >>> rng = galsim.BaseDeviate(215324) + >>> ud = galsim.UniformDeviate(rng) + >>> ud() + 0.58736140513792634 + >>> ud2 = galsim.UniformDeviate(215324) + >>> ud2() + 0.58736140513792634 + """ + def __init__(self, seed=None): + self._rng_type = _galsim.BaseDeviateImpl + self._rng_args = () + self.reset(seed) + +
[docs] def seed(self, seed=0): + """Seed the pseudo-random number generator with a given integer value. + + Parameters: + seed: An int value to be used to seed the random number generator. Using 0 + means to generate a seed from the system. [default: 0] + """ + if seed == int(seed): + self._seed(int(seed)) + else: + raise TypeError("BaseDeviate seed must be an integer. Got %s"%seed)
+ +
[docs] def _seed(self, seed=0): + """Equivalent to `seed`, but without any type checking. + """ + self._rng.seed(seed)
+ +
[docs] def reset(self, seed=None): + """Reset the pseudo-random number generator, severing connections to any other deviates. + Providing another `BaseDeviate` object as the seed connects this deviate with the other + one, so they will both use the same underlying random number generator. + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using None means to generate a seed from the system. + [default: None] + """ + if isinstance(seed, BaseDeviate): + self._reset(seed) + elif isinstance(seed, str): + self._rng = self._rng_type(_galsim.BaseDeviateImpl(seed), *self._rng_args) + elif seed is None: + self._rng = self._rng_type(_galsim.BaseDeviateImpl(0), *self._rng_args) + elif isinteger(seed): + self._rng = self._rng_type(_galsim.BaseDeviateImpl(int(seed)), *self._rng_args) + else: + raise TypeError("BaseDeviate must be initialized with either an int or another " + "BaseDeviate")
+ +
[docs] def _reset(self, rng): + """Equivalent to `reset`, but rng must be a `BaseDeviate` (not an int), and there + is no type checking. + """ + self._rng = self._rng_type(rng._rng, *self._rng_args)
+ + @property + def np(self): + """Shorthand for self.as_numpy_generator() + """ + return self.as_numpy_generator() + +
[docs] def as_numpy_generator(self): + """Return a numpy.random.Generator object that uses the current BaseDeviate for the + underlying bit generations. + + This allows you to use the (probably) more familiar numpy functions, while maintaining + GalSim's guarantees about random number stability across platforms. + + Example:: + + >>> rng = galsim.BaseDeviate(1234) + >>> gen = rng.as_numpy_generator() + >>> uniform = gen.uniform(1, 10, size=10) + >>> norm = gen.normal(0, 3, size=20) + + There is also a shorthand syntax that may be convenient. + The property `np` is equivalent to this method, so you can also write:: + + >>> uniform = rng.np.uniform(1, 10, size=10) + >>> norm = rng.np.normal(0, 3, size=20) + """ + return np.random.Generator(GalSimBitGenerator(self))
+ +
[docs] def duplicate(self): + """Create a duplicate of the current `BaseDeviate` object. + + The subsequent series from each copy of the `BaseDeviate` will produce identical values:: + + >>> u = galsim.UniformDeviate(31415926) + >>> u() + 0.17100770119577646 + >>> u2 = u.duplicate() + >>> u() + 0.49095047544687986 + >>> u() + 0.10306670609861612 + >>> u2() + 0.49095047544687986 + >>> u2() + 0.10306670609861612 + >>> u2() + 0.13129289541393518 + >>> u() + 0.13129289541393518 + """ + ret = BaseDeviate.__new__(self.__class__) + ret.__dict__.update(self.__dict__) + ret._rng = self._rng.duplicate() + return ret
+ + def __copy__(self): + return self.duplicate() + + def __getstate__(self): + d = self.__dict__.copy() + d['rng_str'] = self.serialize() + d.pop('_rng') + return d + + def __setstate__(self, d): + self.__dict__ = d + rng = _galsim.BaseDeviateImpl(d['rng_str']) + self._rng = self._rng_type(rng, *self._rng_args) + +
[docs] def clearCache(self): + """Clear the internal cache of the `BaseDeviate`, if any. This is currently only relevant + for `GaussianDeviate`, since it generates two values at a time, saving the second one to + use for the next output value. + """ + self._rng.clearCache()
+ +
[docs] def discard(self, n, suppress_warnings=False): + """Discard n values from the current sequence of pseudo-random numbers. + + This is typically used to keep two random number generators in sync when one of them + is used to generate some random values. The other can quickly discard the same number + of random values to stay in sync with the first one. + + Parameters: + n: The number of raw random numbers to discard. + suppress_warnings: Whether to suppress warnings related to detecting when this + action is not likely to reliably keep two random number + generators in sync. [default: False] + """ + if not self.has_reliable_discard and not suppress_warnings: + galsim_warn(self.__class__.__name__ + + " does not use a consistent number of randoms per generated value, " + + "so discard cannot be guaranteed to keep two random deviates in sync.") + if n%2 == 1 and self.generates_in_pairs and not suppress_warnings: + galsim_warn(self.__class__.__name__ + + " uses two randoms per pair of generated values, so discarding " + + "an odd number of randoms probably doesn't make sense.") + self._rng.discard(int(n))
+ + @property + def has_reliable_discard(self): + """Indicates whether the generator always uses 1 rng per value. + + If it does, then `discard` can reliably be used to keep two generators in sync when one + of them generated some values and the other didn't. + + This is False for PoissonDeviate, Chi2Deviate, and GammaDeviate. + + See also `BaseDeviate.generates_in_pairs`. + """ + return True + + @property + def generates_in_pairs(self): + """Indicates whether the generator uses 2 rngs values per 2 returned values. + + GaussianDeviate has a slight wrinkle to the `BaseDeviate.has_reliable_discard` story. + It always uses two rng values to generate two Gaussian deviates. So if the number of + generated values is even, then discard can keep things in sync. However, if an odd + number of values are generated, then you to generate one more value (and discard it) + to keep things synced up. + + This is only True for GaussianDeviate. + """ + return False + +
[docs] def raw(self): + """Generate the next pseudo-random number and rather than return the appropriate kind + of random deviate for this class, just return the raw integer value that would have been + used to generate this value. + """ + return int(self._rng.raw())
+ +
[docs] def generate(self, array): + """Generate many pseudo-random values, filling in the values of a numpy array. + """ + array_1d = np.ascontiguousarray(array.ravel(),dtype=float) + #assert(array_1d.strides[0] == array_1d.itemsize) + _a = array_1d.__array_interface__['data'][0] + self._rng.generate(len(array_1d), _a) + if array_1d.data != array.data: + # array_1d is not a view into the original array. Need to copy back. + np.copyto(array, array_1d.reshape(array.shape), casting='unsafe')
+ +
[docs] def add_generate(self, array): + """Generate many pseudo-random values, adding them to the values of a numpy array. + """ + array_1d = np.ascontiguousarray(array.ravel(),dtype=float) + #assert(array_1d.strides[0] == array_1d.itemsize) + _a = array_1d.__array_interface__['data'][0] + self._rng.add_generate(len(array_1d), _a) + if array_1d.data != array.data: + # array_1d is not a view into the original array. Need to copy back. + np.copyto(array, array_1d.reshape(array.shape), casting='unsafe')
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, self.__class__) and + self._rng_type == other._rng_type and + self._rng_args == other._rng_args and + self.serialize() == other.serialize())) + + def __ne__(self, other): + return not self.__eq__(other) + + __hash__ = None + + def serialize(self): + return str(self._rng.serialize()) + + def _seed_repr(self): + s = self.serialize().split(' ') + return " ".join(s[:3])+" ... "+" ".join(s[-3:]) + + def __repr__(self): + return "galsim.BaseDeviate(%r)"%self._seed_repr() + + def __str__(self): + return "galsim.BaseDeviate(%r)"%self._seed_repr()
+ +
[docs]class UniformDeviate(BaseDeviate): + """Pseudo-random number generator with uniform distribution in interval [0.,1.). + + Successive calls to ``u()`` generate pseudo-random values distributed uniformly in the interval + [0., 1.):: + + >>> u = galsim.UniformDeviate(31415926) + >>> u() + 0.17100770119577646 + >>> u() + 0.49095047544687986 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + + """ + def __init__(self, seed=None): + self._rng_type = _galsim.UniformDeviateImpl + self._rng_args = () + self.reset(seed) + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a uniform deviate between 0 and 1. + """ + return self._rng.generate1()
+ + def __repr__(self): + return 'galsim.UniformDeviate(seed=%r)'%(self._seed_repr()) + def __str__(self): + return 'galsim.UniformDeviate()'
+ + +
[docs]class GaussianDeviate(BaseDeviate): + """Pseudo-random number generator with Gaussian distribution. + + See http://en.wikipedia.org/wiki/Gaussian_distribution for further details. + + Successive calls to ``g()`` generate pseudo-random values distributed according to a Gaussian + distribution with the provided ``mean``, ``sigma``:: + + >>> g = galsim.GaussianDeviate(31415926) + >>> g() + 0.5533754000847082 + >>> g() + 1.0218588970190354 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + mean: Mean of Gaussian distribution. [default: 0.] + sigma: Sigma of Gaussian distribution. [default: 1.; Must be > 0] + """ + def __init__(self, seed=None, mean=0., sigma=1.): + if sigma < 0.: + raise GalSimRangeError("GaussianDeviate sigma must be > 0.", sigma, 0.) + self._rng_type = _galsim.GaussianDeviateImpl + self._rng_args = (float(mean), float(sigma)) + self.reset(seed) + + @property + def mean(self): + """The mean of the Gaussian distribution. + """ + return self._rng_args[0] + + @property + def sigma(self): + """The sigma of the Gaussian distribution. + """ + return self._rng_args[1] + + @property + def generates_in_pairs(self): + return True + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a Gaussian deviate with the given mean and sigma. + """ + return self._rng.generate1()
+ +
[docs] def generate_from_variance(self, array): + """Generate many Gaussian deviate values using the existing array values as the + variance for each. + """ + array_1d = np.ascontiguousarray(array.ravel(), dtype=float) + #assert(array_1d.strides[0] == array_1d.itemsize) + _a = array_1d.__array_interface__['data'][0] + self._rng.generate_from_variance(len(array_1d), _a) + if array_1d.data != array.data: + # array_1d is not a view into the original array. Need to copy back. + np.copyto(array, array_1d.reshape(array.shape), casting='unsafe')
+ + def __repr__(self): + return 'galsim.GaussianDeviate(seed=%r, mean=%r, sigma=%r)'%( + self._seed_repr(), self.mean, self.sigma) + def __str__(self): + return 'galsim.GaussianDeviate(mean=%r, sigma=%r)'%(self.mean, self.sigma)
+ + +
[docs]class BinomialDeviate(BaseDeviate): + """Pseudo-random Binomial deviate for ``N`` trials each of probability ``p``. + + ``N`` is number of 'coin flips,' ``p`` is probability of 'heads,' and each call returns an + integer value where 0 <= value <= N gives the number of heads. See + http://en.wikipedia.org/wiki/Binomial_distribution for more information. + + Successive calls to ``b()`` generate pseudo-random integer values distributed according to a + binomial distribution with the provided ``N``, ``p``:: + + >>> b = galsim.BinomialDeviate(31415926, N=10, p=0.3) + >>> b() + 2 + >>> b() + 3 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + N: The number of 'coin flips' per trial. [default: 1; Must be > 0] + p: The probability of success per coin flip. [default: 0.5; Must be > 0] + """ + def __init__(self, seed=None, N=1, p=0.5): + self._rng_type = _galsim.BinomialDeviateImpl + self._rng_args = (int(N), float(p)) + self.reset(seed) + + @property + def n(self): + """The number of 'coin flips'. + """ + return self._rng_args[0] + + @property + def p(self): + """The probability of success per 'coin flip'. + """ + return self._rng_args[1] + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a Binomial deviate with the given n and p. + """ + return self._rng.generate1()
+ + def __repr__(self): + return 'galsim.BinomialDeviate(seed=%r, N=%r, p=%r)'%(self._seed_repr(), self.n, self.p) + def __str__(self): + return 'galsim.BinomialDeviate(N=%r, p=%r)'%(self.n, self.p)
+ + +
[docs]class PoissonDeviate(BaseDeviate): + """Pseudo-random Poisson deviate with specified ``mean``. + + The input ``mean`` sets the mean and variance of the Poisson deviate. An integer deviate with + this distribution is returned after each call. + See http://en.wikipedia.org/wiki/Poisson_distribution for more details. + + Successive calls to ``p()`` generate pseudo-random integer values distributed according to a + Poisson distribution with the specified ``mean``:: + + >>> p = galsim.PoissonDeviate(31415926, mean=100) + >>> p() + 94 + >>> p() + 106 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + mean: Mean of the distribution. [default: 1; Must be > 0] + """ + def __init__(self, seed=None, mean=1.): + if mean < 0: + raise GalSimValueError("PoissonDeviate is only defined for mean >= 0.", mean) + self._rng_type = _galsim.PoissonDeviateImpl + self._rng_args = (float(mean),) + self.reset(seed) + + @property + def mean(self): + """The mean of the distribution. + """ + return self._rng_args[0] + + @property + def has_reliable_discard(self): + return False + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a Poisson deviate with the given mean. + """ + return self._rng.generate1()
+ +
[docs] def generate_from_expectation(self, array): + """Generate many Poisson deviate values using the existing array values as the + expectation value (aka mean) for each. + """ + if np.any(array < 0): + raise GalSimValueError("Expectation array may not have values < 0.", array) + array_1d = np.ascontiguousarray(array.ravel(), dtype=float) + #assert(array_1d.strides[0] == array_1d.itemsize) + _a = array_1d.__array_interface__['data'][0] + self._rng.generate_from_expectation(len(array_1d), _a) + if array_1d.data != array.data: + # array_1d is not a view into the original array. Need to copy back. + np.copyto(array, array_1d.reshape(array.shape), casting='unsafe')
+ + def __repr__(self): + return 'galsim.PoissonDeviate(seed=%r, mean=%r)'%(self._seed_repr(), self.mean) + def __str__(self): + return 'galsim.PoissonDeviate(mean=%r)'%(self.mean)
+ + +
[docs]class WeibullDeviate(BaseDeviate): + """Pseudo-random Weibull-distributed deviate for shape parameter ``a`` and scale parameter ``b``. + + The Weibull distribution is related to a number of other probability distributions; in + particular, it interpolates between the exponential distribution (a=1) and the Rayleigh + distribution (a=2). + See http://en.wikipedia.org/wiki/Weibull_distribution (a=k and b=lambda in the notation adopted + in the Wikipedia article) for more details. The Weibull distribution is real valued and + produces deviates >= 0. + + Successive calls to ``w()`` generate pseudo-random values distributed according to a Weibull + distribution with the specified shape and scale parameters ``a`` and ``b``:: + + >>> w = galsim.WeibullDeviate(31415926, a=1.3, b=4) + >>> w() + 1.1038481241018219 + >>> w() + 2.957052966368049 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + a: Shape parameter of the distribution. [default: 1; Must be > 0] + b: Scale parameter of the distribution. [default: 1; Must be > 0] + """ + def __init__(self, seed=None, a=1., b=1.): + self._rng_type = _galsim.WeibullDeviateImpl + self._rng_args = (float(a), float(b)) + self.reset(seed) + + @property + def a(self): + """The shape parameter, a. + """ + return self._rng_args[0] + + @property + def b(self): + """The scale parameter, b. + """ + return self._rng_args[1] + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a Weibull-distributed deviate with the given shape parameters a and b. + """ + return self._rng.generate1()
+ + def __repr__(self): + return 'galsim.WeibullDeviate(seed=%r, a=%r, b=%r)'%(self._seed_repr(), self.a, self.b) + def __str__(self): + return 'galsim.WeibullDeviate(a=%r, b=%r)'%(self.a, self.b)
+ + +
[docs]class GammaDeviate(BaseDeviate): + """A Gamma-distributed deviate with shape parameter ``k`` and scale parameter ``theta``. + See http://en.wikipedia.org/wiki/Gamma_distribution. + (Note: we use the k, theta notation. If you prefer alpha, beta, use k=alpha, theta=1/beta.) + The Gamma distribution is a real valued distribution producing deviates >= 0. + + Successive calls to ``g()`` generate pseudo-random values distributed according to a gamma + distribution with the specified shape and scale parameters ``k`` and ``theta``:: + + >>> gam = galsim.GammaDeviate(31415926, k=1, theta=2) + >>> gam() + 0.37508882726316 + >>> gam() + 1.3504199388358704 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + k: Shape parameter of the distribution. [default: 1; Must be > 0] + theta: Scale parameter of the distribution. [default: 1; Must be > 0] + """ + def __init__(self, seed=None, k=1., theta=1.): + self._rng_type = _galsim.GammaDeviateImpl + self._rng_args = (float(k), float(theta)) + self.reset(seed) + + @property + def k(self): + """The shape parameter, k. + """ + return self._rng_args[0] + + @property + def theta(self): + """The scale parameter, theta. + """ + return self._rng_args[1] + + @property + def has_reliable_discard(self): + return False + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a Gamma-distributed deviate with the given k and theta. + """ + return self._rng.generate1()
+ + def __repr__(self): + return 'galsim.GammaDeviate(seed=%r, k=%r, theta=%r)'%( + self._seed_repr(), self.k, self.theta) + def __str__(self): + return 'galsim.GammaDeviate(k=%r, theta=%r)'%(self.k, self.theta)
+ + +
[docs]class Chi2Deviate(BaseDeviate): + """Pseudo-random Chi^2-distributed deviate for degrees-of-freedom parameter ``n``. + + See http://en.wikipedia.org/wiki/Chi-squared_distribution (note that k=n in the notation + adopted in the Boost.Random routine called by this class). The Chi^2 distribution is a + real-valued distribution producing deviates >= 0. + + Successive calls to ``chi2()`` generate pseudo-random values distributed according to a + chi-square distribution with the specified degrees of freedom, ``n``:: + + >>> chi2 = galsim.Chi2Deviate(31415926, n=7) + >>> chi2() + 7.9182211987712385 + >>> chi2() + 6.644121724269535 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + n: Number of degrees of freedom for the output distribution. [default: 1; + Must be > 0] + """ + def __init__(self, seed=None, n=1.): + self._rng_type = _galsim.Chi2DeviateImpl + self._rng_args = (float(n),) + self.reset(seed) + + @property + def n(self): + """The number of degrees of freedom. + """ + return self._rng_args[0] + + @property + def has_reliable_discard(self): + return False + +
[docs] def __call__(self): + """Draw a new random number from the distribution. + + Returns a Chi2-distributed deviate with the given number of degrees of freedom. + """ + return self._rng.generate1()
+ + def __repr__(self): + return 'galsim.Chi2Deviate(seed=%r, n=%r)'%(self._seed_repr(), self.n) + def __str__(self): + return 'galsim.Chi2Deviate(n=%r)'%(self.n)
+ + +
[docs]class DistDeviate(BaseDeviate): + """A class to draw random numbers from a user-defined probability distribution. + + DistDeviate is a `BaseDeviate` class that can be used to draw from an arbitrary probability + distribution. The probability distribution passed to DistDeviate can be given one of three + ways: as the name of a file containing a 2d ASCII array of x and P(x), as a `LookupTable` + mapping x to P(x), or as a callable function. + + Once given a probability, DistDeviate creates a table of the cumulative probability and draws + from it using a `UniformDeviate`. The precision of its outputs can be controlled with the + keyword ``npoints``, which sets the number of points DistDeviate creates for its internal table + of CDF(x). To prevent errors due to non-monotonicity, the interpolant for this internal table + is always linear. + + Two keywords, ``x_min`` and ``x_max``, define the support of the function. They must be passed + if a callable function is given to DistDeviate, unless the function is a `LookupTable`, which + has its own defined endpoints. If a filename or `LookupTable` is passed to DistDeviate, the + use of ``x_min`` or ``x_max`` will result in an error. + + If given a table in a file, DistDeviate will construct an interpolated `LookupTable` to obtain + more finely gridded probabilities for generating the cumulative probability table. The default + ``interpolant`` is linear, but any interpolant understood by `LookupTable` may be used. We + caution against the use of splines because they can cause non-monotonic behavior. Passing the + ``interpolant`` keyword next to anything but a table in a file will result in an error. + + **Examples**: + + Some sample initialization calls:: + + >>> d = galsim.DistDeviate(function=f, x_min=x_min, x_max=x_max) + + Initializes d to be a DistDeviate instance with a distribution given by the callable function + ``f(x)`` from ``x=x_min`` to ``x=x_max`` and seeds the PRNG using current time:: + + >>> d = galsim.DistDeviate(1062533, function=file_name, interpolant='floor') + + Initializes d to be a DistDeviate instance with a distribution given by the data in file + ``file_name``, which must be a 2-column ASCII table, and seeds the PRNG using the integer + seed 1062533. It generates probabilities from ``file_name`` using the interpolant 'floor':: + + >>> d = galsim.DistDeviate(rng, function=galsim.LookupTable(x,p)) + + Initializes d to be a DistDeviate instance with a distribution given by P(x), defined as two + arrays ``x`` and ``p`` which are used to make a callable `LookupTable`, and links the + DistDeviate PRNG to the already-existing random number generator ``rng``. + + Successive calls to ``d()`` generate pseudo-random values with the given probability + distribution:: + + >>> d = galsim.DistDeviate(31415926, function=lambda x: 1-abs(x), x_min=-1, x_max=1) + >>> d() + -0.4151921102709466 + >>> d() + -0.00909781188974034 + + Parameters: + seed: Something that can seed a `BaseDeviate`: an integer seed or another + `BaseDeviate`. Using 0 means to generate a seed from the system. + [default: None] + function: A callable function giving a probability distribution or the name of a + file containing a probability distribution as a 2-column ASCII table. + [required] + x_min: The minimum desired return value (required for non-`LookupTable` + callable functions; will raise an error if not passed in that case, or if + passed in any other case) [default: None] + x_max: The maximum desired return value (required for non-`LookupTable` + callable functions; will raise an error if not passed in that case, or if + passed in any other case) [default: None] + interpolant: Type of interpolation used for interpolating a file (causes an error if + passed alongside a callable function). Options are given in the + documentation for `LookupTable`. [default: 'linear'] + npoints: Number of points DistDeviate should create for its internal interpolation + tables. [default: 256, unless the function is a non-log `LookupTable`, in + which case it uses the table's x values] + clip_neg: Clip any negative input values to zero. [default: False; an error will + be raised if any negative probabilities are found.] + """ + def __init__(self, seed=None, function=None, x_min=None, + x_max=None, interpolant=None, npoints=None, clip_neg=False): + + # Set up the PRNG + self._rng_type = _galsim.UniformDeviateImpl + self._rng_args = () + self.reset(seed) + + # Basic input checking and setups + if function is None: + raise TypeError('You must pass a function to DistDeviate!') + + self._interpolant = interpolant + self._npoints = npoints + self._xmin = x_min + self._xmax = x_max + + # Figure out if a string is a filename or something we should be using in an eval call + if isinstance(function, str): + self._function = function # Save the inputs to be used in repr + if os.path.isfile(function): + if interpolant is None: + interpolant='linear' + if x_min or x_max: + raise GalSimIncompatibleValuesError( + "Cannot pass x_min or x_max with a filename argument", + function=function, x_min=x_min, x_max=x_max) + function = LookupTable.from_file(function, interpolant=interpolant) + x_min = function.x_min + x_max = function.x_max + else: + try: + function = math_eval('lambda x : ' + function) + if x_min is not None: # is not None in case x_min=0. + function(x_min) + else: + # Somebody would be silly to pass a string for evaluation without x_min, + # but we'd like to throw reasonable errors in that case anyway + function(0.6) # A value unlikely to be a singular point of a function + except Exception as e: + raise GalSimValueError( + "String function must either be a valid filename or something that " + "can eval to a function of x.\n" + "Caught error: {0}".format(e), self._function) + else: + # Check that the function is actually a function + if not hasattr(function, '__call__'): + raise TypeError('function must be a callable function or a string') + if interpolant: + raise GalSimIncompatibleValuesError( + "Cannot provide an interpolant with a callable function argument", + interpolant=interpolant, function=function) + if isinstance(function, LookupTable): + if (x_min not in (None, function.x_min)) or (x_max not in (None, function.x_max)): + raise GalSimIncompatibleValuesError( + "Cannot provide x_min or x_max with a LookupTable function", + function=function, x_min=x_min, x_max=x_max) + x_min = function.x_min + x_max = function.x_max + else: + if x_min is None or x_max is None: + raise GalSimIncompatibleValuesError( + "Must provide x_min and x_max when function argument is a regular " + "python callable function", + function=function, x_min=x_min, x_max=x_max) + + self._function = function # Save the inputs to be used in repr + + # Compute the probability distribution function, pdf(x) + if (npoints is None and isinstance(function, LookupTable) and + not function.x_log and not function.f_log): + xarray = np.array(function.x, dtype=float) + pdf = np.array(function.f, dtype=float) + # Set up pdf, so cumsum basically does a cumulative trapz integral + # On Python 3.4, doing pdf[1:] += pdf[:-1] the last value gets messed up. + # Writing it this way works. (Maybe slightly slower though, so if we stop + # supporting python 3.4, consider switching to the += version.) + pdf[1:] = pdf[1:] + pdf[:-1] + pdf[1:] *= np.diff(xarray) + pdf[0] = 0. + else: + if npoints is None: npoints = 256 + xarray = x_min+(1.*x_max-x_min)/(npoints-1)*np.array(range(npoints),float) + # Integrate over the range of x in case the function is doing something weird here. + pdf = [0.] + [integ.int1d(function, xarray[i], xarray[i+1]) + for i in range(npoints - 1)] + pdf = np.array(pdf) + + # Check that the probability is nonnegative + if clip_neg: + # Write it this way so nan -> 0 as well as negative values. + w = np.where(~(pdf >= 0)) + pdf[w] = 0. + elif not np.all(pdf >= 0.): + raise GalSimValueError('Negative probability found in DistDeviate.',function) + + # Compute the cumulative distribution function = int(pdf(x),x) + cdf = np.cumsum(pdf) + + # Quietly renormalize the probability if it wasn't already normalized + totalprobability = cdf[-1] + cdf /= totalprobability + + self._inverse_cdf = LookupTable(cdf, xarray, interpolant='linear') + self.x_min = x_min + self.x_max = x_max + +
[docs] def val(self, p): + r""" + Return the value :math:`x` of the input function to `DistDeviate` such that ``p`` = + :math:`F(x)`, where :math:`F` is the cumulattive probability distribution function: + + .. math:: + + F(x) = \int_{-\infty}^x \mathrm{pdf}(t) dt + + This function is typically called by `__call__`, which generates a random p + between 0 and 1 and calls ``self.val(p)``. + + Parameters: + p: The desired cumulative probabilty p. + + Returns: + the corresponding x such that :math:`p = F(x)`. + """ + if p<0 or p>1: + raise GalSimRangeError('Invalid cumulative probability for DistDeviate', p, 0., 1.) + return self._inverse_cdf(p)
+ +
[docs] def __call__(self): + """Draw a new random number from the distribution. + """ + return self._inverse_cdf(self._rng.generate1())
+ +
[docs] def generate(self, array): + """Generate many pseudo-random values, filling in the values of a numpy array. + """ + p = np.empty_like(array) + BaseDeviate.generate(self, p) # Fill with unform deviate values + np.copyto(array, self._inverse_cdf(p)) # Convert from p -> x
+ +
[docs] def add_generate(self, array): + """Generate many pseudo-random values, adding them to the values of a numpy array. + """ + p = np.empty_like(array) + BaseDeviate.generate(self, p) + array += self._inverse_cdf(p)
+ + def __repr__(self): + return ('galsim.DistDeviate(seed=%r, function=%r, x_min=%r, x_max=%r, interpolant=%r, ' + 'npoints=%r)')%(self._seed_repr(), self._function, self._xmin, self._xmax, + self._interpolant, self._npoints) + def __str__(self): + return 'galsim.DistDeviate(function="%s", x_min=%s, x_max=%s, interpolant=%s, npoints=%s)'%( + self._function, self._xmin, self._xmax, self._interpolant, self._npoints) + + def __eq__(self, other): + return (self is other or + (isinstance(other, DistDeviate) and + self.serialize() == other.serialize() and + self._function == other._function and + self._xmin == other._xmin and + self._xmax == other._xmax and + self._interpolant == other._interpolant and + self._npoints == other._npoints))
+ + +class GalSimBitGenerator(np.random.BitGenerator): + """A numpy.random.BitGenerator that uses the GalSim C++-layer random number generator + for the random bit generation. + + Parameters: + rng: The galsim.BaseDeviate object to use for the underlying bit generation. + """ + def __init__(self, rng): + super().__init__(0) + self.rng = rng + self.rng._rng.setup_bitgen(self.capsule) + +def permute(rng, *args): + """Randomly permute one or more lists. + + If more than one list is given, then all lists will have the same random permutation + applied to it. + + Parameters: + rng: The random number generator to use. (This will be converted to a `UniformDeviate`.) + args: Any number of lists to be permuted. + """ + ud = UniformDeviate(rng) + if len(args) == 0: + raise TypeError("permute called with no lists to permute") + + # We use an algorithm called the Knuth shuffle, which is based on the Fisher-Yates shuffle. + # See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle for more information. + n = len(args[0]) + for i in range(n-1,1,-1): + j = int((i+1) * ud()) + if j == i+1: j = i # I'm not sure if this is possible, but just in case... + for lst in args: + lst[i], lst[j] = lst[j], lst[i] +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/real.html b/docs/_build/html/_modules/galsim/real.html new file mode 100644 index 00000000000..c8317fb1758 --- /dev/null +++ b/docs/_build/html/_modules/galsim/real.html @@ -0,0 +1,1448 @@ + + + + + + galsim.real — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.real

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'RealGalaxy', 'RealGalaxyCatalog', 'ChromaticRealGalaxy', ]
+
+import os
+import numpy as np
+import copy
+from multiprocessing import Lock
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .bounds import BoundsI
+from .utilities import lazy_property, doc_inherit, convert_interpolant, merge_sorted
+from .interpolant import Quintic
+from .interpolatedimage import InterpolatedImage, _InterpolatedKImage
+from .convolve import Convolve, Deconvolve
+from .image import Image, ImageCD
+from .correlatednoise import UncorrelatedNoise, BaseCorrelatedNoise, CovarianceSpectrum
+from .errors import GalSimValueError, GalSimIncompatibleValuesError
+from .errors import GalSimIndexError
+from .table import LookupTable
+from .random import BaseDeviate, UniformDeviate
+from .sed import SED
+from .bandpass import Bandpass
+from .chromatic import ChromaticSum
+from . import meta_data
+from ._pyfits import pyfits
+
+
+HST_area = 45238.93416  # Area of HST primary mirror in cm^2 from Synphot User's Guide.
+
+# Currently, have bandpasses available for HST COSMOS, AEGIS, and CANDELS.
+# ACS zeropoints (AB magnitudes) from
+# http://www.stsci.edu/hst/acs/analysis/zeropoints/old_page/localZeropoints#tablestart
+# WFC3 zeropoints (AB magnitudes) from
+# http://www.stsci.edu/hst/wfc3/phot_zp_lbn
+# Format of dictionary entry is:
+#    'KEY' : tuple(bandpass filename, zeropoint)
+real_galaxy_bandpasses = {
+        'F275W': ('WFC3_uvis_F275W.dat', 24.1305),
+        'F336W': ('WFC3_uvis_F336W.dat', 24.6682),
+        'F435W': ('ACS_wfc_F435W.dat', 25.65777),
+        'F606W': ('ACS_wfc_F606W.dat', 26.49113),
+        'F775W': ('ACS_wfc_F775W.dat', 25.66504),
+        'F814W': ('ACS_wfc_F814W.dat', 25.94333),
+        'F850LP': ('ACS_wfc_F850LP.dat', 24.84245),
+        'F105W': ('WFC3_ir_F105W.dat', 26.2687),
+        'F125W': ('WFC3_ir_F125W.dat', 26.2303),
+        'F160W': ('WFC3_ir_F160W.dat', 25.9463)
+}
+
+
[docs]class RealGalaxy(GSObject): + """A class describing real galaxies from some training dataset. Its underlying implementation + uses a Convolution instance of an `InterpolatedImage` (for the observed galaxy) with a + `Deconvolution` of another `InterpolatedImage` (for the PSF). + + This class uses a catalog describing galaxies in some training data (for more details, see the + `RealGalaxyCatalog` documentation) to read in data about realistic galaxies that can be used for + simulations based on those galaxies. Also included in the class is additional information that + might be needed to make or interpret the simulations, e.g., the noise properties of the training + data. Users who wish to draw RealGalaxies that have well-defined flux scalings in various + passbands, and/or parametric representations, should use the COSMOSGalaxy class. + + Because RealGalaxy involves a `Deconvolution`, ``method = 'phot'`` is unavailable for the + `GSObject.drawImage` function, and it is essential that users convolve each RealGalaxy with a + PSF that is at least as large as the original HST PSF (stored as an attribute) before rendering + any images. This is necessary to eliminate noise that was amplified due to deconvolution of the + HST PSF. + + Example:: + + >>> real_galaxy = galsim.RealGalaxy(real_galaxy_catalog, index=None, id=None, random=False, + ... rng=None, x_interpolant=None, k_interpolant=None, + ... flux=None, pad_factor=4, noise_pad_size=0, + ... gsparams=None) + + This initializes ``real_galaxy`` with three `InterpolatedImage` objects (one for the deconvolved + galaxy, and saved versions of the original HST image and PSF). Note that there are multiple + keywords for choosing a galaxy; exactly one must be set. + + Note that tests suggest that for optimal balance between accuracy and speed, ``k_interpolant`` + and ``pad_factor`` should be kept at their default values. The user should be aware that + significant inaccuracy can result from using other combinations of these parameters; more + details can be found in http://arxiv.org/abs/1401.2636, especially table 1, and in comment + https://github.com/GalSim-developers/GalSim/issues/389#issuecomment-26166621 and the following + comments. + + If you don't set a flux, the flux of the returned object will be the flux of the original + HST data, scaled to correspond to a 1 second HST exposure (though see the ``area_norm`` + parameter below, and also caveats related to using the ``flux`` parameter). If you want a flux + appropriate for a longer exposure, or for a telescope with a different collecting area than HST, + you can either renormalize the object with the ``flux_rescale`` parameter, or by using the + ``exptime`` and ``area`` parameters to `GSObject.drawImage`. + + Note that RealGalaxy objects use arcsec for the units of their linear dimension. If you + are using a different unit for other things (the PSF, WCS, etc.), then you should dilate + the resulting object with ``gal.dilate(galsim.arcsec / scale_unit)``. + + Parameters: + real_galaxy_catalog: A `RealGalaxyCatalog` object with basic information about where to + find the data, etc. + index: Index of the desired galaxy in the catalog. [One of ``index``, + ``id``, or ``random`` is required.] + id: Object ID for the desired galaxy in the catalog. [One of ``index``, + ``id``, or ``random`` is required.] + random: If True, then select a random galaxy from the catalog. If the + catalog has a 'weight' associated with it to allow for correction of + selection effects in which galaxies were included, the 'weight' + factor is used to remove those selection effects rather than + selecting a completely random object. + [One of ``index``, ``id``, or ``random`` is required.] + rng: A random number generator to use for selecting a random galaxy + (may be any kind of `BaseDeviate` or None) and to use in generating + any noise field when padding. [default: None] + x_interpolant: Either an `Interpolant` instance or a string indicating which + real-space interpolant should be used. Options are 'nearest', + 'sinc', 'linear', 'cubic', 'quintic', or 'lanczosN' where N should + be the integer order to use. [default: galsim.Quintic()] + k_interpolant: Either an `Interpolant` instance or a string indicating which + k-space interpolant should be used. Options are 'nearest', 'sinc', + 'linear', 'cubic', 'quintic', or 'lanczosN' where N should be the + integer order to use. We strongly recommend leaving this parameter + at its default value; see text above for details. + [default: galsim.Quintic()] + flux: Total flux, if None then original flux in image is adopted without + change. Note that, technically, this parameter sets the flux of the + postage stamp image and not the flux of the contained galaxy. + These two values will be strongly correlated when the signal-to- + noise ratio of the galaxy is large, but may be considerably + different if the flux of the galaxy is small with respect to the + noise variations in the postage stamp. To avoid complications with + faint galaxies, consider using the flux_rescale parameter. + [default: None] + flux_rescale: Flux rescaling factor; if None, then no rescaling is done. Either + ``flux`` or ``flux_rescale`` may be set, but not both. + [default: None] + pad_factor: Factor by which to pad the `Image` when creating the + `InterpolatedImage`. We strongly recommend leaving this parameter + at its default value; see text above for details. [default: 4] + noise_pad_size: If provided, the image will be padded out to this size (in arcsec) + with the noise specified in the real galaxy catalog. This is + important if you are planning to whiten the resulting image. You + should make sure that the padded image is larger than the postage + stamp onto which you are drawing this object. + [default: None] + area_norm: Area in cm^2 by which to normalize the flux of the returned object. + When area_norm=1 (the default), drawing with `GSObject.drawImage` + keywords exptime=1 and area=1 will simulate an image with the + appropriate number of counts for a 1 second exposure with the + original telescope/camera (e.g., with HST when using the COSMOS + catalog). + If you would rather explicitly specify the collecting area of the + telescope when using `GSObject.drawImage` with a `RealGalaxy`, + then you should set area_norm equal to the collecting area of the + source catalog telescope when creating the `RealGalaxy` (e.g., + area_norm=45238.93416 for HST). [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + logger: A logger object for output of progress statements if the user wants + them. [default: None] + """ + _opt_params = { "x_interpolant" : str , + "k_interpolant" : str , + "flux" : float , + "flux_rescale" : float , + "pad_factor" : float, + "noise_pad_size" : float, + "area_norm" : float + } + _single_params = [ { "index" : int , "id" : str , "random" : bool } ] + _takes_rng = True + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, real_galaxy_catalog, index=None, id=None, random=False, + rng=None, x_interpolant=None, k_interpolant=None, flux=None, flux_rescale=None, + pad_factor=4, noise_pad_size=0, area_norm=1.0, gsparams=None, logger=None): + if rng is None: + rng = BaseDeviate() + elif not isinstance(rng, BaseDeviate): + raise TypeError("The rng provided to RealGalaxy is not a BaseDeviate") + self.rng = rng + + if flux is not None and flux_rescale is not None: + raise GalSimIncompatibleValuesError( + "Cannot supply a flux and a flux rescaling factor.", + flux=flux, flux_rescale=flux_rescale) + + logger = LoggerWrapper(logger) # So don't need to check `if logger:` all the time. + + if isinstance(real_galaxy_catalog, tuple): + # Special (undocumented) way to build a RealGalaxy without needing the rgc directly + # by providing the things we need from it. Used by COSMOSGalaxy. + self.gal_image, self.psf_image, noise_image, pixel_scale, var = real_galaxy_catalog + use_index = 0 # For the logger statements below. + logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) + self.catalog_file = None + self.catalog = '' + else: + # Get the index to use in the catalog + if index is not None: + if id is not None or random: + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", + index=index, id=id, random=random) + use_index = index + elif id is not None: + if random: + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", id=id, random=random) + use_index = real_galaxy_catalog.getIndexForID(id) + elif random: + ud = UniformDeviate(self.rng) + use_index = int(real_galaxy_catalog.nobjects * ud()) + if real_galaxy_catalog.weight is not None: + # If weight factors are available, make sure the random selection uses the + # weights to remove the catalog-level selection effects (flux_radius-dependent + # probability of making a postage stamp for a given object). + while ud() > real_galaxy_catalog.weight[use_index]: + # Pick another one to try. + use_index = int(real_galaxy_catalog.nobjects * ud()) + else: + raise GalSimIncompatibleValuesError( + "No method specified for selecting a galaxy.", + index=index, id=id, random=random) + logger.debug('RealGalaxy %d: Start RealGalaxy constructor.',use_index) + + # Read in the galaxy, PSF images; for now, rely on pyfits to make I/O errors. + self.gal_image = real_galaxy_catalog.getGalImage(use_index) + logger.debug('RealGalaxy %d: Got gal_image',use_index) + + self.psf_image = real_galaxy_catalog.getPSFImage(use_index) + logger.debug('RealGalaxy %d: Got psf_image',use_index) + + #self._gal_noise = real_galaxy_catalog.getNoise(use_index, self.rng, gsparams) + # We need to duplication some of the RealGalaxyCatalog.getNoise() function, since we + # want it to be possible to have the RealGalaxyCatalog in another process, and the + # BaseCorrelatedNoise object is not picklable. So we just build it here instead. + noise_image, pixel_scale, var = real_galaxy_catalog.getNoiseProperties(use_index) + logger.debug('RealGalaxy %d: Got noise_image',use_index) + self.catalog_file = real_galaxy_catalog.getFileName() + self.catalog = real_galaxy_catalog + + self._gsparams = GSParams.check(gsparams) + + if noise_image is None: + self._gal_noise = UncorrelatedNoise(var, rng=self.rng, scale=pixel_scale, + gsparams=self._gsparams) + else: + ii = InterpolatedImage(noise_image, normalization="sb", + calculate_stepk=False, calculate_maxk=False, + x_interpolant='linear', gsparams=self._gsparams) + self._gal_noise = BaseCorrelatedNoise(self.rng, ii, noise_image.wcs) + self._gal_noise = self._gal_noise.withVariance(var) + logger.debug('RealGalaxy %d: Finished building noise',use_index) + + # Save any other relevant information as instance attributes + self.index = use_index + self.pixel_scale = float(pixel_scale) + self._x_interpolant = x_interpolant + self._k_interpolant = k_interpolant + self._pad_factor = pad_factor + self._noise_pad_size = noise_pad_size + self._input_flux = flux + self._flux_rescale = flux_rescale + self._area_norm = area_norm + + # Convert noise_pad to the right noise to pass to InterpolatedImage + if noise_pad_size: + noise_pad = self._gal_noise + else: + noise_pad = 0. + + # Build the InterpolatedImage of the PSF. + self.original_psf = InterpolatedImage( + self.psf_image, x_interpolant=x_interpolant, k_interpolant=k_interpolant, + flux=1.0, gsparams=self._gsparams) + logger.debug('RealGalaxy %d: Made original_psf',use_index) + + # Build the InterpolatedImage of the galaxy. + # Use the stepk value of the PSF as a maximum value for stepk of the galaxy. + # (Otherwise, low surface brightness galaxies can get a spuriously high stepk, which + # leads to problems.) + self.original_gal = InterpolatedImage( + self.gal_image, x_interpolant=x_interpolant, k_interpolant=k_interpolant, + pad_factor=pad_factor, noise_pad_size=noise_pad_size, + calculate_stepk=self.original_psf.stepk, + calculate_maxk=self.original_psf.maxk, + noise_pad=noise_pad, rng=self.rng, gsparams=self._gsparams) + logger.debug('RealGalaxy %d: Made original_gal',use_index) + + # Only alter normalization if a change is requested + if flux is not None or flux_rescale is not None or area_norm != 1: + if flux_rescale is None: + flux_rescale = 1.0 + flux_rescale /= area_norm + if flux is not None: + flux_rescale *= flux/self.original_gal.flux + self.original_gal *= flux_rescale + self._gal_noise *= flux_rescale**2 + + logger.debug('RealGalaxy %d: Finished building RealGalaxy',use_index) + +
[docs] @doc_inherit + def withGSParams(self, gsparams=None, **kwargs): + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, **kwargs) + ret.original_gal = self.original_gal.withGSParams(ret._gsparams, **kwargs) + ret.original_psf = self.original_psf.withGSParams(ret._gsparams, **kwargs) + ret._gal_noise = self._gal_noise.withGSParams(ret._gsparams, **kwargs) + return ret
+ +
[docs] @classmethod + def makeFromImage(cls, image, PSF, xi, **kwargs): + """Create a `RealGalaxy` directly from image, PSF, and noise description. + + Parameters: + image: `Image` of the galaxy you want to simulate. + PSF: `GSObject` representing the PSF of the galaxy image. Note that this PSF + should include the response of the pixel convolution. + xi: `BaseCorrelatedNoise` object characterizing the noise correlations in the input + image. + """ + noise_image = xi.drawImage() + pixel_scale = noise_image.scale + var = xi.getVariance() + psf_image = PSF.drawImage(method='no_pixel') + return RealGalaxy((image, psf_image, noise_image, pixel_scale, var))
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, RealGalaxy) and + self.catalog == other.catalog and + self.index == other.index and + self._x_interpolant == other._x_interpolant and + self._k_interpolant == other._k_interpolant and + self._pad_factor == other._pad_factor and + self._noise_pad_size == other._noise_pad_size and + self._input_flux == other._input_flux and + self._flux_rescale == other._flux_rescale and + self._area_norm == other._area_norm and + self._gsparams == other._gsparams)) + + def __hash__(self): + return hash(("galsim.RealGalaxy", self.catalog, self.index, self._x_interpolant, + self._k_interpolant, self._pad_factor, self._noise_pad_size, self._input_flux, + self._flux_rescale, self._area_norm, self._gsparams)) + + def __repr__(self): + s = 'galsim.RealGalaxy(%r, index=%r, '%(self.catalog, self.index) + if self._x_interpolant is not None: + s += 'x_interpolant=%r, '%self._x_interpolant + if self._k_interpolant is not None: + s += 'k_interpolant=%r, '%self._k_interpolant + if self._pad_factor != 4: + s += 'pad_factor=%r, '%self._pad_factor + if self._noise_pad_size != 0: + s += 'noise_pad_size=%r, '%self._noise_pad_size + if self._input_flux is not None: + s += 'flux=%r, '%self._input_flux + if self._flux_rescale is not None: + s += 'flux_rescale=%r, '%self._flux_rescale + if self._area_norm != 1: + s += 'area_norm=%r, '%self._area_norm + s += 'rng=%r, '%self.rng + s += 'gsparams=%r)'%self._gsparams + return s + + def __str__(self): + # I think this is more intuitive without the RealGalaxyCatalog parameter listed. + return 'galsim.RealGalaxy(index=%s, flux=%s)'%(self.index, self.flux) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_conv',None) + d.pop('_psf_inv',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @lazy_property + def _psf_inv(self): + return Deconvolve(self.original_psf, gsparams=self._gsparams) + + @lazy_property + def _conv(self): + return Convolve([self.original_gal, self._psf_inv], gsparams=self._gsparams) + + @property + def _noise(self): + # We just store the original noise, not convolved with psf_inv until we need it, + # mostly so we don't have to invalidate this if gsparams changes. + return self._gal_noise.convolvedWith(self._psf_inv, self._gsparams) + + @property + def _maxk(self): + return self._conv._maxk + + @property + def _stepk(self): + return self._conv._stepk + + @property + def _centroid(self): + return self._conv._centroid + + @property + def _flux(self): + return self._conv._flux + + @property + def _positive_flux(self): + return self._conv._positive_flux + + @property + def _negative_flux(self): + return self._conv._negative_flux + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @property + def _max_sb(self): + return self._conv._max_sb + + def _kValue(self, kpos): + return self._conv._kValue(kpos) + + def _drawKImage(self, image, jac=None): + self._conv._drawKImage(image, jac)
+ + +
[docs]class RealGalaxyCatalog: + """Class containing a catalog with information about real galaxy training data. + + The RealGalaxyCatalog class reads in and stores information about a specific training sample of + realistic galaxies. We assume that all files containing the images (galaxies and PSFs) live in + one directory; they could be individual files, or multiple HDUs of the same file. Currently + there is no functionality that lets this be a FITS data cube, because we assume that the object + postage stamps will in general need to be different sizes depending on the galaxy size. + + Note that when simulating galaxies based on HST but using either realistic or parametric galaxy + models, the COSMOSCatalog class may be more useful. It allows the imposition of selection + criteria and other subtleties that are more difficult to impose with RealGalaxyCatalog. + + While you could create your own catalog to use with this class, the typical use cases would + be to use one of the catalogs that we have created and distributed. There are three such + catalogs currently, which can be use with one of the following initializations: + + 1. A small example catalog is distributed with the GalSim distribution. This catalog only + has 100 galaxies, so it is not terribly useful as a representative galaxy population. + But for simplistic use cases, it might be sufficient. We use it for our unit tests and + in some of the demo scripts (demo6, demo10, and demo11). To use this catalog, you would + initialize with:: + + >>> rgc = galsim.RealGalaxyCatalog('real_galaxy_catalog_23.5_example.fits', + dir='path/to/GalSim/examples/data') + + 2. There are two larger catalogs based on HST observations of the COSMOS field with around + 26,000 and 56,000 galaxies each with a limiting magnitude of F814W=23.5. (The former is + a subset of the latter.) For information about how to download these catalogs, see the + RealGalaxy Data Download Page on the GalSim Wiki: + + https://github.com/GalSim-developers/GalSim/wiki/RealGalaxy%20Data + + Be warned that the catalogs are quite large. The larger one is around 11 GB after unpacking + the tarball. To use one of these catalogs, you would initialize with:: + + >>> rgc = galsim.RealGalaxyCatalog('real_galaxy_catalog_23.5.fits', + dir='path/to/download/directory') + + 3. There is a catalog containing a random subsample of the HST COSMOS images with a limiting + magnitude of F814W=25.2. More information about downloading these catalogs can be found on + the RealGalaxy Data Download page linked above. + + 4. Finally, we provide a program that will download the large COSMOS sample for you and + put it in the $PREFIX/share/galsim directory of your installation path. The program is:: + + galsim_download_cosmos + + which gets installed in the $PREFIX/bin directory when you install GalSim. If you use + this program to download the COSMOS catalog, then you can use it with:: + + >>> rgc = galsim.RealGalaxyCatalog() + + GalSim knows the location of the installation share directory, so it will automatically + look for it there. + + Parameters: + file_name: The file containing the catalog. [default: None, which will look for the + F814W<25.2 COSMOS catalog in $PREFIX/share/galsim. It will raise an + exception if the catalog is not there telling you to run + galsim_download_cosmos.] + sample: A keyword argument that can be used to specify the sample to use, i.e., + "23.5" or "25.2". At most one of ``file_name`` and ``sample`` should be + specified. + [default: None, which results in the same default as ``file_name=None``.] + dir: The directory containing the catalog, image, and noise files, or symlinks to + them. [default: None] + preload: Whether to preload the header information. If ``preload=True``, the bulk of + the I/O time is in the constructor. If ``preload=False``, there is + approximately the same total I/O time (assuming you eventually use most of + the image files referenced in the catalog), but it is spread over the + various calls to `getGalImage` and `getPSFImage`. [default: False] + logger: An optional logger object to log progress. [default: None] + """ + _opt_params = { 'file_name' : str, 'sample' : str, 'dir' : str, + 'preload' : bool } + + # _nobject_only is an intentionally undocumented kwarg that should be used only by + # the config structure. It indicates that all we care about is the nobjects parameter. + # So skip any other calculations that might normally be necessary on construction. + def __init__(self, file_name=None, sample=None, dir=None, preload=False, logger=None): + if sample is not None and file_name is not None: + raise GalSimIncompatibleValuesError( + "Cannot specify both the sample and file_name.", + sample=sample, file_name=file_name) + + logger = LoggerWrapper(logger) + + self.file_name, self.image_dir, self.sample = _parse_files_dirs(file_name, dir, sample) + + with pyfits.open(self.file_name) as fits: + self.cat = fits[1].data + self.nobjects = len(self.cat) # number of objects in the catalog + logger.debug('RealGalaxyCatalog %s has %d objects',self.file_name,self.nobjects) + + self._preload = preload + self.loaded_files = {} + self.saved_noise_im = {} + # The pyfits commands aren't thread safe. So we need to make sure the methods that + # use pyfits are not run concurrently from multiple threads. + self.gal_lock = Lock() # Use this when accessing gal files + self.psf_lock = Lock() # Use this when accessing psf files + self.loaded_lock = Lock() # Use this when opening new files from disk + self.noise_lock = Lock() # Use this for building the noise image(s) (usually just one) + + + # Some lazy properties that we set up the first time they are used. + @lazy_property + def ident(self): + ident = self.cat.field('ident') # ID for object in the training sample + # We want to make sure that the ident array contains all strings. + # Strangely, ident.astype(str) produces a string with each element == '1'. + # Hence this way of doing the conversion: + return [ "%s"%val for val in ident ] + + @lazy_property + def gal_file_name(self): + gal_file_name = self.cat.field('gal_filename') # file containing the galaxy image + # Add the directories: + # Note the strip call. Sometimes the filenames have an extra space at the end. + # This gets rid of that space. + return [os.path.join(self.image_dir,f.strip()) for f in gal_file_name] + + @lazy_property + def psf_file_name(self): + psf_file_name = self.cat.field('PSF_filename') # file containing the PSF image + return [os.path.join(self.image_dir,f.strip()) for f in psf_file_name] + + @lazy_property + def noise_file_name(self): + # We don't require the noise_filename column. If it is not present, we will use + # Uncorrelated noise based on the variance column. + try: + noise_file_name = self.cat.field('noise_filename') # file containing the noise cf + except KeyError: + return None + else: + return [os.path.join(self.image_dir,f) for f in noise_file_name] + + @lazy_property + def gal_hdu(self): + return self.cat.field('gal_hdu') # HDU containing the galaxy image + + @lazy_property + def psf_hdu(self): + return self.cat.field('PSF_hdu') # HDU containing the PSF image + + @lazy_property + def pixel_scale(self): + return self.cat.field('pixel_scale') # pixel scale for image (could be different + # if we have training data from other datasets... let's be general here and make it a + # vector in case of mixed training set) + + @lazy_property + def variance(self): + return self.cat.field('noise_variance') # noise variance for image + + @lazy_property + def mag(self): + return self.cat.field('mag') # apparent magnitude + + @lazy_property + def band(self): + return self.cat.field('band') # bandpass in which apparent mag is measured, e.g., F814W + + @lazy_property + def weight(self): + # The weight factor should be a float value >=0 (so that random selections of indices can + # use it to remove any selection effects in the catalog creation process). + # Here we renormalize by the maximum weight. If the maximum is below 1, that just means + # that all galaxies were subsampled at some level, and here we only want to account for + # relative selection effects within the catalog, not absolute subsampling. If the maximum + # is above 1, then our random number generation test used to draw a weighted sample will + # fail since we use uniform deviates in the range 0 to 1. + try: + weight = self.cat.field('weight') + except KeyError: # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.sample)) + else: + return weight/np.max(weight) + + @lazy_property + def stamp_flux(self): + try: + return self.cat.field('stamp_flux') + except KeyError: # pragma: no cover + raise OSError("You still have the old COSMOS catalog. Run the program " + "`galsim_download_cosmos -s %s` to upgrade."%(self.sample)) + + def __del__(self): + # Make sure to clean up pyfits open files if people forget to call close() + self.close() + + def close(self): + # Need to close any open files. + # Make sure to check if loaded_files exists, since the constructor could abort + # before it gets to the place where loaded_files is built. + if hasattr(self, 'loaded_files'): + for f in self.loaded_files.values(): + f.close() + self.loaded_files = {} + + def getNObjects(self) : return self.nobjects + def __len__(self): return self.nobjects + def getFileName(self) : return self.file_name + +
[docs] def getIndexForID(self, id): + """Internal function to find which index number corresponds to the value ID in the ident + field. + """ + # Just to be completely consistent, convert id to a string in the same way we + # did above for the ident array: + id = "%s"%id + if id in self.ident: + return self.ident.index(id) + else: + raise GalSimValueError('ID not found in list of IDs',id, self.ident)
+ + def _maybe_preload(self): + # Preload all files if desired. + # This is delayed until the first time we might need it, since we might only need + # to know nobjects and not load the data at all. The first time we try to do something + # that needs the files, we'll call preload (if requested). + if self._preload: + self.preload() + self._preload = False # Once we've loaded them. Don't do it again. + +
[docs] def preload(self): + """Preload the files into memory. + + There are memory implications to this, so we don't do this by default. However, it can be + a big speedup if memory isn't an issue. + """ + with self.loaded_lock: + for file_name in np.concatenate((self.gal_file_name , self.psf_file_name)): + # numpy sometimes add a space at the end of the string that is not present in + # the original file. Stupid. But this next line removes it. + file_name = file_name.strip() + if file_name not in self.loaded_files: + # I use memmap=False, because I was getting problems with running out of + # file handles in the great3 real_gal run, which uses a lot of rgc files. + # I think there must be a bug in pyfits that leaves file handles open somewhere + # when memmap = True. Anyway, I don't know what the performance implications + # are (since I couldn't finish the run with the default memmap=True), but I + # don't think there is much impact either way with memory mapping in our case. + f = pyfits.open(file_name,memmap=False) + self.loaded_files[file_name] = f + # Access all the data from all hdus to force PyFits to read the data + for hdu in f: + hdu.data
+ + def _getFile(self, file_name): + self._maybe_preload() + if file_name in self.loaded_files: + f = self.loaded_files[file_name] + else: + with self.loaded_lock: + # Check again in case two processes both hit the else at the same time. + if file_name in self.loaded_files: # pragma: no cover + f = self.loaded_files[file_name] + else: + f = pyfits.open(file_name,memmap=False) + self.loaded_files[file_name] = f + return f + +
[docs] def getBandpass(self): + """Returns a `Bandpass` object for the catalog. + """ + try: + bp = real_galaxy_bandpasses[self.band[0].upper()] + except KeyError: + raise GalSimValueError("Bandpass not found. To use this bandpass, please add an entry " + "to the galsim.real.real_galaxy_bandpasses dictionary.", + self.band[0], real_galaxy_bandpasses.keys()) + return Bandpass(bp[0], wave_type='nm', zeropoint=bp[1])
+ +
[docs] def getGalImage(self, i): + """Returns the galaxy at index ``i`` as an `Image` object. + """ + if i >= len(self.gal_file_name): + raise GalSimIndexError('index out of range (0..%d)'%(len(self.gal_file_name)-1),i) + f = self._getFile(self.gal_file_name[i]) + with self.gal_lock: + array = f[self.gal_hdu[i]].data + im = Image(np.ascontiguousarray(array.astype(np.float64)), scale=self.pixel_scale[i]) + return im
+ +
[docs] def getPSFImage(self, i): + """Returns the PSF at index ``i`` as an `Image` object. + """ + if i >= len(self.psf_file_name): + raise GalSimIndexError('index out of range (0..%d)'%(len(self.psf_file_name)-1),i) + f = self._getFile(self.psf_file_name[i]) + with self.psf_lock: + array = f[self.psf_hdu[i]].data + return Image(np.ascontiguousarray(array.astype(np.float64)), scale=self.pixel_scale[i])
+ +
[docs] def getPSF(self, i, x_interpolant=None, k_interpolant=None, gsparams=None): + """Returns the PSF at index ``i`` as a `GSObject`. + """ + psf_image = self.getPSFImage(i) + return InterpolatedImage(psf_image, + x_interpolant=x_interpolant, k_interpolant=k_interpolant, + flux=1.0, gsparams=gsparams)
+ +
[docs] def getNoiseProperties(self, i): + """Returns the components needed to make the noise correlation function at index ``i``. + Specifically, the noise image (or None), the pixel_scale, and the noise variance, + as a tuple (im, scale, var). + """ + if self.noise_file_name is None: + im = None + else: + if i >= len(self.noise_file_name): + raise GalSimIndexError('index out of range (0..%d)'%(len(self.noise_file_name)-1),i) + if self.noise_file_name[i] in self.saved_noise_im: + im = self.saved_noise_im[self.noise_file_name[i]] + else: + with self.noise_lock: + # Again, a second check in case two processes get here at the same time. + if self.noise_file_name[i] in self.saved_noise_im: # pragma: no cover + im = self.saved_noise_im[self.noise_file_name[i]] + else: + with pyfits.open(self.noise_file_name[i]) as fits: + array = fits[0].data + im = Image(np.ascontiguousarray(array.astype(np.float64)), + scale=self.pixel_scale[i]) + self.saved_noise_im[self.noise_file_name[i]] = im + + return im, self.pixel_scale[i], self.variance[i]
+ +
[docs] def getNoise(self, i, rng=None, gsparams=None): + """Returns the noise correlation function at index ``i`` as a `BaseCorrelatedNoise` object. + """ + im, scale, var = self.getNoiseProperties(i) + if im is None: + cf = UncorrelatedNoise(var, rng=rng, scale=scale, gsparams=gsparams) + else: + ii = InterpolatedImage(im, normalization="sb", + calculate_stepk=False, calculate_maxk=False, + x_interpolant='linear', gsparams=gsparams) + cf = BaseCorrelatedNoise(rng, ii, im.wcs) + cf = cf.withVariance(var) + return cf
+ + def __repr__(self): + return 'galsim.RealGalaxyCatalog(%r)'%self.file_name + + def __eq__(self, other): + return (self is other or + (isinstance(other, RealGalaxyCatalog) and + self.file_name == other.file_name and + self.image_dir == other.image_dir)) + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): return hash(repr(self)) + + def __getstate__(self): + d = self.__dict__.copy() + d['loaded_files'] = {} + d['saved_noise_im'] = {} + del d['gal_lock'] + del d['psf_lock'] + del d['loaded_lock'] + del d['noise_lock'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self.gal_lock = Lock() + self.psf_lock = Lock() + self.loaded_lock = Lock() + self.noise_lock = Lock() + pass
+ +def _parse_files_dirs(file_name, image_dir, sample): + if sample is None: + if file_name is None: + use_sample = '25.2' + elif '25.2' in file_name: + use_sample = '25.2' + elif '23.5' in file_name: + use_sample = '23.5' + else: + use_sample = None + else: + use_sample = sample + + if file_name is None: + file_name = 'real_galaxy_catalog_' + use_sample + '.fits' + if image_dir is None: + use_meta_dir = True # Used to give a more helpful error message + image_dir = os.path.join(meta_data.share_dir, + 'COSMOS_'+use_sample+'_training_sample') + else: + use_meta_dir = False + full_file_name = os.path.join(image_dir,file_name) + if not os.path.isfile(full_file_name) and use_meta_dir: + if use_sample not in ('23.5', '25.2'): + raise GalSimValueError("Sample name not recognized.",use_sample, ('23.5', '25.2')) + else: + raise OSError('No RealGalaxy catalog found in %s. Run the program ' + 'galsim_download_cosmos -s %s to download catalog and accompanying ' + 'image files.'%(image_dir, use_sample)) + elif image_dir is None: + full_file_name = file_name + image_dir = os.path.dirname(file_name) + else: + full_file_name = os.path.join(image_dir,file_name) + if not os.path.isfile(full_file_name): + raise OSError(full_file_name+' not found.') + + return full_file_name, image_dir, use_sample + + +
[docs]class ChromaticRealGalaxy(ChromaticSum): + """A class describing real galaxies over multiple wavelengths, using some multi-band training + dataset. The underlying implementation models multi-band images of individual galaxies + as chromatic PSF convolutions (and integrations over wavelength) with a sum of profiles + separable into spatial and spectral components. The spectral components are specified by the + user, and the spatial components are determined one Fourier mode at a time by the class. This + decomposition can be thought of as a constrained chromatic deconvolution of the multi-band + images by the associated PSFs, similar in spirit to `RealGalaxy`. + + Because ChromaticRealGalaxy involves an `InterpolatedKImage`, ``method = 'phot'`` is unavailable + for the `ChromaticObject.drawImage` function. + + Fundamentally, the required inputs for this class are: + + (1) a series of high resolution input `Image` instances of a single galaxy in different bands, + (2) a list of `Bandpass` corresponding to those images, + (3) the PSFs of those images as either `GSObject` or `ChromaticObject` instances, and + (4) the noise properties of the input images as `BaseCorrelatedNoise` instances. + + If you want to specify these inputs directly, that is possible via the `makeFromImages` factory + method of this class:: + + >>> crg = galsim.ChromaticRealGalaxy.makeFromImages(imgs, bands, PSFs, xis, ...) + + Alternatively, you may create a ChromaticRealGalaxy via a list of `RealGalaxyCatalog` that + correspond to a set of galaxies observed in different bands:: + + >>> crg = galsim.ChromaticRealGalaxy(real_galaxy_catalogs, index=0, ...) + + The above will use the 1st object in the catalogs, which should be the same galaxy, just + observed in different bands. Note that there are multiple keywords for choosing a galaxy from + a catalog; exactly one must be set. In the future we may add more such options, e.g., to + choose at random but accounting for the non-constant weight factors (probabilities for + objects to make it into the training sample). + + The flux normalization of the returned object will by default match the original data, scaled to + correspond to a 1 second HST exposure (though see the ``area_norm`` parameter). If you want + a flux appropriate for a longer exposure or telescope with different collecting area, you can + use the `ChromaticObject.withScaledFlux` method on the returned object, or use the ``exptime`` + and ``area`` keywords to `ChromaticObject.drawImage`. + + Note that while you can also use `ChromaticObject.withFlux`, `ChromaticObject.withMagnitude`, + and `ChromaticObject.withFluxDensity` to set the absolute normalization, these methods + technically adjust the flux of the entire postage stamp image (including noise!) and not + necessarily the flux of the galaxy itself. (These two fluxes will be strongly correlated for + high signal-to-noise ratio galaxies, but may be considerably different at low signal-to-noise + ratio.) + + Note that ChromaticRealGalaxy objects use arcsec for the units of their linear dimension. If + you are using a different unit for other things (the PSF, WCS, etc.), then you should dilate the + resulting object with ``gal.dilate(galsim.arcsec / scale_unit)``. + + Noise from the original images is propagated by this class, though certain restrictions apply + to when and how that noise is made available. The propagated noise depends on which `Bandpass` + the ChromaticRealGalaxy is being imaged through, so the noise is only available after the + `ChromaticObject.drawImage` method has been called. Also, since ChromaticRealGalaxy will + only produce reasonable images when convolved with a (suitably wide) PSF, the noise attribute is + attached to the `ChromaticConvolution` (or `ChromaticTransformation` of the + `ChromaticConvolution`) which holds as one of its convolutants the `ChromaticRealGalaxy`.:: + + >>> crg = galsim.ChromaticRealGalaxy(...) + >>> psf = ... + >>> obj = galsim.Convolve(crg, psf) + >>> bandpass = galsim.Bandpass(...) + >>> assert not hasattr(obj, 'noise') + >>> image = obj.drawImage(bandpass) + >>> assert hasattr(obj, 'noise') + >>> noise1 = obj.noise + + Note that the noise attribute is only associated with the most recently used bandpass. If you + draw another image of the same object using a different bandpass, the noise object will be + replaced.:: + + >>> bandpass2 = galsim.Bandpass(...) + >>> image2 = obj.drawImage(bandpass2) + >>> assert noise1 != obj.noise + + Parameters: + real_galaxy_catalogs: A list of `RealGalaxyCatalog` objects from which to create + `ChromaticRealGalaxy` objects. Each catalog should represent the + same set of galaxies, and in the same order, just imaged through + different filters. + index: Index of the desired galaxy in the catalog. [One of ``index``, + ``id``, or ``random`` is required.] + id: Object ID for the desired galaxy in the catalog. [One of ``index``, + ``id``, or ``random`` is required.] + random: If True, then just select a completely random galaxy from the + catalog. [One of ``index``, ``id``, or ``random`` is required.] + rng: A random number generator to use for selecting a random galaxy (may + be any kind of `BaseDeviate` or None) and to use in generating any + noise field when padding. + SEDs: An optional list of `SED` instances to use when representing real + galaxies as sums of separable profiles. By default, it will use + ``len(real_galaxy_catalogs)`` SEDs that are polynomials in + wavelength. Note that if given, ``len(SEDs)`` must equal + ``len(real_galaxy_catalogs)``. [default: None] + k_interpolant: Either an `Interpolant` instance or a string indicating which + k-space interpolant should be used. Options are 'nearest', 'sinc', + 'linear', 'cubic', 'quintic', or 'lanczosN' where N should be the + integer order to use. We strongly recommend leaving this parameter + at its default value; see text above for details. + [default: galsim.Quintic()] + maxk: Optional maxk argument. If you know you will be convolving the + resulting `ChromaticRealGalaxy` with a "fat" PSF in a subsequent + step, then it can be more efficient to limit the range of Fourier + modes used when solving for the sum of separable profiles below. + [default: None] + pad_factor: Factor by which to internally oversample the Fourier-space images + that represent the `ChromaticRealGalaxy` (equivalent to zero-padding + the real-space profiles). We strongly recommend leaving this + parameter at its default value; see text in Realgalaxy docstring + for details. [default: 4] + noise_pad_size: If provided, the image will be padded out to this size (in arcsec) + with the noise specified in the real galaxy catalog. This is + important if you are planning to whiten the resulting image. You + should make sure that the padded image is larger than the postage + stamp onto which you are drawing this object. + [default: None] + area_norm: Area in cm^2 by which to normalize the flux of the returned object. + When area_norm=1 (the default), using ``exptime=1`` and ``area=1`` + arguments in `ChromaticObject.drawImage` (also the default) will + simulate an image with the appropriate number of counts for a 1 + second exposure with the original telescope/camera (e.g., with HST + when using the COSMOS catalog). + If you would rather explicitly specify the collecting area of the + telescope when using `ChromaticObject.drawImage` with a + `ChromaticRealGalaxy`, then you should set area_norm equal to the + collecting area of the source catalog telescope when creating the + `ChromaticRealGalaxy` (e.g., area_norm=45238.93416 for HST). + [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + logger: A logger object for output of progress statements if the user wants + them. [default: None] + + """ + # TODO: SEDs isn't implemented yet in config parser. + _opt_params = { "k_interpolant" : str , + "maxk" : float, + "pad_factor" : float, + "noise_pad_size" : float, + "area_norm" : float + } + _single_params = [ { "index" : int , "id" : str , "random" : bool } ] + _takes_rng = True + + def __init__(self, real_galaxy_catalogs, index=None, id=None, random=False, rng=None, + gsparams=None, logger=None, **kwargs): + if rng is None: + rng = BaseDeviate() + elif not isinstance(rng, BaseDeviate): + raise TypeError("The rng provided to ChromaticRealGalaxy is not a BaseDeviate") + self.rng = rng + + logger = LoggerWrapper(logger) # So don't need to check `if logger:` all the time. + + # Get the index to use in the catalog + if index is not None: + if id is not None or random: + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", index=index, id=id, random=random) + use_index = index + elif id is not None: + if random: + raise GalSimIncompatibleValuesError( + "Too many methods for selecting a galaxy.", id=id, random=random) + use_index = real_galaxy_catalogs[0].getIndexForID(id) + elif random: + uniform_deviate = UniformDeviate(self.rng) + use_index = int(real_galaxy_catalogs[0].nobjects * uniform_deviate()) + else: + raise GalSimIncompatibleValuesError( + "No method specified for selecting a galaxy.", index=index, id=id, random=random) + logger.debug('ChromaticRealGalaxy %d: Start ChromaticRealGalaxy constructor.', use_index) + self.index = use_index + + # Read in the galaxy, PSF images; for now, rely on pyfits to make I/O errors. + imgs = [rgc.getGalImage(use_index) for rgc in real_galaxy_catalogs] + logger.debug('ChromaticRealGalaxy %d: Got gal_image', use_index) + + PSFs = [rgc.getPSF(use_index) for rgc in real_galaxy_catalogs] + logger.debug('ChromaticRealGalaxy %d: Got psf', use_index) + + bands = [rgc.getBandpass() for rgc in real_galaxy_catalogs] + + xis = [] + for rgc in real_galaxy_catalogs: + noise_image, pixel_scale, var = rgc.getNoiseProperties(use_index) + # Make sure xi image is odd-sized. + if noise_image.array.shape[0] % 2 == 0: #pragma: no branch + bds = noise_image.bounds + new_bds = BoundsI(bds.xmin+1, bds.xmax, bds.ymin+1, bds.ymax) + noise_image = noise_image[new_bds] + ii = InterpolatedImage(noise_image, normalization='sb', + calculate_stepk=False, calculate_maxk=False, + x_interpolant='linear', gsparams=gsparams) + xi = BaseCorrelatedNoise(self.rng, ii, noise_image.wcs) + xi = xi.withVariance(var) + xis.append(xi) + logger.debug('ChromaticRealGalaxy %d: Got noise_image',use_index) + self.catalog_files = [rgc.getFileName() for rgc in real_galaxy_catalogs] + + self._initialize(imgs, bands, xis, PSFs, gsparams=gsparams, **kwargs) + +
[docs] @classmethod + def makeFromImages(cls, images, bands, PSFs, xis, **kwargs): + """Create a `ChromaticRealGalaxy` directly from images, bandpasses, PSFs, and noise + descriptions. See the `ChromaticRealGalaxy` docstring for more information. + + Parameters: + images: An iterable of high resolution `Image` instances of a galaxy + through different bandpasses. + bands: An iterable of `Bandpass` objects corresponding to the input + images. + PSFs: Either an iterable of `GSObject` or `ChromaticObject` indicating + the PSFs of the different input images, or potentially a single + `GSObject` or `ChromaticObject` that will be used as the PSF for + all images. + xis: An iterable of `BaseCorrelatedNoise` objects characterizing the + noise in the input images. + SEDs: An optional list of `SED` instances to use when representing real + galaxies as sums of separable profiles. By default, it will use + ``len(images)`` SEDs that are polynomials in wavelength. Note that + if given, ``len(SEDs)`` must equal ``len(images)``. [default: None] + k_interpolant: Either an `Interpolant` instance or a string indicating which + k-space interpolant should be used. Options are 'nearest', 'sinc', + 'linear', 'cubic', 'quintic', or 'lanczosN' where N should be the + integer order to use. We strongly recommend leaving this parameter + at its default value; see text above for details. [default: + galsim.Quintic()] + maxk: Optional maxk argument. If you know you will be convolving the + resulting `ChromaticRealGalaxy` with a "fat" PSF in a subsequent + step, then it can be more efficient to limit the range of Fourier + modes used when solving for the sum of separable profiles below. + [default: None] + pad_factor: Factor by which to internally oversample the Fourier-space images + that represent the `ChromaticRealGalaxy` (equivalent to zero-padding + the real-space profiles). We strongly recommend leaving this + parameter at its default value; see text in Realgalaxy docstring + for details. [default: 4] + noise_pad_size: If provided, the image will be padded out to this size (in arcsec) + with the noise specified in the real galaxy catalog. This is + important if you are planning to whiten the resulting image. You + should make sure that the padded image is larger than the postage + stamp onto which you are drawing this object. + [default: None] + area_norm: Area in cm^2 by which to normalize the flux of the returned object. + When area_norm=1 (the default), using ``exptime=1`` and ``area=1`` + arguments in `ChromaticObject.drawImage` (also the default) will + simulate an image with the appropriate number of counts for a 1 + second exposure with the original telescope/camera (e.g., with HST + when using the COSMOS catalog). + If you would rather explicitly specify the collecting area of the + telescope when using `ChromaticObject.drawImage` with a + `ChromaticRealGalaxy`, then you should set area_norm equal to the + collecting area of the source catalog telescope when creating the + `ChromaticRealGalaxy` (e.g., area_norm=45238.93416 for HST). + [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + logger: A logger object for output of progress statements if the user wants + them. [default: None] + + """ + if not hasattr(PSFs, '__iter__'): + PSFs = [PSFs]*len(images) + obj = cls.__new__(cls) + obj.index = None + obj.catalog_files = None + obj.rng = kwargs.pop('rng', BaseDeviate()) + + if len(images) != len(bands) or len(images) != len(xis) or len(images) != len(PSFs): + raise GalSimIncompatibleValuesError( + "The number of images, bands, xis, and PSFs must match.", + images=images, bands=bands, xis=xis, PSFs=PSFs) + + obj._initialize(images, bands, xis, PSFs, **kwargs) + return obj
+ + def _initialize(self, imgs, bands, xis, PSFs, + SEDs=None, k_interpolant=None, maxk=None, pad_factor=4., area_norm=1.0, + noise_pad_size=0, gsparams=None): + + if SEDs is None: + SEDs = self._poly_SEDs(bands) + elif len(SEDs) > len(imgs): + raise GalSimIncompatibleValuesError( + "The number of SEDs must be <= the number of images", + images=imgs, SEDs=SEDs) + self.SEDs = SEDs + + if k_interpolant is None: + k_interpolant = Quintic() + else: + k_interpolant = convert_interpolant(k_interpolant) + + self._area_norm = area_norm + self._k_interpolant = k_interpolant + self._gsparams = GSParams.check(gsparams) + + NSED = len(self.SEDs) + Nim = len(imgs) + #assert Nim == len(bands) + #assert Nim == len(xis) + #assert Nim == len(PSFs) + #assert Nim >= NSED + + if area_norm != 1.0: + imgs = [img/area_norm for img in imgs] + xis = [xi/area_norm**2 for xi in xis] + + # Need to sample three different types of objects on the same Fourier grid: the input + # effective PSFs, the input images, and the input correlation-functions/power-spectra. + # There are quite a few potential options for implementing this Fourier sampling. Some + # examples include: + # * draw object in real space, interpolate onto the real-space grid conjugate to the + # desired Fourier-space grid and then DFT with numpy.fft methods. + # * Use numpy.fft methods on pre-sampled real-space input (like the input images), then + # use an InterpolatedKImage object to regrid onto desired Fourier grid. + # * Create an InterpolatedImage from pre-sampled input then use drawKImage to directly + # sample on desired Fourier grid. + # I'm sure there are other options too. The options chosen below were chosen empirically + # based on tests of propagating both (chromatic) galaxy images and images of pure noise. + + # Select maxk by requiring modes to be resolved both by the marginal PSFs (i.e., the + # achromatic PSFs obtained by evaluating the chromatic PSF at the blue and red edges of + # each of the filters provided) and also by the input images' pixel scales. + + img_maxk = np.min([np.pi/img.scale for img in imgs]) + marginal_PSFs = [PSF.evaluateAtWavelength(band.blue_limit) + for PSF in PSFs for band in bands] + marginal_PSFs += [PSF.evaluateAtWavelength(band.red_limit) + for PSF in PSFs for band in bands] + psf_maxk = np.min([p.maxk for p in marginal_PSFs]) + + # In practice, the output PSF should almost always cut off at smaller maxk than obtained + # above. In this case, the user can set the maxk keyword argument for improved efficiency. + if maxk is None: + maxk = np.min([img_maxk, psf_maxk]) + else: + maxk = np.min([img_maxk, psf_maxk, maxk]) + + # Setting stepk is trickier. We'll assume that the postage stamp inputs are already at the + # critical size to avoid significant aliasing and use the implied stepk. We'll insist that + # the WCS is a simple PixelScale. We'll also use the same trick that InterpolatedImage + # uses to improve accuracy, namely, increase the Fourier-space resolution a factor of + # `pad_factor`. + stepk = np.min([2*np.pi/(img.scale*max(img.array.shape))/pad_factor for img in imgs]) + nk = 2*int(np.floor(maxk/stepk)) + + # Create Fourier-space kimages of effective PSFs + PSF_eff_kimgs = np.empty((Nim, NSED, nk, nk), dtype=np.complex128) + for i, (img, band, PSF) in enumerate(zip(imgs, bands, PSFs)): + for j, sed in enumerate(self.SEDs): + # assume that PSF already includes pixel, so don't convolve one in again. + PSF_eff_kimgs[i, j] = (PSF * sed).drawKImage(band, nx=nk, ny=nk, scale=stepk).array + + # Get Fourier-space representations of input imgs. + kimgs = np.empty((Nim, nk, nk), dtype=np.complex128) + + if noise_pad_size == 0: + noise_pad = 0. + + for i, (img, xi) in enumerate(zip(imgs, xis)): + if noise_pad_size != 0: + noise_pad = xi + ii = InterpolatedImage(img, noise_pad_size=noise_pad_size, noise_pad=noise_pad, + rng=self.rng, pad_factor=pad_factor) + kimgs[i] = ii.drawKImage(nx=nk, ny=nk, scale=stepk).array + + # Setup input noise power spectra + pks = np.empty((Nim, nk, nk), dtype=np.float64) + for i, (img, xi) in enumerate(zip(imgs, xis)): + pks[i] = xi.drawKImage(nx=nk, ny=nk, scale=stepk).array.real / xi.wcs.pixelArea() + ny, nx = img.array.shape + pks[i] *= nx * ny + w = 1./np.sqrt(pks) + + # Allocate and fill output coefficients and covariances. + # Note: put NSED axis last, since significantly faster to compute them this way, + # even though we eventually convert to images which are strided in this format. + coef = np.zeros((nk, nk, NSED), dtype=np.complex128) + Sigma = np.empty((nk, nk, NSED, NSED), dtype=np.complex128) + + # Solve the weighted linear least squares problem for each Fourier mode. This is + # effectively a constrained chromatic deconvolution. Take advantage of symmetries. + _coef = coef.__array_interface__['data'][0] + _Sigma = Sigma.__array_interface__['data'][0] + _w = w.__array_interface__['data'][0] + _kimgs = kimgs.__array_interface__['data'][0] + _psf = PSF_eff_kimgs.__array_interface__['data'][0] + _galsim.ComputeCRGCoefficients(_coef, _Sigma, _w, _kimgs, _psf, NSED, Nim, nk, nk) + + # Reorder these so they correspond to (NSED, nky, nkx) and (NSED, NSED, nky, nkx) shapes. + coef = np.transpose(coef, (2,0,1)) + Sigma = np.transpose(Sigma, (2,3,0,1)) + + # Set up obj_list as required of ChromaticSum subclass. + obj_list = [] + for i, sed in enumerate(self.SEDs): + obj_list.append(sed * _InterpolatedKImage( + ImageCD(coef[i], scale=stepk), + k_interpolant=self._k_interpolant, + gsparams=self._gsparams)) + + Sigma_dict = {} + for i in range(NSED): + for j in range(i, NSED): + obj = _InterpolatedKImage( + ImageCD(Sigma[i, j], scale=stepk), + k_interpolant=self._k_interpolant, + gsparams=self._gsparams) + obj /= (imgs[0].array.shape[0] * imgs[0].array.shape[1] * imgs[0].scale**2) + Sigma_dict[(i, j)] = obj + + self.covspec = CovarianceSpectrum(Sigma_dict, self.SEDs) + + ChromaticSum.__init__(self, obj_list) + + @staticmethod + def _poly_SEDs(bands): + # Use polynomial SEDs by default; up to the number of bands provided. + waves = [] + for bp in bands: + waves = merge_sorted([waves, bp.wave_list]) + SEDs = [] + for i in range(len(bands)): + SEDs.append( + SED(LookupTable(waves, waves**i, interpolant='linear'), 'nm', 'fphotons') + .withFlux(1.0, bands[0])) + return SEDs + + def __eq__(self, other): + return (self is other or + (isinstance(other, ChromaticRealGalaxy) and + self.catalog_files == other.catalog_files and + self.index == other.index and + self.SEDs == other.SEDs and + self._k_interpolant == other._k_interpolant and + self._area_norm == other._area_norm and + self._gsparams == other._gsparams)) + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): + return hash(("galsim.ChromaticRealGalaxy", tuple(self.catalog_files), self.index, + tuple(self.SEDs), self._k_interpolant, self._area_norm, self._gsparams)) + + def __str__(self): + return "galsim.ChromaticRealGalaxy(%r, index=%r)"%(self.catalog_files, self.index) + + def __repr__(self): + return ("galsim.ChromaticRealGalaxy(%r, SEDs=%r, index=%r, k_interpolant=%r, " + "area_norm=%r, gsparams=%r)"%(self.catalog_files, self.SEDs, self.index, + self._k_interpolant, self._area_norm, self._gsparams))
+ +# Put this at the bottom to avoid circular import error. +from .config import LoggerWrapper + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/roman/roman_backgrounds.html b/docs/_build/html/_modules/galsim/roman/roman_backgrounds.html new file mode 100644 index 00000000000..d406f5f6523 --- /dev/null +++ b/docs/_build/html/_modules/galsim/roman/roman_backgrounds.html @@ -0,0 +1,265 @@ + + + + + + galsim.roman.roman_backgrounds — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for galsim.roman.roman_backgrounds

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+"""
+@file roman_backgrounds.py
+
+Part of the Roman Space Telescope module.  This file includes any routines needed to define the
+background level, for which the main contribution is zodiacal light.
+"""
+
+import numpy as np
+import os
+
+from .. import degrees, radians, CelestialCoord
+from .. import GalSimValueError
+
+
[docs]def getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None): + """ + Get the expected sky level for a Roman ST observation due to zodiacal light for this bandpass + and position. + + This routine requires Bandpass objects that were loaded by galsim.roman.getBandpasses(). That + routine will have stored tables containing the sky background as a function of position on the + sky for that bandpass. This routine then interpolates between the values in those tables to + arbitrary positions on the sky. + + The numbers that are stored in the Bandpass object ``bandpass`` are background level in units of + e-/s/arcsec^2. Multiplying by the exposure time gives a result in e-/arcsec^2. The + result can either be multiplied by the approximate pixel area to get e-/pix, or the result can + be used with wcs.makeSkyImage() to make an image of the sky that properly includes the actual + pixel area as a function of position on the detector. + + The source of the tables that are being interpolated is Chris Hirata's publicly-available Roman + exposure time calculator (ETC): + + http://www.tapir.caltech.edu/~chirata/web/software/space-etc/ + + Using the throughput files loaded into the ``bandpass`` object, the calculation nominally + returns photons/s/arcsec^2, but the input bandpasses used internally by the ETC + code include the quantum efficiency, to effectively convert to e-/s/arcsec^2. Note that in + general results will depend on the adopted model for zodiacal light, and these are uncertain at + the ~10% level. One must also better sample the integration in the zodiacal light calculation + to match the output tables used by GalSim here. + + Positions should be specified with the ``world_pos`` keyword, which must be a CelestialCoord + object. If no ``world_pos`` is supplied, then the routine will use a default position that + looks sensibly away from the sun. + + Parameters: + bandpass: A Bandpass object. + world_pos: A position, given as a CelestialCoord object. If None, then the routine + will use an ecliptic longitude of 90 degrees with respect to the sun + position (as a fair compromise between 0 and 180), and an ecliptic latitude + of 30 degrees with respect to the sun position (decently out of the plane + of the Earth-sun orbit). [default: None] + exptime: Exposure time in seconds. If None, use the default Roman exposure time. + [default: None] + epoch: The epoch to be used for estimating the obliquity of the ecliptic when + converting ``world_pos`` to ecliptic coordinates. This keyword is only used + if ``date`` is None, otherwise ``date`` is used to determine the ``epoch``. + [default: 2025] + date: The date of the observation, provided as a python datetime object. If None, + then the conversion to ecliptic coordinates assumes the sun is at ecliptic + coordinates of (0,0), as it is at the vernal equinox. [default: None] + + Returns: + the expected sky level in e-/arcsec^2. + """ + if exptime is None: + from . import exptime + + # Check for cached sky level information for this filter. If not, raise exception + if not hasattr(bandpass, '_sky_level'): + raise GalSimValueError("Only bandpasses returned from galsim.roman.getBandpasses() are " + "allowed here!", bandpass) + + # Check for proper type for position, and extract the ecliptic coordinates. + if world_pos is None: + # Use our defaults for the case of unspecified position. + ecliptic_lat = 30.*degrees + ecliptic_lon = 90.*degrees + else: + if not isinstance(world_pos, CelestialCoord): + raise TypeError("world_pos must be supplied as a CelestialCoord.") + if date is not None: + epoch = date.year + ecliptic_lon, ecliptic_lat = world_pos.ecliptic(epoch=epoch, date=date) + + # Check the position in our table, and make sure to take advantage of the latitude / longitude + # symmetries: + # The table only includes positive values of latitude, because there is symmetry about zero. So + # we take the absolute value of the input ecliptic latitude. + # The table only includes longitude in the range [0, 180] because there is symmetry in that a + # negative longitude in the range[-180, 0] should have the same sky level as at the positive + # value of longitude (given that the Sun is at 0). + ecliptic_lon = ecliptic_lon.wrap() + ecliptic_lon = abs(ecliptic_lon.rad)*radians + ecliptic_lat = abs(ecliptic_lat.rad)*radians + sin_ecliptic_lat = np.sin(ecliptic_lat) + + # Take the lookup table, and turn negative numbers (indicating failure because of proximity to + # sun) to large positive values so that we can identify them as bad after interpolation. + max_sky = np.max(bandpass._sky_level) + sky_level = bandpass._sky_level.copy() + sky_level[sky_level<0] = 1.e6 + + # Interpolate in 2d on the table. + s = sky_level.reshape(46,42).transpose() + xlat = sin_ecliptic_lat*41 + xlon = abs(ecliptic_lon.wrap() / degrees)/4. + ilat = int(xlat) + ilon = int(xlon) + xlat -= ilat + xlon -= ilon + sky_val = (s[ilat, ilon] * (1.-xlat)*(1.-xlon) + + s[ilat, ilon+1] * (1.-xlat)*xlon + + s[ilat+1, ilon] * xlat*(1.-xlon) + + s[ilat+1, ilon+1] * xlat*xlon) + + # If the result is too large, then raise an exception: we should not look at this position! + if sky_val > max_sky: + raise GalSimValueError("world_pos is too close to sun. Would not observe here.", world_pos) + + # Now, convert to the right units, and return. (See docstring for explanation.) + # Multiply by exposure time. + sky_val *= exptime + + # The result is now the sky level in e-/arcsec^2. + return sky_val
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/roman/roman_bandpass.html b/docs/_build/html/_modules/galsim/roman/roman_bandpass.html new file mode 100644 index 00000000000..cdf03568751 --- /dev/null +++ b/docs/_build/html/_modules/galsim/roman/roman_bandpass.html @@ -0,0 +1,291 @@ + + + + + + galsim.roman.roman_bandpass — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.roman.roman_bandpass

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+"""
+@file roman_bandpass.py
+
+Part of the Roman Space Telescope module.  This file includes any routines needed to define the
+Roman ST bandpasses.
+"""
+
+import numpy as np
+import os
+
+from .. import meta_data
+from ..errors import galsim_warn
+from .. import Bandpass, LookupTable
+
+
[docs]def getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands=False, **kwargs): + """Utility to get a dictionary containing the Roman ST bandpasses used for imaging. + + This routine reads in a file containing a list of wavelengths and throughput for all Roman + bandpasses, and uses the information in the file to create a dictionary. This file is in units + of effective area (m^2), which includes the nominal mirror size and obscuration in each + bandpass. We divide these by the nominal roman.collecting_area, so the bandpass objects + include both filter transmission losses and the obscuration differences relevant for + each bandpass. I.e. you should always use roman.collecting_area for the collecting area + in any flux calculation, and the bandpass will account for the differences from this. + + In principle it should be possible to replace the version of the file with another one, provided + that the format obeys the following rules: + + - There is a column called 'Wave', containing the wavelengths in microns. + - The other columns are labeled by the name of the bandpass. + + The bandpasses can be either truncated or thinned before setting the zero points, by passing in + the keyword arguments that need to get propagated through to the Bandpass.thin() and/or + Bandpass.truncate() routines. Or, if the user wishes to thin and truncate using the defaults + for those two routines, they can use ``default_thin_trunc=True``. This option is the default, + because the stored 'official' versions of the bandpasses cover a wide wavelength range. So even + if thinning is not desired, truncation is recommended. + + By default, the routine will set an AB zeropoint (unless ``AB_zeropoint=False``). The + zeropoint in GalSim is defined such that the flux is 1 photon/cm^2/sec through the + bandpass. This differs from an instrumental bandpass, which is typically defined such that the + flux is 1 photon/sec for that instrument. The difference between the two can be calculated as + follows:: + + # Shift zeropoint based on effective collecting area in cm^2. + delta_zp = 2.5 * np.log10(galsim.roman.collecting_area) + + ``delta_zp`` will be a positive number that should be added to the GalSim zeropoints to compare + with externally calculated instrumental zeropoints. When using the GalSim zeropoints for + normalization of fluxes, the ``area`` kwarg to drawImage can be used to get the right + normalization (giving it the quantity ``galsim.roman.collecting_area``). + + This routine also loads information about sky backgrounds in each filter, to be used by the + galsim.roman.getSkyLevel() routine. The sky background information is saved as an attribute in + each Bandpass object. + + There are some subtle points related to the filter edges, which seem to depend on the field + angle at some level. This is more important for the grism than for the imaging, so currently + this effect is not included in the Roman bandpasses in GalSim. + + The bandpass throughput file is translated from a spreadsheet Roman_effarea_20201130.xlsx at + https://roman.gsfc.nasa.gov/science/WFI_technical.html. + + Example:: + + >>> roman_bandpasses = galsim.roman.getBandpasses() + >>> f184_bp = roman_bandpasses['F184'] + + Parameters: + AB_zeropoint: Should the routine set an AB zeropoint before returning the bandpass? + If False, then it is up to the user to set a zero point. [default: + True] + default_thin_trunc: Use the default thinning and truncation options? Users who wish to + use no thinning and truncation of bandpasses, or who want control over + the level of thinning and truncation, should have this be False. + [default: True] + include_all_bands: Should the routine include the non-imaging bands (e.g., grisms)? + This does not implement any dispersion physics by itself. + There is currently no estimate for the thermal background for these + bands and they are set to zero arbitrarily. + [default: False] + **kwargs: Other kwargs are passed to either `Bandpass.thin` or + `Bandpass.truncate` as appropriate. + + @returns A dictionary containing bandpasses for all Roman imaging filters. + """ + from . import collecting_area, non_imaging_bands + + # Begin by reading in the file containing the info. + datafile = os.path.join(meta_data.share_dir, "roman", "Roman_effarea_20210614.txt") + # One line with the column headings, and the rest as a NumPy array. + data = np.genfromtxt(datafile, names=True) + wave = 1000.*data['Wave'] + + # Read in and manipulate the sky background info. + sky_file = os.path.join(meta_data.share_dir, "roman", "roman_sky_backgrounds.txt") + sky_data = np.loadtxt(sky_file).transpose() + ecliptic_lat = sky_data[0, :] + ecliptic_lon = sky_data[1, :] + + # Parse kwargs for truncation, thinning, etc., and check for nonsense. + truncate_kwargs = ['blue_limit', 'red_limit', 'relative_throughput'] + thin_kwargs = ['rel_err', 'trim_zeros', 'preserve_range', 'fast_search'] + tmp_truncate_dict = {} + tmp_thin_dict = {} + if default_thin_trunc: + if len(kwargs) > 0: + galsim_warn('default_thin_trunc is true, but other arguments have been passed' + ' to getBandpasses(). Using the other arguments and ignoring' + ' default_thin_trunc.') + default_thin_trunc = False + if len(kwargs) > 0: + for key in list(kwargs.keys()): + if key in truncate_kwargs: + tmp_truncate_dict[key] = kwargs.pop(key) + if key in thin_kwargs: + tmp_thin_dict[key] = kwargs.pop(key) + if len(kwargs) != 0: + raise TypeError("Unknown kwargs: %s"%(' '.join(kwargs.keys()))) + + # Set up a dictionary. + bandpass_dict = {} + # Loop over the bands. + for index, bp_name in enumerate(data.dtype.names[1:]): + if include_all_bands is False and bp_name in non_imaging_bands: + continue + + # Initialize the bandpass object. + # Convert effective area units from m^2 to cm^2. + # Also divide by the nominal Roman collecting area to get a dimensionless throughput. + bp = Bandpass(LookupTable(wave, data[bp_name] * 1.e4/collecting_area), wave_type='nm') + + # Use any arguments related to truncation, thinning, etc. + if len(tmp_truncate_dict) > 0 or default_thin_trunc: + bp = bp.truncate(**tmp_truncate_dict) + if len(tmp_thin_dict) > 0 or default_thin_trunc: + bp = bp.thin(**tmp_thin_dict) + + # Set the zeropoint if requested by the user: + if AB_zeropoint: + bp = bp.withZeropoint('AB') + + # Store the sky level information as an attribute. + bp._ecliptic_lat = ecliptic_lat + bp._ecliptic_lon = ecliptic_lon + bp._sky_level = sky_data[2+index, :] + + # Add it to the dictionary. + bp.name = bp_name if bp_name != 'W149' else 'W146' + bandpass_dict[bp.name] = bp + + return bandpass_dict
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/roman/roman_detectors.html b/docs/_build/html/_modules/galsim/roman/roman_detectors.html new file mode 100644 index 00000000000..f2dc4c162d9 --- /dev/null +++ b/docs/_build/html/_modules/galsim/roman/roman_detectors.html @@ -0,0 +1,375 @@ + + + + + + galsim.roman.roman_detectors — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.roman.roman_detectors

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+"""
+@file roman_detectors.py
+
+Part of the Roman Space Telescope module.  This file includes helper routines to apply image
+defects that are specific to Roman.
+"""
+
+import numpy as np
+import os
+
+from . import exptime, persistence_coefficients, nonlinearity_beta
+from . import dark_current, read_noise, gain
+from . import reciprocity_alpha
+from . import ipc_kernel
+from . import persistence_fermi_parameters
+
+from .. import BaseDeviate, PoissonNoise, DeviateNoise, GaussianNoise, PoissonDeviate
+from .. import GalSimValueError
+
+
+def NLfunc(x):
+    return x + nonlinearity_beta*(x**2)
+
+
[docs]def applyNonlinearity(img): + """ + Applies the Roman nonlinearity function to the supplied image ``im``. + + For more information about nonlinearity, see the docstring for galsim.Image.applyNonlinearity. + Unlike that routine, this one does not require any arguments, since it uses the nonlinearity + function defined within the Roman module. + + After calling this method, the Image instance ``img`` is transformed to include the + nonlinearity. + + Parameters: + img: The Image to be transformed. + """ + img.applyNonlinearity(NLfunc=NLfunc)
+ +
[docs]def addReciprocityFailure(img, exptime=exptime): + img.addReciprocityFailure(exp_time=exptime, alpha=reciprocity_alpha, base_flux=1.0)
+ +# Note: Formatted doc strings don't work if put in the normal place. Unless the function is +# actually called, the formatting statement is never executed. So put it here instead. +addReciprocityFailure.__doc__ = """ +Accounts for the reciprocity failure for the Roman directors and includes it in the original +Image ``img`` directly. + +For more information about reciprocity failure, see the docstring for +galsim.Image.addReciprocityFailure. Unlike that routine, this one does not need the parameters +for reciprocity failure to be provided, though it still takes exposure time as an optional +argument. + +Parameters: + img: The Image to be transformed. + exptime: The exposure time (t) in seconds, which goes into the expression for + reciprocity failure given in the docstring. If None, then the routine + will use the default Roman exposure time in galsim.roman.exptime. + [default: {exptime}] +""".format(exptime=exptime) + + +
[docs]def applyIPC(img, edge_treatment='extend', fill_value=None): + """ + Applies the effect of interpixel capacitance (IPC) to the Image instance. + + For more information about IPC, see the docstring for galsim.Image.applyIPC. Unlike that + routine, this one does not need the IPC kernel to be specified, since it uses the IPC kernel + defined within the Roman module. + + Parameters: + img: The Image to be transformed. + edge_treatment: Specifies the method of handling edges and should be one of + 'crop', 'extend' or 'wrap'. See galsim.Image.applyIPC docstring + for more information. + [default: 'extend'] + fill_value: Specifies the value (including nan) to fill the edges with when + edge_treatment is 'crop'. If unspecified or set to 'None', the + original pixel values are retained at the edges. If + edge_treatment is not 'crop', then this is ignored. + """ + img.applyIPC(ipc_kernel, edge_treatment=edge_treatment, fill_value=fill_value)
+ +
[docs]def applyPersistence(img, prev_exposures, method='fermi'): + if not hasattr(prev_exposures,'__iter__'): + raise TypeError("In roman.applyPersistence, prev_exposures must be a list of Image instances") + + if method == 'linear': + + n_exp = min(len(prev_exposures),len(persistence_coefficients)) + img.applyPersistence(prev_exposures[:n_exp], persistence_coefficients[:n_exp]) + + elif method == 'fermi': + + n_exp = len(prev_exposures) + for i in range(n_exp): + # The slew/settle time and the reset time should be specified. + # Now we simply assume them as 0 and take the persitence current at the mid-time of + # exposures as the average persistence until we get more information about the + # observation timeline. + img.array[:,:] += fermi_linear(prev_exposures[i].array, (0.5+i)*exptime)*exptime + + else: + raise GalSimValueError("applyPersistence only accepts 'linear' or 'fermi' methods, got", + method)
+ +# Again, need to put the doc outside the function to get formatting to work. +applyPersistence.__doc__ = """ +This method applies either of the two different persistence models: 'linear' and 'fermi'. +Slew between pointings and consecutive resets after illumination are not considered. + +'linear' persistence model + Applies the persistence effect to the Image instance by adding a small fraction of the + previous exposures (up to {ncoeff}) supplied as the 'prev_exposures' argument. + For more information about persistence, see `galsim.Image.applyPersistence`. + Unlike that routine, this one does not need the coefficients to be specified. However, + the list of previous {ncoeff} exposures will have to be supplied. Earlier exposures, if + supplied, will be ignored. + +'fermi' persistence model + Applies the persistence effect to the Image instance by adding the accumulated persistence + dark current of previous exposures supplied as the 'prev_exposures' argument. + Unlike galsim.Image.applyPersistence, this one does not use constant coefficients but a + fermi model plus a linear tail below half of saturation. + + For more info about the fermi model, see: + + http://www.stsci.edu/hst/wfc3/ins_performance/persistence/ + +Parameters: + img: The Image to be transformed. + prev_exposures: List of Image instances in the order of exposures, with the recent + exposure being the first element. In the linear model, the exposures + exceeding the limit ({ncoeff} exposures) will be ignored. + method: The persistence model ('linear' or 'fermi') to be applied. + [default: 'fermi'] +""".format(ncoeff=len(persistence_coefficients)) + + +def fermi_linear(x, t): + """ + The fermi model for persistence: A* (x/x0)**a * (t/1000.)**(-r) / (exp( -(x-x0)/dx ) +1. ) + For influence level below the half well, the persistence is linear in x. + + Parameters: + x: Array of pixel influence levels in unit of electron counts. + t: Time (in seconds) since reset. + + Returns: + The persistence signal of the input exposure x. + """ + x = np.asarray(x) + y = np.zeros_like(x) + + A, x0, dx, a, r, half_well = persistence_fermi_parameters + ps = A* ( x /x0)**a * (t/1000.)**(-r)/(np.exp( -(x-x0)/dx) +1.) + ps_hf = A* (half_well/x0)**a * (t/1000.)**(-r)/(np.exp( -(half_well-x0)/dx) +1.) + + mask1 = x > half_well + mask2 = (x > 0.) & (x <= half_well) + + y[mask1] += ps[mask1] + y[mask2] += ps_hf*x[mask2]/half_well + + return y + +# Again, need to put the doc outside the function to get formatting to work. +
[docs]def allDetectorEffects(img, prev_exposures=(), rng=None, exptime=exptime): + # Make sure we don't have any negative values. + img.replaceNegative(0.) + + # Add Poisson noise. + rng = BaseDeviate(rng) + poisson_noise = PoissonNoise(rng) + img.addNoise(poisson_noise) + + # Quantize: have an integer number of photons in every pixel after inclusion of sky noise. + img.quantize() + + # Reciprocity failure (use Roman routine, with the supplied exposure time). + addReciprocityFailure(img, exptime=exptime) + + # Dark current (use exposure time). + total_dark_current = dark_current*exptime + dark_noise = DeviateNoise(PoissonDeviate(rng, total_dark_current)) + img.addNoise(dark_noise) + + # Persistence (use Roman H4RG-lo fermi model) + prev_exposures = list(prev_exposures) + applyPersistence(img, prev_exposures, method='fermi') + # Update the 'prev_exposures' queue. + prev_exposures = [img.copy()] + prev_exposures[:] + + # Nonlinearity (use Roman routine). + applyNonlinearity(img) + + # IPC (use Roman routine). + applyIPC(img) + + # Read noise. + gn = GaussianNoise(rng, sigma=read_noise) + img.addNoise(gn) + + # Gain. + img /= gain + + # Quantize. + img.quantize() + + return prev_exposures
+ +allDetectorEffects.__doc__ = """ +This utility applies all sources of noise and detector effects for Roman that are implemented +in GalSim. In terms of noise, this includes the Poisson noise due to the signal (sky + +background), dark current, and read noise. The detector effects that are included are +reciprocity failure, quantization, persistence, nonlinearity, and interpixel capacitance. It +also includes the necessary factors of gain. In short, the user should be able to pass in an +Image with all sources of signal (background plus astronomical objects), and the Image will be +modified to include all subsequent steps in the image generation process for Roman that are +implemented in GalSim. However, to include the effect of persistence, the user needs to provide +a list of recent exposures (without the readout effects) and the routine +returns an updated list of recent exposures. + +Parameters: + img: The Image to be modified. + prev_exposures: List of Image instances in the order of exposures, with + the recent exposure being the first element. [default: ()] + rng: An optional galsim.BaseDeviate to use for the addition of noise. If + None, a new one will be initialized. [default: None] + exptime: The exposure time, in seconds. If None, then the Roman default + exposure time will be used. [default: {exptime}] + +Returns: + prev_exposures: Updated list of previous exposures Image instances. +""".format(exptime=exptime) + +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/roman/roman_psfs.html b/docs/_build/html/_modules/galsim/roman/roman_psfs.html new file mode 100644 index 00000000000..6a8e5c9f803 --- /dev/null +++ b/docs/_build/html/_modules/galsim/roman/roman_psfs.html @@ -0,0 +1,550 @@ + + + + + + galsim.roman.roman_psfs — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.roman.roman_psfs

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+import os
+
+from . import pixel_scale, n_pix, pixel_scale_mm
+from . import n_pix, n_sca, longwave_bands, shortwave_bands
+from . import diameter, obscuration
+from .roman_bandpass import getBandpasses
+
+from ..utilities import LRU_Cache
+from ..position import PositionD
+from ..errors import GalSimValueError, GalSimRangeError
+from ..bandpass import Bandpass
+from ..wcs import PixelScale
+from ..gsparams import GSParams
+from ..phase_psf import Aperture
+from .. import OpticalPSF, ChromaticOpticalPSF
+from .. import fits
+from .. import meta_data
+
+
+"""
+@file roman_psfs.py
+
+Part of the Roman Space Telescope module.  This file includes routines needed to define a realistic
+PSF for Roman.
+"""
+
+# Define a default set of bandpasses for which this routine works.
+default_bandpass_list = ['J129', 'F184', 'W149', 'Y106', 'Z087', 'H158']
+# Prefix for files containing information about Zernikes for each SCA for cycle 7.
+zemax_filepref = "Roman_Cycle-9_WFI_zim_zernikes_082623"
+zemax_filesuff = '.txt'
+zemax_wavelength = 1293. #nm
+
+# These need 'SCA*' prepended to the start to get the file name, and they live in
+# the share/roman directory.
+pupil_plane_file_longwave = 'RST_WIM_Filter_F184_SCA_'
+pupil_plane_file_shortwave = 'RST_WIM_Filter_skinny_SCA_'
+pupil_plane_filesuff = '.fits.gz'
+
[docs]def getPSF(SCA, bandpass, + SCA_pos=None, pupil_bin=4, wcs=None, + n_waves=None, extra_aberrations=None, + wavelength=None, gsparams=None, + logger=None, high_accuracy=None, approximate_struts=None): + """Get a single PSF for Roman ST observations. + + The user must provide the SCA and bandpass; the latter is used when setting up the pupil + plane configuration and when interpolating chromatic information, if requested. + + This routine carries out linear interpolation of the aberrations within a given SCA, based on + the Roman Cycle 9 specification of the aberrations as a function of focal plane + position, more specifically from the WebbPSF data files from webbpsf-data-1.2.1.tar.gz + downloaded from https://webbpsf.readthedocs.io/en/latest/installation.html#data-install. + The abberation file is webbpsf-data/WFI/wim_zernikes_cycle9.csv. + + The mask images for the Roman pupil plane are available in the same WebbPSF data files. + There are separate files for each SCA, since the view of the spider pattern varies somewhat + across the field of view of the wide field camera. Users usually don't need + to worry about any of this, as GalSim will select the correct pupil image automatically based + on the SCA and bandpass provided. + + The full pupil plane images are 4096 x 4096, which use a lot of memory and are somewhat slow + to use, so we normally bin them by a factor of 4 (resulting in 1024 x 1024 images). This + provides enough detail for most purposes and is much faster to render than using the full pupil + plane images. This bin factor is a settable parameter, called ``pupil_bin``. If you want the + more accurate, slower calculation using the full images, you can set it to 1. In the other + direction, using pupil_bin=8 (resulting in a 512 x 512 image) still provides fairly reasonable + results and is even faster to render. It is not generally recommended to use higher binning + than that, as the diffraction spikes will become noticeably degraded. + + .. note:: + + This function will cache the aperture calculation, so repeated calls with the same + SCA and bandpass should be much faster after the first call, as the pupil plane will + already be loaded. If you need to clear the cache for memory reasons, you may call:: + + galsim.roman.roman_psfs._make_aperture.clear() + + to recover any memory currently being used for this cache. Of course, subsequent calls to + `getPSF` will need to rebuild the aperture at that point. + + The PSF that is returned by default will be oriented with respect to the SCA coordinates, + not world coordinates as is typical in GalSim. The pupil plane has a fixed orientation + with respect to the focal plane, so the PSF rotates with the telescope. To obtain a + PSF in world coordinates, which can be convolved with galaxies (that are normally described + in world coordinates), you may pass in a ``wcs`` parameter to this function. This will + project the PSF into world coordinates according to that WCS before returning it. Otherwise, + the return value is equivalent to using ``wcs=galim.PixelScale(galsim.roman.pixel_scale)``. + + The calculation takes advantage of the fact that the diffraction limit and aberrations have a + simple, understood wavelength-dependence. (The Roman abberation data for Cycle 9 does in fact + provide aberrations as a function of wavelength, but the deviation from the expected chromatic + dependence is sub-percent so we neglect it here.) For reference, the script used to parse the + Zernikes given on the webpage and create the files in the GalSim repository can be found in + ``devel/external/parse_roman_zernikes_1217.py``. The resulting chromatic object can be used to + draw into any of the Roman bandpasses, though the pupil plane configuration will only be + correct for those bands in the same range (i.e., long- or short-wavelength bands). + + For applications that require very high accuracy in the modeling of the PSF, with very limited + aliasing, you may want to lower the folding_threshold in the gsparams. Otherwise very bright + stars will show some reflections in the spider pattern and possibly some boxiness at the + outskirts of the PSF. Using ``gsparams = GSParams(folding_threshold=2.e-3)`` generally + provides good results even for very bright (e.g. mag=10) stars. In these cases, you probably + also want to reduce ``pupil_bin`` somewhat from the default value of 4. + + By default, no additional aberrations are included above the basic design. However, users can + provide an optional keyword ``extra_aberrations`` that will be included on top of those that are + part of the design. This should be in the same format as for the ChromaticOpticalPSF class, + with units of waves at the fiducial wavelength, 1293 nm. Currently, only aberrations up to order + 22 (Noll convention) are simulated. For Roman, the tolerance for additional + aberrations was a total of 90 nanometers RMS as of mid-2015, distributed largely among coma, + astigmatism, trefoil, and spherical aberrations (NOT defocus). This information might serve as + a guide for reasonable ``extra_aberrations`` inputs. The reference for that number is + an earlier Cycle 5 document: + + http://roman.gsfc.nasa.gov/science/sdt_public/wps/references/instrument/README_AFTA_C5_WFC_Zernike_and_Field_Data.pdf + + However, the default (non-extra) aberrations are from Cycle 7 material linked earlier in this + docstring. + + Jitter and charge diffusion are, by default, not included. Users who wish to include these can + find some guidelines for typical length scales of the Gaussians that can represent these + effects, and convolve the ChromaticOpticalPSF with appropriate achromatic Gaussians. + + The PSFs are always defined assuming the user will specify length scales in arcsec. + + Users may find they do not have to call `getPSF` for all objects in their simulations; for a + given SCA and position within the SCA, and a given pupil plane configuration and wavelength + information, it should be possible to reuse the PSFs. + + Parameters: + SCA: Single value specifying the SCA for which the PSF should be + loaded. + bandpass: Single string specifying the bandpass to use when defining the + pupil plane configuration and/or interpolation of chromatic PSFs. + You may also pass a string 'long' or 'short' for this argument, in + which case, the correct pupil plane configuration will be used for + long- or short-wavelength bands (F184 is long, all else is short). + In this case, no interpolation can be used, since it is defined + using the extent of the chosen bandpass. If ``wavelength`` is given, + then bandpass may be None, which will use the short-wavelength pupil + plane image. + SCA_pos: Single galsim.PositionD indicating the position within the SCA + for which the PSF should be created. If None, the exact center of + the SCA is chosen. [default: None] + pupil_bin: The binning to apply to the pupil plane image. (See discussion above.) + [default: 4] + wcs: The WCS to use to project the PSF into world coordinates. + [default: galsim.PixelScale(galsim.roman.pixel_scale)] + n_waves: Number of wavelengths to use for setting up interpolation of the + chromatic PSF objects, which can lead to much faster image + rendering. If None, then no interpolation is used. Note that + users who want to interpolate can always set up the interpolation + later on even if they do not do so when calling `getPSF`. + [default: None] + extra_aberrations: Array of extra aberrations to include in the PSF model, on top of + those that are part of the Roman design. These should be + provided in units of waves at the fiducial wavelength of 1293 nm, + as an array of length 23 with entries 4 through 22 corresponding + to defocus through the 22nd Zernike in the Noll convention. + [default: None] + wavelength: An option to get an achromatic PSF for a single wavelength, for + users who do not care about chromaticity of the PSF. If None, + then the fully chromatic PSF is returned. Alternatively the user + should supply either (a) a wavelength in nanometers, and they + will get achromatic OpticalPSF objects for that wavelength, or + (b) a bandpass object, in which case they will get achromatic + OpticalPSF objects defined at the effective wavelength of that + bandpass. [default: False] + gsparams: An optional GSParams argument. See the docstring for GSParams + for details. [default: None] + + Returns: + A single PSF object (either a ChromaticOpticalPSF or an OpticalPSF depending on the + inputs). + + """ + + # Deprecated options + if bandpass == 'W149': + from ..deprecated import depr + depr('W149', 2.5, 'W146', 'Note: this is to match current Roman filter naming schemes') + bandpass = 'W146' + if high_accuracy: + if approximate_struts: + from ..deprecated import depr + depr('high_accuracy=True,approximate_struts=True', 2.3, + 'pupil_bin=4, gsparams=galsim.GSParams(folding_threshold=2.e-3)', + 'Note: this is not actually equivalent to the old behavior, but it should ' + 'be both faster and more accurate than the corresponding PSF in v2.2.') + # Set folding_threshold 2.5x smaller than default. + gsparams = GSParams.check(gsparams, folding_threshold=2.e-3) + pupil_bin = 4 + else: + from ..deprecated import depr + depr('high_accuracy=True', 2.3, + 'pupil_bin=1, gsparams=galsim.GSParams(folding_threshold=2.e-3)', + 'Note: this is not actually equivalent to the old behavior, but it should ' + 'be both faster and more accurate than the corresponding PSF in v2.2.') + # Set folding_threshold 2.5x smaller than default. + gsparams = GSParams.check(gsparams, folding_threshold=2.e-3) + pupil_bin = 1 + elif approximate_struts: + from ..deprecated import depr + depr('approximate_struts=True', 2.3, 'pupil_bin=8', + 'Note: this is not actually equivalent to the old behavior, but it should ' + 'be both faster and more accurate than the corresponding PSF in v2.2.') + pupil_bin = 8 + elif approximate_struts is False or high_accuracy is False: + # If they are explicitly given, rather than default (None), then trigger this. + from ..deprecated import depr + depr('approximate_struts=False, high_accuracy=False', 2.3, 'pupil_bin=4', + 'Note: this is not actually equivalent to the old behavior, but it should ' + 'be both faster and more accurate than the corresponding PSF in v2.2.') + pupil_bin = 4 + + if SCA <= 0 or SCA > n_sca: + raise GalSimRangeError("Invalid SCA.", SCA, 1, n_sca) + + # SCA_pos: if None, then all should just be center of the SCA. + if SCA_pos is None: + SCA_pos = PositionD(n_pix/2, n_pix/2) + + # Parse the bandpasses to see which pupil plane image is needed + pupil_plane_type = None + if bandpass in longwave_bands or bandpass=='long': + pupil_plane_type = 'long' + elif bandpass in shortwave_bands or bandpass=='short': + pupil_plane_type = 'short' + elif bandpass is None and n_waves is None: + pupil_plane_type = 'short' + else: + raise GalSimValueError("Bandpass not a valid Roman bandpass or 'short'/'long'.", + bandpass, default_bandpass_list) + + # If bandpass is 'short'/'long', then make sure that interpolation is not called for, since that + # requires an actual bandpass. + if bandpass in ['short','long'] and n_waves is not None: + raise GalSimValueError("Cannot use bandpass='short'/'long' with interpolation.", bandpass) + + if not isinstance(wavelength, (Bandpass, float, type(None))): + raise TypeError("wavelength should either be a Bandpass, float, or None.") + + # Now call _get_single_PSF(). + psf = _get_single_PSF(SCA, bandpass, SCA_pos, pupil_bin, + n_waves, extra_aberrations, wavelength, + pupil_plane_type, gsparams) + + # Apply WCS. + # The current version is in arcsec units, but oriented parallel to the image coordinates. + # So to apply the right WCS, project to pixels using the Roman mean pixel_scale, then + # project back to world coordinates with the provided wcs. + if wcs is not None: + scale = PixelScale(pixel_scale) + psf = wcs.toWorld(scale.toImage(psf), image_pos=SCA_pos) + + return psf
+ +def __make_aperture(SCA, pupil_plane_type, pupil_bin, wave, gsparams): + # Load the pupil plane image. + if pupil_plane_type == 'long': + pupil_plane_im = os.path.join(meta_data.share_dir, 'roman', + pupil_plane_file_longwave + '%d'%SCA + pupil_plane_filesuff) + else: + pupil_plane_im = os.path.join(meta_data.share_dir, 'roman', + pupil_plane_file_shortwave + '%d'%SCA + pupil_plane_filesuff) + pupil_plane_im = fits.read(pupil_plane_im, read_header=True) + # Native pixel scale in the file is for the exit pupil. We want the scale of the + # entrance pupil. Fortunately, they provide the conversion as 'HIERARCH PUPIL SCALE FACTOR' in the header. + # They also use microns for units, and we want meters, hence the extra 1.e-6. + pupil_plane_im.scale *= pupil_plane_im.header['HIERARCH PUPIL SCALE FACTOR'] * 1.e-6 + + pupil_plane_im = pupil_plane_im.bin(pupil_bin,pupil_bin) + + aper = Aperture(lam=wave, diam=diameter, + obscuration=obscuration, + pupil_plane_im=pupil_plane_im, + gsparams=gsparams) + return aper + +# Usually a given run will only need one or a few different apertures for repeated getPSF calls. +# So cache those apertures here to avoid having to remake them. +_make_aperture = LRU_Cache(__make_aperture) + +def _get_single_PSF(SCA, bandpass, SCA_pos, pupil_bin, + n_waves, extra_aberrations, wavelength, + pupil_plane_type, gsparams): + """Routine for making a single PSF. This gets called by `getPSF` after it parses all the + options that were passed in. Users will not directly interact with this routine. + """ + if wavelength is None: + wave = zemax_wavelength + elif isinstance(wavelength, Bandpass): + wave = wavelength = wavelength.effective_wavelength + else: + wave = wavelength + + # All parameters relevant to the aperture. We may be able to use a cached version. + aper = _make_aperture(SCA, pupil_plane_type, pupil_bin, wave, gsparams) + + # Start reading in the aberrations for that SCA + aberrations, x_pos, y_pos = _read_aberrations(SCA) + # Do bilinear interpolation, unless we're exactly at the center (default). + use_aberrations = _interp_aberrations_bilinear(aberrations, x_pos, y_pos, SCA_pos) + + if extra_aberrations is not None: + use_aberrations[:len(extra_aberrations)] += extra_aberrations + # We don't want to use piston, tip, or tilt aberrations. The former doesn't affect the + # appearance of the PSF, and the latter cause centroid shifts. So, we set the first 4 + # numbers (corresponding to a place-holder, piston, tip, and tilt) to zero. + use_aberrations[0:4] = 0. + + # Now set up the PSF, including the option to interpolate over waves + if wavelength is None: + PSF = ChromaticOpticalPSF(lam=zemax_wavelength, + diam=diameter, aberrations=use_aberrations, + aper=aper, gsparams=gsparams) + if n_waves is not None: + # To decide the range of wavelengths to use, check the bandpass. + bp_dict = getBandpasses() + bp = bp_dict[bandpass] + PSF = PSF.interpolate(waves=np.linspace(bp.blue_limit, bp.red_limit, n_waves), + oversample_fac=1.5) + else: + tmp_aberrations = use_aberrations * zemax_wavelength / wavelength + PSF = OpticalPSF(lam=wavelength, diam=diameter, + aberrations=tmp_aberrations, + aper=aper, gsparams=gsparams) + + return PSF + +def _read_aberrations(SCA): + """ + This is a helper routine that reads in aberrations for a particular SCA and wavelength (given as + galsim.roman.roman_psfs.zemax_wavelength) from stored files, and returns them along with the + field positions. + + Parameters: + SCA: The identifier for the SCA, from 1-18. + + Returns: + NumPy arrays containing the aberrations, and x and y field positions. + """ + # Construct filename. + sca_str = '_%02d'%SCA + infile = os.path.join(meta_data.share_dir, 'roman', + zemax_filepref + sca_str + zemax_filesuff) + + # Read in data. + dat = np.loadtxt(infile) + # It actually has 5 field positions, not just 1, to allow us to make position-dependent PSFs + # within an SCA eventually. Put it in the required format: an array of length (5 field + # positions, 23 Zernikes), with the first entry empty (Zernike polynomials are 1-indexed so we + # use entries 1-22). The units are waves. + aberrations = np.zeros((5,23)) + aberrations[:,1:] = dat[:,5:] + # Also get the field position. The file gives it in mm with respect to the center, but we + # want it in pixels with respect to the corner. The pixel size of the detector is 0.01 mm/pixel + + x = dat[:,1]/pixel_scale_mm + y = dat[:,2]/pixel_scale_mm + if SCA % 3 != 0: + # For these, the SCA is rotated 180 degrees relative to the nominal X, Y coordinates. + x = -x + y = -y + + x += n_pix/2 + y += n_pix/2 + + return aberrations, x, y + +def _interp_aberrations_bilinear(aberrations, x_pos, y_pos, SCA_pos): + """ + This is a helper routine to do bilinear interpolation of aberrations defined at 4 field + positions: the four corners. Note that we also have aberrations at the center position, + but these are generally quite close (within a few percent) of what would come from this bilinear + interpolation. So for simplicity, we just do the bilinear interpolation. + """ + # The data comprise 5 rows: center, lower-left, upper-left, upper-right, lower-right. + # The x,y values at the corners aren't precisely identical, but despite that, just + # take the outer one and do a simple bilinear interpolation as though it were a rectangle. + + # First, figure out which point is which corner. (0 is always the center.) + for i in range(1,5): + if x_pos[i] < x_pos[0] and y_pos[i] < y_pos[0]: + ll = i # lower-left + if x_pos[i] < x_pos[0] and y_pos[i] > y_pos[0]: + ul = i # upper-left + if x_pos[i] > x_pos[0] and y_pos[i] < y_pos[0]: + lr = i # lower-right + if x_pos[i] > x_pos[0] and y_pos[i] > y_pos[0]: + ur = i # upper-right + assert x_pos[ll] < x_pos[0] and y_pos[ll] < y_pos[0] + assert x_pos[ul] < x_pos[0] and y_pos[ul] > y_pos[0] + assert x_pos[lr] > x_pos[0] and y_pos[lr] < y_pos[0] + assert x_pos[ur] > x_pos[0] and y_pos[ur] > y_pos[0] + min_x = np.min(x_pos) + min_y = np.min(y_pos) + max_x = np.max(x_pos) + max_y = np.max(y_pos) + x_frac = (SCA_pos.x - min_x) / (max_x - min_x) + y_frac = (SCA_pos.y - min_y) / (max_y - min_y) + ll_ab = aberrations[ll, :] + ul_ab = aberrations[ul, :] + lr_ab = aberrations[lr, :] + ur_ab = aberrations[ur, :] + interp_ab = (1.0-x_frac)*(1.0-y_frac)*ll_ab + (1.0-x_frac)*y_frac*ul_ab + \ + x_frac*(1.0-y_frac)*lr_ab + x_frac*y_frac*ur_ab + + return interp_ab.flatten() +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/roman/roman_wcs.html b/docs/_build/html/_modules/galsim/roman/roman_wcs.html new file mode 100644 index 00000000000..4caff623c8f --- /dev/null +++ b/docs/_build/html/_modules/galsim/roman/roman_wcs.html @@ -0,0 +1,773 @@ + + + + + + galsim.roman.roman_wcs — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.roman.roman_wcs

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+"""
+@file roman_wcs.py
+
+Part of the Roman Space Telescope module.  This file includes any routines needed to define and use
+the Roman WCS.  Current version is consistent with Roman wide-field channel optical design version
+7.6.8, generated during Phase A and presented at the Roman System Requirements Review and Mission
+Definition Review.
+"""
+import numpy as np
+import os
+import coord
+import datetime
+
+from . import n_sca, n_pix, max_sun_angle
+from .. import meta_data
+from .. import GSFitsWCS, FitsHeader
+from .. import PositionD
+from .. import BoundsI
+from .. import GalSimRangeError, GalSimError
+
+
+# Basic Roman reference info, with lengths in mm.
+pixel_size_mm = 0.01
+focal_length = 18714
+pix_scale = (pixel_size_mm/focal_length)*coord.radians
+n_sip = 5 # Number of SIP coefficients used, where arrays are n_sip x n_sip in dimension
+
+# Version-related information, for reference back to material provided by Jeff Kruk.
+tel_name = "Roman"
+instr_name = "WFC"
+optics_design_ver = "20210204"
+prog_version = "d2"
+
+# Information about center points of the SCAs in the WFI focal plane coordinate system (f1, f2)
+# coordinates.  These are rotated by an angle theta_fpa with respect to the payload axes, as
+# projected onto the sky.  The origin is centered on the telescope boresight, but can be related to
+# the center of the FPA by subtracting fpa_xc_mm and fpa_yc_mm.
+#
+# Since the SCAs are 1-indexed, these arrays have a non-used entry with index 0.  i.e., the maximum
+# SCA is 18, and its value is in sca_xc_mm[18].  Units are mm.
+infile = os.path.join(meta_data.share_dir, 'roman', 'sca_positions_20210204.txt')
+sca_data = np.loadtxt(infile).transpose()
+sca_xc_mm = sca_data[3,:]
+sca_yc_mm = sca_data[4,:]
+sca_xc_mm = np.insert(sca_xc_mm, 0, 0)
+sca_yc_mm = np.insert(sca_yc_mm, 0, 0)
+sca_crval_u_deg = sca_data[5,:]
+sca_crval_v_deg = sca_data[6,:]
+sca_crval_u_deg = np.insert(sca_crval_u_deg, 0, 0)
+sca_crval_v_deg = np.insert(sca_crval_v_deg, 0, 0)
+# Nominal center of FPA from the payload axis in this coordinate system, in mm and as an angle
+# (neglecting distortions - to be included later).
+fpa_xc_mm = 0.0
+fpa_yc_mm = 160.484
+xc_fpa = np.arctan(fpa_xc_mm/focal_length)*coord.radians
+yc_fpa = np.arctan(fpa_yc_mm/focal_length)*coord.radians
+
+# The next array contains rotation offsets of individual SCA Y axis relative to FPA f2 axis. Same
+# sign convention as theta_fpa. These represent mechanical installation deviations from perfect
+# alignment and are ideally zero. These will be measured during focal plane integration and
+# testing.
+sca_rot = np.zeros_like(sca_xc_mm)
+
+# Rotation of WFI local axes relative to payload axes: this is expressed as a CCW rotation
+# relative to observatory +Z direction.
+theta_fpa = 120.0*coord.degrees
+
+# File with SIP coefficients.
+sip_filename = os.path.join(meta_data.share_dir, 'roman', 'sip_20210204.txt')
+
+
[docs]def getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False): + """ + This routine returns a dict containing a WCS for each of the Roman SCAs (Sensor Chip Array, the + equivalent of a chip in an optical CCD). The Roman SCAs are labeled 1-18, so these numbers are + used as the keys in the dict. Alternatively the user can request a subset of the SCAs using the + ``SCAs`` option. The basic instrument parameters used to create the WCS correspond to those in + Cycle 6, which includes some significant updates from Cycle 5, including a 90 degree rotation of + the focal plane axes relative to the payload axes, and two rows of SCAs are swapped. + + The user must specify a position for observation, at which the center of the focal plane array + will point. This must be supplied as a CelestialCoord ``world_pos``. In general, only certain + positions are observable on certain dates, and for a given position there is an optimal position + angle for the observatory (with the solar panels pointed as directly towards the sun as + possible). Users who are knowledgable about these details may choose to supply a position angle + as ``PA``, either for the observatory or for the focal plane (using ``PA_is_FPA`` to indicate + this). But otherwise, the routine will simply choose the optimal position angle for a given + date. + + To fully understand all possible inputs and outputs to this routine, users may wish to consult + the diagram on the GalSim wiki, + https://github.com/GalSim-developers/GalSim/wiki/GalSim-Roman-module-diagrams + + Parameters: + world_pos: A `galsim.CelestialCoord` indicating the position to observe at the center + of the focal plane array (FPA). Note that if the given position is not + observable on the given date, then the routine will raise an exception. + PA: A `galsim.Angle` representing the position angle of the observatory +Y + axis, unless ``PA_is_FPA=True``, in which case it's the position angle of + the FPA. For users to do not care about this, then leaving this as None + will result in the routine using the supplied ``date`` and ``world_pos`` to + select the optimal orientation for the observatory. Note that if a user + supplies a ``PA`` value, the routine does not check whether this orientation + is actually allowed. [default: None] + date: The date of the observation, as a python datetime object. If None, then the + vernal equinox in 2025 will be used. [default: None] + PA_is_FPA: If True, then the position angle that was provided was the PA of the focal + plane array, not the observatory. [default: False] + SCAs: A single number or iterable giving the SCAs for which the WCS should be + obtained. If None, then the WCS is calculated for all SCAs. + [default: None] + + Returns: + A dict of WCS objects for each SCA. + """ + # First just parse the input quantities. + date, SCAs, pa_fpa, pa_obsy = _parse_WCS_inputs(world_pos, PA, date, PA_is_FPA, SCAs) + + # Further gory details on coordinate systems, for developers: Observatory coordinate system is + # defined such that +X_obs points along the boresight into the sky, +Z_obs points towards the + # Sun in the absence of a roll offset (i.e., roll offset = 0 defines the optimal position angle + # for the observatory), +Y_obs makes a right-handed system. + # + # The x,y axes of each SCA are shown in the figure mapping_v210503.pdf in the devel/roman + # directory. Some are 180 rotated with respect to others. + # The data in sip_filename give the coordinate transformation from each SCA's image x,y + # coordinates to the WFI Local coordinate system. +Y in this system points away from the + # center of the observatory. And +X is to the right if +Y is up. + # + # These coordinates are rotated by an angle theta_fpa CCW from observatory +Z. + + # Note, this routine reads in the coeffs. We don't use them until later, but read them in for + # all SCAs at once. + a_sip, b_sip = _parse_sip_file(sip_filename) + + # Loop over SCAs: + wcs_dict = {} + for i_sca in SCAs: + # Set up the header. + header = [] + # Populate some necessary variables in the FITS header that are always the same, regardless of + # input and SCA number. + _populate_required_fields(header) + + # And populate some things that just depend on the overall locations or other input, not on + # the SCA. + header.extend([ + ('RA_TARG', world_pos.ra / coord.degrees, + "right ascension of the target (deg) (J2000)"), + ('DEC_TARG', world_pos.dec / coord.degrees, + "declination of the target (deg) (J2000)"), + ('PA_OBSY', pa_obsy / coord.degrees, "position angle of observatory Y axis (deg)"), + ('PA_FPA', pa_fpa / coord.degrees, "position angle of FPA Y axis (deg)"), + ('SCA_NUM', i_sca, "SCA number (1 - 18)"), + ]) + + # Leave phi_p at 180 (0 if dec_targ==-90), so that tangent plane axes remain oriented along + # celestial coordinates. In other words, phi_p is the angle of the +Y axis in the tangent + # plane, which is of course pi if we're measuring these phi angles clockwise from the -Y + # axis. Note that this quantity is not used in any calculations at all, but for consistency + # with the WCS code that comes from the Roman project office, we calculate this quantity + # and put it in the FITS header. + if world_pos.dec / coord.degrees > -90.: + phi_p = np.pi*coord.radians + else: + phi_p = 0.*coord.radians + + # Get position of SCA center given the center of the FPA and the orientation angle of the + # focal plane. + crval, u, v = _get_sca_center_pos(i_sca, world_pos, pa_fpa) + + # Compute the position angle of the local pixel Y axis. + # This requires projecting local North onto the detector axes. + # Start by adding any SCA-unique rotation relative to FPA axes: + sca_tp_rot = pa_fpa + sca_rot[i_sca]*coord.degrees + + # Go some reasonable distance from crval in the +y direction. Say, 1 degree. + plus_y = world_pos.deproject(u, v + 1*coord.degrees, projection='gnomonic') + # Find the angle between this point, crval and due north. + north = coord.CelestialCoord(0.*coord.degrees, 90.*coord.degrees) + pa_sca = sca_tp_rot - crval.angleBetween(plus_y, north) + + # Compute CD coefficients: extract the linear terms from the a_sip, b_sip arrays. These + # linear terms are stored in the SIP arrays for convenience. + a10 = a_sip[i_sca,1,0] + a11 = a_sip[i_sca,0,1] + b10 = b_sip[i_sca,1,0] + b11 = b_sip[i_sca,0,1] + + # Rotate by pa_fpa. + cos_pa_sca = np.cos(pa_sca) + sin_pa_sca = np.sin(pa_sca) + + header.extend([ + ('CRVAL1', crval.ra / coord.degrees, "first axis value at reference pixel"), + ('CRVAL2', crval.dec / coord.degrees, "second axis value at reference pixel"), + ('CD1_1', cos_pa_sca * a10 + sin_pa_sca * b10, + "partial of first axis coordinate w.r.t. x"), + ('CD1_2', cos_pa_sca * a11 + sin_pa_sca * b11, + "partial of first axis coordinate w.r.t. y"), + ('CD2_1', -sin_pa_sca * a10 + cos_pa_sca * b10, + "partial of second axis coordinate w.r.t. x"), + ('CD2_2', -sin_pa_sca * a11 + cos_pa_sca * b11, + "partial of second axis coordinate w.r.t. y"), + ('ORIENTAT', pa_sca / coord.degrees, "position angle of image y axis (deg. e of n)"), + ('LONPOLE', phi_p / coord.degrees, "Native longitude of celestial pole"), + ]) + for i in range(n_sip): + for j in range(n_sip): + if i+j >= 2 and i+j < n_sip: + sipstr = "A_%d_%d"%(i,j) + header.append( (sipstr, a_sip[i_sca,i,j]) ) + sipstr = "B_%d_%d"%(i,j) + header.append( (sipstr, b_sip[i_sca,i,j]) ) + + header = FitsHeader(header) + wcs = GSFitsWCS(header=header) + # Store the original header as an attribute of the WCS. This ensures that we have all the + # extra keywords for whenever an image with this WCS is written to file. + wcs.header = header + wcs_dict[i_sca]=wcs + + return wcs_dict
+ +
[docs]def convertCenter(world_pos, SCA, PA=None, date=None, PA_is_FPA=False, tol=0.5*coord.arcsec): + """ + This is a simple helper routine that takes an input position ``world_pos`` that is meant to + correspond to the position of the center of an SCA, and tells where the center of the focal + plane array should be. The goal is to provide a position that can be used as an input to + getWCS(), which wants the center of the focal plane array. + + The results of the calculation are deterministic if given a fixed position angle (PA). If it's + not given one, it will try to determine the best one for this location and date, like getWCS() + does. + + Because of distortions varying across the focal plane, this routine has to iteratively correct + its initial result based on empirical tests. The ``tol`` kwarg can be used to adjust how + careful it will be, but it always does at least one iteration. + + To fully understand all possible inputs and outputs to this routine, users may wish to consult + the diagram on the GalSim wiki, + https://github.com/GalSim-developers/GalSim/wiki/GalSim-Roman-module-diagrams + + Parameters: + world_pos: A galsim.CelestialCoord indicating the position to observe at the center of the + given SCA. Note that if the given position is not observable on + the given date, then the routine will raise an exception. + SCA: A single number giving the SCA for which the center should be located at + ``world_pos``. + PA: galsim.Angle representing the position angle of the observatory +Y axis, unless + ``PA_is_FPA=True``, in which case it's the position angle of the FPA. For + users to do not care about this, then leaving this as None will result in the + routine using the supplied ``date`` and ``world_pos`` to select the optimal + orientation for the observatory. Note that if a user supplies a ``PA`` value, + the routine does not check whether this orientation is actually allowed. + [default: None] + date: The date of the observation, as a python datetime object. If None, then the + vernal equinox in 2025 will be used. [default: None] + PA_is_FPA: If True, then the position angle that was provided was the PA of the focal + plane array, not the observatory. [default: False] + tol: Tolerance for errors due to distortions, as a galsim.Angle. + [default: 0.5*galsim.arcsec] + + Returns: + A CelestialCoord object indicating the center of the focal plane array. + """ + if not isinstance(SCA, int): + raise TypeError("Must pass in an int corresponding to the SCA") + if not isinstance(tol, coord.Angle): + raise TypeError("tol must be a galsim.Angle") + use_SCA = SCA + # Parse inputs appropriately. + _, _, pa_fpa, _ = _parse_WCS_inputs(world_pos, PA, date, PA_is_FPA, [SCA]) + + # Now pretend world_pos was the FPA center and we want to find the location of this SCA: + _, u, v = _get_sca_center_pos(use_SCA, world_pos, pa_fpa) + # The (u, v) values give an offset, and we can invert this. + fpa_cent = world_pos.deproject(-u, -v, projection='gnomonic') + # This is only approximately correct, especially for detectors that are far from the center of + # the FPA, because of distortions etc. We can do an iterative correction. + # For the default value of 'tol', typically just 1-2 iterations are needed. + shift_val = 1000.0 # arcsec + while shift_val > tol/coord.arcsec: + test_wcs = getWCS(fpa_cent, PA, date, use_SCA, PA_is_FPA)[use_SCA] + im_cent_pos = PositionD(n_pix/2, n_pix/2) + test_sca_pos = test_wcs.toWorld(im_cent_pos) + delta_ra = np.cos(world_pos.dec)*(world_pos.ra-test_sca_pos.ra) + delta_dec = world_pos.dec-test_sca_pos.dec + shift_val = np.abs(world_pos.distanceTo(test_sca_pos)/coord.arcsec) + fpa_cent = coord.CelestialCoord(fpa_cent.ra + delta_ra, fpa_cent.dec + delta_dec) + + return fpa_cent
+ +
[docs]def findSCA(wcs_dict, world_pos, include_border=False): + """ + This is a subroutine to take a dict of WCS (one per SCA) from galsim.roman.getWCS() and query + which SCA a particular real-world coordinate would be located on. The position (``world_pos``) + should be specified as a galsim.CelestialCoord. If the position is not located on any of the + SCAs, the result will be None. Note that if ``wcs_dict`` does not include all SCAs in it, then + it's possible the position might lie on one of the SCAs that was not included. + + Depending on what the user wants to do with the results, they may wish to use the + ``include_border`` keyword. This keyword determines whether or not to include an additional + border corresponding to half of the gaps between SCAs. For example, if a user is drawing a + single image they may wish to only know whether a given position falls onto an SCA, and if so, + which one (ignoring everything in the gaps). In contrast, a user who plans to make a sequence + of dithered images might find it most useful to know whether the position is either on an SCA or + close enough that in a small dither sequence it might appear on the SCA at some point. Use of + ``include_border`` switches between these scenarios. + + Parameters: + wcs_dict: The dict of WCS's output from galsim.roman.getWCS(). + world_pos: A galsim.CelestialCoord indicating the sky position of interest. + include_border: If True, then include the half-border around SCA to cover the gap + between each sensor. [default: False] + + Returns: + an integer value of the SCA on which the position falls, or None if the position is not + on any SCA. + + """ + # Sanity check args. + if not isinstance(wcs_dict, dict): + raise TypeError("wcs_dict should be a dict containing WCS output by galsim.roman.getWCS.") + + if not isinstance(world_pos, coord.CelestialCoord): + raise TypeError("Position on the sky must be given as a galsim.CelestialCoord.") + + # Set up the minimum and maximum pixel values, depending on whether or not to include the + # border. We put it immediately into a galsim.BoundsI(), since the routine returns xmin, xmax, + # ymin, ymax: + xmin, xmax, ymin, ymax = _calculate_minmax_pix(include_border) + bounds_list = [ BoundsI(x1,x2,y1,y2) for x1,x2,y1,y2 in zip(xmin,xmax,ymin,ymax) ] + + sca = None + for i_sca in wcs_dict: + wcs = wcs_dict[i_sca] + image_pos = wcs.toImage(world_pos) + if bounds_list[i_sca].includes(image_pos): + sca = i_sca + break + + return sca
+ +def _calculate_minmax_pix(include_border=False): + """ + This is a helper routine to calculate the minimum and maximum pixel values that should be + considered within an SCA, possibly including the complexities of including 1/2 of the gap + between SCAs. In that case it depends on the detailed geometry of the Roman focal plane. + + Parameters: + include_border: A boolean value that determines whether to include 1/2 of the gap + between SCAs as part of the SCA itself. [default: False] + + Returns: + a tuple of NumPy arrays for the minimum x pixel value, maximum x pixel value, minimum y + pixel value, and maximum y pixel value for each SCA. + """ + # First, set up the default (no border). + # The minimum and maximum pixel values are (1, n_pix). + min_x_pix = np.ones(n_sca+1).astype(int) + max_x_pix = min_x_pix + n_pix - 1 + min_y_pix = min_x_pix.copy() + max_y_pix = max_x_pix.copy() + + # Then, calculate the half-gaps, grouping together SCAs whenever possible. + if include_border: + # Currently, the configuration in the focal plane is such that all the horizontal chip gaps + # are the same, but that won't always be the case, so for the sake of generality we only + # group together those that are forced to be the same. + # + # We figure out the borders based on the FPA coordinates, but when we apply the + # adjustments to min/max x/y, we take into account the orientations of the SCAs. + # cf. mapping_v210503.pdf in the devel/roman directory. + # + # Positive side of 1/2/3, same as negative side of 10/11/12 + border_mm = (sca_xc_mm[10]-sca_xc_mm[1])-n_pix*pixel_size_mm + # assert statement help ensure that these pairings continue to work if positions + # are updated in the future. + assert 0 < border_mm < 5 + half_border_pix = int(0.5*border_mm / pixel_size_mm) + max_x_pix[ [3,10,11] ] += half_border_pix + min_x_pix[ [1,2,12] ] -= half_border_pix + + # Negative side of 1/2/3 and 13/14/15, same as positive side of 4/5/6 and 10/11/12 + border_mm = (sca_xc_mm[1]-sca_xc_mm[4])-n_pix*pixel_size_mm + assert 0 < border_mm < 5 + half_border_pix = int(0.5*border_mm / pixel_size_mm) + min_x_pix[ [3,4,5,10,11,15] ] -= half_border_pix + max_x_pix[ [1,2,6,12,13,14] ] += half_border_pix + + # Negative side of 4/5/6 and 16/17/18, same as positive side of 7/8/9 and 13/14/15 + # Also add this same chip gap to the outside chips. Neg side of 7/8/9, pos 16/17/18. + border_mm = (sca_xc_mm[4]-sca_xc_mm[7])-n_pix*pixel_size_mm + assert 0 < border_mm < 5 + half_border_pix = int(0.5*border_mm / pixel_size_mm) + min_x_pix[ [6,7,8,13,14,18] ] -= half_border_pix + max_x_pix[ [4,5,9,15,16,17] ] += half_border_pix + + # In the vertical direction, the gaps vary, with the gap between one pair of rows being + # significantly larger than between the other pair of rows. The reason for this has to do + # with asymmetries in the electronics that stick out from the top and bottom of the SCAs, + # and choices in which way to arrange each SCA to maximize the usable space in the focal + # plane. + + # Top of 2/5/8/11/14/17, same as bottom of 1/4/7/10/13/16. + # Also use this for top of top row: 1/4/7/10/13/16. + border_mm = (sca_yc_mm[1]-sca_yc_mm[2])-n_pix*pixel_size_mm + # One of the two vertical borders is larger. Just test them both at <10. + # (In the 20210204 setup, this one is the wider border.) + assert border_mm < 10 + half_border_pix = int(0.5*border_mm / pixel_size_mm) + list_1 = np.arange(1,18,3) + list_2 = list_1 + 1 + list_3 = list_1 + 2 + max_y_pix[list_1] += half_border_pix + min_y_pix[list_2] -= half_border_pix + min_y_pix[list_1] -= half_border_pix + + # Top of 3/6/9/12/15/18, same as bottom of 2/5/8/11/14/17. + # Also use this for bottom of bottom row: 3/6/9/12/15/18. + border_mm = (sca_yc_mm[2]-sca_yc_mm[3])-n_pix*pixel_size_mm + assert border_mm < 10 + half_border_pix = int(0.5*border_mm / pixel_size_mm) + max_y_pix[list_2] += half_border_pix + min_y_pix[list_3] -= half_border_pix + max_y_pix[list_3] += half_border_pix + + return min_x_pix, max_x_pix, min_y_pix, max_y_pix + +def _populate_required_fields(header): + """ + Utility routine to do populate some of the basic fields for the WCS headers for Roman that + don't require any interesting calculation. + """ + header.extend([ + ('EQUINOX', 2000.0, "equinox of celestial coordinate system"), + ('WCSAXES', 2, "number of World Coordinate System axes"), + ('A_ORDER', 4), + ('B_ORDER', 4), + ('WCSNAME', 'wfiwcs_'+optics_design_ver+'_'+prog_version), + ('CRPIX1', n_pix/2, "x-coordinate of reference pixel"), + ('CRPIX2', n_pix/2, "y-coordinate of reference pixel"), + ('CTYPE1', "RA---TAN-SIP", "coordinate type for the first axis"), + ('CTYPE2', "DEC--TAN-SIP", "coordinate type for the second axis"), + ('SIMPLE', 'True'), + ('BITPIX', 16), + ('NAXIS', 0), + ('EXTEND', 'True'), + ('BZERO', 0), + ('BSCALE', 1), + ('TELESCOP', tel_name, "telescope used to acquire data"), + ('INSTRUME', instr_name, "identifier for instrument used to acquire data"), + ]) + +def _parse_sip_file(file): + """ + Utility routine to parse the file with the SIP coefficients and hand back some arrays to be used + for later calculations. + """ + if not os.path.exists(file): + raise OSError("Cannot find file that should have Roman SIP coefficients: %s"%file) + + # Parse the file, generated by make_sip_file.py in devel/roman directory. + data = np.loadtxt(file, usecols=[0, 3, 4, 5, 6, 7]).transpose() + + a_sip = np.zeros((n_sca+1, n_sip, n_sip)) + b_sip = np.zeros((n_sca+1, n_sip, n_sip)) + for i_sca in range(1, n_sca+1): + i_sca_m1 = i_sca - 1 + # Take the data for this SCA + use_data = data[:, data[0,:]==i_sca_m1] + # Split it into a and b-type coefficients + a_data = use_data[:, 0:n_sip] + b_data = use_data[:, n_sip:] + # Assign the data to our master array of coefficients + a_sip[i_sca,:,:] = a_data[1:,:].transpose() + b_sip[i_sca,:,:] = b_data[1:,:].transpose() + + return a_sip, b_sip + +def _get_sca_center_pos(i_sca, world_pos, pa_fpa): + """ + This helper routine calculates the center position for a given SCA ``sca`` given the position of + the center of the focal plane array ``world_pos`` and an orientation angle for the observation. + It is used by getWCS() and other routines. + """ + # Go from the tangent plane position of the SCA center, to the actual celestial coordinate, + # using `world_pos` as the center point of the tangent plane projection. This celestial + # coordinate for the SCA center is `crval`, which goes into the WCS as CRVAL1, CRVAL2. + cos_pa = np.cos(pa_fpa) + sin_pa = np.sin(pa_fpa) + u = sca_crval_u_deg[i_sca] * cos_pa - sca_crval_v_deg[i_sca] * sin_pa + v = sca_crval_u_deg[i_sca] * sin_pa + sca_crval_v_deg[i_sca] * cos_pa + u = u * coord.degrees + v = v * coord.degrees + crval = world_pos.deproject(u, v, projection='gnomonic') + return crval, u, v + +def _parse_SCAs(SCAs): + # This is a helper routine to parse the input SCAs (single number or iterable) and put it into a + # convenient format. It is used in roman_wcs.py. + # + # Check which SCAs are to be done. Default is all (and they are 1-indexed). + all_SCAs = np.arange(1, n_sca + 1, 1) + # Later we will use the list of selected SCAs to decide which ones we're actually going to do + # the calculations for. For now, just check for invalid numbers. + if SCAs is not None: + # Make sure SCAs is iterable. + if not hasattr(SCAs, '__iter__'): + SCAs = [SCAs] + # Then check for reasonable values. + if min(SCAs) <= 0 or max(SCAs) > n_sca: + raise GalSimRangeError("Invalid SCA.", SCAs, 1, n_sca) + # Check for uniqueness. If not unique, make it unique. + SCAs = list(set(SCAs)) + else: + SCAs = all_SCAs + return SCAs + +def _parse_WCS_inputs(world_pos, PA, date, PA_is_FPA, SCAs): + """ + This routine parses the various input options to getWCS() and returns what the routine needs to + do its job. The reason to pull this out is so other helper routines can use it. + """ + + # Parse input position + if not isinstance(world_pos, coord.CelestialCoord): + raise TypeError("Position on the sky must be given as a galsim.CelestialCoord!") + + # Get the date. (Vernal equinox in 2025, taken from + # http://www.astropixels.com/ephemeris/soleq2001.html, if none was supplied.) + if date is None: + date = datetime.datetime(2025,3,20,9,2,0) + + # Are we allowed to look here? + if not allowedPos(world_pos, date): + raise GalSimError("Error, Roman cannot look at this position on this date!") + + # If position angle was not given, then get the optimal one: + if PA is None: + PA_is_FPA = False + PA = bestPA(world_pos, date) + else: + # Just enforce type + if not isinstance(PA, coord.Angle): + raise TypeError("Position angle must be a galsim.Angle!") + + # Check which SCAs are to be done using a helper routine in the galsim.roman module. + SCAs = _parse_SCAs(SCAs) + + # Compute position angle of FPA f2 axis, where positive corresponds to the angle east of North. + if PA_is_FPA: + pa_fpa = PA + pa_obsy = PA - theta_fpa + else: + pa_obsy = PA + pa_fpa = PA + theta_fpa + + return date, SCAs, pa_fpa, pa_obsy + +
[docs]def allowedPos(world_pos, date): + """ + This routine can be used to check whether Roman would be allowed to look at a particular + position (``world_pos``) on a given ``date``. This is determined by the angle of this position + relative to the Sun. + + In general, Roman can point at angles relative to the Sun in the range 90+/-36 degrees. + Obviously, pointing too close to the Sun would result in overly high sky backgrounds. It is + less obvious why Roman cannot look at a spot directly opposite from the Sun (180 degrees on the + sky). The reason is that the observatory is aligned such that if the observer is looking at + some sky position, the solar panels are oriented at 90 degrees from that position. So it's + always optimal for the observatory to be pointing at an angle of 90 degrees relative to the + Sun. It is also permitted to look within 36 degrees of that optimal position. + + Parameters: + world_pos: A galsim.CelestialCoord indicating the position at which the observer + wishes to look. + date: A python datetime object indicating the desired date of observation. + + Returns: + True or False, indicating whether it is permitted to look at this position on this date. + """ + # Find the Sun's location on the sky on this date. + lam = coord.util.sun_position_ecliptic(date) + sun = coord.CelestialCoord.from_ecliptic(lam, 0*coord.radians, date.year) + + # Find the angle between that and the supplied position + angle_deg = abs(world_pos.distanceTo(sun)/coord.degrees) + + # Check if it's within tolerance. + min_ang = 90. - max_sun_angle + max_ang = 90. + max_sun_angle + return min_ang <= angle_deg <= max_ang
+ +
[docs]def bestPA(world_pos, date): + """ + This routine determines the best position angle for the observatory for a given observation date + and position on the sky. + + The best/optimal position angle is determined by the fact that the solar panels are at 90 + degrees to the position being observed, and it is best to have those facing the Sun as directly + as possible. Note that if a given ``world_pos`` is not actually observable on the given + ``date``, then this routine will return None. + + Parameters: + world_pos: A galsim.CelestialCoord indicating the position at which the observer + wishes to look. + date: A python datetime object indicating the desired date of observation. + + Returns: + the best position angle for the observatory, as a galsim.Angle, or None if the position + is not observable. + """ + # First check for observability. + if not allowedPos(world_pos, date): + return None + + # Find the location of the sun on this date. +X_observatory points out into the sky, towards + # world_pos, while +Z is in the plane of the sky pointing towards the sun as much as possible. + lam = coord.util.sun_position_ecliptic(date) + sun = coord.CelestialCoord.from_ecliptic(lam, 0*coord.radians, date.year) + # Now we do a projection onto the sky centered at world_pos to find the (u, v) for the Sun. + sun_tp_x, sun_tp_y = world_pos.project(sun, 'gnomonic') + + # We want to rotate around by 90 degrees to find the +Y obs direction. Specifically, we want + # (+X, +Y, +Z)_obs to form a right-handed coordinate system. + y_obs_tp_x, y_obs_tp_y = -sun_tp_y, sun_tp_x + y_obs = world_pos.deproject(y_obs_tp_x, y_obs_tp_y, 'gnomonic') + + # Finally the observatory position angle is defined by the angle between +Y_observatory and the + # celestial north pole. It is defined as position angle east of north. + north = coord.CelestialCoord(y_obs.ra, 90.*coord.degrees) + obs_pa = world_pos.angleBetween(y_obs, north) + return obs_pa
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/second_kick.html b/docs/_build/html/_modules/galsim/second_kick.html new file mode 100644 index 00000000000..10c0d69d22e --- /dev/null +++ b/docs/_build/html/_modules/galsim/second_kick.html @@ -0,0 +1,378 @@ + + + + + + galsim.second_kick — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.second_kick

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'SecondKick' ]
+
+import astropy.units as u
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .angle import arcsec, AngleUnit, radians
+from .deltafunction import DeltaFunction
+
+
[docs]class SecondKick(GSObject): + """Class describing the expectation value of the high-k turbulence portion of an atmospheric + PSF convolved by an `Airy` PSF. + + The power spectrum of atmospheric phase fluctuations is assumed to follow the von Karman + prescription, but possibly modified by the addition of a critical scale below which the power + is zero. (See the `VonKarman` docstring for more details). + + As an expectation value, this profile is formally only exact in the infinite-exposure limit. + However, at least for large apertures, we have found that this expectation value is approached + rapidly, and can be applied for even fairly small exposure times. + + The intended use for this profile is as a correction to applying the geometric approximation to + `PhaseScreenPSF` objects when drawing using geometric photon shooting. In this case, the + `PhaseScreenPSF` will simulate the effects of the low frequency turbulence modes, which can be + treated purely using refraction, while the SecondKick handles the high frequency modes. + + The geometric approximation is only valid for length scales larger than some critical scale + where the effects of interference are unimportant. For smaller length scales, interference + (diffraction) must be handled using an optical paradigm that acknowledges the wave nature of + light, such as Fourier optics. + + Fourier optics calculations are many orders of magnitude slower than geometric optics + calculations for typical flux levels, however, so we implement a scale-splitting algorithm first + described in Peterson et al. (2015) for the LSST PhoSim package. Essentially, phase + fluctuations below a critical mode in Fourier space, labeled ``kcrit``, are handled by the fast + geometric optics calculations present in `PhaseScreenPSF`. Fluctuations for Fourier modes above + ``kcrit`` are then calculated analytically by SecondKick. Because very many oscillations of + these high-k modes both fit within a given telescope aperture and pass by the aperture during a + moderate length exposure time, we can use the same analytic expectation value calculation for + the high-k component of all PSFs across a field of view, thus incurring the somewhat expensive + calculation for Fourier optics only once. + + There are two limiting cases for this profile that may helpful for readers trying to understand + how this class works. When kcrit = 0, then all turbulent modes are included, and this surface + brightness profile becomes identical to the convolution of an `Airy` profile and a Von Karman + profile. In contrast, when kcrit = inf, then none of the turbulent modes are included, and this + surface brightness profile is just an `Airy` profile. In other words, the full effect of an + `Airy` profile, and additionally some portion (which depends on kcrit) of a `VonKarman` profile + are modeled. + + For more details, we refer the reader to the original implementation described in + + Peterson et al. 2015 ApJSS vol. 218 + + Parameters: + lam: Wavelength, either as an astropy Quantity or a float in nanometers. + r0: Fried parameter, either as an astropy Quantity or a float in meters. + diam: Aperture diameter, either as an astropy Quantity or a float in nanmeters. + obscuration: Linear dimension of central obscuration as fraction of aperture + linear dimension. [0., 1.). [default: 0.0] + kcrit: Critical Fourier mode (in units of 1/r0) below which the turbulence + power spectrum will be truncated. [default: 0.2] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + scale_unit: Units assumed when drawing this profile or evaluating xValue, kValue, + etc. Should be a `galsim.AngleUnit` or a string that can be used to + construct one (e.g., 'arcsec', 'radians', etc.). [default: galsim.arcsec] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { + "lam" : (float, u.Quantity), + "r0" : (float, u.Quantity), + "diam" : (float, u.Quantity), + } + _opt_params = { "obscuration" : float, "kcrit" : float, "flux" : float, "scale_unit" : str } + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = False + _is_analytic_k = True + + def __init__(self, lam, r0, diam, obscuration=0, kcrit=0.2, flux=1, + scale_unit=arcsec, gsparams=None): + if isinstance(lam, u.Quantity): + lam = lam.to_value(u.nm) + if isinstance(r0, u.Quantity): + r0 = r0.to_value(u.m) + if isinstance(diam, u.Quantity): + diam = diam.to_value(u.m) + + if isinstance(scale_unit, str): + self._scale_unit = AngleUnit.from_name(scale_unit) + else: + self._scale_unit = scale_unit + self._scale = radians / self._scale_unit + + self._flux = float(flux) + self._r0 = float(r0) + self._lam = float(lam) + self._diam = float(diam) + self._obscuration = float(obscuration) + self._kcrit = float(kcrit) + self._gsparams = GSParams.check(gsparams) + + @lazy_property + def _sbs(self): + lam_over_r0 = (1.e-9*self._lam/self._r0)*self._scale + return _galsim.SBSecondKick(lam_over_r0, self._kcrit, self._flux, self._gsparams._gsp) + + @lazy_property + def _sba(self): + lam_over_diam = (1.e-9*self._lam/self._diam)*self._scale + return _galsim.SBAiry(lam_over_diam, self._obscuration, 1., self._gsparams._gsp) + + @lazy_property + def _sbd(self): + return _galsim.SBDeltaFunction(self._sbs.getDelta(), self._gsparams._gsp) + + @lazy_property + def _sbp(self): + full_sbs = _galsim.SBAdd([self._sbs, self._sbd], self._gsparams._gsp) + return _galsim.SBConvolve([full_sbs, self._sba], False, self._gsparams._gsp) + + @property + def lam(self): + """The input lam value. + """ + return self._lam + + @property + def r0(self): + """The input r0 value. + """ + return self._r0; + + @property + def diam(self): + """The input diam value. + """ + return self._diam; + + @property + def obscuration(self): + """The input obscuration value. + """ + return self._obscuration; + + @property + def kcrit(self): + """The input kcrit value. + """ + return self._kcrit; + + @property + def scale_unit(self): + """The input scale_unit value. + """ + return self._scale_unit + + def _structure_function(self, rho): + return self._sbs.structureFunction(rho) + + def __eq__(self, other): + return (self is other or + (isinstance(other, SecondKick) and + self.lam == other.lam and + self.r0 == other.r0 and + self.diam == other.diam and + self.obscuration == other.obscuration and + self.kcrit == other.kcrit and + self.flux == other.flux and + self.scale_unit == other.scale_unit and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.SecondKick", self.lam, self.r0, self.diam, self.obscuration, + self.kcrit, self.flux, self.scale_unit, self.gsparams)) + + def __repr__(self): + out = "galsim.SecondKick(" + out += "lam=%r"%self.lam + out += ", r0=%r"%self.r0 + out += ", diam=%r"%self.diam + if self.obscuration != 0.0: + out += ", obscuration=%r"%self.obscuration + out += ", kcrit=%r"%self.kcrit + if self.flux != 1: + out += ", flux=%r"%self.flux + if self.scale_unit != arcsec: + out += ", scale_unit=%r"%self.scale_unit + out += ", gsparams=%r)"%self.gsparams + return out + + def __str__(self): + return "galsim.SecondKick(lam=%r, r0=%r, kcrit=%r)"%(self.lam, self.r0, self.kcrit) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + d.pop('_sba',None) + d.pop('_sbd',None) + d.pop('_sbs',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _max_sb(self): + return DeltaFunction._mock_inf + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return SecondKick(lam=self.lam, r0=self.r0, diam=self.diam, obscuration=self.obscuration, + kcrit=self.kcrit, flux=flux, scale_unit=self.scale_unit, + gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/sed.html b/docs/_build/html/_modules/galsim/sed.html new file mode 100644 index 00000000000..08e90ac1283 --- /dev/null +++ b/docs/_build/html/_modules/galsim/sed.html @@ -0,0 +1,1399 @@ + + + + + + galsim.sed — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.sed

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'SED', 'EmissionLine' ]
+
+import numpy as np
+from astropy import units
+from astropy import constants
+from numbers import Real
+from pathlib import PosixPath
+
+from .table import LookupTable, _LookupTable
+from ._utilities import WeakMethod, lazy_property, basestring
+from .errors import GalSimError, GalSimValueError, GalSimRangeError, GalSimSEDError
+from .errors import GalSimIncompatibleValuesError
+from .random import DistDeviate
+from . import utilities
+from . import integ
+from . import dcr
+from . import gsobject
+
+
[docs]class SED: + """Object to represent the spectral energy distributions of stars and galaxies. + + SEDs are callable, usually returning the flux density in photons/nm/cm^2/s as a function of + wavelength, though SEDs are also used by GalSim to track dimensionless wavelength-dependent + normalizations, and may thus also return dimensionless values. By default, the above wavelength + used by __call__ is nanometers, but it's possible to use other units via the astropy.units + module (at least, if the SED keyword argument ``fast=False``, see below). For instance,:: + + >>> sed = galsim.SED(...) + >>> from astropy import units as u + >>> assert sed(500) == sed(5000 * u.AA) # 500 nm == 5000 Angstroms + + The python type of the return value depends on the type of the input wavelength(s). A scalar + input wavelength yields a scalar flux density, a tuple yields a tuple, a list yields a list, and + a numpy.ndarray yields a numpy.ndarray. A scalar astropy.units.Quantity yields a python scalar, + and a vector astropy.units.Quantity yields a numpy.ndarray. + + SEDs are immutable; all transformative SED methods return *new* SEDs, and leave their + originating SEDs unaltered. + + SEDs have ``blue_limit`` and ``red_limit`` attributes, which indicate the range over which the + SED is defined. An exception will be raised if the flux density or normalization is requested + outside of this range. Note that ``blue_limit`` and ``red_limit`` are always in nanometers and + in the observed frame when ``redshift != 0``. + + SEDs may be multiplied by scalars or scalar functions of wavelength. In particular, an SED + multiplied by a `Bandpass` will yield the appropriately filtered SED. Two SEDs may be + multiplied together if at least one of them represents a dimensionless normalization. + + SEDs may be added together if they are at the same redshift. The resulting SED will only be + defined on the wavelength region where both of the operand SEDs are defined. ``blue_limit`` and + ``red_limit`` will be reset accordingly. + + The input parameter, ``spec``, may be one of several possible forms: + + 1. a regular python function (or an object that acts like a function) + 2. a `LookupTable` + 3. a file from which a `LookupTable` can be read in + 4. a string which can be evaluated to a function of ``wave`` via ``eval('lambda wave:'+spec)``, + e.g.:: + + spec = '0.8 + 0.2 * (wave-800)' + + 5. a python scalar (only possible for dimensionless SEDs) + + The argument of ``spec`` should be the wavelength in units specified by ``wave_type``, which + should be an instance of ``astropy.units.Unit`` of equivalency class ``astropy.units.spectral``, + or one of the case-insensitive aliases 'nm', 'nanometer', 'nanometers', 'A', 'Ang', 'Angstrom', + or 'Angstroms'. Note that ``astropy.units.spectral`` includes not only units with dimensions + of length, but also frequency, energy, or wavenumber. + + The return value of ``spec`` should be a spectral density with units specified by ``flux_type``, + which should be an instance of ``astropy.units.Unit`` of equivalency class + ``astropy.units.spectral_density``, or one of the case-insensitive aliases: + + 1. 'flambda': erg/wave_type/cm^2/s, where wave_type is as above. + 2. 'fnu': erg/Hz/cm^2/s + 3. 'fphotons': photons/wave_type/cm^2/s, where wave_type is as above. + 4. '1': dimensionless + + Note that the ``astropy.units.spectral_density`` class includes units with dimensions of + [energy/time/area/unit-wavelength], [energy/time/area/unit-frequency], + [photons/time/area/unit-wavelength], and so on. + + Finally, the optional ``fast`` keyword option is used to specify when unit and dimension changes + are executed, particularly for SEDs specified by a `LookupTable`. If ``fast=True``, the + default, then the input units/dimensions may be converted to an internal working unit before + interpolation in wavelength is performed. Alternatively, ``fast=False`` implies that + interpolation should take place in the native units of the input ``spec``, and subsequently flux + density converted to photons/cm^2/s/nm afterwards. Generally, the former option is faster, but + may be less accurate since interpolation and dimensionality conversion do not commute. One + consequence of using ``fast=True`` is that __call__ can not accept an ``astropy.units.Quantity`` + in this case. + + Parameters: + spec: Function defining the z=0 spectrum at each wavelength. See above for + valid options for this parameter. + wave_type: String or astropy.unit specifying units for wavelength input to ``spec``. + flux_type: String or astropy.unit specifying what type of spectral density or + dimensionless normalization ``spec`` represents. See above for valid + options for this parameter. + redshift: Optionally shift the spectrum to the given redshift. [default: 0] + fast: Convert units on initialization instead of on __call__. [default: True] + interpolant: If reading from a file, what interpolant to use. [default: 'linear'] + """ + # We'll use these multiple times below, and they are ridiculously slow to construct, + # so just make them once at the class level. + _fphotons_base = units.astrophys.photon/(units.s * units.cm**2) + _flambda_base = units.erg/(units.s * units.cm**2) + _fphotons = _fphotons_base / units.nm + _flambda = _flambda_base / units.nm + _fnu = units.erg / (units.s * units.Hz * units.cm**2) + _spec_nm = units.spectral_density(1*units.nm) + _c = constants.c.to('nm/s').value + _h = constants.h.to('erg s').value + _dimensionless = units.dimensionless_unscaled + _bolo_max_wave = 1.e30 # What we use as "infinity" for bolometric flux calculations. + + def __init__(self, spec, wave_type, flux_type, redshift=0., fast=True, interpolant='linear', + _blue_limit=0.0, _red_limit=np.inf, _wave_list=None): + self._flux_type = flux_type # Need to save the original for repr + self.interpolant = interpolant + + # Parse the various options for wave_type + self.wave_type, self.wave_factor = self._parse_wave_type(wave_type) + + # Parse the various options for flux_type + self.flux_factor = None + if isinstance(flux_type, str): + if flux_type.lower() == 'flambda': + self.flux_type = 'flambda' + self.spectral = True + self.flux_factor = 1. / (SED._h * SED._c) + elif flux_type.lower() == 'fphotons': + self.spectral = True + if self.wave_factor is not None: + self.flux_type = 'fphotons' + self.flux_factor = self.wave_factor + else: + self.flux_type = SED._fphotons + elif flux_type.lower() == 'fnu': + self.spectral = True + if self.wave_factor is not None: + self.flux_type = 'fnu' + self.flux_factor = self.wave_factor / SED._h + else: + self.flux_type = SED._fnu + elif flux_type == '1': + self.flux_type = '1' + self.spectral = False + else: + raise GalSimValueError("Unknown flux_type", flux_type, + ('flambda', 'fnu', 'fphotons', '1')) + else: + self.flux_type = flux_type + self.spectral = self.check_spectral() + if not self.spectral and not self.check_dimensionless(): + raise GalSimValueError( + "Flux_type must be equivalent to a spectral density or dimensionless.", + flux_type) + try: + if self.wave_factor and self.spectral: + self.flux_factor = (1*self.flux_type).to(SED._fphotons).value + self.flux_type = 'fphotons' + except units.UnitConversionError: + try: + self.flux_factor = (1*self.flux_type).to(SED._flambda).value + self.flux_factor /= SED._h * SED._c * self.wave_factor + self.flux_type = 'flambda' + except units.UnitConversionError: + try: + self.flux_factor = (1*self.flux_type).to(SED._fnu).value + self.flux_factor *= self.wave_factor / SED._h + self.flux_type = 'fnu' + except units.UnitConversionError: + self.wave_type = units.Unit(self.wave_type) + + self.redshift = redshift + self.fast = fast + + # Convert string input into a real function (possibly a LookupTable) + self._orig_spec = spec # Save this for pickling + self._initialize_spec() + + # Setup the wave_list, red_limit, blue_limit + if _wave_list is not None: + self.wave_list = _wave_list + self.blue_limit = float(_blue_limit) + self.red_limit = float(_red_limit) + elif isinstance(self._spec, LookupTable): + self.wave_list = np.array(self._spec.getArgs()) + if self.wave_factor: + self.wave_list *= (1.0 + self.redshift) / self.wave_factor + else: + self.wave_list = (self.wave_list*self.wave_type).to(units.nm, units.spectral()).value + self.wave_list *= (1.0 + self.redshift) + self.blue_limit = float(np.min(self.wave_list)) + self.red_limit = float(np.max(self.wave_list)) + else: + self.blue_limit = 0.0 + self.red_limit = np.inf + self.wave_list = np.array([], dtype=float) + + # Define the appropriate functions to call + self._setup_funcs() + + @staticmethod + def _parse_wave_type(wave_type): + # Parse the various options for wave_type. + # Returns wave_type, wave_factor + if isinstance(wave_type, str): + if wave_type.lower() in ('nm', 'nanometer', 'nanometers'): + return 'nm', 1. + elif wave_type.lower() in ('a', 'ang', 'angstrom', 'angstroms'): + return 'Angstrom', 10. + else: + raise GalSimValueError("Unknown wave_type", wave_type, ('nm', 'Angstrom')) + else: + try: + wave_factor = (1*units.nm).to(wave_type).value + if wave_factor == 1.: + return 'nm', 1. + elif abs(wave_factor-10.) < 2.e-15: # This doesn't come out exactly 10. + return 'Angstrom', 10. + else: + return units.Unit(wave_type), wave_factor + except units.UnitConversionError: + return units.Unit(wave_type), None + + def _setup_funcs(self): + # Set up the various functions we use to do the right calculation based on which + # wave type and/or flux type we have for _spec. + # The astropy unit functions are horribly slow, so we want to avoid them as much as + # possible. If the wave_type and flux_type are one of the simpler (and most common) + # types, then we have custom functions that do the necessary conversions directly. + if self.wave_factor == 1: + self._get_native_waves = WeakMethod(self._get_native_waves_trivial) + elif self.wave_factor: + self._get_native_waves = WeakMethod(self._get_native_waves_fast) + else: + self._get_native_waves = WeakMethod(self._get_native_waves_slow) + + if self.redshift == 0.: + self._get_rest_native_waves = self._get_native_waves + elif self.wave_factor: + self._get_rest_native_waves = WeakMethod(self._get_rest_native_waves_fast) + else: + self._get_rest_native_waves = WeakMethod(self._get_rest_native_waves_slow) + + if self.flux_type == 'fphotons': + #assert self.flux_factor is not None + self._flux_to_photons = WeakMethod(self._flux_to_photons_fphot) + elif self.flux_type == 'flambda': + #assert self.flux_factor is not None + self._flux_to_photons = WeakMethod(self._flux_to_photons_flam) + elif self.flux_type == 'fnu': + #assert self.flux_factor is not None + self._flux_to_photons = WeakMethod(self._flux_to_photons_fnu) + else: + self._flux_to_photons = WeakMethod(self._flux_to_photons_slow) + + if self.fast: + self._call = WeakMethod(self._call_fast) + else: + self._call = WeakMethod(self._call_slow) + + # Here are the definitions for the various functions we can use depending on the wave_type + # and flux_type (cf. _setup_funcs). + def _get_native_waves_trivial(self, wave): + return wave + + def _get_native_waves_fast(self, wave): + return np.asarray(wave) * self.wave_factor + + def _get_native_waves_slow(self, wave): + return (wave * units.nm).to(self.wave_type, units.spectral()).value + + def _get_rest_native_waves_fast(self, wave): + return np.asarray(wave) * (self.wave_factor / (1.0+self.redshift)) + + def _get_rest_native_waves_slow(self, wave): + return (wave / (1.0+self.redshift) * units.nm).to(self.wave_type, units.spectral()).value + + def _flux_to_photons_fphot(self, flux_native, wave_native): + return flux_native * self.flux_factor + + def _flux_to_photons_flam(self, flux_native, wave_native): + return flux_native * wave_native * self.flux_factor + + def _flux_to_photons_fnu(self, flux_native, wave_native): + return flux_native / wave_native * self.flux_factor + + def _flux_to_photons_slow(self, flux_native, wave_native): + return (flux_native * self.flux_type).to( + SED._fphotons, units.spectral_density(wave_native * self.wave_type)).value + + + def _initialize_spec(self): + # Turn the input _orig_spec into a real function _spec. + # The function cannot be pickled, so will need to do this in getstate as well as init. + + self._const = False + if isinstance(self._orig_spec, (int, float)): + if not self.dimensionless: + raise GalSimSEDError("Attempt to set spectral SED using float or integer.", self) + self._const = True + self._spec = lambda w: float(self._orig_spec) + elif isinstance(self._orig_spec, (basestring, PosixPath)): + isfile, filename = utilities.check_share_file(self._orig_spec, 'SEDs') + if isfile: + self._spec = LookupTable.from_file(filename, interpolant=self.interpolant) + else: + # If a constant function is input as a string (e.g. '1'), then we want to + # make sure it is flagged as a const SED. + try: + float(self._orig_spec) + except ValueError: + pass + else: + self._const = True + self._spec = lambda w: float(self._orig_spec) + return + + # Don't catch ArithmeticErrors when testing to see if the the result of `eval()` + # is valid since `spec = '1./(wave-700)'` will generate a ZeroDivisionError (which + # is a subclass of ArithmeticError) despite being a valid spectrum specification, + # while `spec = 'blah'` where `blah` is undefined generates a NameError and is not + # a valid spectrum specification. + # Are there any other types of errors we should trap here? + try: + self._spec = utilities.math_eval('lambda wave : ' + self._orig_spec) + test_value = self._spec(700.0) + except ArithmeticError: + test_value = 0 + except Exception as e: + raise GalSimValueError( + "String spec must either be a valid filename or something that " + "can eval to a function of wave.\n" + "Caught error: {0}".format(e), self._orig_spec) + if not isinstance(test_value, Real): + raise GalSimValueError("The given SED function did not return a valid number " + "at test wavelength %s: got %s"%(700.0, test_value), + self._orig_spec) + + else: + self._spec = self._orig_spec + +
[docs] def check_spectral(self): + """Return boolean indicating if SED has units compatible with a spectral density.""" + return self.flux_type.is_equivalent(SED._fphotons, SED._spec_nm)
+ +
[docs] def check_dimensionless(self): + """Return boolean indicating if SED is dimensionless.""" + if self.flux_type.is_equivalent(SED._dimensionless): + self._flux_type = '1' + # The astropy.units.dimensionless_unscaled object isn't properly reprable. + # So switch to using '1' in these cases. + return True + else: + return False
+ + @property + def dimensionless(self): # for convenience + """Whether the object is dimensionless (rather than spectral). + """ + return not self.spectral + + def _rest_nm_to_photons(self, wave): + wave_native = self._get_native_waves(wave) + flux_native = self._spec(wave_native) + return self._flux_to_photons(flux_native, wave_native) + + def _rest_nm_to_dimensionless(self, wave): + return self._spec(self._get_native_waves(wave)) + + def _check_bounds(self, wave): + if hasattr(wave, '__iter__'): + wmin = np.min(wave) + wmax = np.max(wave) + else: + wmin = wmax = wave + + extrapolation_slop = 1.e-6 # allow a small amount of extrapolation + if wmin < self.blue_limit - extrapolation_slop: + raise GalSimRangeError("Requested wavelength is bluer than blue_limit.", + wave, self.blue_limit, self.red_limit) + if wmax > self.red_limit + extrapolation_slop: + raise GalSimRangeError("Requested wavelength is redder than red_limit.", + wave, self.blue_limit, self.red_limit) + + @lazy_property + def _fast_spec(self): + # Create a fast version of self._spec by constructing a LookupTable on self.wave_list + if self.wave_factor == 1. and self.flux_factor == 1.: + return self._spec + else: + if len(self.wave_list) == 0: + if self.spectral: + return WeakMethod(self._rest_nm_to_photons) + else: + return WeakMethod(self._rest_nm_to_dimensionless) + else: + x = self.wave_list / (1.0 + self.redshift) + if self.spectral: + f = self._rest_nm_to_photons(x) + else: + f = self._rest_nm_to_dimensionless(x) + interp = self._spec.interpolant if isinstance(self._spec, LookupTable) else 'linear' + return _LookupTable(x, f, interpolant=interp) + + def _call_fast(self, wave): + """Return either flux in photons / sec / cm^2 / nm, or dimensionless normalization. + + Assumes that self._spec has already been transformed to accept correct wavelength units and + yield correct flux units. + + Parameters: + wave: Wavelength in nanometers. + + Returns: + Flux or normalization. + """ + self._check_bounds(wave) + return self._fast_spec(np.asarray(wave) / (1.0 + self.redshift)) + + def _call_slow(self, wave): + """Return flux in photons / sec / cm^2 / nm or dimensionless normalization. + + Uses self._spec that has not been pre-transformed for desired units, instead does all unit + conversions inside this method. + + Parameters: + wave: Wavelength. If not an astropy.units.Quantity, then assumed units are + nanometers. + + Returns: + Flux. + """ + wave_in = wave + # Convert wave to nanometers if needed. + if isinstance(wave, units.Quantity): + wave = wave.to(units.nm, units.spectral()).value + + wave = np.asarray(wave) + + self._check_bounds(wave) + + # Figure out rest-frame wave_type wavelength array for query to self._spec. + rest_wave_native = self._get_rest_native_waves(wave) + + out = self._spec(rest_wave_native) + + # Manipulate output units + if self.spectral: + out = self._flux_to_photons(out, rest_wave_native) + return out + +
[docs] def __call__(self, wave): + """Return photon flux density or dimensionless normalization at wavelength ``wave``. + + Note that outside of the wavelength range defined by the ``blue_limit`` and ``red_limit`` + attributes, the SED is considered undefined, and this method will raise an exception if a + wavelength outside the defined range is passed as an argument. + + Parameters: + wave: Wavelength in nanometers at which to evaluate the SED. May be a scalar, + a numpy.array, or an astropy.units.Quantity + + Returns: + photon flux density in units of photons/nm/cm^2/s if self.spectral, or + dimensionless normalization if self.dimensionless. + """ + ret = self._call(wave) + if self._const: + ret = np.full_like(wave, ret, dtype=float) + return ret
+ +
[docs] def _mul_sed(self, other): + """Equivalent to self * other when other is an SED, but no sanity checks.""" + # There should only be one SED with a non-trivial redshift, so adding them + # should always give us the right net redshift to use. + redshift = self.redshift + other.redshift + + fast = self.fast and other.fast + + wave_list, blue_limit, red_limit = utilities.combine_wave_list(self, other) + if fast: + if (isinstance(self._fast_spec, LookupTable) + and not self._fast_spec.x_log + and not self._fast_spec.f_log): + x = wave_list / (1.0 + self.redshift) + # Add in 500 uniformly spaced values to help improve accuracy. + x = utilities.merge_sorted([x, np.linspace(x[0], x[-1], 500)]) + zfactor2 = (1.+redshift) / (1.+other.redshift) + f = self._fast_spec(x) * other._fast_spec(x*zfactor2) + spec = _LookupTable(x, f, self._fast_spec.interpolant) + elif (isinstance(other._fast_spec, LookupTable) + and not other._fast_spec.x_log + and not other._fast_spec.f_log): + x = wave_list / (1.0 + other.redshift) + x = utilities.merge_sorted([x, np.linspace(x[0], x[-1], 500)]) + zfactor1 = (1.+redshift) / (1.+other.redshift) + f = self._fast_spec(x*zfactor1) * other._fast_spec(x) + spec = _LookupTable(x, f, other._fast_spec.interpolant) + else: + zfactor1 = (1.+redshift) / (1.+self.redshift) + zfactor2 = (1.+redshift) / (1.+other.redshift) + spec = lambda w: self._fast_spec(w * zfactor1) * other._fast_spec(w * zfactor2) + else: + spec = lambda w: self(w * (1.+redshift)) * other(w * (1.+redshift)) + spectral = self.spectral or other.spectral + flux_type = 'fphotons' if spectral else '1' + return SED(spec, 'nm', flux_type, redshift=redshift, fast=fast, + _blue_limit=blue_limit, _red_limit=red_limit, _wave_list=wave_list)
+ +
[docs] def _mul_bandpass(self, other): + """Equivalent to self * other when other is a Bandpass""" + wave_list, blue_limit, red_limit = utilities.combine_wave_list(self, other) + zfactor = (1.0+self.redshift) / other.wave_factor + if self.fast: + if (isinstance(self._fast_spec, LookupTable) + and not self._fast_spec.x_log + and not self._fast_spec.f_log): + x = wave_list / (1.0 + self.redshift) + # Add in 500 uniformly spaced values to help improve accuracy. + x = utilities.merge_sorted([x, np.linspace(x[0], x[-1], 500)]) + f = self._fast_spec(x) * other._tp(x*zfactor) + spec = _LookupTable(x, f, self._fast_spec.interpolant) + else: + spec = lambda w: self._fast_spec(w) * other._tp(w*zfactor) + else: + spec = lambda w: self(w*(1.0+self.redshift)) * other._tp(w*zfactor) + return SED(spec, 'nm', 'fphotons', redshift=self.redshift, fast=self.fast, + _blue_limit=blue_limit, _red_limit=red_limit, _wave_list=wave_list)
+ + +
[docs] def _mul_scalar(self, other, spectral): + """Equivalent to self * other when other is a scalar""" + # If other is a scalar and self._spec a LookupTable, then remake that LookupTable. + if isinstance(self._spec, LookupTable): + wave_type = self.wave_type + flux_type = self._flux_type + x = self._spec.getArgs() + f = np.array(self._spec.getVals()) * other + spec = _LookupTable(x, f, x_log=self._spec.x_log, f_log=self._spec.f_log, + interpolant=self._spec.interpolant) + elif self._const and not spectral: + spec = self._spec(42.0) * other + wave_type = 'nm' + flux_type = '1' + else: + wave_type = 'nm' + flux_type = 'fphotons' if spectral else '1' + if self.fast: + spec = lambda w: self._fast_spec(w) * other + else: + spec = lambda w: self(w*(1.0+self.redshift)) * other + return SED(spec, wave_type, flux_type, redshift=self.redshift, fast=self.fast, + _blue_limit=self.blue_limit, _red_limit=self.red_limit, + _wave_list=self.wave_list)
+ + +
[docs] def __mul__(self, other): + """Multiply the SED by something. + + There are several possibilities: + + 1. SED * SED -> SED (at least one must be dimensionless) + 2. SED * GSObject -> ChromaticObject + 3. SED * Bandpass -> SED (treating throughput similarly to dimensionless SED) + 4. SED * callable function -> SED (treating function as dimensionless SED) + 5. SED * scalar -> SED + """ + # Watch out for 5 types of `other`: + # 1. SED: Check that not both spectral densities. + # 2. GSObject: return a ChromaticObject(). + # 3. Bandpass: return an SED, but carefully propagate blue/red limit and wave_list. + # 4. Callable: return an SED + # 5. Scalar: return an SED + # + # Additionally, check for shortcuts when self._const + + # Product of two SEDs + if isinstance(other, SED): + if self.spectral and other.spectral: + raise GalSimIncompatibleValuesError( + "Cannot multiply two spectral densities together.", self_sed=self, other=other) + + if other._const: + # const, so can eval anywhere. + return self._mul_scalar(other._spec(42.0), self.spectral or other.spectral) + elif self._const: + return other._mul_scalar(self._spec(42.0), self.spectral or other.spectral) + else: + return self._mul_sed(other) + + # Product of SED and achromatic GSObject is a SimpleChromaticTransformation. + elif isinstance(other, gsobject.GSObject): + return other * self + + # Product of SED and Bandpass is (filtered) SED. The `redshift` attribute is retained. + elif isinstance(other, Bandpass): + return self._mul_bandpass(other) + + # Product of SED with generic callable is also a (filtered) SED, with retained `redshift`. + elif hasattr(other, '__call__'): + if self.fast: + spec = lambda w: self._fast_spec(w) * other(w*(1.0+self.redshift)) + else: + spec = lambda w: self(w*(1.0+self.redshift)) * other(w*(1.0+self.redshift)) + flux_type = 'fphotons' if self.spectral else '1' + return SED(spec, 'nm', flux_type, redshift=self.redshift, fast=self.fast, + _blue_limit=self.blue_limit, _red_limit=self.red_limit, + _wave_list=self.wave_list) + + elif isinstance(other, (int, float)): + return self._mul_scalar(other, self.spectral) + + else: + raise TypeError("Cannot multiply an SED by %s"%(other))
+ + def __rmul__(self, other): + return self*other + + def __div__(self, other): + # Enable division by scalars or dimensionless callables (including dimensionless SEDs.) + if isinstance(other, SED) and other.spectral: + raise GalSimSEDError("Cannot divide by spectral SED.", other) + if hasattr(other, '__call__'): + spec = lambda w: self(w * (1.0 + self.redshift)) / other(w * (1.0 + self.redshift)) + elif isinstance(self._spec, LookupTable): + # If other is not a function, then there is no loss of accuracy by applying the + # factor directly to the LookupTable, if that's what we are using. + # Make sure to keep the same properties about the table, flux_type, wave_type. + x = self._spec.getArgs() + f = [ val / other for val in self._spec.getVals() ] + spec = _LookupTable(x, f, x_log=self._spec.x_log, f_log=self._spec.f_log, + interpolant=self._spec.interpolant) + else: + spec = lambda w: self(w * (1.0 + self.redshift)) / other + + return SED(spec, flux_type=self.flux_type, wave_type=self.wave_type, + redshift=self.redshift, fast=self.fast, + _wave_list=self.wave_list, + _blue_limit=self.blue_limit, _red_limit=self.red_limit) + + __truediv__ = __div__ + + def __add__(self, other): + # Add together two SEDs, with the following caveats: + # 1) The SEDs must have the same redshift. + # 2) The resulting SED will be defined on the wavelength range set by the overlap of the + # wavelength ranges of the two SED operands. + # 3) The new `wave_list` will be the union of the operand `wave_list`s in the intersecting + # region, even if one or both of the `wave_list`s are empty. + # These conditions ensure that SED addition is commutative. + + if self.redshift != other.redshift: + raise GalSimIncompatibleValuesError( + "Can only add SEDs with same redshift.", self_sed=self, other=other) + + if self.dimensionless and other.dimensionless: + flux_type = '1' + elif self.spectral and other.spectral: + flux_type = 'fphotons' + else: + raise GalSimIncompatibleValuesError( + "Cannot add SEDs with incompatible dimensions.", self_sed=self, other=other) + + wave_list, blue_limit, red_limit = utilities.combine_wave_list(self, other) + + # If both SEDs are `fast`, and both `_fast_spec`s are LookupTables, then make a new + # LookupTable instead and preserve picklability. + # First need to make sure self._fast_spec and other._fast_spec are initialized. Can do this + # by evaluating them at a good wavelength. blue_limit should work. + self(blue_limit) + other(blue_limit) + if (self.fast + and other.fast + and isinstance(self._fast_spec, LookupTable) + and isinstance(other._fast_spec, LookupTable) + and not self._fast_spec.x_log + and not other._fast_spec.x_log + and not self._fast_spec.f_log + and not other._fast_spec.f_log + and self._fast_spec.interpolant == 'linear' + and other._fast_spec.interpolant == 'linear'): + x = wave_list / (1.0 + self.redshift) + f = self._fast_spec(x) + other._fast_spec(x) + # Note: adding splines doesn't quite work at full precision, so only do this for + # linear interpolants. + spec = _LookupTable(x, f, interpolant='linear') + else: + spec = lambda w: self(w*(1.0+self.redshift)) + other(w*(1.0+self.redshift)) + + return SED(spec, wave_type='nm', flux_type=flux_type, + redshift=self.redshift, fast=self.fast, _wave_list=wave_list, + _blue_limit=blue_limit, _red_limit=red_limit) + + def __sub__(self, other): + # Subtract two SEDs, with the same caveats as adding two SEDs. + return self.__add__(-1.0 * other) + +
[docs] def withFluxDensity(self, target_flux_density, wavelength): + """Return a new `SED` with flux density set to ``target_flux_density`` at wavelength + ``wavelength``. + + See `ChromaticObject` docstring for information about how `SED` normalization affects + `ChromaticObject` normalization. + + Parameters: + target_flux_density: The target normalization in photons/nm/cm^2/s. + wavelength: The wavelength, in nm, at which the flux density will be set. + + Returns: + the new normalized SED. + """ + if self.dimensionless: + raise GalSimSEDError("Cannot set flux density of dimensionless SED.", self) + if isinstance(wavelength, units.Quantity): + wavelength_nm = wavelength.to(units.nm, units.spectral()) + current_flux_density = self._call(wavelength_nm.value) + else: + wavelength_nm = wavelength * units.nm + current_flux_density = self._call(wavelength) + if isinstance(target_flux_density, units.Quantity): + target_flux_density = target_flux_density.to( + SED._fphotons, units.spectral_density(wavelength_nm)).value + factor = target_flux_density / current_flux_density + return self * factor
+ +
[docs] def withFlux(self, target_flux, bandpass): + """Return a new `SED` with flux through the `Bandpass` ``bandpass`` set to ``target_flux``. + + See `ChromaticObject` docstring for information about how `SED` normalization affects + `ChromaticObject` normalization. + + Parameters: + target_flux: The desired flux normalization of the SED. + bandpass: A `Bandpass` object defining a filter bandpass. + + Returns: + the new normalized `SED`. + """ + current_flux = self.calculateFlux(bandpass) + norm = target_flux/current_flux + return self * norm
+ +
[docs] def withMagnitude(self, target_magnitude, bandpass): + """Return a new `SED` with magnitude through the `Bandpass` ``bandpass`` set to + ``target_magnitude``. + + Note that this requires ``bandpass`` to have been assigned a zeropoint using + `Bandpass.withZeropoint`. See `ChromaticObject` docstring for information about how `SED` + normalization affects `ChromaticObject` normalization. + + Parameters: + target_magnitude: The desired magnitude of the `SED`. + bandpass: A `Bandpass` object defining a filter bandpass. + + Returns: + the new normalized `SED`. + """ + if bandpass.zeropoint is None: + raise GalSimError("Cannot call SED.withMagnitude on this bandpass, because it does " + "not have a zeropoint. See Bandpass.withZeropoint()") + current_magnitude = self.calculateMagnitude(bandpass) + norm = 10**(-0.4*(target_magnitude - current_magnitude)) + return self * norm
+ +
[docs] def atRedshift(self, redshift): + """Return a new `SED` with redshifted wavelengths. + + Parameters: + redshift: The redshift for the returned `SED` + + Returns: + the redshifted `SED`. + """ + if redshift == self.redshift: + return self + if redshift <= -1: + raise GalSimRangeError("Invalid redshift", redshift, -1.) + zfactor = (1.0 + redshift) / (1.0 + self.redshift) + wave_list = self.wave_list * zfactor + blue_limit = self.blue_limit * zfactor + red_limit = self.red_limit * zfactor + + return SED(self._spec, self.wave_type, self.flux_type, redshift, self.fast, + _wave_list=wave_list, _blue_limit=blue_limit, _red_limit=red_limit)
+ +
[docs] def calculateFlux(self, bandpass): + """Return the flux (photons/cm^2/s) of the `SED` through the `Bandpass` bandpass. + + Parameters: + bandpass: A `Bandpass` object representing a filter, or None to compute the + bolometric flux. For the bolometric flux the integration limits will be + set to (0, infinity), which implies that the `SED` needs to be evaluable + over this entire range. + + Returns: + the flux through the bandpass. + """ + if self.dimensionless: + raise GalSimSEDError("Cannot calculate flux of dimensionless SED.", self) + if bandpass is None: # compute bolometric flux + bandpass = Bandpass(lambda w: 1., 'nm', blue_limit=0., red_limit=SED._bolo_max_wave) + if len(bandpass.wave_list) > 0 or len(self.wave_list) > 0: + slop = 1e-6 # nm + if (self.blue_limit > bandpass.blue_limit + slop + or self.red_limit < bandpass.red_limit - slop): + raise GalSimRangeError("Bandpass is not completely within defined wavelength " + "range for this SED.", + (bandpass.blue_limit, bandpass.red_limit), + self.blue_limit, self.red_limit) + wmin = max(self.blue_limit, bandpass.blue_limit) + wmax = min(self.red_limit, bandpass.red_limit) + if self.fast and isinstance(self._fast_spec, LookupTable): + wf = 1./(1.+self.redshift) / bandpass.wave_factor + ff = 1./bandpass.wave_factor + wmin *= bandpass.wave_factor + wmax *= bandpass.wave_factor + return self._fast_spec.integrate_product(bandpass._tp, wmin, wmax, wf) * ff + else: + w, _, _ = utilities.combine_wave_list(self, bandpass) + if not self.fast and self.flux_type != 'fphotons': + # When not fast, the SED definition is not linear between the wave_list + # points, so this can be slightly inaccurate if the waves are too far apart. + # Add in 100 uniformly spaced points to achieve relative accurace ~few e-6. + w = utilities.merge_sorted([w, np.linspace(w[0], w[-1], 100)]) + interpolant = (bandpass._tp.interpolant if hasattr(bandpass._tp, 'interpolant') + else 'linear') + return _LookupTable(w, bandpass(w), interpolant).integrate_product(self) + else: + return integ.int1d(lambda w: bandpass(w)*self(w), + bandpass.blue_limit, bandpass.red_limit)
+ +
[docs] def calculateMagnitude(self, bandpass): + """Return the `SED` magnitude through a `Bandpass` ``bandpass``. + + Note that this requires ``bandpass`` to have been assigned a zeropoint using + `Bandpass.withZeropoint`. + + Parameters: + bandpass: A `Bandpass` object representing a filter, or None to compute the + bolometric magnitude. For the bolometric magnitude the integration + limits will be set to (0, infinity), which implies that the `SED` needs + to be evaluable over this entire range. + + Returns: + the bandpass magnitude. + """ + if self.dimensionless: + raise GalSimSEDError("Cannot calculate magnitude of dimensionless SED.", self) + if bandpass.zeropoint is None: + raise GalSimError("Cannot do this calculation for a bandpass without an assigned " + "zeropoint") + flux = self.calculateFlux(bandpass) + return -2.5 * np.log10(flux) + bandpass.zeropoint
+ +
[docs] def thin(self, rel_err=1.e-4, trim_zeros=True, preserve_range=True, fast_search=True): + """Remove some tabulated values while keeping the integral over the set of tabulated values + still accurate to ``rel_err``. + + This is only relevant if the `SED` was initialized with a `LookupTable` or from a file + (which internally creates a `LookupTable`). + + Parameters: + rel_err: The relative error allowed in the integral over the `SED` + [default: 1.e-4] + trim_zeros: Remove redundant leading and trailing points where f=0? (The last + leading point with f=0 and the first trailing point with f=0 will + be retained). Note that if both trim_leading_zeros and + preserve_range are True, then the only the range of ``x`` *after* + zero trimming is preserved. [default: True] + preserve_range: Should the original range (``blue_limit`` and ``red_limit``) of the + `SED` be preserved? (True) Or should the ends be trimmed to + include only the region where the integral is significant? (False) + [default: True] + fast_search: If set to True, then the underlying algorithm will use a + relatively fast O(N) algorithm to select points to include in the + thinned approximation. If set to False, then a slower O(N^2) + algorithm will be used. We have found that the slower algorithm + tends to yield a thinned representation that retains fewer samples + while still meeting the relative error requirement. + [default: True] + + Returns: + the thinned `SED`. + """ + if len(self.wave_list) > 0: + rest_wave_native = self._get_rest_native_waves(self.wave_list) + spec_native = self._spec(rest_wave_native) + + # Note that this is thinning in native units, not nm and photons/nm. + interpolant = (self.interpolant if not isinstance(self._spec, LookupTable) + else self._spec.interpolant) + newx, newf = utilities.thin_tabulated_values( + rest_wave_native, spec_native, rel_err=rel_err, + trim_zeros=trim_zeros, preserve_range=preserve_range, + fast_search=fast_search, interpolant=interpolant) + + newspec = _LookupTable(newx, newf, interpolant=interpolant) + return SED(newspec, self.wave_type, self.flux_type, redshift=self.redshift, + fast=self.fast) + else: + return self
+ +
[docs] def calculateDCRMomentShifts(self, bandpass, **kwargs): + """Calculates shifts in first and second moments of PSF due to differential chromatic + refraction (DCR). + + I.e., equations (1) and (2) from Plazas and Bernstein (2012): + + http://arxiv.org/abs/1204.1346). + + Parameters: + bandpass: `Bandpass` through which object is being imaged. + zenith_angle: `Angle` from object to zenith + parallactic_angle: Parallactic angle, i.e. the position angle of the zenith, + measured from North through East. [default: 0] + obj_coord: Celestial coordinates of the object being drawn as a + `CelestialCoord`. [default: None] + zenith_coord: Celestial coordinates of the zenith as a `CelestialCoord`. + [default: None] + HA: Hour angle of the object as an `Angle`. [default: None] + latitude: Latitude of the observer as an `Angle`. [default: None] + pressure: Air pressure in kiloPascals. [default: 69.328 kPa] + temperature: Temperature in Kelvins. [default: 293.15 K] + H2O_pressure: Water vapor pressure in kiloPascals. [default: 1.067 kPa] + + Returns: + a tuple: + + - The first element is the vector of DCR first moment shifts + - The second element is the 2x2 matrix of DCR second (central) moment shifts. + """ + if self.dimensionless: + raise GalSimSEDError("Cannot calculate DCR shifts of dimensionless SED.", self) + + zenith_angle, parallactic_angle, kwargs = dcr.parse_dcr_angles(**kwargs) + + # Any remaining kwargs will get forwarded to galsim.dcr.get_refraction + # Check that they're valid + for kw in kwargs: + if kw not in ('temperature', 'pressure', 'H2O_pressure'): + raise (TypeError("Got unexpected keyword in calculateDCRMomentShifts: {0}" + .format(kw))) + + # Now actually start calculating things. + flux = self.calculateFlux(bandpass) + if len(self.wave_list) > 0 or len(bandpass.wave_list) > 0: + w, _, _ = utilities.combine_wave_list(self, bandpass) + interpolant = (bandpass._tp.interpolant if hasattr(bandpass._tp, 'interpolant') + else 'linear') + bp = _LookupTable(w, bandpass(w), interpolant) + R = lambda w: dcr.get_refraction(w, zenith_angle, **kwargs) + Rbar = bp.integrate_product(lambda w: self(w) * R(w)) / flux + V = bp.integrate_product(lambda w: self(w) * (R(w)-Rbar)**2) / flux + else: + weight = lambda w: bandpass(w) * self(w) + Rbar_kernel = lambda w: dcr.get_refraction(w, zenith_angle, **kwargs) + Rbar = integ.int1d(lambda w: weight(w) * Rbar_kernel(w), + bandpass.blue_limit, bandpass.red_limit) + Rbar /= flux + V_kernel = lambda w: (dcr.get_refraction(w, zenith_angle, **kwargs) - Rbar)**2 + V = integ.int1d(lambda w: weight(w) * V_kernel(w), + bandpass.blue_limit, bandpass.red_limit) + V /= flux + # Rbar and V are computed above assuming that the parallactic angle is 0. Hence we + # need to rotate our frame by the parallactic angle to get the desired output. + sinp, cosp = parallactic_angle.sincos() + rot = np.array([[cosp, -sinp], [sinp, cosp]]) + Rbar = Rbar * rot.dot(np.array([0,1])) + V = rot.dot(np.array([[0, 0], [0, V]])).dot(rot.T) + return Rbar, V
+ +
[docs] def calculateSeeingMomentRatio(self, bandpass, alpha=-0.2, base_wavelength=500): + """Calculates the relative size of a PSF compared to the monochromatic PSF size at + wavelength ``base_wavelength``. + + Parameters: + bandpass: `Bandpass` through which object is being imaged. + alpha: Power law index for wavelength-dependent seeing. [default: + -0.2, the prediction for Kolmogorov turbulence] + base_wavelength: Reference wavelength in nm from which to compute the relative + PSF size. [default: 500] + + Returns: + the ratio of the PSF second moments to the second moments of the reference PSF. + """ + if self.dimensionless: + raise GalSimSEDError("Cannot calculate seeing moment ratio of dimensionless SED.", self) + flux = self.calculateFlux(bandpass) + if len(self.wave_list) > 0 or len(bandpass.wave_list) > 0: + # With three things multiplied together, we can't rely on integrate_product + # being completely accurate if the waves are spaced too far apart, especially with + # a power law being one of the factors. + # So make sure to include a uniform density of points along with the native sed and + # bandpass points. The error goes like dx**3, so 100 points should give relative + # errors of order ~few e-6. + w, _, _ = utilities.combine_wave_list([self, bandpass]) + w = utilities.merge_sorted([w, np.linspace(w[0], w[-1], 100)]) + interpolant = (bandpass._tp.interpolant if hasattr(bandpass._tp, 'interpolant') + else 'linear') + bp = _LookupTable(w, bandpass(w), interpolant) + return bp.integrate_product(lambda w: self(w) * (w/base_wavelength)**(2*alpha)) / flux + else: + weight = lambda w: bandpass(w) * self(w) + kernel = lambda w: (w/base_wavelength)**(2*alpha) + return integ.int1d(lambda w: weight(w) * kernel(w), + bandpass.blue_limit, bandpass.red_limit) / flux
+ + @lazy_property + def _cache_deviate(self): + return dict() + +
[docs] def sampleWavelength(self, nphotons, bandpass, rng=None, npoints=None): + """Sample a number of random wavelength values from the `SED`, possibly as observed through + a `Bandpass` bandpass. + + Parameters: + nphotons: Number of samples (photons) to randomly draw. + bandpass: A `Bandpass` object representing a filter, or None to sample over the full + `SED` wavelength range. + rng: If provided, a random number generator that is any kind of `BaseDeviate` + object. If ``rng`` is None, one will be automatically created from the + system. [default: None] + npoints: Number of points `DistDeviate` should use for its internal interpolation + tables. [default: None, which uses the `DistDeviate` default] + """ + nphotons=int(nphotons) + + key = (bandpass,npoints) + if key in self._cache_deviate: + dev = self._cache_deviate[key] + else: + if bandpass is None: + sed = self + else: + sed = self._mul_bandpass(bandpass) + + xmin = sed.blue_limit / (1.+self.redshift) + xmax = sed.red_limit / (1.+self.redshift) + dev = DistDeviate(function=sed._fast_spec, x_min=xmin, x_max=xmax, + npoints=npoints, clip_neg=True) + self._cache_deviate[key] = dev + + # Reset the deviate explicitly + if rng is not None: dev.reset(rng) + + ret = np.empty(nphotons) + dev.generate(ret) + + if self.redshift != 0: + ret *= (1. + self.redshift) + # Rarely, with the redshift round trip, this can produce wavelengths < blue_limit. + # If this happens, set those values equal to blue_limit. + # I'm not sure if the red limit overrun can happen (we didn't see any in the use case + # that noticed the blue overruns), but it seems prudent to also correct any of these + # that may occur too. Plus it's not noticeably slower using clip to do both at once. + if bandpass is not None: + np.clip(ret, bandpass.blue_limit, bandpass.red_limit, out=ret) + + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, SED) and + self._orig_spec == other._orig_spec and + self.fast == other.fast and + self.wave_type == other.wave_type and + self.flux_type == other.flux_type and + self.redshift == other.redshift and + self.red_limit == other.red_limit and + self.blue_limit == other.blue_limit and + np.array_equal(self.wave_list,other.wave_list))) + + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): + # Cache this in case self._orig_spec or self.wave_list is long. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.SED", self._orig_spec, self.wave_type, self.flux_type, + self.redshift, self.fast, self.blue_limit, self.red_limit, + tuple(self.wave_list))) + return self._hash + + def __repr__(self): + outstr = ('galsim.SED(%r, wave_type=%r, flux_type=%r, redshift=%r, fast=%r, ' + 'interpolant=%r, _wave_list=%r, _blue_limit=%r, _red_limit=%s)')%( + self._orig_spec, self.wave_type, self._flux_type, self.redshift, self.fast, + self.interpolant, self.wave_list, self.blue_limit, + "float('inf')" if self.red_limit == np.inf else repr(self.red_limit)) + return outstr + + def __str__(self): + orig_spec = repr(self._orig_spec) + if len(orig_spec) > 80: + orig_spec = str(self._orig_spec) + return 'galsim.SED(%s, redshift=%s)'%(orig_spec, self.redshift) + + def __getstate__(self): + d = self.__dict__.copy() + if not isinstance(d['_spec'], LookupTable): + del d['_spec'] + d.pop('_fast_spec',None) + del d['_call'] + del d['_get_native_waves'] + del d['_get_rest_native_waves'] + del d['_flux_to_photons'] + return d + + def __setstate__(self, d): + self.__dict__ = d + if '_spec' not in d: + self._initialize_spec() + self._setup_funcs()
+ + +
[docs]class EmissionLine(SED): + """Emission line SED. + + Creates a simple triangle-shaped emission line with a given wavelength and + FWHM. + + Parameters: + wavelength: The wavelength of the line. + fwhm: The full-width-half-max of the line. [default: 1.0] + flux: The integrated flux of the line. [default: 1.0] + wave_type: The units of ``wavelength`` and ``fwhm`` above. See SED + constructor for options. [default: 'nm'] + flux_type: The units of flux used for this SED. See SED constructor + for options. [default: 'fphotons'] + max_wave The maximum wavelength to use in the LookupTable for the SED. + [default: {}; must be > wavelength+fwhm] + **kwargs: Any additional keyword arguments to pass to the `SED` + constructor. + """.format(SED._bolo_max_wave) + def __init__( + self, + wavelength, + fwhm=1.0, + flux=1.0, + wave_type='nm', + flux_type='fphotons', + max_wave=SED._bolo_max_wave, + **kwargs + ): + self.wavelength = wavelength + self.fwhm = fwhm + self.flux = flux + _, wave_factor = SED._parse_wave_type(wave_type) + if wave_factor is None: + raise GalSimValueError("wave_type must be a distance unit", wave_type) + assert max_wave > wavelength + fwhm + + w = wavelength + # Some operations can turn a 0 into 1.e-15, which can lead to large errors + # when integrating from w+fwhm to max_wave. So add a second set of 0's at + # w +- 2*fwhm to make sure it's exactly 0 for most of the range. + spec = LookupTable( + [0.0, w-2*fwhm, w-fwhm, w, w+fwhm, w+2*fwhm, max_wave*wave_factor], + [0, 0, 0, flux/fwhm, 0, 0, 0], + interpolant='linear' + ) + super().__init__( + spec, + wave_type=wave_type, + flux_type=flux_type, + **kwargs + ) + +
[docs] def atRedshift(self, redshift): + """Return a new `EmissionLine` with redshifted wavelengths. + + Parameters: + redshift: The redshift for the returned `EmissionLine` + + Returns: + the redshifted `EmissionLine`. + """ + if redshift == self.redshift: + return self + if redshift <= -1: + raise GalSimRangeError("Invalid redshift", redshift, -1.) + return EmissionLine( + self.wavelength, + self.fwhm, + flux=self.flux, + wave_type=self.wave_type, + flux_type=self.flux_type, + redshift=redshift, + fast=self.fast + )
+ + def __mul__(self, other): + if isinstance(other, (int, float)): + flux = self.flux * other + return EmissionLine( + self.wavelength, + self.fwhm, + flux=flux, + wave_type=self.wave_type, + flux_type=self.flux_type, + redshift=self.redshift, + fast=self.fast + ) + else: + return super().__mul__(other) + + def __div__(self, other): + if isinstance(other, (int, float)): + flux = self.flux / other + return EmissionLine( + self.wavelength, + self.fwhm, + flux=flux, + wave_type=self.wave_type, + flux_type=self.flux_type, + redshift=self.redshift, + fast=self.fast + ) + else: + return super().__div__(other) + + __truediv__ = __div__ + + def __eq__(self, other): + return (self is other or + (isinstance(other, EmissionLine) and + self.wavelength == other.wavelength and + self.fwhm == other.fwhm and + self.flux == other.flux and + self.wave_type == other.wave_type and + self.flux_type == other.flux_type and + self.redshift == other.redshift)) + + def __hash__(self): + return hash(("galsim.EmissionLine", self.wavelength, self.fwhm, self.flux)) + + def __repr__(self): + outstr = ('galsim.EmissionLine(%r, %r, flux=%r, wave_type=%r, flux_type=%r, redshift=%r,' + ' fast=%r)')%( + self.wavelength, self.fwhm, self.flux, self.wave_type, self._flux_type, + self.redshift, self.fast) + return outstr + + def __str__(self): + outstr = 'galsim.EmissionLine(wavelength=%s, fwhm=%s'%(self.wavelength, self.fwhm) + if self.flux != 1.0: + outstr += ', flux=%s'%self.flux + if self.redshift != 0.0: + outstr += ', redshift=%s'%self.redshift + outstr += ')' + return outstr
+ + +# Put this at the bottom to avoid circular import errors. +from .bandpass import Bandpass +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/sensor.html b/docs/_build/html/_modules/galsim/sensor.html new file mode 100644 index 00000000000..efabbbba797 --- /dev/null +++ b/docs/_build/html/_modules/galsim/sensor.html @@ -0,0 +1,615 @@ + + + + + + galsim.sensor — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.sensor

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+
+__all__ = [ 'Sensor', 'SiliconSensor' ]
+
+import numpy as np
+import glob
+import os
+
+from . import _galsim
+from .table import LookupTable
+from .position import PositionI, PositionD
+from .table import LookupTable
+from .random import UniformDeviate
+from . import meta_data
+from .errors import GalSimUndefinedBoundsError, GalSimError
+from .wcs import PixelScale
+
+
[docs]class Sensor: + """ + The base class for other sensor models, and also an implementation of the simplest possible + sensor model that just converts each photon into an electron and drops it in the appropriate + pixel. + """ + def __init__(self): + pass + +
[docs] def accumulate(self, photons, image, orig_center=None, resume=False): + """Accumulate the photons incident at the surface of the sensor into the appropriate + pixels in the image. + + Each photon has a position, which corresponds to the (x,y) position at the top of the + sensor. In general, they may also have incidence directions and wavelengths, although + these are not used by the base class implementation. + + The base class implementation simply accumulates the photons above each pixel into that + pixel. + + Parameters: + photons: A `PhotonArray` instance describing the incident photons. + image: The `Image` into which the photons should be accumuated. + orig_center: The `Position` of the (0,0) point in the original image coordinates. + [default: (0,0)] + resume: Resume accumulating on the same image as a previous call to accumulate. + In the base class, this has no effect, but it can provide an efficiency + gain for some derived classes. [default: False] + + Returns: + the total flux that fell onto the image. + """ + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Calling accumulate on image with undefined bounds") + return photons.addTo(image)
+ +
[docs] def calculate_pixel_areas(self, image, orig_center=PositionI(0,0), use_flux=True): + """Return the pixel areas according to the given sensor. + + If the pixels are all the same size, then this should just return 1.0. + + But if the pixels vary in size, it should return an Image with the pixel areas + relative to the nominal pixel size. The input image gives the flux values if relevant + (e.g. to set the current levels of the brighter-fatter distortions). + + The returned image will have the same size and bounds as the input image, and will have + for its flux values the net pixel area for each pixel according to the sensor model. + + Parameters: + image: The `Image` with the current flux values. + orig_center: The `Position` of the (0,0) point in the original image coordinates. + [default: (0,0)] + use_flux: Whether to properly handle the current flux in the image (True) or + to just calculate the pixel areas for a zero-flux image (False). + [default: True] + + Returns: + either 1.0 or an `Image` with the pixel areas + (The base class return 1.0.) + """ + return 1.
+ + def updateRNG(self, rng): + pass + + def __repr__(self): + return 'galsim.Sensor()' + + def __eq__(self, other): + return (self is other or + (isinstance(other, Sensor) and + repr(self) == repr(other))) # Checks that neither is a subclass + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): return hash(repr(self))
+ + +
[docs]class SiliconSensor(Sensor): + """ + A model of a silicon-based CCD sensor that converts photons to electrons at a wavelength- + dependent depth (probabilistically) and drifts them down to the wells, properly taking + into account the repulsion of previously accumulated electrons (known as the brighter-fatter + effect). + + There are currently four up-to-date sensors shipped with GalSim, which you can specify as the + ``name`` parameter mentioned below. The _50_ indicates 50V back-bias. + + lsst_itl_50_8 + The ITL sensor being used for LSST, using 8 points along each side of the + pixel boundaries. + + lsst_itl_50_32 + The ITL sensor being used for LSST, using 32 points along each side of the + pixel boundaries. (This is more accurate than the lsst_itl_8, but slower.) + + lsst_e2v_50_8 + The E2V sensor being used for LSST, using 8 points along each side of the + pixel boundaries. + + lsst_e2v_50_32 + The E2V sensor being used for LSST, using 32 points along each side of the + pixel boundaries. (This is more accurate than the lsst_e2v_8, but slower.) + + The SiliconSensor model is asymmetric in the behavior along rows and columns in the CCD. + The traditional meaning of (x,y) is (col,row), and the brighter-fatter effect is stronger + along the columns than across the rows, since charge flows more easily in the readout + direction. + + There is also an option to include "tree rings" in the SiliconSensor model, which add small + distortions to the sensor pixel positions due to non-uniform background doping in the silicon + sensor. The tree rings are defined by a center and a radial amplitude function. The radial + function needs to be a `galsim.LookupTable` instance. Note that if you just want a simple + cosine radial function, you can use the helper class method `simple_treerings` to build the + `LookupTable` for you. + + Note that there is an option to transpose the effect if your definition of the image is to + have the readout "columns" along the x direction. E.g. to conform with the LSST Camera + Coordinate System definitions of x,y, which are transposed relative to the usual FITS meanings. + This only affects the direction of the brighter-fatter effect. It does not change the meaning + of treering_center, which should still be defined in terms of the coordinate system of the + images being passed to `accumulate`. + + + Parameters: + name: The base name of the files which contains the sensor information, + presumably calculated from the Poisson_CCD simulator, which may + be specified either as an absolute path or as one of the above names + that are in the ``galsim.meta_data.share_dir/sensors`` directory. + name.cfg should be the file used to simulate the pixel distortions, + and name.dat should containt the distorted pixel information. + [default: 'lsst_itl_50_8'] + strength: Set the strength of the brighter-fatter effect relative to the + amount specified by the Poisson simulation results. [default: 1] + rng: A `BaseDeviate` object to use for the random number generation + for the stochastic aspects of the electron production and drift. + [default: None, in which case one will be made for you] + diffusion_factor: A factor by which to multiply the diffusion. Use 0.0 to turn off the + effect of diffusion entirely. [default: 1.0] + qdist: The maximum number of pixels away to calculate the distortion due to + the charge accumulation. A large value will increase accuracy but + take more time. If it is increased larger than 4, the size of the + Poisson simulation must be increased to match. [default: 3] + nrecalc: The number of electrons to accumulate before recalculating the + distortion of the pixel shapes. [default: 10000] + treering_func: A `LookupTable` giving the tree ring pattern f(r). [default: None] + treering_center: A `PositionD` object with the center of the tree ring pattern in pixel + coordinates, which may be outside the pixel region. [default: None; + required if treering_func is provided] + transpose: Transpose the meaning of (x,y) so the brighter-fatter effect is + stronger along the x direction. [default: False] + """ + _opt_params = { 'name' : str, 'strength' : float, 'diffusion_factor' : float, + 'qdist' : int, 'nrecalc' : float, 'transpose' : bool, + 'treering_func' : LookupTable, 'treering_center' : PositionD } + _takes_rng = True + + def __init__(self, name='lsst_itl_50_8', strength=1.0, rng=None, diffusion_factor=1.0, qdist=3, + nrecalc=10000, treering_func=None, treering_center=PositionD(0,0), + transpose=False): + self.name = name + self.strength = float(strength) + self.rng = UniformDeviate(rng) + self.diffusion_factor = float(diffusion_factor) + self.qdist = int(qdist) + self.nrecalc = float(nrecalc) + self.treering_func = treering_func + self.treering_center = treering_center + self.transpose = bool(transpose) + self._last_image = None + + self.config_file = name + '.cfg' + self.vertex_file = name + '.dat' + if not os.path.isfile(self.config_file): + cfg_file = os.path.join(meta_data.share_dir, 'sensors', self.config_file) + if not os.path.isfile(cfg_file): + raise OSError("Cannot locate file %s or %s"%(self.config_file, cfg_file)) + self.config_file = cfg_file + self.vertex_file = os.path.join(meta_data.share_dir, 'sensors', self.vertex_file) + if not os.path.isfile(self.vertex_file): # pragma: no cover + raise OSError("Cannot locate vertex file %s"%(self.vertex_file)) + + self.config = self._read_config_file(self.config_file) + + # Get the Tree ring radial function, if it exists + if treering_func is None: + # This is a dummy table in the case where no function is specified + # A bit kludgy, but it works + self.treering_func = LookupTable(x=[0.0,1.0], f=[0.0,0.0], interpolant='linear') + elif not isinstance(treering_func, LookupTable): + raise TypeError("treering_func must be a galsim.LookupTable") + if not isinstance(treering_center, PositionD): + raise TypeError("treering_center must be a galsim.PositionD") + + # Now we read in the absorption length table: + abs_file = os.path.join(meta_data.share_dir, 'sensors', 'abs_length.dat') + self._read_abs_length(abs_file) + self._init_silicon() + + def _init_silicon(self): + diff_step = self._calculate_diff_step() * self.diffusion_factor + NumVertices = self.config['NumVertices'] + Nx = self.config['PixelBoundaryNx'] + Ny = self.config['PixelBoundaryNy'] + # This parameter may be either PixelSize or PixelSizeX. + PixelSize = self.config['PixelSizeX'] + SensorThickness = self.config['SensorThickness'] + num_elec = float(self.config['CollectedCharge_0_0']) / self.strength + # Scale this too, especially important if strength >> 1 + self.effective_nrecalc = float(self.nrecalc) / self.strength + vertex_data = np.loadtxt(self.vertex_file, skiprows = 1) + + if vertex_data.shape != (Nx * Ny * (4 * NumVertices + 4), 5): # pragma: no cover + raise OSError("Vertex file %s does not match config file %s"%( + self.vertex_file, self.config_file)) + + _vertex_data = vertex_data.__array_interface__['data'][0] + self._silicon = _galsim.Silicon(NumVertices, num_elec, Nx, Ny, self.qdist, + diff_step, PixelSize, SensorThickness, _vertex_data, + self.treering_func._tab, self.treering_center._p, + self.abs_length_table._tab, self.transpose) + + def updateRNG(self, rng): + self.rng.reset(rng) + + def __str__(self): + s = 'galsim.SiliconSensor(%r'%self.name + if self.strength != 1.: s += ', strength=%f'%self.strength + if self.diffusion_factor != 1.: s += ', diffusion_factor=%f'%self.diffusion_factor + if self.transpose: s += ', transpose=True' + s += ')' + return s + + def __repr__(self): + return ('galsim.SiliconSensor(name=%r, strength=%f, rng=%r, diffusion_factor=%f, ' + 'qdist=%d, nrecalc=%f, treering_func=%r, treering_center=%r, transpose=%r)')%( + self.name, self.strength, self.rng, + self.diffusion_factor, self.qdist, self.nrecalc, + self.treering_func, self.treering_center, self.transpose) + + def __eq__(self, other): + return (self is other or + (isinstance(other, SiliconSensor) and + self.config == other.config and + self.strength == other.strength and + self.rng == other.rng and + self.diffusion_factor == other.diffusion_factor and + self.qdist == other.qdist and + self.nrecalc == other.nrecalc and + self.treering_func == other.treering_func and + self.treering_center == other.treering_center and + self.transpose == other.transpose)) + + __hash__ = None + + def __getstate__(self): + d = self.__dict__.copy() + del d['_silicon'] + d['_last_image'] = None # Don't save this through a serialization. + return d + + def __setstate__(self, d): + self.__dict__ = d + self._init_silicon() # Build the _silicon object. + +
[docs] def accumulate(self, photons, image, orig_center=PositionI(0,0), resume=False): + """Accumulate the photons incident at the surface of the sensor into the appropriate + pixels in the image. + + Parameters: + photons: A `PhotonArray` instance describing the incident photons + image: The `Image` into which the photons should be accumuated. + orig_center: The `Position` of the (0,0) point in the original image coordinates. + [default: (0,0)] + resume: Resume accumulating on the same image as a previous call to accumulate. + This skips an initial (slow) calculation at the start of the + accumulation to see what flux is already on the image, which can + be more efficient, especially when the number of pixels is large. + [default: False] + + Returns: + the total flux that fell onto the image. + """ + if resume and image is not self._last_image: + if self._last_image is None: + raise GalSimError("accumulate called with resume, but accumulate has not " + "been been run yet.") + else: + raise GalSimError("accumulate called with resume, but provided image does " + "not match one used in the previous accumulate call.") + self._last_image = image + if not image.bounds.isDefined(): + raise GalSimUndefinedBoundsError("Calling accumulate on image with undefined bounds") + + if resume: + # The number in this batch is the total per recalc minus the number of photons + # shot in the last pass(es) of this function since being updated. + nbatch = self.effective_nrecalc - self._accum_flux_since_update + + # We also need to subtract off the delta image from the last pass. + # This represents the flux in the image that hasn't updated the pixel boundaries + # yet. So the first accumulate below will continue to add to this, and the whole + # delta image will be added at the end of that call. Thus we remove it now, so it's + # not added twice. + self._silicon.subtractDelta(image._image) + else: + nbatch = self.effective_nrecalc + self._silicon.initialize(image._image, orig_center._p); + self._accum_flux_since_update = 0 + + i1 = 0 + nphotons = len(photons) + # added_flux is how much flux acctually lands on the sensor. + # accum_flux is how much flux is in the photons that we have accumulated so far. + # cumsum_flux is an array with the cumulate sum of the photon fluxes in the photon array. + added_flux = accum_flux = 0. + cumsum_flux = np.cumsum(photons.flux) + while i1 < nphotons: + i2 = np.searchsorted(cumsum_flux, accum_flux+nbatch) + 1 + i2 = min(i2, nphotons) + added_flux += self._silicon.accumulate(photons._pa, i1, i2, self.rng._rng, image._image) + if i2 < nphotons: + self._silicon.update(image._image) + nbatch = self.effective_nrecalc # In case the first pass was a resume + accum_flux = cumsum_flux[i2-1] + self._accum_flux_since_update = 0. + else: + self._accum_flux_since_update += cumsum_flux[-1] - accum_flux + i1 = i2 + + # On the last pass, we don't update the pixel positions, but we do need to add the + # current running delta image to the full image. + self._silicon.addDelta(image._image) + + return added_flux
+ +
[docs] def calculate_pixel_areas(self, image, orig_center=PositionI(0,0), use_flux=True): + """Create an image with the corresponding pixel areas according to the `SiliconSensor` + model. + + The input image gives the flux values used to set the current levels of the brighter-fatter + distortions. + + The returned image will have the same size and bounds as the input image, and will have + for its flux values the net pixel area for each pixel according to the `SiliconSensor` + model. + + Note: The areas here are in units of the nominal pixel area. This does not account for + any conversion from pixels to sky units using the image wcs (if any). + + Parameters: + image: The `Image` with the current flux values. + orig_center: The `Position` of the (0,0) point in the original image coordinates. + [default: (0,0)] + use_flux: Whether to properly handle the current flux in the image (True) or + to just calculate the pixel areas for a zero-flux image (False). + [default: True] (Note that use_flux=True potentially uses a lot of + memory!) + + Returns: + an `Image` with the pixel areas + """ + area_image = image.copy() + area_image.wcs = PixelScale(1.0) + self._silicon.fill_with_pixel_areas(area_image._image, orig_center._p, use_flux) + return area_image
+ + def _read_config_file(self, filename): + # This reads the Poisson simulator config file for + # the settings that were run + # and returns a dictionary with the values + + with open(filename,'r') as file: + lines=file.readlines() + lines = [ l.strip() for l in lines ] + lines = [ l.split() for l in lines if len(l) > 0 and l[0] != '#' ] + if any([l[1] != '=' for l in lines]): # pragma: no cover + raise OSError("Error reading config file %s"%filename) + config = dict([(l[0], l[2]) for l in lines]) + # convert strings to int or float values when appropriate + for k in config: + try: + config[k] = eval(config[k]) + except (SyntaxError, NameError): + pass + return config + + def _read_abs_length(self, filename): + # This reads in a table of absorption + # length vs wavelength in Si. + # The ipython notebook that created the data + # file from astropy is in the same directory + # in share/sensors/absorption + abs_data = np.loadtxt(filename, skiprows = 1) + xarray = abs_data[:,0] + farray = abs_data[:,1] + table = LookupTable(x=xarray, f=farray, interpolant='linear') + self.abs_length_table = table + return + + def _calculate_diff_step(self): + NumPhases = self.config['NumPhases'] + CollectingPhases = self.config['CollectingPhases'] + # I'm assuming square pixels for now. + PixelSize = self.config['PixelSizeX'] + SensorThickness = self.config['SensorThickness'] + ChannelStopWidth = self.config['ChannelStopWidth'] + FieldOxideTaper = self.config["FieldOxideTaper"] + Vbb = self.config['Vbb'] + Vparallel_lo = self.config['Vparallel_lo'] + Vparallel_hi = self.config['Vparallel_hi'] + qfh = self.config["qfh"] + CCDTemperature = self.config['CCDTemperature'] + # This calculates the diffusion step size given the detector + # parameters. The diffusion step size is the mean radius of diffusion + # assuming the electron propagates the full width of the sensor. + # It depends on the temperature, the sensor voltages, and + # the diffusion_factor parameter. + # The diffusion sigma will be scaled in Silicon.cpp + # depending on the conversion depth + + # Set up the diffusion step size at the operating temperature + # First, calculate the approximate front side voltage + VChannelStop = qfh # near zero + VCollect = Vparallel_hi + 12.0 # Estimate from simulation + VBarrier = Vparallel_lo + 15.0 # Estimate from simulation + ChannelStopRegionWidth = 2.0 * (ChannelStopWidth / 2.0 + FieldOxideTaper) + ChannelStopRegionArea = ChannelStopRegionWidth * PixelSize + CollectArea = (PixelSize - ChannelStopRegionWidth) * PixelSize * CollectingPhases / NumPhases + BarrierArea = (PixelSize - ChannelStopRegionWidth) * PixelSize * (NumPhases - CollectingPhases) / NumPhases + Vfront = (ChannelStopRegionArea * VChannelStop + CollectArea * VCollect + BarrierArea * VBarrier) / (PixelSize**2) + # Then, the total voltage across the silicon + Vdiff = max(Vfront - Vbb, 1.0) # This just makes sure that Vdiff is always > 1.0V + MobilityFactor = 0.27 # This is the factor from Green et.al. + # 0.026 is kT/q at room temp (298 K) + diff_step = np.sqrt(2 * 0.026 * CCDTemperature / 298.0 / Vdiff / MobilityFactor) * SensorThickness + return diff_step + +
[docs] @classmethod + def simple_treerings(cls, amplitude=0.5, period=100., r_max=8000., dr=None): + r"""Make a simple sinusoidal tree ring pattern that can be used as the ``treering_func`` + parameter of `SiliconSensor`. + + The functional form is :math:`f(r) = A \cos(2 \pi r/P)` where :math:`A` is the + ``amplitude`` and :math:`P` is the ``period``. + + Parameters: + amplitude: The amplitude of the tree ring pattern distortion. Typically + this is less than 0.01 pixels. [default: 0.5] + period: The period of the tree ring distortion pattern, in pixels. + [default: 100.] + r_max: The maximum value of r to store in the lookup table. [default: 8000] + dr: The spacing to use for the r values. [default: period/100] + """ + k = 2.*np.pi/float(period) + func = lambda r: amplitude * np.cos(k * r) + if dr is None: + dr = period/100. + npoints = int(r_max / dr) + 1 + return LookupTable.from_func(func, x_min=0., x_max=r_max, npoints=npoints)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/sersic.html b/docs/_build/html/_modules/galsim/sersic.html new file mode 100644 index 00000000000..8ba539d0b58 --- /dev/null +++ b/docs/_build/html/_modules/galsim/sersic.html @@ -0,0 +1,549 @@ + + + + + + galsim.sersic — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.sersic

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Sersic', 'DeVaucouleurs' ]
+
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors
+
+
[docs]class Sersic(GSObject): + r"""A class describing a Sersic profile. + + The Sersic surface brightness profile is characterized by three properties: its Sersic index + ``n``, its ``flux``, and either the ``half_light_radius`` or ``scale_radius``. Given these + properties, the surface brightness profile scales as + + .. math:: + I(r) \sim e^{-(r/r_0)^{1/n}} + + where :math:`r_0` is the ``scale_radius``, or + + .. math:: + I(r) \sim e^{-b (r/r_e)^{1/n}} + + where :math:`r_e` is the ``half_light_radius`` and :math:`b` is calculated to give the right + half-light radius. + + For more information, refer to + + http://en.wikipedia.org/wiki/Sersic_profile + + The allowed range of values for the ``n`` parameter is 0.3 <= n <= 6.2. An exception will be + thrown if you provide a value outside that range. Below n=0.3, there are severe numerical + problems. Above n=6.2, we found that the code begins to be inaccurate when sheared or + magnified (at the level of upcoming shear surveys), so we do not recommend extending beyond + this. See Issues #325 and #450 for more details. + + Sersic profile calculations take advantage of Hankel transform tables that are precomputed for a + given value of n when the Sersic profile is initialized. Making additional objects with the + same n can therefore be many times faster than making objects with different values of n that + have not been used before. Moreover, these Hankel transforms are only cached for a maximum of + 100 different n values at a time. For this reason, for large sets of simulations, it is worth + considering the use of only discrete n values rather than allowing it to vary continuously. For + more details, see https://github.com/GalSim-developers/GalSim/issues/566. + + Note that if you are building many Sersic profiles using truncation, the code will be more + efficient if the truncation is always the same multiple of ``scale_radius``, since it caches + many calculations that depend on the ratio ``trunc/scale_radius``. + + A Sersic can be initialized using one (and only one) of two possible size parameters: + ``scale_radius`` or ``half_light_radius``. Exactly one of these two is required. + + Flux of a truncated profile: + + If you are truncating the profile, the optional parameter, ``flux_untruncated``, specifies + whether the ``flux`` and ``half_light_radius`` specifications correspond to the untruncated + profile (``True``) or to the truncated profile (``False``, default). The impact of this + parameter is a little subtle, so we'll go through a few examples to show how it works. + + First, let's examine the case where we specify the size according to the half-light radius. + If ``flux_untruncated`` is True (and ``trunc > 0``), then the profile will be identical + to the version without truncation up to the truncation radius, beyond which it drops to 0. + In this case, the actual half-light radius will be different from the specified half-light + radius. The half_light_radius property will return the true half-light radius. Similarly, + the actual flux will not be the same as the specified value; the true flux is also returned + by the flux property. + + Example:: + + >>> sersic_obj1 = galsim.Sersic(n=3.5, half_light_radius=2.5, flux=40.) + >>> sersic_obj2 = galsim.Sersic(n=3.5, half_light_radius=2.5, flux=40., trunc=10.) + >>> sersic_obj3 = galsim.Sersic(n=3.5, half_light_radius=2.5, flux=40., trunc=10., \\ + flux_untruncated=True) + + >>> sersic_obj1.xValue(galsim.PositionD(0.,0.)) + 237.3094228615618 + >>> sersic_obj2.xValue(galsim.PositionD(0.,0.)) + 142.54505376530574 # Normalization and scale radius adjusted (same half-light radius) + >>> sersic_obj3.xValue(galsim.PositionD(0.,0.)) + 237.30942286156187 + + >>> sersic_obj1.xValue(galsim.PositionD(10.0001,0.)) + 0.011776164687304694 + >>> sersic_obj2.xValue(galsim.PositionD(10.0001,0.)) + 0.0 + >>> sersic_obj3.xValue(galsim.PositionD(10.0001,0.)) + 0.0 + + >>> sersic_obj1.half_light_radius + 2.5 + >>> sersic_obj2.half_light_radius + 2.5 + >>> sersic_obj3.half_light_radius + 1.9795101383056892 # The true half-light radius is smaller than the specified value + + >>> sersic_obj1.flux + 40.0 + >>> sersic_obj2.flux + 40.0 + >>> sersic_obj3.flux + 34.56595186009519 # Flux is missing due to truncation + + >>> sersic_obj1.scale_radius + 0.003262738739834598 + >>> sersic_obj2.scale_radius + 0.004754602453641744 # the scale radius needed adjustment to accommodate HLR + >>> sersic_obj3.scale_radius + 0.003262738739834598 # the scale radius is still identical to the untruncated case + + When the truncated Sersic scale is specified with ``scale_radius``, the behavior between the + three cases (untruncated, ``flux_untruncated=True`` and ``flux_untruncated=False``) will be + somewhat different from above. Since it is the scale radius that is being specified, and since + truncation does not change the scale radius the way it can change the half-light radius, the + scale radius will remain unchanged in all cases. This also results in the half-light radius + being the same between the two truncated cases (although different from the untruncated case). + The flux normalization is the only difference between ``flux_untruncated=True`` and + ``flux_untruncated=False`` in this case. + + Example:: + + >>> sersic_obj1 = galsim.Sersic(n=3.5, scale_radius=0.05, flux=40.) + >>> sersic_obj2 = galsim.Sersic(n=3.5, scale_radius=0.05, flux=40., trunc=10.) + >>> sersic_obj3 = galsim.Sersic(n=3.5, scale_radius=0.05, flux=40., trunc=10., \\ + flux_untruncated=True) + + >>> sersic_obj1.xValue(galsim.PositionD(0.,0.)) + 1.010507575186637 + >>> sersic_obj2.xValue(galsim.PositionD(0.,0.)) + 5.786692612210923 # Normalization adjusted to accomodate the flux within trunc radius + >>> sersic_obj3.xValue(galsim.PositionD(0.,0.)) + 1.010507575186637 + + >>> sersic_obj1.half_light_radius + 38.311372735390016 + >>> sersic_obj2.half_light_radius + 5.160062547614234 + >>> sersic_obj3.half_light_radius + 5.160062547614234 # For the truncated cases, the half-light radii are the same + + >>> sersic_obj1.flux + 40.0 + >>> sersic_obj2.flux + 40.0 + >>> sersic_obj3.flux + 6.985044085834393 # Flux is missing due to truncation + + >>> sersic_obj1.scale_radius + 0.05 + >>> sersic_obj2.scale_radius + 0.05 + >>> sersic_obj3.scale_radius + 0.05 + half_light_radius: The half-light radius + + Parameters: + n: The Sersic index, n. + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + scale_radius: The scale radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + trunc: An optional truncation radius at which the profile is made to drop to + zero, in the same units as the size parameter. + [default: 0, indicating no truncation] + flux_untruncated: Should the provided ``flux`` and ``half_light_radius`` refer to the + untruncated profile? See below for more details. [default: False] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "n" : float } + _opt_params = { "flux" : float, "trunc" : float, "flux_untruncated" : bool } + _single_params = [ { "scale_radius" : float , "half_light_radius" : float } ] + + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + _minimum_n = 0.3 # Lower bounds has hard limit at ~0.29 + _maximum_n = 6.2 # Upper bounds is just where we have tested that code works well. + + # The conversion from hlr to scale radius is complicated for Sersic, especially since we + # allow it to be truncated. So we do these calculations in the C++-layer constructor. + def __init__(self, n, half_light_radius=None, scale_radius=None, + flux=1., trunc=0., flux_untruncated=False, gsparams=None): + self._n = float(n) + self._flux = float(flux) + self._trunc = float(trunc) + self._gsparams = GSParams.check(gsparams) + + if self._n < Sersic._minimum_n: + raise GalSimRangeError("Requested Sersic index is too small", + self._n, Sersic._minimum_n, Sersic._maximum_n) + if self._n > Sersic._maximum_n: + raise GalSimRangeError("Requested Sersic index is too large", + self._n, Sersic._minimum_n, Sersic._maximum_n) + + if self._trunc < 0: + raise GalSimRangeError("Sersic trunc must be > 0", self._trunc, 0.) + + # Parse the radius options + if half_light_radius is not None: + if scale_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius or half_light_radius may be specified for Spergel", + half_light_radius=half_light_radius, scale_radius=scale_radius) + self._hlr = float(half_light_radius) + if self._trunc == 0. or flux_untruncated: + self._flux_fraction = 1. + self._r0 = self._hlr / self.calculateHLRFactor() + else: + if self._trunc <= math.sqrt(2.) * self._hlr: + raise GalSimRangeError("Sersic trunc must be > sqrt(2) * half_light_radius", + self._trunc, math.sqrt(2.) * self._hlr) + with convert_cpp_errors(): + self._r0 = _galsim.SersicTruncatedScale(self._n, self._hlr, self._trunc) + elif scale_radius is not None: + self._r0 = float(scale_radius) + self._hlr = 0. + else: + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified for Spergel", + half_light_radius=half_light_radius, scale_radius=scale_radius) + + if self._trunc > 0.: + self._flux_fraction = self.calculateIntegratedFlux(self._trunc) + if flux_untruncated: + # Then update the flux and hlr with the correct values + self._flux *= self._flux_fraction + self._hlr = 0. # This will be updated by getHalfLightRadius if necessary. + else: + self._flux_fraction = 1. + +
[docs] def calculateIntegratedFlux(self, r): + """Return the fraction of the total flux enclosed within a given radius, r""" + with convert_cpp_errors(): + return _galsim.SersicIntegratedFlux(self._n, float(r)/self._r0)
+ +
[docs] def calculateHLRFactor(self): + """Calculate the half-light-radius in units of the scale radius. + """ + with convert_cpp_errors(): + return _galsim.SersicHLR(self._n, self._flux_fraction)
+ + @lazy_property + def _sbp(self): + with convert_cpp_errors(): + return _galsim.SBSersic(self._n, self._r0, self._flux, self._trunc, self.gsparams._gsp) + + @property + def n(self): + """The Sersic parameter n. + """ + return self._n + + @property + def scale_radius(self): + """The scale radius. + """ + return self._r0 + + @property + def trunc(self): + """The truncation radius (if any). + """ + return self._trunc + + @property + def half_light_radius(self): + """The half-light radius. + """ + if self._hlr == 0.: + self._hlr = self._r0 * self.calculateHLRFactor() + return self._hlr + + def __eq__(self, other): + return (self is other or + (isinstance(other, Sersic) and + self.n == other.n and + self.scale_radius == other.scale_radius and + self.trunc == other.trunc and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.SBSersic", self.n, self.scale_radius, self.trunc, self.flux, + self.gsparams)) + + def __repr__(self): + return 'galsim.Sersic(n=%r, scale_radius=%r, trunc=%r, flux=%r, gsparams=%r)'%( + self.n, self.scale_radius, self.trunc, self.flux, self.gsparams) + + def __str__(self): + # Note: for the repr, we use the scale_radius, since that should just flow as is through + # the constructor, so it should be exact. But most people use half_light_radius + # for Sersics, so use that in the looser str() function. + s = 'galsim.Sersic(n=%s, half_light_radius=%s'%(self.n, self.half_light_radius) + if self.trunc != 0.: + s += ', trunc=%s'%self.trunc + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _has_hard_edges(self): + return self._trunc != 0. + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Sersic(n=self.n, scale_radius=self.scale_radius, trunc=self.trunc, flux=flux, + gsparams=self.gsparams)
+ +
[docs]class DeVaucouleurs(Sersic): + r"""A class describing DeVaucouleurs profile objects. + + Surface brightness profile with + + .. math:: + I(r) \sim e^{-(r/r_0)^{1/4}} + + where :math:`r_0` is the ``scale_radius``. This is completely equivalent to a Sersic with n=4. + + For more information, refer to + + http://en.wikipedia.org/wiki/De_Vaucouleurs'_law + + A DeVaucouleurs can be initialized using one (and only one) of two possible size parameters: + ``scale_radius`` or ``half_light_radius``. Exactly one of these two is required. + + Parameters: + scale_radius: The value of scale radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + trunc: An optional truncation radius at which the profile is made to drop to + zero, in the same units as the size parameter. + [default: 0, indicating no truncation] + flux_untruncated: Should the provided ``flux`` and ``half_light_radius`` refer to the + untruncated profile? See the docstring for Sersic for more details. + [default: False] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = {} + _opt_params = { "flux" : float, "trunc" : float, "flux_untruncated" : bool } + _single_params = [ { "scale_radius" : float , "half_light_radius" : float } ] + + def __init__(self, half_light_radius=None, scale_radius=None, flux=1., trunc=0., + flux_untruncated=False, gsparams=None): + super(DeVaucouleurs, self).__init__(n=4, half_light_radius=half_light_radius, + scale_radius=scale_radius, flux=flux, + trunc=trunc, flux_untruncated=flux_untruncated, + gsparams=gsparams) + + def __repr__(self): + return 'galsim.DeVaucouleurs(scale_radius=%r, trunc=%r, flux=%r, gsparams=%r)'%( + self.scale_radius, self.trunc, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.DeVaucouleurs(half_light_radius=%s'%self.half_light_radius + if self.trunc != 0.: + s += ', trunc=%s'%self.trunc + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + +
[docs] @doc_inherit + def withFlux(self, flux): + return DeVaucouleurs(scale_radius=self.scale_radius, trunc=self.trunc, flux=flux, + gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/shapelet.html b/docs/_build/html/_modules/galsim/shapelet.html new file mode 100644 index 00000000000..25b6585360c --- /dev/null +++ b/docs/_build/html/_modules/galsim/shapelet.html @@ -0,0 +1,427 @@ + + + + + + galsim.shapelet — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.shapelet

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Shapelet' ]
+
+import numpy as np
+
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .position import PositionD
+from .image import Image
+from .utilities import lazy_property
+from . import _galsim
+from .errors import GalSimValueError, GalSimIncompatibleValuesError, GalSimNotImplementedError
+
+
+
[docs]class Shapelet(GSObject): + r"""A class describing polar shapelet surface brightness profiles. + + This class describes an arbitrary profile in terms of a shapelet decomposition. A shapelet + decomposition is an eigenfunction decomposition of a 2-d function using the eigenfunctions + of the 2-d quantum harmonic oscillator. The functions are Laguerre polynomials multiplied + by a Gaussian. See Bernstein & Jarvis, 2002 or Massey & Refregier, 2005 for more detailed + information about this kind of decomposition. For this class, we follow the notation of + Bernstein & Jarvis. + + The decomposition is described by an overall scale length, ``sigma``, and a vector of + coefficients, ``b``. The ``b`` vector is indexed by two values, which can be either (p,q) or + (N,m). In terms of the quantum solution of the 2-d harmonic oscillator, p and q are the number + of quanta with positive and negative angular momentum (respectively). Then, N=p+q, m=p-q. + + The 2D image is given by (in polar coordinates): + + .. math:: + I(r,\theta) = \frac{1}{\sigma^2} \sum_{pq} b_{pq} \psi_{pq}(r/\sigma, \theta) + + where :math:`\psi_{pq}` are the shapelet eigenfunctions, given by: + + .. math:: + \psi_pq(r,\theta) = \frac{(-)^q}{\sqrt{\pi}} \sqrt{\frac{q!}{p!}} + r^m \exp(i m \theta) \exp(-r^2/2) L_q^{(m)}(r^2) + + and :math:`L_q^{(m)}(x)` are generalized Laguerre polynomials. + + The coeffients :math:`b_{pq}` are in general complex. However, we require that the resulting + :math:`I(r,\theta)` be purely real, which implies that :math:`b_{pq} = b_{qp}^*` + (where :math:`{}^*` means complex conjugate). + This further implies that :math:`b_{pp}` (i.e. :math:`b_{pq}` with :math:`p==q`) is real. + + 1. Make a blank Shapelet instance with all :math:`b_{pq} = 0.`:: + + >>> shapelet = galsim.Shapelet(sigma, order) + + 2. Make a Shapelet instance using a given vector for the :math:`b_{pq}` values.:: + + >>> order = 2 + >>> bvec = [ 1, 0, 0, 0.2, 0.3, -0.1 ] + >>> shapelet = galsim.Shapelet(sigma, order, bvec) + + We use the following order for the coefficients, where the subscripts are in terms of p,q. + + [ b00 Re(b10) Im(b10) Re(b20) Im(b20) b11 Re(b30) Im(b30) Re(b21) Im(b21) ... ] + + i.e. we progressively increase N, and for each value of N, we start with m=N and go down to + m=0 or 1 as appropriate. And since m=0 is intrinsically real, it only requires one spot + in the list. + + Parameters: + sigma: The scale size in the standard units (usually arcsec). + order: The order of the shapelet decomposition. This is the maximum + N=p+q included in the decomposition. + bvec: The initial vector of coefficients. [default: None, which means to use + all zeros] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "sigma" : float, "order" : int } + + _has_hard_edges = False + _is_axisymmetric = False + _is_analytic_x = True + _is_analytic_k = True + + def __init__(self, sigma, order, bvec=None, gsparams=None): + # Make sure order and sigma are the right type: + self._order = int(order) + self._sigma = float(sigma) + bvec_size = self.size(order) + self._gsparams = GSParams.check(gsparams) + + # Make bvec if necessary + if bvec is None: + self._bvec = np.empty(bvec_size, dtype=float) + else: + if len(bvec) != bvec_size: + raise GalSimIncompatibleValuesError( + "bvec is the wrong size for the provided order", bvec=bvec, order=order) + self._bvec = np.ascontiguousarray(bvec, dtype=float) + + @lazy_property + def _sbp(self): + _bvec = self._bvec.__array_interface__['data'][0] + return _galsim.SBShapelet(self._sigma, self._order, _bvec, self.gsparams._gsp) + +
[docs] @classmethod + def size(cls, order): + """The size of the shapelet vector. + """ + return (order+1)*(order+2)//2;
+ + @property + def sigma(self): + """The scale size, sigma. + """ + return self._sigma + + @property + def order(self): + """The shapelet order. + """ + return self._order + + @property + def bvec(self): + """The vector of shapelet coefficients + """ + return self._bvec + +
[docs] def getPQ(self,p,q): + """Return the (p,q) coefficient. + + Parameters: + p: The p index to get. + q: The q index to get. + + Returns: + a tuple (Re(b_pq), Im(b_pq)) + """ + pq = (p+q)*(p+q+1)//2 + 2*min(p,q) + if p == q: + return self._bvec[pq], 0 + elif p > q: + return self._bvec[pq], self._bvec[pq+1] + else: + return self._bvec[pq], -self._bvec[pq+1]
+ +
[docs] def getNM(self,N,m): + """Return the coefficient according to N,m rather than p,q where N=p+q and m=p-q. + + Parameters: + N: The value of N=p+q to get. + m: The value of m=p-q to get. + + Returns: + a tuple (Re(b_pq), Im(b_pq)) + """ + return self.getPQ((N+m)//2,(N-m)//2)
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Shapelet) and + self.sigma == other.sigma and + self.order == other.order and + np.array_equal(self.bvec, other.bvec) and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Shapelet", self.sigma, self.order, tuple(self.bvec), self.gsparams)) + + def __repr__(self): + return 'galsim.Shapelet(sigma=%r, order=%r, bvec=%r, gsparams=%r)'%( + self.sigma, self.order, self.bvec, self.gsparams) + + def __str__(self): + return 'galsim.Shapelet(sigma=%s, order=%s, bvec=%s)'%(self.sigma, self.order, self.bvec) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbp.maxK() + + @property + def _stepk(self): + return self._sbp.stepK() + + @property + def _centroid(self): + return PositionD(self._sbp.centroid()) + + @property + def _flux(self): + return self._sbp.getFlux() + + @property + def _positive_flux(self): + return self._sbp.getPositiveFlux() + + @property + def _negative_flux(self): + return self._sbp.getNegativeFlux() + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @classmethod + def fit(cls, sigma, order, image, center=None, normalization='flux', gsparams=None): + """Fit for a shapelet decomposition of a given image. + + The optional ``normalization`` parameter mirrors the parameter of the `InterpolatedImage` + class. The following sequence should produce drawn images that are approximate matches to + the original image:: + + >>> image = [...] + >>> shapelet = galsim.FitShapelet(sigma, order, image, normalization='sb') + >>> im2 = shapelet.drawImage(image=im2, scale=image.scale, method='sb') + >>> shapelet = galsim.FitShapelet(sigma, order, image, normalization='flux') + >>> im3 = shapelet.drawImage(image=im3, scale=image.scale, method='no_pixel') + + Then ``im2`` and ``im3`` should be as close as possible to ``image`` for the given ``sigma`` + and ``order``. Increasing the order can improve the fit, as can having ``sigma`` match the + natural scale size of the image. However, it should be noted that some images are not well + fit by a shapelet for any (reasonable) order. + + Parameters: + sigma: The scale size in the standard units (usually arcsec). + order: The order of the shapelet decomposition. This is the maximum + N=p+q included in the decomposition. + image: The `Image` for which to fit the shapelet decomposition + center: The position in pixels to use for the center of the decomposition. + [default: image.true_center] + normalization: The normalization to assume for the image. + [default: "flux"] + gsparams: An optional `GSParams` argument. [default: None] + + Returns: + the fitted Shapelet profile + """ + if not center: + center = image.true_center + # convert from PositionI if necessary + center = PositionD(center) + + if not normalization.lower() in ("flux", "f", "surface brightness", "sb"): + raise GalSimValueError("Invalid normalization requested.", normalization, + ('flux', 'f', 'surface brightneess', 'sb')) + + ret = Shapelet(sigma, order, bvec=None, gsparams=gsparams) + + if image.wcs is not None and not image.wcs._isPixelScale: + # TODO: Add ability for ShapeletFitImage to take jacobian matrix. + raise GalSimNotImplementedError("Sorry, cannot (yet) fit a shapelet model to an image " + "with a non-trivial WCS.") + + # Make it double precision if it is not. + image = Image(image, dtype=np.float64, copy=False) + + _bvec = ret._bvec.__array_interface__['data'][0] + _galsim.ShapeletFitImage(ret._sigma, ret._order, _bvec, + image._image, image.scale, center._p) + + if normalization.lower() == "flux" or normalization.lower() == "f": + ret._bvec /= image.scale**2 + + return ret
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/shear.html b/docs/_build/html/_modules/galsim/shear.html new file mode 100644 index 00000000000..a689d516ceb --- /dev/null +++ b/docs/_build/html/_modules/galsim/shear.html @@ -0,0 +1,555 @@ + + + + + + galsim.shear — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.shear

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Shear', '_Shear' ]
+
+import numpy as np
+
+from .angle import Angle, _Angle, radians
+from .errors import GalSimRangeError, GalSimIncompatibleValuesError
+
+
[docs]class Shear: + r"""A class to represent shears in a variety of ways. + + The Shear object can be initialized in a variety of ways to represent shape distortions. + A shear is an operation that transforms a circle into an ellipse with minor-to-major axis ratio + b/a, with position angle beta, while conserving the area (see below for a discussion of the + implications of this choice). Given the multiple definitions of ellipticity, we have multiple + definitions of shear: + + reduced shear + :math:`|g| = \frac{a - b}{a + b}` + distortion + :math:`|e| = \frac{a^2 - b^2}{a^2 + b^2}` + conformal shear + :math:`\eta = \log(b/a)` + minor-to-major axis ratio + :math:`q = \frac{b}{a}` + + These can be thought of as a magnitude and a real-space position angle :math:`\beta`, or as + two components, e.g., :math:`g_1` and :math:`g_2`, with: + + .. math:: + + g_1 &= |g| \cos(2 \beta) \\ + g_2 &= |g| \sin(2 \beta) + + Note: :math:`\beta` is _not_ the phase of a complex valued shear. Rather, the complex shear is + :math:`g_1 + i g_2 = g \exp(2 i \beta)`. Likewise for :math:`\eta` or :math:`e`. + The phase of the complex value is :math:`2 \beta`. + + The following are all examples of valid calls to initialize a Shear object:: + + >>> s = galsim.Shear() # empty constructor sets ellipticity/shear to zero + >>> s = galsim.Shear(g1=0.05, g2=0.05) + >>> s = galsim.Shear(g1=0.05) # assumes g2=0 + >>> s = galsim.Shear(e1=0.05, e2=0.05) + >>> s = galsim.Shear(e2=0.05) # assumes e1=0 + >>> s = galsim.Shear(eta1=0.07, eta2=-0.1) + >>> s = galsim.Shear(eta=0.05, beta=45.0*galsim.degrees) + >>> s = galsim.Shear(g=0.05, beta=0.25*numpy.pi*galsim.radians) + >>> s = galsim.Shear(e=0.3, beta=30.0*galsim.degrees) + >>> s = galsim.Shear(q=0.5, beta=0.0*galsim.radians) + >>> s = galsim.Shear(0.05 + 0.03j) # Uses the g1,g2 reduced shear definition + + There can be no mixing and matching, e.g., specifying ``g1`` and ``e2``. It is permissible to + only specify one of two components, with the other assumed to be zero. If a magnitude such as + ``e``, ``g``, ``eta``, or ``q`` is specified, then ``beta`` is also required to be specified. + It is possible to initialize a Shear with zero reduced shear by specifying no args or kwargs, + i.e. ``galsim.Shear()``. + + In addition, for use cases where extreme efficiency is required, you can skip all the + normal sanity checks and branches in the regular Shear constructor by using a leading + underscore with the complex shear ``g1 + 1j * g2``:: + + >>> s = galsim._Shear(0.05 + 0.03j) # Equivalent to galsim.Shear(g1=0.05, g2=0.03) + + Analagous to the construction options, one can access the shear in the same variety of + definitions. + + Attributes: + g1: The first component of the shear in the "reduced shear" definition. + g2: The second component of the shear in the "reduced shear" definition. + g: The magnitude of the shear in the "reduced shear" definition. + e1: The first component of the shear in the "distortion" definition. + e2: The second component of the shear in the "distortion" definition. + e: The magnitude of the shear in the "distortion" definition. + eta1: The first component of the shear in the "conformal shear" definition. + eta2: The second component of the shear in the "conformal shear" definition. + eta: The magnitude of the shear in the "conformal shear" definition. + q: The minor-to-major axis ratio + beta: The position angle as an `Angle` instance + shear: The reduced shear as a complex number g1 + 1j * g2. + + .. note:: + Since we have defined a Shear as a transformation that preserves area, this means that it + is not a precise description of what happens during the process of weak lensing. + + The coordinate transformation that occurs during the actual weak lensing process is such + that if a galaxy is sheared by some :math:`(\gamma_1, \gamma_2)`, and then sheared by + :math:`(-\gamma_1, -\gamma_2)``, it will in the end return to its original shape, but will + have changed in area due to the magnification, + + .. math:: + + \mu = \frac{1}{(1-\kappa)^2 - (\gamma_1^2 + \gamma_2^2)} + + which is not equal to 1 for non-zero shear even for convergence :math:`\kappa=0`. + + Application of a `Shear` using the `GSObject.shear` method does not include this area + change. To properly incorporate the effective change in area due to shear, it is necessary + to either: + + (a) define the Shear object, use the `GSObject.shear` method, and separately use the + `GSObject.magnify` method, or + (b) use the `GSObject.lens` method that simultaneously magnifies and shears. + """ + def __init__(self, *args, **kwargs): + + # There is no valid set of >2 keyword arguments, so raise an exception in this case: + if len(kwargs) > 2: + raise TypeError( + "Shear constructor received >2 keyword arguments: %s"%kwargs.keys()) + + if len(args) > 1: + raise TypeError( + "Shear constructor received >1 non-keyword arguments: %s"%str(args)) + + # If a component of e, g, or eta, then require that the other component is zero if not set, + # and don't allow specification of mixed pairs like e1 and g2. + # Also, require a position angle if we didn't get g1/g2, e1/e2, or eta1/eta2 + + # Unnamed arg must be a complex shear + if len(args) == 1: + self._g = args[0] + if not isinstance(self._g, complex): + raise TypeError("Non-keyword argument to Shear must be complex g1 + 1j * g2") + + # Empty constructor means shear == (0,0) + elif not kwargs: + self._g = 0j + + # g1,g2 + elif 'g1' in kwargs or 'g2' in kwargs: + g1 = kwargs.pop('g1', 0.) + g2 = kwargs.pop('g2', 0.) + self._g = g1 + 1j * g2 + if abs(self._g) > 1.: + raise GalSimRangeError("Requested shear exceeds 1.", self._g, 0., 1.) + + # e1,e2 + elif 'e1' in kwargs or 'e2' in kwargs: + e1 = kwargs.pop('e1', 0.) + e2 = kwargs.pop('e2', 0.) + absesq = e1**2 + e2**2 + if absesq > 1.: + raise GalSimRangeError("Requested distortion exceeds 1.",np.sqrt(absesq), 0., 1.) + self._g = (e1 + 1j * e2) * self._e2g(absesq) + + # eta1,eta2 + elif 'eta1' in kwargs or 'eta2' in kwargs: + eta1 = kwargs.pop('eta1', 0.) + eta2 = kwargs.pop('eta2', 0.) + eta = eta1 + 1j * eta2 + abseta = abs(eta) + self._g = eta * self._eta2g(abseta) + + # g,beta + elif 'g' in kwargs: + if 'beta' not in kwargs: + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when g is specified.", + g=kwargs['g'], beta=None) + beta = kwargs.pop('beta') + if not isinstance(beta, Angle): + raise TypeError("beta must be an Angle instance.") + g = kwargs.pop('g') + if g > 1 or g < 0: + raise GalSimRangeError("Requested |shear| is outside [0,1].",g, 0., 1.) + self._g = g * np.exp(2j * beta.rad) + + # e,beta + elif 'e' in kwargs: + if 'beta' not in kwargs: + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when e is specified.", + e=kwargs['e'], beta=None) + beta = kwargs.pop('beta') + if not isinstance(beta, Angle): + raise TypeError("beta must be an Angle instance.") + e = kwargs.pop('e') + if e > 1 or e < 0: + raise GalSimRangeError("Requested distortion is outside [0,1].", e, 0., 1.) + self._g = self._e2g(e**2) * e * np.exp(2j * beta.rad) + + # eta,beta + elif 'eta' in kwargs: + if 'beta' not in kwargs: + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when eta is specified.", + eta=kwargs['eta'], beta=None) + beta = kwargs.pop('beta') + if not isinstance(beta, Angle): + raise TypeError("beta must be an Angle instance.") + eta = kwargs.pop('eta') + if eta < 0: + raise GalSimRangeError("Requested eta is below 0.", eta, 0.) + self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) + + # q,beta + elif 'q' in kwargs: + if 'beta' not in kwargs: + raise GalSimIncompatibleValuesError( + "Shear constructor requires beta when q is specified.", + q=kwargs['q'], beta=None) + beta = kwargs.pop('beta') + if not isinstance(beta, Angle): + raise TypeError("beta must be an Angle instance.") + q = kwargs.pop('q') + if q <= 0 or q > 1: + raise GalSimRangeError("Cannot use requested axis ratio.", q, 0., 1.) + eta = -np.log(q) + self._g = self._eta2g(eta) * eta * np.exp(2j * beta.rad) + + elif 'beta' in kwargs: + raise GalSimIncompatibleValuesError( + "beta provided to Shear constructor, but not g/e/eta/q", + beta=kwargs['beta'], e=None, g=None, q=None, eta=None) + + # check for the case where there are 1 or 2 kwargs that are not valid ones for + # initializing a Shear + if kwargs: + raise TypeError( + "Shear constructor got unexpected extra argument(s): %s"%kwargs.keys()) + + @property + def g1(self): + """The first component of the shear in the "reduced shear" definition. + """ + return self._g.real + + @property + def g2(self): + """The second component of the shear in the "reduced shear" definition. + """ + return self._g.imag + + @property + def g(self): + """The magnitude of the shear in the "reduced shear" definition. + """ + return abs(self._g) + + @property + def beta(self): + """The position angle as an `Angle` instance + """ + return _Angle(0.5 * np.angle(self._g)) + + @property + def shear(self): + """The reduced shear as a complex number g1 + 1j * g2. + """ + + return self._g + + @property + def e1(self): + """The first component of the shear in the "distortion" definition. + """ + return self._g.real * self._g2e(self.g**2) + + @property + def e2(self): + """The second component of the shear in the "distortion" definition. + """ + return self._g.imag * self._g2e(self.g**2) + + @property + def e(self): + """The magnitude of the shear in the "distortion" definition. + """ + return self.g * self._g2e(self.g**2) + + @property + def esq(self): + """The square of the magnitude of the shear in the "distortion" definition. + """ + return self.e**2 + + @property + def eta1(self): + """The first component of the shear in the "conformal shear" definition. + """ + return self._g.real * self._g2eta(self.g) + + @property + def eta2(self): + """The second component of the shear in the "conformal shear" definition. + """ + return self._g.imag * self._g2eta(self.g) + + @property + def eta(self): + """The magnitude of the shear in the "conformal shear" definition. + """ + return self.g * self._g2eta(self.g) + + @property + def q(self): + """The minor-to-major axis ratio + """ + return (1.-self.g) / (1.+self.g) + + # Helpers to convert between different conventions + # Note: These return the scale factor by which to multiply. Not the final value. + def _g2e(self, absgsq): + return 2. / (1.+absgsq) + + def _e2g(self, absesq): + if absesq > 1.e-4: + #return (1. - np.sqrt(1.-absesq)) / absesq + return 1. / (1. + np.sqrt(1.-absesq)) + else: + # Avoid numerical issues near e=0 using Taylor expansion + return 0.5 + absesq*(0.125 + absesq*(0.0625 + absesq*0.0390625)) + + def _g2eta(self, absg): + if absg > 1.e-4: + return 2.*np.arctanh(absg)/absg + else: + # This doesn't have as much trouble with accuracy, but have to avoid absg=0, + # so might as well Taylor expand for small values. + absgsq = absg * absg + return 2. + absgsq*((2./3.) + absgsq*0.4) + + def _eta2g(self, abseta): + if abseta > 1.e-4: + return np.tanh(0.5*abseta)/abseta + else: + absetasq = abseta * abseta + return 0.5 + absetasq*((-1./24.) + absetasq*(1./240.)) + + # define all the various operators on Shear objects + def __neg__(self): + return _Shear(-self._g) + + # order of operations: shear by other._shear, then by self._shear + def __add__(self, other): + return _Shear((self._g + other._g) / (1. + self._g.conjugate() * other._g)) + + # order of operations: shear by -other._shear, then by self._shear + def __sub__(self, other): + return self + (-other) + + def __eq__(self, other): + return self is other or (isinstance(other, Shear) and self._g == other._g) + def __ne__(self, other): + return not self.__eq__(other) + +
[docs] def getMatrix(self): + r"""Return the matrix that tells how this shear acts on a position vector: + + If a field is sheared by some shear, s, then the position (x,y) -> (x',y') + according to: + + .. math:: + + \left( \begin{array}{c} x^\prime \\ y^\prime \end{array} \right) + = S \left( \begin{array}{c} x \\ y \end{array} \right) + + and :math:`S` is the return value of this function ``S = shear.getMatrix()``. + + Specifically, the matrix is + + .. math:: + + S = \frac{1}{\sqrt{1-g^2}} + \left( \begin{array}{cc} 1+g_1 & g_2 \\ + g_2 & 1-g_1 \end{array} \right) + """ + return np.array([[ 1.+self.g1, self.g2 ], + [ self.g2 , 1.-self.g1 ]]) / np.sqrt(1.-self.g**2)
+ +
[docs] def rotationWith(self, other): + r"""Return the rotation angle associated with the addition of two shears. + + The effect of two shears is not just a single net shear. There is also a rotation + associated with it. This is easiest to understand in terms of the matrix representations: + + If ``shear3 = shear1 + shear2`` is a sum of two shears, and the corresponding shear + matrices are :math:`S_1`, :math:`S_2`, and :math:`S_3`, then :math:`S_3 R = S_1 S_2`, + where :math:`R` is a rotation matrix: + + .. math:: + + R = \left( \begin{array}{cc} cos(\theta) & -sin(\theta) \\ + sin(\theta) & cos(\theta) \end{array} \right) + + and :math:`\theta` is the return value (as a `galsim.Angle`) from + ``shear1.rotationWith(shear2)``. + """ + # Save a little time by only working on the first column. + S3 = self.getMatrix().dot(other.getMatrix()[:,:1]) + R = (-(self + other)).getMatrix().dot(S3) + theta = np.arctan2(R[1,0], R[0,0]) + return theta * radians
+ + def __repr__(self): + return 'galsim.Shear(%r)'%(self.shear) + + def __str__(self): + return 'galsim.Shear(g1=%s,g2=%s)'%(self.g1,self.g2) + + def __hash__(self): return hash(self._g)
+ +
[docs]def _Shear(shear): + """Equivalent to ``galsim.Shear(shear)``, but without the overhead of the normal sanity checks + and other options for how to specify the shear. + + Parameters: + shear: The complex shear g1 + 1j * g2. + + Returns: + a `galsim.Shear` instance + """ + ret = Shear.__new__(Shear) + ret._g = shear + return ret
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/spergel.html b/docs/_build/html/_modules/galsim/spergel.html new file mode 100644 index 00000000000..fa3e34d5aa9 --- /dev/null +++ b/docs/_build/html/_modules/galsim/spergel.html @@ -0,0 +1,361 @@ + + + + + + galsim.spergel — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.spergel

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Spergel' ]
+
+import numpy as np
+import math
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .errors import GalSimRangeError, GalSimIncompatibleValuesError, convert_cpp_errors
+
+
+
[docs]class Spergel(GSObject): + r"""A class describing a Spergel profile. + + The Spergel surface brightness profile is characterized by three properties: its Spergel index + ``nu``, its ``flux``, and either the ``half_light_radius`` or ``scale_radius``. Given these + properties, the surface brightness profile scales as + + .. math:: + I(r) \sim \left(\frac{r}{r_0}\right)^\nu K_\nu\left(\frac{r}{r_0}\right) + + where :math:`r_0` is the ``scale_radius`` and :math:`K_\nu` is the modified Bessel function of + the second kind. + + The Spergel profile is intended as a generic galaxy profile, somewhat like a `Sersic` profile, + but with the advantage of being analytic in both real space and Fourier space. The Spergel + index :math:`\nu` plays a similar role to the Sersic index :math:`n`, in that it adjusts the + relative peakiness of the profile core and the relative prominence of the profile wings. + At :math:`\nu = 0.5`, the Spergel profile is equivalent to an `Exponential` profile (or + alternatively an :math`n = 1` `Sersic` profile). At :math:`\nu = -0.6` (and in the radial + range near the half-light radius), the Spergel profile is similar to a `DeVaucouleurs` profile + or :math:`n = 4` `Sersic` profile. + + Note that for :math:`\nu <= 0`, the Spergel profile surface brightness diverges at the origin. + This may lead to rendering problems if the profile is not convolved by either a PSF or a pixel + and the profile center is precisely on a pixel center. + + Due to its analytic Fourier transform and depending on the indices :math:`n` and :math:`\nu`, + the Spergel profile can be considerably faster to draw than the roughly equivalent `Sersic` + profile. For example, the :math:`\nu = -0.6` Spergel profile is roughly 3x faster to draw than + an :math:`n = 4` `Sersic` profile once the `Sersic` profile cache has been set up. However, if + not taking advantage of the cache, for example, if drawing `Sersic` profiles with :math:`n` + continuously varying near 4.0 and Spergel profiles with :math:`\nu` continuously varying near + -0.6, then the Spergel profiles are about 50x faster to draw. At the other end of the galaxy + profile spectrum, the :math:`\nu = 0.5` Spergel profile, :math:`n = 1` `Sersic` profile, and + the `Exponential` profile all take about the same amount of time to draw if cached, and the + Spergel profile is about 2x faster than the `Sersic` profile if uncached. + + For more information, refer to + + D. N. Spergel, "ANALYTICAL GALAXY PROFILES FOR PHOTOMETRIC AND LENSING ANALYSIS," + ASTROPHYS J SUPPL S 191(1), 58-65 (2010) [doi:10.1088/0067-0049/191/1/58]. + + The allowed range of values for the ``nu`` parameter is -0.85 <= ``nu`` <= 4. An exception + will be thrown if you provide a value outside that range. The lower limit is set above the + theoretical lower limit of -1 due to numerical difficulties integrating the *very* peaky + ``nu`` < -0.85 profiles. The upper limit is set to avoid numerical difficulties evaluating the + modified Bessel function of the second kind. + + A Spergel profile can be initialized using one (and only one) of two possible size parameters: + ``scale_radius`` or ``half_light_radius``. Exactly one of these two is required. + + Parameters: + nu: The Spergel index, nu. + half_light_radius: The half-light radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + scale_radius: The scale radius of the profile. Typically given in arcsec. + [One of ``scale_radius`` or ``half_light_radius`` is required.] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "nu" : float } + _opt_params = { "flux" : float} + _single_params = [ { "scale_radius" : float , "half_light_radius" : float } ] + + _has_hard_edges = False + _is_axisymmetric = True + _is_analytic_x = True + _is_analytic_k = True + + # Constrain range of allowed Spergel index nu. Spergel (2010) Table 1 lists values of nu + # from -0.9 to +0.85. We found that nu = -0.9 is too tricky for the GKP integrator to + # handle, however, so the lower limit is -0.85 instead. The upper limit is set by the + # cyl_bessel_k function, which runs into overflow errors for nu larger than about 4.0. + _minimum_nu = -0.85 + _maximum_nu = 4.0 + + def __init__(self, nu, half_light_radius=None, scale_radius=None, + flux=1., gsparams=None): + self._nu = float(nu) + self._flux = float(flux) + self._gsparams = GSParams.check(gsparams) + + if self._nu < Spergel._minimum_nu: + raise GalSimRangeError("Requested Spergel index is too small", + self._nu, Spergel._minimum_nu, Spergel._maximum_nu) + if self._nu > Spergel._maximum_nu: + raise GalSimRangeError("Requested Spergel index is too large", + self._nu, Spergel._minimum_nu, Spergel._maximum_nu) + + # Parse the radius options + if half_light_radius is not None: + if scale_radius is not None: + raise GalSimIncompatibleValuesError( + "Only one of scale_radius or half_light_radius may be specified", + half_light_radius=half_light_radius, scale_radius=scale_radius) + self._hlr = float(half_light_radius) + with convert_cpp_errors(): + self._r0 = self._hlr / _galsim.SpergelCalculateHLR(self._nu) + elif scale_radius is not None: + self._r0 = float(scale_radius) + self._hlr = 0. + else: + raise GalSimIncompatibleValuesError( + "Either scale_radius or half_light_radius must be specified for Spergel", + half_light_radius=half_light_radius, scale_radius=scale_radius) + + @lazy_property + def _sbp(self): + with convert_cpp_errors(): + return _galsim.SBSpergel(self._nu, self._r0, self._flux, self.gsparams._gsp) + + @property + def nu(self): + """The Spergel index, nu + """ + return self._nu + + @property + def scale_radius(self): + """The scale radius + """ + return self._r0 + + @property + def half_light_radius(self): + """The half-light radius + """ + if self._hlr == 0.: + with convert_cpp_errors(): + self._hlr = self._r0 * _galsim.SpergelCalculateHLR(self._nu) + return self._hlr + +
[docs] def calculateIntegratedFlux(self, r): + """Return the integrated flux out to a given radius, r""" + return self._sbp.calculateIntegratedFlux(float(r))
+ +
[docs] def calculateFluxRadius(self, f): + """Return the radius within which the total flux is f""" + return self._sbp.calculateFluxRadius(float(f))
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Spergel) and + self.nu == other.nu and + self.scale_radius == other.scale_radius and + self.flux == other.flux and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.Spergel", self.nu, self.scale_radius, self.flux, self.gsparams)) + + def __repr__(self): + return 'galsim.Spergel(nu=%r, scale_radius=%r, flux=%r, gsparams=%r)'%( + self.nu, self.scale_radius, self.flux, self.gsparams) + + def __str__(self): + s = 'galsim.Spergel(nu=%s, half_light_radius=%s'%(self.nu, self.half_light_radius) + if self.flux != 1.0: + s += ', flux=%s'%self.flux + s += ')' + return s + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + # (1+k^2)^(-1-nu) = maxk_threshold + return math.sqrt(self.gsparams.maxk_threshold ** (-1./(1.+self._nu)) - 1.0) / self._r0 + + @property + def _stepk(self): + R = self.calculateFluxRadius(1.0 - self.gsparams.folding_threshold) * self._r0 + # Go to at least 5*hlr + R = max(R, self.gsparams.stepk_minimum_hlr * self.half_light_radius) + return math.pi / R + + @property + def _max_sb(self): + return self._sbp.maxSB() + + def _xValue(self, pos): + return self._sbp.xValue(pos._p) + + def _kValue(self, kpos): + ksq = (kpos.x**2 + kpos.y**2) * self._r0**2 + return self._flux * (1.+ksq)**(-1.-self._nu) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return Spergel(nu=self.nu, scale_radius=self.scale_radius, flux=flux, + gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/sum.html b/docs/_build/html/_modules/galsim/sum.html new file mode 100644 index 00000000000..2a1e2039797 --- /dev/null +++ b/docs/_build/html/_modules/galsim/sum.html @@ -0,0 +1,478 @@ + + + + + + galsim.sum — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.sum

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Add', 'Sum', ]
+
+import numpy as np
+import copy
+
+from .gsparams import GSParams
+from .gsobject import GSObject
+from .utilities import lazy_property
+from . import _galsim
+from .photon_array import PhotonArray
+from .random import UniformDeviate
+from . import chromatic as chrom
+
+
+
[docs]def Add(*args, **kwargs): + """A function for adding 2 or more `GSObject` or `ChromaticObject` instances. + + This function will inspect its input arguments to decide if a `Sum` object or a + `ChromaticSum` object is required to represent the sum of surface brightness profiles. + + Typically, you do not need to call Add() explicitly. Normally, you would just use the + + operator, which returns a Sum:: + + >>> bulge = galsim.Sersic(n=3, half_light_radius=0.8) + >>> disk = galsim.Exponential(half_light_radius=1.4) + >>> gal = bulge + disk + >>> psf = galsim.Gaussian(sigma=0.3, flux=0.3) + galsim.Gaussian(sigma=0.8, flux=0.7) + + If one of the items is chromatic, it will return a `ChromaticSum`:: + + >>> disk = galsim.Exponential(half_light_radius=1.4) * galsim.SED(sed_file) + >>> gal = bulge + disk + + Parameters: + args: Unnamed args should be a list of objects to add. + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + a `Sum` or `ChromaticSum` instance as appropriate. + """ + if len(args) == 0: + raise TypeError("At least one ChromaticObject or GSObject must be provided.") + elif len(args) == 1: + # 1 argument. Should be either a GSObject or a list of GSObjects + if isinstance(args[0], (GSObject, chrom.ChromaticObject)): + args = [args[0]] + elif isinstance(args[0], list) or isinstance(args[0], tuple): + args = args[0] + else: + raise TypeError("Single input argument must be a GSObject, ChromaticObject or " + + "a (possibly mixed) list of them.") + # else args is already the list of objects + + if any([isinstance(a, chrom.ChromaticObject) for a in args]): + return chrom.ChromaticSum(*args, **kwargs) + else: + return Sum(*args, **kwargs)
+ + +
[docs]class Sum(GSObject): + """A class for adding 2 or more `GSObject` instances. + + The Sum class is used to represent the sum of multiple `GSObject` instances. For example, it + might be used to represent a multiple-component galaxy as the sum of an Exponential and a + DeVaucouleurs, or to represent a PSF as the sum of multiple Gaussian objects. + + Typically, you do not need to construct a Sum object explicitly. Normally, you would just + use the + operator, which returns a Sum:: + + >>> bulge = galsim.Sersic(n=3, half_light_radius=0.8) + >>> disk = galsim.Exponential(half_light_radius=1.4) + >>> gal = bulge + disk + >>> psf = galsim.Gaussian(sigma=0.3, flux=0.3) + galsim.Gaussian(sigma=0.8, flux=0.7) + + You can also use the Add() factory function, which returns a Sum object if none of the + individual objects are chromatic:: + + >>> gal = galsim.Add([bulge,disk]) + + Parameters: + args: Unnamed args should be a list of objects to add. + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to each of the components. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Note: if ``gsparams`` is unspecified (or None), then the Sum instance will use the most + restrictive combination of parameters from each of the component objects. Normally, this means + the smallest numerical value (e.g. folding_threshold, xvalue_accuracy, etc.), but for a few + parameters, the largest numerical value is used. See `GSParams.combine` for details. + + Furthermore, the gsparams used for the Sum (either given explicitly or derived from the + components) will normally be applied to each of the components. It doesn't usually make much + sense to apply stricter-than-normal accuracy or threshold values to one component but not + another in a Sum, so this ensures that they all have consistent rendering behavior. + However, if you want to keep the existing gsparams of the component objects (e.g. because + one object is much fainter and can thus use looser accuracy tolerances), then you may + set ``propagate_gsparams=False``. + """ + def __init__(self, *args, **kwargs): + + # Check kwargs first: + gsparams = kwargs.pop("gsparams", None) + self._propagate_gsparams = kwargs.pop('propagate_gsparams', True) + + # Make sure there is nothing left in the dict. + if kwargs: + raise TypeError( + "Sum constructor got unexpected keyword argument(s): %s"%kwargs.keys()) + + if len(args) == 0: + raise TypeError("At least one ChromaticObject or GSObject must be provided.") + elif len(args) == 1: + # 1 argument. Should be either a GSObject or a list of GSObjects + if isinstance(args[0], GSObject): + args = [args[0]] + elif isinstance(args[0], list) or isinstance(args[0], tuple): + args = args[0] + else: + raise TypeError("Single input argument must be a GSObject or list of them.") + # else args is already the list of objects + + # Consolidate args for Sums of Sums... + new_args = [] + for a in args: + if isinstance(a, Sum): + new_args.extend(a._obj_list) + else: + new_args.append(a) + args = new_args + + # Save the list as an attribute, so it can be inspected later if necessary. + self._obj_list = args + + for obj in args: + if not isinstance(obj, GSObject): + raise TypeError("Arguments to Sum must be GSObjects, not %s"%obj) + + # Figure out what gsparams to use + if gsparams is None: + # If none is given, take the most restrictive combination from the obj_list. + self._gsparams = GSParams.combine([obj.gsparams for obj in args]) + else: + # If something explicitly given, then use that. + self._gsparams = GSParams.check(gsparams) + + # Apply gsparams to all in obj_list. + if self._propagate_gsparams: + self._obj_list = [obj.withGSParams(self._gsparams) for obj in args] + else: + self._obj_list = args + + + @property + def obj_list(self): + """The list of objects being added. + """ + return self._obj_list + + @lazy_property + def _sbp(self): + # NB. I only need this until compound and transform are reimplemented in Python... + sb_list = [obj._sbp for obj in self.obj_list] + return _galsim.SBAdd(sb_list, self.gsparams._gsp) + + @lazy_property + def _flux(self): + flux_list = [obj.flux for obj in self.obj_list] + return np.sum(flux_list) + + @lazy_property + def _noise(self): + # If any of the objects have a noise attribute, then we propagate the sum of the + # noises (they add like variances) to the final sum. + _noise = None + for obj in self.obj_list: + if obj.noise is not None: + if _noise is None: + _noise = obj.noise + else: + _noise += obj.noise + return _noise + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of each object being added. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._obj_list = [ obj.withGSParams(ret._gsparams) for obj in self.obj_list ] + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Sum) and + self.obj_list == other.obj_list and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.Sum", tuple(self.obj_list), self.gsparams, self._propagate_gsparams)) + + def __repr__(self): + return 'galsim.Sum(%r, gsparams=%r, propagate_gsparams=%r)'%(self.obj_list, self.gsparams, + self._propagate_gsparams) + + def __str__(self): + str_list = [ str(obj) for obj in self.obj_list ] + return '(' + ' + '.join(str_list) + ')' + + def _prepareDraw(self): + for obj in self.obj_list: + obj._prepareDraw() + + @lazy_property + def _maxk(self): + maxk_list = [obj.maxk for obj in self.obj_list] + return np.max(maxk_list) + + @lazy_property + def _stepk(self): + stepk_list = [obj.stepk for obj in self.obj_list] + return np.min(stepk_list) + + @lazy_property + def _has_hard_edges(self): + hard_list = [obj.has_hard_edges for obj in self.obj_list] + return bool(np.any(hard_list)) + + @lazy_property + def _is_axisymmetric(self): + axi_list = [obj.is_axisymmetric for obj in self.obj_list] + return bool(np.all(axi_list)) + + @lazy_property + def _is_analytic_x(self): + ax_list = [obj.is_analytic_x for obj in self.obj_list] + return bool(np.all(ax_list)) + + @lazy_property + def _is_analytic_k(self): + ak_list = [obj.is_analytic_k for obj in self.obj_list] + return bool(np.all(ak_list)) + + @lazy_property + def _centroid(self): + cen_list = [obj.centroid * obj.flux for obj in self.obj_list] + return sum(cen_list[1:], cen_list[0]) / self.flux + + @lazy_property + def _positive_flux(self): + pflux_list = [obj.positive_flux for obj in self.obj_list] + return np.sum(pflux_list) + + @lazy_property + def _negative_flux(self): + nflux_list = [obj.negative_flux for obj in self.obj_list] + return np.sum(nflux_list) + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @lazy_property + def _max_sb(self): + sb_list = [obj.max_sb for obj in self.obj_list] + return np.sum(sb_list) + + def _xValue(self, pos): + xv_list = [obj.xValue(pos) for obj in self.obj_list] + return np.sum(xv_list) + + def _kValue(self, pos): + kv_list = [obj.kValue(pos) for obj in self.obj_list] + return np.sum(kv_list) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + self.obj_list[0]._drawReal(image, jac, offset, flux_scaling) + if len(self.obj_list) > 1: + im1 = image.copy() + for obj in self.obj_list[1:]: + obj._drawReal(im1, jac, offset, flux_scaling) + image += im1 + + def _shoot(self, photons, rng): + # We probabilistically choose a component for each photon based on the + # relative flux density of that component for the given wavelength. + comp_flux = np.array([obj.positive_flux + obj.negative_flux for obj in self._obj_list]) + total_flux = np.sum(comp_flux) + + prob = comp_flux / total_flux + cdf = np.cumsum(prob) + + u = np.empty(len(photons)) + UniformDeviate(rng).generate(u) + use_k = np.sum((u >= cdf[:,None]), axis=0) + n = len(self._obj_list) + use_k[use_k==n] = n-1 # This shouldn't be possible, but maybe with numerical rounding... + + fluxPerPhoton = total_flux / len(photons) + + # Shoot the corresponding photons from each component. + for kk, obj in enumerate(self.obj_list): + use = (use_k == kk) # True for each photon where this is the object to shoot from + this_n = np.sum(use) + if this_n == 0: + continue + temp = PhotonArray(this_n) + temp._copyFrom(photons, slice(None), use, do_xy=False, do_flux=False) + obj._shoot(temp, rng) + temp.flux = fluxPerPhoton + photons._copyFrom(temp, use, slice(None)) + + def _drawKImage(self, image, jac=None): + self.obj_list[0]._drawKImage(image, jac) + if len(self.obj_list) > 1: + im1 = image.copy() + for obj in self.obj_list[1:]: + obj._drawKImage(im1, jac) + image += im1 + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + return d + + def __setstate__(self, d): + self.__dict__ = d
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/table.html b/docs/_build/html/_modules/galsim/table.html new file mode 100644 index 00000000000..4450c500d10 --- /dev/null +++ b/docs/_build/html/_modules/galsim/table.html @@ -0,0 +1,1329 @@ + + + + + + galsim.table — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.table

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'LookupTable', 'LookupTable2D', '_LookupTable', '_LookupTable2D', 'trapz', ]
+
+import numpy as np
+import numbers
+import warnings
+
+from . import _galsim
+from ._utilities import lazy_property, basestring
+from .bounds import BoundsD
+from .position import _PositionD
+from .errors import *
+from .interpolant import convert_interpolant
+
+def _str_array(a):
+    # Used by both LookupTable.__str__ and LookupTable2D.__str__
+    # to write the x, f, etc. numpy arrays in an abbreviated form so they don't fill the
+    # screen with numbers.
+    # 1. Write the whole array if it has at most 5 values,
+    # 2. Just write the first two and last two values in the array if longer.
+    # 3. linewidth defaults to 75, which adds annoying linebreaks here.
+    #    1000 should be big enough to mean "never".
+    with np.printoptions(threshold=5, edgeitems=2, linewidth=1000):
+        return repr(a)
+
+
[docs]class LookupTable: + """ + LookupTable represents a lookup table to store function values that may be slow to calculate, + for which interpolating from a lookup table is sufficiently accurate. + + A LookupTable may be constructed from two arrays (lists, tuples, or NumPy arrays of + floats/doubles):: + + >>> args = [...] + >>> vals = [] + >>> for arg in args: + ... val = calculateVal(arg) + ... vals.append(val) + >>> table = galsim.LookupTable(x=args,f=vals) + + Then you can use this table as a replacement for the slow calculation:: + + >>> other_args = [...] + >>> for arg in other_args: + ... val = table(arg) + ... [... use val ...] + + + The default interpolation method is a natural cubic spline. This is usually the best choice, + but we also provide other options, which can be specified by the ``interpolant`` kwarg. The + choices include 'floor', 'ceil', 'linear', 'spline', or a `galsim.Interpolant` object: + + - 'floor' takes the value from the previous argument in the table. + - 'ceil' takes the value from the next argument in the table. + - 'nearest' takes the value from the nearest argument in the table. + - 'linear' does linear interpolation between these two values. + - 'spline' uses a cubic spline interpolation, so the interpolated values are smooth at + each argument in the table. + - a `galsim.Interpolant` object or a string convertible to one. This option can be used for + `Lanczos` or `Quintic` interpolation, for example. + + Note that specifying the string 'nearest' or 'linear' will use a LookupTable-optimized + interpolant instead of `galsim.Nearest` or `galsim.Linear`, though the latter options can still + be used by passing an `Interpolant` object instead of a string. Also note that to use a + `galsim.Interpolant` in a LookupTable, the input data must be equally spaced, or logarithmically + spaced if ``x_log`` is set to True (see below). Finally, although natural cubic spline used + when interpolant='spline' and the cubic convolution interpolant used when the interpolant + is `galsim.Cubic` both produce piecewise cubic polynomial interpolations, their treatments of + the continuity of derivatives are different (the natural spline is smoother). + + There are also two factory functions which can be used to build a LookupTable: + + `LookupTable.from_func` + makes a LookupTable from a callable function + + `LookupTable.from_file` + reads in a file of x and f values. + + The user can also opt to interpolate in log(x) and/or log(f) (if not using a + `galsim.Interpolant`), though this is not the default. It may be a wise choice depending on the + particular function, e.g., for a nearly power-law f(x) (or at least one that is locally + power-law-ish for much of the x range) then it might be a good idea to interpolate in log(x) and + log(f) rather than x and f. + + Parameters: + x: The list, tuple, or NumPy array of ``x`` values. + f: The list, tuple, or NumPy array of ``f(x)`` values. + interpolant: Type of interpolation to use, with the options being 'floor', 'ceil', + 'nearest', 'linear', 'spline', or a `galsim.Interpolant` or string + convertible to one. [default: 'spline'] + x_log: Set to True if you wish to interpolate using log(x) rather than x. Note + that all inputs / outputs will still be x, it's just a question of how the + interpolation is done. [default: False] + f_log: Set to True if you wish to interpolate using log(f) rather than f. Note + that all inputs / outputs will still be f, it's just a question of how the + interpolation is done. [default: False] + """ + def __init__(self, x, f, interpolant='spline', x_log=False, f_log=False): + self.x_log = x_log + self.f_log = f_log + + # Check if interpolant is a string that we understand. If not, try convert_interpolant + if interpolant in ('nearest', 'linear', 'ceil', 'floor', 'spline'): + self._interp1d = None + else: + self._interp1d = convert_interpolant(interpolant) + self.interpolant = interpolant + + # Sanity checks + if len(x) != len(f): + raise GalSimIncompatibleValuesError("Input array lengths don't match", x=x, f=f) + if len(x) < 2: + raise GalSimValueError("Input arrays too small to interpolate", x) + + # turn x and f into numpy arrays so that all subsequent math is possible (unlike for + # lists, tuples). Also make sure the dtype is float + x = np.asarray(x, dtype=float) + if np.all(x[1:] >= x[:-1]): + # Already sorted (a common case, so avoid the sort. + self.x = np.ascontiguousarray(x, dtype=float) + self.f = np.ascontiguousarray(f, dtype=float) + else: + f = np.asarray(f, dtype=float) + s = np.argsort(x) + self.x = np.ascontiguousarray(x[s]) + self.f = np.ascontiguousarray(f[s]) + + self._x_min = self.x[0] + self._x_max = self.x[-1] + if self._x_min == self._x_max: + raise GalSimValueError("All x values are equal", x) + if self.x_log and self.x[0] <= 0.: + raise GalSimValueError("Cannot interpolate in log(x) when table contains x<=0.", x) + if self.f_log and np.any(self.f <= 0.): + raise GalSimValueError("Cannot interpolate in log(f) when table contains f<=0.", f) + + # inf causes problems in some cases, so avoid it if used as the maximum x value. + if self._x_max == np.inf: + self.x[-1] = self._x_max = 1.e300 + + # Check equal-spaced arrays + if self._interp1d is not None: + if self.x_log: + ratio = self.x[1:]/self.x[:-1] + if not np.allclose(ratio, ratio[0]): + raise GalSimIncompatibleValuesError( + "Cannot use a galsim.Interpolant with x_log=True unless log(x) is " + "equally spaced.", + interpolant=interpolant, x_log=x_log, x=x) + else: + dx = np.diff(self.x) + if not np.allclose(dx, dx[0]): + raise GalSimIncompatibleValuesError( + "Cannot use a galsim.Interpolant with x_log=False unless x is " + "equally spaced.", + interpolant=interpolant, x_log=x_log, x=x) + + @lazy_property + def _tab(self): + # Store these as attributes, so don't need to worry about C++ layer persisting them. + self._x = np.log(self.x) if self.x_log else self.x + self._f = np.log(self.f) if self.f_log else self.f + + _x = self._x.__array_interface__['data'][0] + _f = self._f.__array_interface__['data'][0] + if self._interp1d is not None: + return _galsim.LookupTable(_x, _f, len(self._x), self._interp1d._i) + else: + return _galsim.LookupTable(_x, _f, len(self._x), self.interpolant) + + @property + def x_min(self): + """The minimum x value in the lookup table. + """ + return self._x_min + + @property + def x_max(self): + """The maximum x value in the lookup table. + """ + return self._x_max + + def __len__(self): return len(self.x) + +
[docs] def __call__(self, x): + """Interpolate the `LookupTable` to get ``f(x)`` at some ``x`` value(s). + + When the `LookupTable` object is called with a single argument, it returns the value at that + argument. An exception will be thrown automatically if the ``x`` value is outside the + range of the original tabulated values. The value that is returned is the same type as + that provided as an argument, e.g., if a single value ``x`` is provided then a single value + of ``f`` is returned; if a tuple of ``x`` values is provided then a tuple of ``f`` values + is returned; and so on. Even if interpolation was done using the ``x_log`` option, the + user should still provide ``x`` rather than ``log(x)``. + + Parameters: + x: The ``x`` value(s) for which ``f(x)`` should be calculated via interpolation on + the original ``(x,f)`` lookup table. ``x`` can be a single float/double, or a + tuple, list, or arbitrarily shaped 1- or 2-dimensional NumPy array. + + Returns: + the interpolated ``f(x)`` value(s). + """ + orig_x = x + # Handle the log(x) if necessary + if self.x_log: + x = np.log(x) + + x = np.asarray(x, dtype=float) + try: + if x.shape == (): + f = self._tab.interp(float(x)) + else: + dimen = len(x.shape) + if dimen > 1: + f = np.empty_like(x.ravel(), dtype=float) + xx = x.astype(float,copy=False).ravel() + _xx = xx.__array_interface__['data'][0] + _f = f.__array_interface__['data'][0] + self._tab.interpMany(_xx, _f, len(xx)) + f = f.reshape(x.shape) + else: + f = np.empty_like(x, dtype=float) + xx = x.astype(float,copy=False) + _xx = xx.__array_interface__['data'][0] + _f = f.__array_interface__['data'][0] + self._tab.interpMany(_xx, _f, len(xx)) + except RuntimeError: + # If there were points outside the valid range, this will have raised an exception. + # so call _check_range to give a better error message. + self._check_range(orig_x) + raise # pragma: no cover (shouldn't be able to reach here, but just in case.) + + # Handle the log(f) if necessary + if self.f_log: + f = np.exp(f) + return f
+ +
[docs] def integrate(self, x_min=None, x_max=None): + r"""Calculate an estimate of the integral of the tabulated function from x_min to x_max: + + .. math:: + + \int_{x_\mathrm{min}}^{x_\mathrm{max}} f(x) dx + + This function is not implemented for LookupTables that use log for either x or f, + or that use a ``galsim.Interpolant``. Also, if x_min or x_max are beyond the range + of the tabulated function, the function will be considered to be zero there. + + .. note:: + + The simplest version of this function is equivalent in functionality to the numpy + ``trapz`` function. However, it is usually significantly faster. If you have a + time-critical integration for which you are currently using ``np.trapz``:: + + >>> ans = np.trapz(f, x) + + the following replacement may be faster:: + + >>> ans = galsim.trapz(f, x) + + which is an alias for:: + + >>> ans = galsim._LookupTable(x, f, 'linear').integrate() + + Parameters: + x_min: The minimum abscissa to use for the integral. [default: None, which + means to use self.x_min] + x_max: The maximum abscissa to use for the integral. [default: None, which + means to use self.x_max] + + Returns: + an estimate of the integral + """ + if self.x_log: + raise GalSimNotImplementedError("log x spacing not implemented yet.") + if self.f_log: + raise GalSimNotImplementedError("log f values not implemented yet.") + if not isinstance(self.interpolant, basestring): + raise GalSimNotImplementedError( + "Integration with interpolant=%s is not implemented."%(self.interpolant)) + if x_min is None: + x_min = self.x_min + else: + x_min = max(x_min, self.x_min) + if x_max is None: + x_max = self.x_max + else: + x_max = min(x_max, self.x_max) + + if x_min < x_max: + return self._tab.integrate(x_min, x_max) + elif x_min == x_max: + return 0. + else: + return -self.integrate(x_max, x_min)
+ +
[docs] def integrate_product(self, g, x_min=None, x_max=None, x_factor=1.): + r"""Calculate an estimate of the integral of the tabulated function multiplied by a second + function from x_min to x_max: + + .. math:: + + \int_{x_\mathrm{min}}^{x_\mathrm{max}} f(x) g(x) dx + + If the second function, :math:`g(x)`, is another `LookupTable`, then the quadrature will + use the abscissae from both that function and :math:`f(x)` (i.e. ``self``). + Otherwise, the second function will be evaluated at the abscissae of :math:`f(x)`. + + This function is not implemented for LookupTables that use log for either x or f, + or that use a ``galsim.Interpolant``. Also, if x_min or x_max are beyond the range + of either tabulated function, the function will be considered to be zero there. + + Also, the second function :math:`g(x)` is always approximated with linear interpolation + between the abscissae, even if it is a `LookupTable` with a different specified + interpolation. + + Parameters: + g: The function to multiply by the current function for the integral. + x_min: The minimum abscissa to use for the integral. [default: None, which + means to use self.x_min] + x_max: The maximum abscissa to use for the integral. [default: None, which + means to use self.x_max] + x_factor: Optionally scale the x values of f by this factor when doing the integral. + I.e. Find :math:`\int f(x x_\mathrm{factor}) g(x) dx`. [default: 1] + + Returns: + an estimate of the integral + """ + from .utilities import merge_sorted + if self.x_log: + raise GalSimNotImplementedError("log x spacing not implemented yet.") + if self.f_log: + raise GalSimNotImplementedError("log f values not implemented yet.") + if not isinstance(self.interpolant, basestring): + raise GalSimNotImplementedError( + "Integration with interpolant=%s is not implemented."%(self.interpolant)) + if x_min is None: + x_min = self.x_min / x_factor + else: + x_min = max(x_min, self.x_min / x_factor) + if x_max is None: + x_max = self.x_max / x_factor + else: + x_max = min(x_max, self.x_max / x_factor) + if x_min > x_max: + return -self.integrate_product(g, x_max, x_min, x_factor) + elif x_min == x_max: + return 0. + + if isinstance(g, LookupTable): + x_min = max(x_min, g.x_min) + x_max = min(x_max, g.x_max) + if x_min >= x_max: + return 0. + else: + gx = self.x / x_factor + gx = gx[(gx >= x_min) & (gx <= x_max)] + gx = merge_sorted([gx, [x_min, x_max]]) + # Let this raise an appropriate error if g is not a valid function over this domain. + gf = g(gx) + # If g is a constant function (like lambda wave: 1), then this doesn't return + # an array. Make it one. + try: + len(gf) + except TypeError: + gf1 = gf + gf = np.empty_like(gx, dtype=float) + gf.fill(gf1) + g = _LookupTable(gx, gf, 'linear') + + return self._tab.integrate_product(g._tab, float(x_min), float(x_max), float(x_factor))
+ + def _check_range(self, x): + slop = (self.x_max - self.x_min) * 1.e-6 + if np.min(x,initial=self.x_min) < self.x_min - slop: + xx = np.array(x) + raise GalSimRangeError("x value(s) below the range of the LookupTable.", + xx[xx<self.x_min], self.x_min, self.x_max) from None + if np.max(x,initial=self.x_max) > self.x_max + slop: # pragma: no branch + xx = np.array(x) + raise GalSimRangeError("x value(s) above the range of the LookupTable.", + xx[xx>self.x_max], self.x_min, self.x_max) from None + + def getArgs(self): + return self.x + + def getVals(self): + return self.f + + def getInterp(self): + return self.interpolant + + def isLogX(self): + return self.x_log + + def isLogF(self): + return self.f_log + + def __eq__(self, other): + return (self is other or + (isinstance(other, LookupTable) and + np.array_equal(self.x,other.x) and + np.array_equal(self.f,other.f) and + self.x_log == other.x_log and + self.f_log == other.f_log and + self.interpolant == other.interpolant)) + def __ne__(self, other): return not self.__eq__(other) + + def __hash__(self): + # Cache this in case self.x, self.f are long. + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.LookupTable", tuple(self.x), tuple(self.f), self.x_log, + self.f_log, self.interpolant)) + return self._hash + + + def __repr__(self): + return 'galsim.LookupTable(x=array(%r), f=array(%r), interpolant=%r, x_log=%r, f_log=%r)'%( + self.x.tolist(), self.f.tolist(), self.interpolant, self.x_log, self.f_log) + + def __str__(self): + s = 'galsim.LookupTable(x=%s, f=%s'%(_str_array(self.x), _str_array(self.f)) + if self.interpolant != 'spline': + s += ', interpolant=%r'%(self.interpolant) + if self.x_log: + s += ', x_log=True' + if self.f_log: + s += ', f_log=True' + s += ')' + return s + +
[docs] @classmethod + def from_file(cls, file_name, interpolant='spline', x_log=False, f_log=False, amplitude=1.0): + """Create a `LookupTable` from a file of x, f values. + + This reads in a file, which should contain two columns with the x and f values. + + Parameters: + file_name: A file from which to read the ``(x,f)`` pairs. + interpolant: Type of interpolation to use. [default: 'spline'] + x_log: Whether the x values should be uniform in log rather than lienar. + [default: False] + f_log: Whether the f values should be interpolated using their logarithms + rather than their raw values. [default: False] + amplitude: An optional scaling of the f values relative to the values in the file + [default: 1.0] + """ + # We don't require pandas as a dependency, but if it's available, this is much faster. + # cf. http://stackoverflow.com/questions/15096269/the-fastest-way-to-read-input-in-python + ParserError = AttributeError # In case we don't get to the line below where we import + # it from pandas. + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import pandas + from pandas.errors import ParserError + data = pandas.read_csv(file_name, comment='#', sep=r'\s+', header=None) + data = data.values.transpose() + except (ImportError, AttributeError, ParserError): + data = np.loadtxt(file_name).transpose() + if data.shape[0] != 2: + raise GalSimValueError("File provided for LookupTable does not have 2 columns", + file_name) + x=data[0] + f=data[1] + if amplitude != 1.0: + f[:] *= amplitude + return LookupTable(x, f, interpolant=interpolant, x_log=x_log, f_log=f_log)
+ +
[docs] @classmethod + def from_func(cls, func, x_min, x_max, npoints=2000, interpolant='spline', + x_log=False, f_log=False): + """Create a `LookupTable` from a callable function + + This constructs a `LookupTable` over the given range from x_min and x_max, calculating the + corresponding f values from the given function (technically any callable object). + + Parameters: + func: A callable function. + x_min: The minimum x value at which to evalue the function and store in the + lookup table. + x_max: The maximum x value at which to evalue the function and store in the + lookup table. + npoints: Number of x values at which to evaluate the function. [default: 2000] + interpolant: Type of interpolation to use. [default: 'spline'] + x_log: Whether the x values should be uniform in log rather than lienar. + [default: False] + f_log: Whether the f values should be interpolated using their logarithms + rather than their raw values. [default: False] + """ + if x_log: + x = np.exp(np.linspace(np.log(x_min), np.log(x_max), npoints)) + else: + x = np.linspace(x_min, x_max, npoints) + f = np.array([func(xx) for xx in x], dtype=float) + return cls(x, f, interpolant=interpolant, x_log=x_log, f_log=f_log)
+ + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_tab',None) + return d + + def __setstate__(self, d): + self.__dict__ = d
+ +def _LookupTable(x, f, interpolant='spline', x_log=False, f_log=False): + """Make a `LookupTable` but without using any of the sanity checks or array manipulation used + in the normal initializer. + + The input x values must be already sorted. + + Parameters: + x: Strictly increasing NumPy array of ``x`` values. + f: NumPy array of ``f(x)`` values. + interpolant: Type of interpolation to use, with the options being 'floor', 'ceil', + 'nearest', 'linear', 'spline', or a `galsim.Interpolant` or string + convertible to one. [default: 'spline'] + x_log: Set to True if you wish to interpolate using log(x) rather than x. Note + that all inputs / outputs will still be x, it's just a question of how the + interpolation is done. [default: False] + f_log: Set to True if you wish to interpolate using log(f) rather than f. Note + that all inputs / outputs will still be f, it's just a question of how the + interpolation is done. [default: False] + """ + ret = LookupTable.__new__(LookupTable) + ret.x = np.ascontiguousarray(x, dtype=float) + ret.f = np.ascontiguousarray(f, dtype=float) + ret.interpolant = interpolant + ret.x_log = x_log + ret.f_log = f_log + ret._x_min = ret.x[0] + ret._x_max = ret.x[-1] + if interpolant in ('nearest', 'linear', 'ceil', 'floor', 'spline'): + ret._interp1d = None + else: + ret._interp1d = convert_interpolant(interpolant) + return ret + + +
[docs]def trapz(f, x, interpolant='linear'): + """Integrate f(x) using the trapezoidal rule. + + Equivalent to np.trapz(f,x) for 1d array inputs. Intended as a drop-in replacement, + which is usually faster. + + Parameters: + f: The ordinates of the function to integrate. + x: The abscissae of the function to integrate. + interpolant: The interpolant to use between the tabulated points. [default: 'linear', + which matches the behavior of np.trapz] + + Returns: + Estimate of the integral. + """ + if len(x) >= 2: + return _LookupTable(x, f, interpolant=interpolant).integrate() + else: + return 0.
+ + +
[docs]class LookupTable2D: + """ + LookupTable2D represents a 2-dimensional lookup table to store function values that may be slow + to calculate, for which interpolating from a lookup table is sufficiently accurate. A + LookupTable2D is also useful for evaluating periodic 2-d functions given samples from a single + period. + + A LookupTable2D representing the function f(x, y) may be constructed from a list or array of + ``x`` values, a list or array of ``y`` values, and a 2D array of function evaluations at all + combinations of x and y values. For instance:: + + >>> x = np.arange(5) + >>> y = np.arange(8) + >>> z = x + y[:, np.newaxis] # function is x + y, dimensions of z are (8, 5) + >>> tab2d = galsim.LookupTable2D(x, y, z) + + To evaluate new function values with the lookup table, use the () operator:: + + >>> print tab2d(2.2, 3.3) + 5.5 + + The () operator can also accept sequences (lists, tuples, numpy arrays, ...) for the x and y + arguments at which to evaluate the LookupTable2D. Normally, the x and y sequences should have + the same length, which will also be the length of the output sequence:: + + >>> print tab2d([1, 2], [3, 5]) + array([ 4., 7.]) + + If you add ``grid=True`` as an additional kwarg, however, then the () operator will generate + interpolated values at the outer product of x-values and y-values. So in this case, the x and + y sequences can have different lengths Nx and Ny, and the result will be a 2D array with + dimensions (Nx, Ny):: + + >>> print tab2d([1, 2], [3, 5], grid=True) + array([[ 4., 6.], + [ 5., 7.]]) + + The default interpolation method is linear. Other choices for the interpolant are: + + - 'floor' + - 'ceil' + - 'nearest' + - 'spline' (a Catmull-Rom cubic spline). + - a `galsim.Interpolant` or string convertible to one. + + :: + + >>> tab2d = galsim.LookupTable2D(x, y, z, interpolant='floor') + >>> tab2d(2.2, 3.7) + 5.0 + >>> tab2d = galsim.LookupTable2D(x, y, z, interpolant='ceil') + >>> tab2d(2.2, 3.7) + 7.0 + >>> tab2d = galsim.LookupTable2D(x, y, z, interpolant='nearest') + >>> tab2d(2.2, 3.7) + 6.0 + + For interpolant='spline' or a `galsim.Interpolant`, the input arrays must be uniformly spaced. + For interpolant='spline', the derivatives df / dx, df / dy, and d^2 f / dx dy at grid-points may + also optionally be provided if they're known, which will generally yield a more accurate + interpolation (these derivatives will be estimated from finite differences if they're not + provided). + + The ``edge_mode`` keyword describes how to handle extrapolation beyond the initial input range. + Possibilities include: + + - 'raise': raise an exception. (This is the default.) + - 'warn': issues a warning, then falls back to edge_mode='constant'. + - 'constant': Return a constant specified by the ``constant`` keyword. + - 'wrap': infinitely wrap the initial range in both directions. + + In order for LookupTable2D to determine the wrapping period when edge_mode='wrap', either the + x and y grid points need to be equally spaced (in which case the x-period is inferred as + len(x)*(x[1]-x[0]) and similarly for y), or the first/last row/column of f must be identical, + in which case the x-period is inferred as x[-1] - x[0]. (If both conditions are satisfied + (equally-spaced x and y and identical first/last row/column of f, then the x-period is inferred + as len(x)*(x[1]-x[0])):: + + >>> x = np.arange(5) + >>> y = np.arange(8) + >>> z = x + y[:, np.newaxis] # function is x + y, dimensions of z are (8, 5) + >>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='raise') + >>> tab2d(7, 7) + ValueError: Extrapolating beyond input range. + + >>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='constant', constant=1.0) + 1.0 + + >>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='wrap') + ValueError: Cannot wrap `f` array with unequal first/last column/row. + + We extend the x and y arrays with a uniform spacing, though any monotonic spacing would work. + Note that the [(0,1), (0,1)] argument in np.pad below extends the z array by 0 rows/columns in + the leading direction, and 1 row/column in the trailing direction:: + + >>> x = np.append(x, x[-1] + (x[-1]-x[-2])) + >>> y = np.append(y, y[-1] + (y[-1]-y[-2])) + >>> z = np.pad(z, [(0,1), (0,1)], mode='wrap') + >>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='wrap') + >>> tab2d(2., 2.) + 4.0 + >>> tab2d(2.+5, 2.) # The period is 5 in the x direction + 4.0 + >>> tab2d(2.+3*5, 2.+4*8) # The period is 8 in the y direction + 4.0 + + Parameters: + x: Strictly increasing array of ``x`` positions at which to create table. + y: Strictly increasing array of ``y`` positions at which to create table. + f: Nx by Ny input array of function values. + dfdx: Optional first derivative of f wrt x. Only used if interpolant='spline'. + [default: None] + dfdy: Optional first derivative of f wrt y. Only used if interpolant='spline'. + [default: None] + d2fdxdy: Optional cross derivative of f wrt x and y. Only used if + interpolant='spline'. [default: None] + interpolant: Type of interpolation to use. One of 'floor', 'ceil', 'nearest', 'linear', + 'spline', or a `galsim.Interpolant` or string convertible to one. + [default: 'linear'] + edge_mode: Keyword controlling how extrapolation beyond the input range is handled. + See above for details. [default: 'raise'] + constant: A constant to return when extrapolating beyond the input range and + ``edge_mode='constant'``. [default: 0] + """ + def __init__(self, x, y, f, dfdx=None, dfdy=None, d2fdxdy=None, + interpolant='linear', edge_mode='raise', constant=0): + if edge_mode not in ('raise', 'warn', 'wrap', 'constant'): + raise GalSimValueError("Unknown edge_mode.", edge_mode, + ('raise', 'warn', 'wrap', 'constant')) + + x = np.asarray(x, dtype=float) + y = np.asarray(y, dtype=float) + f = np.asarray(f, dtype=float) + + dx = np.diff(x) + dy = np.diff(y) + equal_spaced = np.allclose(dx, dx[0]) and np.allclose(dy, dy[0]) + + if not all(dx > 0): + raise GalSimValueError("x input grids is not strictly increasing.", x) + if not all(dy > 0): + raise GalSimValueError("y input grids is not strictly increasing.", y) + + fshape = f.shape + if fshape != (len(y), len(x)): + raise GalSimIncompatibleValuesError( + "Shape of f incompatible with lengths of x,y", f=f, x=x, y=y) + + # Check if interpolant is a string that we understand. If not, try convert_interpolant + if interpolant in ('nearest', 'linear', 'ceil', 'floor', 'spline'): + self._interp2d = None + padrange = 2 if interpolant == 'spline' else 1 + else: + self._interp2d = convert_interpolant(interpolant) + padrange = int(np.ceil(self._interp2d.xrange)) + self.interpolant = interpolant + + # Check if need equal-spaced arrays + if (self._interp2d is not None or interpolant == 'spline'): + if not equal_spaced: + raise GalSimIncompatibleValuesError( + "Cannot use a galsim.Interpolant in LookupTable2D unless x and y are " + "equally spaced.", interpolant=interpolant, x=x, y=y) + + self.edge_mode = edge_mode + self.constant = float(constant) + + if self.edge_mode == 'wrap': + # Can wrap if x and y arrays are equally spaced ... + if equal_spaced: + if 2*padrange > len(x) or 2*padrange > len(y): + raise GalSimValueError( + "Cannot wrap an image which is smaller than the Interpolant", + (x, y, interpolant)) + # Underlying Table2D requires us to extend x, y, and f. + self.xperiod = x[-1]-x[0]+dx[0] + self.yperiod = y[-1]-y[0]+dy[0] + self.x0 = x[0] + self.y0 = y[0] + x = np.hstack([x[0]-np.cumsum([dx[0]]*(padrange-1)), + x, + x[-1]+np.cumsum([dx[0]]*padrange)]) + y = np.hstack([y[0]-np.cumsum([dy[0]]*(padrange-1)), + y, + y[-1]+np.cumsum([dy[0]]*padrange)]) + f = np.pad(f, [(padrange-1, padrange)]*2, mode='wrap') + # Can also wrap non-uniform grids if edges match + elif (all(f[0] == f[-1]) and all(f[:,0] == f[:,-1])): + self.x0 = x[0] + self.y0 = y[0] + self.xperiod = x[-1] - x[0] + self.yperiod = y[-1] - y[0] + else: + raise GalSimIncompatibleValuesError( + "Cannot use edge_mode='wrap' unless either x and y are equally " + "spaced or first/last row/column of f are identical.", + edge_mode=edge_mode, x=x, y=y, f=f) + + self.x = np.ascontiguousarray(x) + self.y = np.ascontiguousarray(y) + self.f = np.ascontiguousarray(f) + + der_exist = [kw is not None for kw in [dfdx, dfdy, d2fdxdy]] + if self.interpolant == 'spline': + if any(der_exist): + if not all(der_exist): + raise GalSimIncompatibleValuesError( + "Must specify all of dfdx, dfdy, d2fdxdy if one is specified", + dfdx=dfdx, dfdy=dfdy, d2fdxdy=d2fdxdy) + else: + # Use finite differences if derivatives not provided + dfdx = np.empty_like(f) + diffx = self.x[2:] - self.x[:-2] + dfdx[:, 1:-1] = (f[:, 2:] - f[:, :-2])/diffx + dfdx[:, 0] = (f[:, 1] - f[:, 0])/dx[0] + dfdx[:, -1] = (f[:, -1] - f[:, -2])/dx[-1] + + dfdy = np.empty_like(f) + diffy = self.y[2:] - self.y[:-2] + dfdy[1:-1, :] = (f[2:, :] - f[:-2, :])/diffy[:,None] + dfdy[0, :] = (f[1, :] - f[0, :])/dy[0] + dfdy[-1, :] = (f[-1, :] - f[-2, :])/dy[-1] + + d2fdxdy = np.empty_like(f) + d2fdxdy[1:-1, :] = (dfdx[2:, :] - dfdx[:-2, :])/diffy[:,None] + d2fdxdy[0, :] = (dfdx[1, :] - dfdx[0, :])/dy[0] + d2fdxdy[-1, :] = (dfdx[-1, :] - dfdx[-2, :])/dy[-1] + else: + if any(der_exist): + raise GalSimIncompatibleValuesError( + "Only specify dfdx, dfdy, d2fdxdy if interpolant is 'spline'.", + dfdx=dfdx, dfdy=dfdy, d2fdxdy=d2fdxdy, interpolant=interpolant) + + if dfdx is not None: + dfdx = np.ascontiguousarray(dfdx, dtype=float) + dfdy = np.ascontiguousarray(dfdy, dtype=float) + d2fdxdy = np.ascontiguousarray(d2fdxdy, dtype=float) + + if dfdx.shape != f.shape or dfdy.shape != f.shape or d2fdxdy.shape != f.shape: + raise GalSimIncompatibleValuesError( + "derivative shapes must match f shape", + dfdx=dfdx, dfdy=dfdy, d2fdxdy=d2fdxdy) + + self.dfdx = dfdx + self.dfdy = dfdy + self.d2fdxdy = d2fdxdy + + @lazy_property + def _tab(self): + _x = self.x.__array_interface__['data'][0] + _y = self.y.__array_interface__['data'][0] + _f = self.f.__array_interface__['data'][0] + if self._interp2d is not None: + return _galsim.LookupTable2D(_x, _y, _f, len(self.x), len(self.y), + self._interp2d._i) + elif self.interpolant == 'spline': + _dfdx = self.dfdx.__array_interface__['data'][0] + _dfdy = self.dfdy.__array_interface__['data'][0] + _d2fdxdy = self.d2fdxdy.__array_interface__['data'][0] + return _galsim.LookupTable2D(_x, _y, _f, len(self.x), len(self.y), + _dfdx, _dfdy, _d2fdxdy) + else: + return _galsim.LookupTable2D(_x, _y, _f, len(self.x), len(self.y), + self.interpolant) + + def getXArgs(self): + return self.x + + def getYArgs(self): + return self.y + + def getVals(self): + return self.f + + def _inbounds(self, x, y): + """Return whether or not *all* coords specified by x and y are in bounds of the original + interpolated array.""" + # Only used if edge_mode != 'wrap', so original x/y arrays are unmodified. + return (np.min(x) >= self.x[0] and np.max(x) <= self.x[-1] and + np.min(y) >= self.y[0] and np.max(y) <= self.y[-1]) + + def _wrap_args(self, x, y): + """Wrap points back into the fundamental period.""" + # Original x and y may have been modified, so need to use x0 and xperiod attributes here. + #x = (x-self.x0) % self.xperiod + self.x0 + #y = (y-self.y0) % self.yperiod + self.y0 + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _galsim.WrapArrayToPeriod(_x, len(x), self.x0, self.xperiod) + _galsim.WrapArrayToPeriod(_y, len(y), self.y0, self.yperiod) + return x, y + + @property + def _bounds(self): + # Only meaningful if edge_mode is 'raise' or 'warn', in which case original x/y arrays are + # unmodified. + return BoundsD(self.x[0], self.x[-1], self.y[0], self.y[-1]) + + def _call_inbounds(self, x, y, grid=False): + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + if grid: + f = np.empty((len(y), len(x)), dtype=float) + _f = f.__array_interface__['data'][0] + self._tab.interpGrid(_x, _y, _f, len(x), len(y)) + return f + else: + f = np.empty_like(x, dtype=float) + _f = f.__array_interface__['data'][0] + self._tab.interpMany(_x, _y, _f, len(x)) + return f + + def _call_constant(self, x, y, grid=False): + x = np.asarray(x, dtype=float) + y = np.asarray(y, dtype=float) + if grid: + f = np.empty((len(y), len(x)), dtype=float) + # Fill in interpolated values first, then go back and fill in + # constants + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _f = f.__array_interface__['data'][0] + self._tab.interpGrid(_x, _y, _f, len(x), len(y)) + badx = (x < self.x[0]) | (x > self.x[-1]) + bady = (y < self.y[0]) | (y > self.y[-1]) + f[bady, :] = self.constant + f[:, badx] = self.constant + return f + else: + # Start with constant array, then interpolate good positions + f = np.empty_like(x, dtype=float) + f.fill(self.constant) + good = ((x >= self.x[0]) & (x <= self.x[-1]) & + (y >= self.y[0]) & (y <= self.y[-1])) + xx = np.ascontiguousarray(x[good].ravel(), dtype=float) + yy = np.ascontiguousarray(y[good].ravel(), dtype=float) + tmp = np.empty_like(xx, dtype=float) + _xx = xx.__array_interface__['data'][0] + _yy = yy.__array_interface__['data'][0] + _tmp = tmp.__array_interface__['data'][0] + self._tab.interpMany(_xx, _yy, _tmp, len(xx)) + f[good] = tmp + return f + + def _call_raise(self, x, y, grid=False): + if not self._inbounds(x, y): + pos = find_out_of_bounds_position(x, y, self._bounds, grid) + raise GalSimBoundsError("Extrapolating beyond input range.", + pos, self._bounds) + return self._call_inbounds(x, y, grid) + + def _call_warn(self, x, y, grid=False): + if not self._inbounds(x, y): + pos = find_out_of_bounds_position(x, y, self._bounds, grid) + galsim_warn("Extrapolating beyond input range. {!r} not in {!r}".format( + pos, self._bounds)) + return self._call_constant(x, y, grid) + + def _call_wrap(self, x, y, grid=False): + x, y = self._wrap_args(x, y) + return self._call_inbounds(x, y, grid) + +
[docs] def __call__(self, x, y, grid=False): + """Interpolate at an arbitrary point or points. + + Parameters: + x: Either a single x value or an array of x values at which to interpolate. + y: Either a single y value or an array of y values at which to interpolate. + grid: Optional boolean indicating that output should be a 2D array corresponding + to the outer product of input values. If False (default), then the output + array will be congruent to x and y. + + Returns: + a scalar value if x and y are scalar, or a numpy array if x and y are arrays. + """ + if np.__version__ >= "2.0": + # I'm not sure if there is a simpler way to do this in 2.0. + # We want a copy when edge_mode == wrap. Otherwise, only copy if dtype changes or + # x,y aren't already arrays. That used to be simple... + copy = True if self.edge_mode=='wrap' else None + else: + copy = self.edge_mode=='wrap' + x1 = np.array(x, dtype=float, copy=copy) + y1 = np.array(y, dtype=float, copy=copy) + x2 = np.ascontiguousarray(x1.ravel(), dtype=float) + y2 = np.ascontiguousarray(y1.ravel(), dtype=float) + + if self.edge_mode == 'raise': + f = self._call_raise(x2, y2, grid) + elif self.edge_mode == 'warn': + f = self._call_warn(x2, y2, grid) + elif self.edge_mode == 'wrap': + f = self._call_wrap(x2, y2, grid) + else: # constant + f = self._call_constant(x2, y2, grid) + + if isinstance(x, numbers.Real): + return f[0] + else: + if not grid: + f = f.reshape(x1.shape) + return f
+ + def _gradient_inbounds(self, x, y, grid=False): + if grid: + dfdx = np.empty((len(y), len(x)), dtype=float) + dfdy = np.empty((len(y), len(x)), dtype=float) + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _dfdx = dfdx.__array_interface__['data'][0] + _dfdy = dfdy.__array_interface__['data'][0] + self._tab.gradientGrid(_x, _y, _dfdx, _dfdy, len(x), len(y)) + return dfdx, dfdy + else: + dfdx = np.empty_like(x) + dfdy = np.empty_like(x) + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _dfdx = dfdx.__array_interface__['data'][0] + _dfdy = dfdy.__array_interface__['data'][0] + self._tab.gradientMany(_x, _y, _dfdx, _dfdy, len(x)) + return dfdx, dfdy + + def _gradient_raise(self, x, y, grid=False): + if not self._inbounds(x, y): + pos = find_out_of_bounds_position(x, y, self._bounds, grid) + raise GalSimBoundsError("Extrapolating beyond input range.", + pos, self._bounds) + return self._gradient_inbounds(x, y, grid) + + def _gradient_warn(self, x, y, grid=False): + if not self._inbounds(x, y): + pos = find_out_of_bounds_position(x, y, self._bounds, grid) + galsim_warn("Extrapolating beyond input range. {!r} not in {!r}".format( + pos, self._bounds)) + return self._gradient_constant(x, y, grid) + + def _gradient_wrap(self, x, y, grid=False): + x, y = self._wrap_args(x, y) + return self._gradient_inbounds(x, y, grid) + + def _gradient_constant(self, x, y, grid=False): + x = np.asarray(x, dtype=float) + y = np.asarray(y, dtype=float) + if grid: + dfdx = np.empty((len(y), len(x)), dtype=float) + dfdy = np.empty((len(y), len(x)), dtype=float) + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _dfdx = dfdx.__array_interface__['data'][0] + _dfdy = dfdy.__array_interface__['data'][0] + self._tab.gradientGrid(_x, _y, _dfdx, _dfdy, len(x), len(y)) + badx = (x < self.x[0]) | (x > self.x[-1]) + bady = (y < self.y[0]) | (y > self.y[-1]) + dfdx[bady,:] = 0.0 + dfdx[:, badx] = 0.0 + dfdy[bady,:] = 0.0 + dfdy[:, badx] = 0.0 + return dfdx, dfdy + else: + dfdx = np.empty_like(x, dtype=float) + dfdy = np.empty_like(x, dtype=float) + dfdx.fill(0.0) + dfdy.fill(0.0) + good = ((x >= self.x[0]) & (x <= self.x[-1]) & + (y >= self.y[0]) & (y <= self.y[-1])) + x = np.ascontiguousarray(x[good].ravel(), dtype=float) + y = np.ascontiguousarray(y[good].ravel(), dtype=float) + tmp1 = np.empty_like(x, dtype=float) + tmp2 = np.empty_like(x, dtype=float) + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _tmp1 = tmp1.__array_interface__['data'][0] + _tmp2 = tmp2.__array_interface__['data'][0] + self._tab.gradientMany(_x, _y, _tmp1, _tmp2, len(x)) + dfdx[good] = tmp1 + dfdy[good] = tmp2 + return dfdx, dfdy + +
[docs] def gradient(self, x, y, grid=False): + """Calculate the gradient of the function at an arbitrary point or points. + + Parameters: + x: Either a single x value or an array of x values at which to compute + the gradient. + y: Either a single y value or an array of y values at which to compute + the gradient. + grid: Optional boolean indicating that output should be a 2-tuple of 2D arrays + corresponding to the outer product of input values. If False (default), + then the output arrays will be congruent to x and y. + + Returns: + A tuple of (dfdx, dfdy) where dfdx, dfdy are single values (if x,y were single + values) or numpy arrays. + """ + if np.__version__ >= "2.0": + copy = True if self.edge_mode=='wrap' else None + else: + copy = self.edge_mode=='wrap' + x1 = np.array(x, dtype=float, copy=copy) + y1 = np.array(y, dtype=float, copy=copy) + x2 = np.ascontiguousarray(x1.ravel(), dtype=float) + y2 = np.ascontiguousarray(y1.ravel(), dtype=float) + + if self.edge_mode == 'raise': + dfdx, dfdy = self._gradient_raise(x2, y2, grid) + if self.edge_mode == 'warn': + dfdx, dfdy = self._gradient_warn(x2, y2, grid) + elif self.edge_mode == 'wrap': + dfdx, dfdy = self._gradient_wrap(x2, y2, grid) + else: # constant + dfdx, dfdy = self._gradient_constant(x2, y2, grid) + + if isinstance(x, numbers.Real): + return dfdx[0], dfdy[0] + else: + if not grid: + dfdx = dfdx.reshape(x1.shape) + dfdy = dfdy.reshape(x1.shape) + return dfdx, dfdy
+ + def __str__(self): + return "galsim.LookupTable2D(x=%s, y=%s, f=[%s,...,%s], interpolant=%r, edge_mode=%r)"%( + _str_array(self.x), _str_array(self.y), + _str_array(self.f[0]), _str_array(self.f[-1]), + self.interpolant, self.edge_mode) + + def __repr__(self): + return ("galsim.LookupTable2D(x=array(%r), y=array(%r), " + "f=array(%r), interpolant=%r, edge_mode=%r, constant=%r)"%( + self.x.tolist(), self.y.tolist(), self.f.tolist(), self.interpolant, self.edge_mode, + self.constant)) + + def __eq__(self, other): + if self is other: return True + if not (isinstance(other, LookupTable2D) and + np.array_equal(self.x,other.x) and + np.array_equal(self.y,other.y) and + np.array_equal(self.f,other.f) and + self.interpolant == other.interpolant and + self.edge_mode == other.edge_mode and + self.constant == other.constant): + return False + else: + if self.interpolant == 'spline': + return (np.array_equal(self.dfdx, other.dfdx) and + np.array_equal(self.dfdy, other.dfdy) and + np.array_equal(self.d2fdxdy, other.d2fdxdy)) + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __hash__(self): + if not hasattr(self, '_hash'): + self._hash = hash(("galsim.LookupTable2D", tuple(self.x.ravel()), tuple(self.y.ravel()), + tuple(self.f.ravel()), self.interpolant, self.edge_mode, + self.constant, + tuple(self.dfdx.ravel()) if self.dfdx is not None else None, + tuple(self.dfdy.ravel()) if self.dfdy is not None else None, + tuple(self.d2fdxdy.ravel()) if self.d2fdxdy is not None else None)) + return self._hash + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_tab',None) + return d + + def __setstate__(self, d): + self.__dict__ = d
+ + +
[docs]def _LookupTable2D(x, y, f, interpolant, edge_mode, constant, + dfdx=None, dfdy=None, d2fdxdy=None, + x0=None, y0=None, xperiod=None, yperiod=None): + """Make a `LookupTable2D` but without using any of the sanity checks or array manipulation used + in the normal initializer. + """ + ret = LookupTable2D.__new__(LookupTable2D) + ret.x = x + ret.y = y + ret.f = f + ret.interpolant = interpolant + ret.edge_mode = edge_mode + ret.constant = constant + ret.dfdx = dfdx + ret.dfdy = dfdy + ret.d2fdxdy = d2fdxdy + ret.x0 = x0 + ret.y0 = y0 + ret.xperiod = xperiod + ret.yperiod = yperiod + if interpolant in ('nearest', 'linear', 'ceil', 'floor', 'spline'): + ret._interp2d = None + else: + ret._interp2d = convert_interpolant(interpolant) + return ret
+ +
[docs]def find_out_of_bounds_position(x, y, bounds, grid=False): + """Given arrays of x and y values that are known to contain at least one + position that is out-of-bounds of the given bounds instance, return one + such PositionD. + + Parameters: + x: Array of x values + y: Array of y values + bounds: `Bounds` instance + grid: Bool indicating whether to check the outer product of x and y + (grid=True), or each sequential pair of x and y (grid=False). + If the latter, then x and y should have the same shape. + + Returns: + a `PositionD` from x and y that is out-of-bounds of bounds. + """ + if grid: + # It's enough to check corners for grid input + for x_ in (np.min(x), np.max(x)): + for y_ in (np.min(y), np.max(y)): + if (x_ < bounds.xmin or x_ > bounds.xmax or + y_ < bounds.ymin or y_ > bounds.ymax): + return _PositionD(x_, y_) + else: + # Faster to check all points than to iterate through them one-by-one? + w = np.where((x < bounds.xmin) | (x > bounds.xmax) | + (y < bounds.ymin) | (y > bounds.ymax)) + if len(w[0]) > 0: + return _PositionD(x[w[0][0]], y[w[0][0]]) + raise GalSimError("No out-of-bounds position")
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/transform.html b/docs/_build/html/_modules/galsim/transform.html new file mode 100644 index 00000000000..3b237c2e499 --- /dev/null +++ b/docs/_build/html/_modules/galsim/transform.html @@ -0,0 +1,767 @@ + + + + + + galsim.transform — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.transform

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'Transform', 'Transformation', '_Transform', ]
+
+import numpy as np
+import math
+import cmath
+import copy
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, WeakMethod
+from .position import PositionD, _PositionD, parse_pos_args
+from .errors import GalSimError
+from .sum import Sum
+from .sed import SED
+from .wcs import JacobianWCS
+from .shear import Shear, _Shear
+from .angle import Angle
+from . import convolve
+from . import chromatic as chrom
+
+
[docs]def Transform(obj, jac=None, offset=(0.,0.), flux_ratio=1., gsparams=None, + propagate_gsparams=True): + """A function for transforming either a `GSObject` or `ChromaticObject`. + + This function will inspect its input argument to decide if a `Transformation` object or a + `ChromaticTransformation` object is required to represent the resulting transformed object. + + Note: the name of the flux_ratio parameter is technically wrong here if the jacobian has a + non-unit determinant, since that would also scale the flux. The flux_ratio parameter actually + only refers to an overall amplitude ratio for the surface brightness profile. The total + flux scaling is actually ``|det(jac)| * flux_ratio``. + + Parameters: + obj: The object to be transformed. + jac: A list or tuple ( dudx, dudy, dvdx, dvdy ) describing the Jacobian + of the transformation. Use None to indicate that the Jacobian is the + 2x2 unit matrix. [default: None] + offset: A galsim.PositionD or tuple giving the offset by which to shift the + profile. [default: (0.,0.)] + flux_ratio: A factor by which to multiply the surface brightness of the object. + (Technically, not necessarily the flux. See above.) [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the transformed object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Returns: + a `Transformation` or `ChromaticTransformation` instance as appropriate. + """ + # Usual cases are a bit simpler to handle, so check for them first. + if (isinstance(obj, (GSObject, chrom.SimpleChromaticTransformation)) + and not hasattr(jac,'__call__') and not hasattr(offset,'__call__')): + + if isinstance(obj, GSObject) : + if not hasattr(flux_ratio,'__call__'): + # This is the usual achromatic case, so check for this first. + return Transformation(obj, jac, offset, flux_ratio, gsparams, propagate_gsparams) + + elif jac is None and offset == (0.,0.): + # This is obj * SED. Return a SimpleChromaticTransformation. + if not isinstance(flux_ratio, SED): + flux_ratio = SED(flux_ratio, 'nm', '1') + return chrom.SimpleChromaticTransformation(obj, flux_ratio, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + + else: + # Too complicated for simple. + return chrom.ChromaticTransformation(obj, jac, offset, flux_ratio, + gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + + else: + # obj is a SimpleChromaticTransformation + # Try to keep it that way if possible. + if not hasattr(flux_ratio,'__call__'): + # Then transformations are all achromatic. Apply to original. + new_obj = Transformation(obj.original, jac, offset, flux_ratio, gsparams, + propagate_gsparams) + return chrom.SimpleChromaticTransformation(new_obj, obj._flux_ratio, + gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + + elif jac is None and offset == (0.,0.): + # Just a flux_ratio. Apply to SED. + new_sed = obj._flux_ratio * flux_ratio + return chrom.SimpleChromaticTransformation(obj.original, new_sed, + gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + + else: + # Too complicated for simple. + return chrom.ChromaticTransformation(obj, jac, offset, flux_ratio, + gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + + elif not isinstance(obj, (GSObject, chrom.ChromaticObject)): + raise TypeError("Argument to Transform must be either a GSObject or a ChromaticObject.") + + else: + # Now we know we are dealing with a more complicated chromatic transformation. + + # Sometimes for Chromatic compound types, it is more efficient to apply the + # transformation to the components rather than the whole. In particular, this can + # help preserve separability in many cases. + + # Don't transform ChromaticSum object, better to just transform the arguments. + if isinstance(obj, chrom.ChromaticSum): + new_obj = chrom.ChromaticSum( + [ Transform(o,jac,offset,flux_ratio,gsparams,propagate_gsparams) + for o in obj.obj_list ]) + if hasattr(obj, 'covspec'): + if jac is None: + new_obj.covspec = obj.covspec * flux_ratio**2 + else: + dudx, dudy, dvdx, dvdy = np.asarray(jac, dtype=float).flatten() + new_obj.covspec = obj.covspec.transform(dudx, dudy, dvdx, dvdy) * flux_ratio**2 + return new_obj + + # If we are just flux scaling, then a Convolution can do that to the first element. + # NB. Even better, if the flux scaling is chromatic, would be to find a component + # that is already non-separable. But we don't bother trying to do that currently. + elif (isinstance(obj, chrom.ChromaticConvolution or isinstance(obj, convolve.Convolution)) + and jac is None and offset == (0.,0.)): + first = Transform(obj.obj_list[0], flux_ratio=flux_ratio, gsparams=gsparams, + propagate_gsparams=propagate_gsparams) + return chrom.ChromaticConvolution( [first] + [o for o in obj.obj_list[1:]] ) + + else: + return chrom.ChromaticTransformation(obj, jac, offset, flux_ratio, gsparams=gsparams, + propagate_gsparams=propagate_gsparams)
+ + +
[docs]class Transformation(GSObject): + """A class for modeling an affine transformation of a `GSObject` instance. + + Typically, you do not need to construct a Transformation object explicitly. This is the type + returned by the various transformation methods of `GSObject` such as `GSObject.shear`, + `GSObject.rotate`, `GSObject.shift`, `GSObject.transform`, etc. + + All the various transformations can be described as a combination of a jacobian matrix + (i.e. `GSObject.transform`) and a translation (`GSObject.shift`), which are described by + (dudx,dudy,dvdx,dvdy) and (dx,dy) respectively. + + .. note:: + The name of the flux_ratio parameter is technically wrong here if the jacobian has a + non-unit determinant, since that would also scale the flux. The flux_ratio parameter + actually only refers to an overall amplitude ratio for the surface brightness profile. + The total flux scaling is actually ``|det(jac)| * flux_ratio``. + + Parameters: + obj: The object to be transformed. + jac: A list, tuple or numpy array ( dudx, dudy, dvdx, dvdy ) describing + the Jacobian of the transformation. Use None to indicate that the + Jacobian is the 2x2 unit matrix. [default: None] + offset: A galsim.PositionD or tuple giving the offset by which to shift the + profile. [default: (0.,0.)] + flux_ratio: A factor by which to multiply the surface brightness of the object. + (Technically, not necessarily the flux. See above.) [default: 1] + gsparams: An optional `GSParams` argument. [default: None] + propagate_gsparams: Whether to propagate gsparams to the transformed object. This + is normally a good idea, but there may be use cases where one + would not want to do this. [default: True] + + Attributes: + original: The original object that is being transformed. + jac: The jacobian of the transformation matrix. + offset: The offset being applied. + flux_ratio: The amount by which the overall surface brightness amplitude is multiplied. + gsparams: The usual gsparams attribute that all `GSObject` classes have. + + Note: if ``gsparams`` is unspecified (or None), then the Transformation instance inherits the + `GSParams` from obj. Also, note that parameters related to the Fourier-space calculations must + be set when initializing obj, NOT when creating the Transform (at which point the accuracy and + threshold parameters will simply be ignored). + """ + unit_jac = np.array([[1,0],[0,1]], dtype=float) + + def __init__(self, obj, jac=None, offset=(0.,0.), flux_ratio=1., + gsparams=None, propagate_gsparams=True): + if jac is None: + self._jac = None + else: + self._jac = np.asarray(jac, dtype=float).reshape(2,2) + offset = PositionD(offset) + self._dx = offset.x + self._dy = offset.y + self._flux_ratio = float(flux_ratio) + self._gsparams = GSParams.check(gsparams, obj.gsparams) + self._propagate_gsparams = propagate_gsparams + if self._propagate_gsparams: + obj = obj.withGSParams(self._gsparams) + + if isinstance(obj, Transformation): + # Combine the two affine transformations into one. + if obj._has_offset: + if jac is None: + dx1, dy1 = obj._dx, obj._dy + else: + dx1, dy1 = self._fwd_normal(obj._dx, obj._dy) + self._dx += dx1 + self._dy += dy1 + if jac is None: + self._jac = obj._jac + else: + self._jac = self._jac if obj._jac is None else self._jac.dot(obj.jac) + self._flux_ratio *= obj._flux_ratio + self._original = obj._original + else: + self._original = obj + self._has_offset = (self._dx != 0. or self._dy != 0.) + if self._det==0.: + raise GalSimError("Attempt to Transform with degenerate matrix"); + + @property + def original(self): + """The original object being transformed. + """ + return self._original + + @property + def jac(self): + """The Jacobian of the transforamtion. + """ + return self.unit_jac if self._jac is None else self._jac + + @property + def offset(self): + """The offset of the transformation. + """ + return _PositionD(self._dx, self._dy) + + @property + def flux_ratio(self): + """The flux ratio of the transformation. + """ + return self._flux_ratio + + @lazy_property + def _flux(self): + return self._flux_scaling * self._original.flux + + @lazy_property + def _sbp(self): + _jac = 0 if self._jac is None else self._jac.__array_interface__['data'][0] + return _galsim.SBTransform(self._original._sbp, _jac, + self._dx, self._dy, self._flux_ratio, self.gsparams._gsp) + + @lazy_property + def _noise(self): + if self.original.noise is None: + return None + else: + return BaseCorrelatedNoise( + self.original.noise.rng, + _Transform(self.original.noise._profile, self._jac, + flux_ratio=self.flux_ratio**2), + self.original.noise.wcs) + +
[docs] def withGSParams(self, gsparams=None, **kwargs): + """Create a version of the current object with the given gsparams + + .. note:: + + Unless you set ``propagate_gsparams=False``, this method will also update the gsparams + of the wrapped component object. + """ + if gsparams == self.gsparams: return self + ret = copy.copy(self) + ret._gsparams = GSParams.check(gsparams, self.gsparams, **kwargs) + if self._propagate_gsparams: + ret._original = self._original.withGSParams(ret._gsparams) + return ret
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Transformation) and + self.original == other.original and + np.array_equal(self.jac, other.jac) and + np.array_equal(self.offset, other.offset) and + self.flux_ratio == other.flux_ratio and + self.gsparams == other.gsparams and + self._propagate_gsparams == other._propagate_gsparams)) + + def __hash__(self): + return hash(("galsim.Transformation", self.original, tuple(self.jac.ravel()), + self._dx, self._dy, self.flux_ratio, self.gsparams, + self._propagate_gsparams)) + + def __repr__(self): + return ('galsim.Transformation(%r, jac=%r, offset=%r, flux_ratio=%r, gsparams=%r, ' + 'propagate_gsparams=%r)')%( + self.original, None if self._jac is None else self._jac.tolist(), + self.offset, self.flux_ratio, self.gsparams, self._propagate_gsparams) + + @classmethod + def _str_from_jac(cls, jac): + dudx, dudy, dvdx, dvdy = jac.ravel() + # Figure out the shear/rotate/dilate calls that are equivalent. + jac = JacobianWCS(dudx,dudy,dvdx,dvdy) + scale, shear, theta, flip = jac.getDecomposition() + s = None + if flip: + s = 0 # Special value indicating to just use transform. + if abs(theta.rad) > 1.e-12: + if s is None: + s = '.rotate(%s)'%theta + else: + s = 0 + if shear.g > 1.e-12: + if s is None: + s = '.shear(%s)'%shear + else: + s = 0 + if abs(scale-1.0) > 1.e-12: + if s is None: + s = '.expand(%s)'%scale + else: + s = 0 + if s == 0: + # If flip or there are two components, then revert to transform as simpler. + s = '.transform(%s,%s,%s,%s)'%(dudx,dudy,dvdx,dvdy) + if s is None: + # If nothing is large enough to show up above, give full detail of transform + s = '.transform(%r,%r,%r,%r)'%(dudx,dudy,dvdx,dvdy) + return s + + def __str__(self): + s = str(self.original) + if self._jac is not None: + s += self._str_from_jac(self._jac) + if self._dx != 0 or self._dy != 0: + s += '.shift(%s,%s)'%(self._dx,self._dy) + if self.flux_ratio != 1.: + s += ' * %s'%self.flux_ratio + return s + + def _prepareDraw(self): + self._original._prepareDraw() + + # Some lazy properties to calculate things as needed. + @lazy_property + def _det(self): + if self._jac is None: + return 1 + if self._jac[0,1] == 0. and self._jac[1,0] == 0.: + if self._jac[0,0] == 1. and self._jac[1,1] == 1.: # jac is (1,0,0,1) + return 1. + else: # jac is (a,0,0,b) + return self._jac[0,0] * self._jac[1,1] + else: # Fully general case + return self._jac[0,0] * self._jac[1,1] - self._jac[0,1] * self._jac[1,0] + + @lazy_property + def _invdet(self): + return 1. if self._jac is None else 1./self._det + + @lazy_property + def _invjac(self): + invjac = np.array([[self._jac[1,1], -self._jac[0,1]], [-self._jac[1,0], self._jac[0,0]]]) + invjac *= self._invdet + return invjac + + # To avoid confusion with the flux vs amplitude scaling, we use these names below, rather + # than flux_ratio, which is really an amplitude scaling. + @property + def _amp_scaling(self): + return self._flux_ratio + + @lazy_property + def _flux_scaling(self): + return abs(self._det) * self._flux_ratio + + # Some helper attributes to make fwd and inv transformation quicker + @lazy_property + def _fwd(self): + if self._jac is None: + return WeakMethod(self._ident) + elif self._jac[0,1] == 0. and self._jac[1,0] == 0.: + return WeakMethod(self._fwd_diag) + else: + return WeakMethod(self._fwd_normal) + + @lazy_property + def _fwdT(self): + if self._jac is None: + return WeakMethod(self._ident) + elif self._jac[0,1] == 0. and self._jac[1,0] == 0.: + return WeakMethod(self._fwd_diag) + else: + return WeakMethod(self._fwdT_normal) + + @lazy_property + def _inv(self): + if self._jac is None: + return WeakMethod(self._ident) + elif self._jac[0,1] == 0. and self._jac[1,0] == 0.: + return WeakMethod(self._inv_diag) + else: + return WeakMethod(self._inv_normal) + + @lazy_property + def _kfactor(self): + if self._dx == self._dy == 0.: + return WeakMethod(self._kf_nophase) + else: + return WeakMethod(self._kf_phase) + + def _ident(self, x, y): + return x, y + + def _fwd_diag(self, x, y): + x *= self._jac[0,0] + y *= self._jac[1,1] + return x, y + + def _fwd_normal(self, x, y): + #return self._jac[0,0] * x + self._jac[0,1] * y, self._jac[1,0] * x + self._jac[1,1] * y + # Do this as much in place as possible + temp = self._jac[0,1] * y + y *= self._jac[1,1] + y += self._jac[1,0] * x + x *= self._jac[0,0] + x += temp + return x, y + + def _fwdT_normal(self, x, y): + #return self._jac[0,0] * x + self._jac[1,0] * y, self._jac[0,1] * x + self._jac[1,1] * y + temp = self._jac[1,0] * y + y *= self._jac[1,1] + y += self._jac[0,1] * x + x *= self._jac[0,0] + x += temp + return x, y + + def _inv_diag(self, x, y): + x /= self._jac[0,0] + y /= self._jac[1,1] + return x, y + + def _inv_normal(self, x, y): + #return (self._invdet * (self._jac[1,1] * x - self._jac[0,1] * y), + # self._invdet * (-self._jac[1,0] * x + self._jac[0,0] * y)) + temp = self._invjac[0,1] * y + y *= self._invjac[1,1] + y += self._invjac[1,0] * x + x *= self._invjac[0,0] + x += temp + return x, y + + def _kf_nophase(self, kx, ky): + return self._flux_scaling + + def _kf_phase(self, kx, ky): + kx *= -1j * self._dx + ky *= -1j * self._dy + kx += ky + return self._flux_scaling * np.exp(kx) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + d.pop('_fwd',None) + d.pop('_fwdT',None) + d.pop('_inv',None) + d.pop('_kfactor',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + def _major_minor(self): + if not hasattr(self, "_major"): + if self._jac is None: + self._major = self._minor = 1. + else: + h1 = math.hypot(self._jac[0,0] + self._jac[1,1], self._jac[0,1] - self._jac[1,0]) + h2 = math.hypot(self._jac[0,0] - self._jac[1,1], self._jac[0,1] + self._jac[1,0]) + self._major = 0.5 * abs(h1+h2) + self._minor = 0.5 * abs(h1-h2) + + @lazy_property + def _maxk(self): + self._major_minor() + return self._original.maxk / self._minor + + @lazy_property + def _stepk(self): + self._major_minor() + stepk = self._original.stepk / self._major + # If we have a shift, we need to further modify stepk + # stepk = Pi/R + # R <- R + |shift| + # stepk <- Pi/(Pi/stepk + |shift|) + if self._dx != 0. or self._dy != 0.: + dr = math.hypot(self._dx, self._dy) + stepk = math.pi / (math.pi/stepk + dr) + return stepk + + @property + def _has_hard_edges(self): + return self._original.has_hard_edges + + @property + def _is_axisymmetric(self): + return bool(self._original.is_axisymmetric and + (self._jac is None or (self._jac[0,0] == self._jac[1,1] and + self._jac[0,1] == -self._jac[1,0])) and + self._dx == self._dy == 0.) + + @property + def _is_analytic_x(self): + return self._original.is_analytic_x + + @property + def _is_analytic_k(self): + return self._original.is_analytic_k + + @property + def _centroid(self): + cen = self._original.centroid + cenx, ceny = self._fwd(cen.x, cen.y) + cenx += self._dx + ceny += self._dy + return _PositionD(cenx,ceny) + + @property + def _positive_flux(self): + return self._flux_scaling * self._original.positive_flux + + @property + def _negative_flux(self): + return self._flux_scaling * self._original.negative_flux + + @lazy_property + def _flux_per_photon(self): + return self._calculate_flux_per_photon() + + @property + def _max_sb(self): + return self._amp_scaling * self._original.max_sb + + def _xValue(self, pos): + x = pos.x - self._dx + y = pos.y - self._dy + inv_pos = _PositionD(*self._inv(x, y)) + return self._original._xValue(inv_pos) * self._amp_scaling + + def _kValue(self, kpos): + fwdT_kpos = _PositionD(*self._fwdT(kpos.x, kpos.y)) + return self._original._kValue(fwdT_kpos) * self._kfactor(kpos.x, kpos.y) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + dx, dy = offset + if self._has_offset: + if jac is not None: + x1 = jac[0,0] * self._dx + jac[0,1] * self._dy + y1 = jac[1,0] * self._dx + jac[1,1] * self._dy + else: + x1, y1 = self._dx, self._dy + dx += x1 + dy += y1 + flux_scaling *= self._flux_scaling + jac = self._jac if jac is None else jac if self._jac is None else jac.dot(self._jac) + self._original._drawReal(image, jac, (dx, dy), flux_scaling) + + def _shoot(self, photons, rng): + self._original._shoot(photons, rng) + photons.x, photons.y = self._fwd(photons.x, photons.y) + photons.x += self._dx + photons.y += self._dy + photons.scaleFlux(self._flux_scaling) + + def _drawKImage(self, image, jac=None): + jac1 = self._jac if jac is None else jac if self._jac is None else jac.dot(self._jac) + self._original._drawKImage(image, jac1) + + if self._has_offset: + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + _galsim.ApplyKImagePhases(image._image, image.scale, _jac, + self._dx, self._dy, self._flux_scaling) + elif abs(self._flux_scaling-1.) > self._gsparams.kvalue_accuracy: + image *= self._flux_scaling
+ + +
[docs]def _Transform(obj, jac=None, offset=(0.,0.), flux_ratio=1.): + """Approximately equivalent to Transform, but without some of the sanity checks (such as + checking for chromatic options) or setting a new gsparams. + + For a `ChromaticObject`, you must use the regular `Transform`. + + Parameters: + obj: The object to be transformed. + jac: A 2x2 numpy array describing the Jacobian of the transformation. + Use None to indicate that the Jacobian is the 2x2 unit matrix. + [default: None] + offset: The offset to apply. [default (0.,0.)] + flux_ratio: A factor by which to multiply the surface brightness of the object. + [default: 1.] + """ + ret = Transformation.__new__(Transformation) + ret._gsparams = obj.gsparams + ret._propagate_gsparams = True + ret._jac = jac + ret._dx, ret._dy = offset + if isinstance(obj, Transformation): + if obj._has_offset: + if jac is None: + dx1, dy1 = obj._dx, obj._dy + else: + dx1, dy1 = ret._fwd_normal(obj._dx, obj._dy) + ret._dx += dx1 + ret._dy += dy1 + if jac is None: + ret._jac = obj._jac + else: + ret._jac = ret._jac if obj._jac is None else ret._jac.dot(obj.jac) + ret._flux_ratio = flux_ratio * obj._flux_ratio + ret._original = obj._original + else: + ret._flux_ratio = flux_ratio + ret._original = obj + ret._has_offset = (ret._dx != 0. or ret._dy != 0.) + return ret
+ +# Put this at the bottom to avoid circular import error. +from .correlatednoise import BaseCorrelatedNoise +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/utilities.html b/docs/_build/html/_modules/galsim/utilities.html new file mode 100644 index 00000000000..64c0fe2dfb4 --- /dev/null +++ b/docs/_build/html/_modules/galsim/utilities.html @@ -0,0 +1,2023 @@ + + + + + + galsim.utilities — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.utilities

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+import functools
+from contextlib import contextmanager
+import weakref
+import os
+import warnings
+import numpy as np
+import pstats
+import math
+import heapq
+import multiprocessing
+import cProfile, pstats
+from io import StringIO
+import logging
+import time
+from collections.abc import Hashable
+from collections import Counter
+import pickle
+import copy
+
+from . import _galsim
+from .errors import GalSimError, GalSimValueError, GalSimIncompatibleValuesError, GalSimRangeError
+from .errors import galsim_warn
+from .position import Position, PositionD, _PositionD
+from .angle import AngleUnit, arcsec
+from .image import Image
+from .table import trapz, _LookupTable, LookupTable2D
+from .wcs import JacobianWCS, PixelScale
+from .random import BaseDeviate, UniformDeviate
+from . import meta_data
+
+# A couple things are documented as galsim.utilties.* functions, but live in other files.
+# Bring them into scope here.
+from .interpolant import convert_interpolant
+from .table import find_out_of_bounds_position
+from .position import parse_pos_args
+from ._utilities import *
+
+
+
[docs]def roll2d(image, shape): + """Perform a 2D roll (circular shift) on a supplied 2D NumPy array, conveniently. + + Parameters: + image: The NumPy array to be circular shifted. + shape: (iroll, jroll) The roll in the i and j dimensions, respectively. + + Returns: + the rolled array + """ + (iroll, jroll) = shape + # The ascontiguousarray bit didn't used to be necessary. But starting with + # numpy v1.12, np.roll doesn't seem to always return a C-contiguous array. + return np.ascontiguousarray(np.roll(np.roll(image, jroll, axis=1), iroll, axis=0))
+ +
[docs]def kxky(array_shape=(256, 256)): + """Return the tuple (kx, ky) corresponding to the DFT of a unit integer-sampled array of + input shape. + + Uses the FFTW conventions for Fourier space, so k varies in approximate range (-pi, pi], + and the (0, 0) array element corresponds to (kx, ky) = (0, 0). + + See also the docstring for np.fftfreq, which uses the same DFT convention, and is called here, + but misses a factor of pi. + + Adopts NumPy array index ordering so that the trailing axis corresponds to kx, rather than + the leading axis as would be expected in IDL/Fortran. See docstring for ``numpy.meshgrid`` + which also uses this convention. + + Parameters: + array_shape: The NumPy array shape desired for kx, ky. + + Returns: + kx, ky + """ + # Note: numpy shape is y,x + k_xaxis = np.fft.fftfreq(array_shape[1]) * 2. * np.pi + k_yaxis = np.fft.fftfreq(array_shape[0]) * 2. * np.pi + return np.meshgrid(k_xaxis, k_yaxis)
+ +
[docs]def g1g2_to_e1e2(g1, g2): + """Convenience function for going from (g1, g2) -> (e1, e2). + + Here g1 and g2 are reduced shears, and e1 and e2 are distortions - see `Shear` + for definitions of reduced shear and distortion in terms of axis ratios or other ways of + specifying ellipses. + + Parameters: + g1: First reduced shear component + g2: Second reduced shear component + + Returns: + the corresponding distortions, e1 and e2. + """ + # Conversion: + # e = (a^2-b^2) / (a^2+b^2) + # g = (a-b) / (a+b) + # b/a = (1-g)/(1+g) + # e = (1-(b/a)^2) / (1+(b/a)^2) + gsq = g1*g1 + g2*g2 + if gsq == 0.: + return 0., 0. + else: + g = np.sqrt(gsq) + boa = (1-g) / (1+g) + e = (1 - boa*boa) / (1 + boa*boa) + e1 = g1 * (e/g) + e2 = g2 * (e/g) + return e1, e2
+ +
[docs]def rotate_xy(x, y, theta): + """Rotates points in the xy-Cartesian plane counter-clockwise through an angle ``theta`` about + the origin of the Cartesian coordinate system. + + Parameters: + x: NumPy array of input x coordinates + y: NumPy array of input y coordinates + theta: Rotation angle (+ve counter clockwise) as an `Angle` instance + + Returns: + the rotated coordinates (x_rot,y_rot). + """ + sint, cost = theta.sincos() + x_rot = x * cost - y * sint + y_rot = x * sint + y * cost + return x_rot, y_rot
+ + +
[docs]class SimpleGenerator: + """A simple class that is constructed with an arbitrary object. + Then generator() will return that object. + + This is useful as a way to use an already existing object in a multiprocessing Proxy, + since that normally needs a factory function. So this is a factory function that + just returns an already existing object. + """ + def __init__(self, obj): self._obj = obj + def __call__(self): # pragma: no cover + return self._obj
+ +
[docs]def rand_arr(shape, deviate): + """Function to make a 2d array of random deviates (of any sort). + + Parameters: + shape: A list of length 2, indicating the desired 2d array dimensions + deviate: Any GalSim deviate (see random.py) such as `UniformDeviate`, `GaussianDeviate`, + etc. to be used to generate random numbers + + Returns: + a NumPy array of the desired dimensions with random numbers generated using the + supplied deviate. + """ + tmp = np.empty(tuple(shape), dtype=float) + deviate.generate(tmp.ravel()) + return tmp
+ +# A helper function for parsing the input position arguments for PowerSpectrum and NFWHalo: +def _convertPositions(pos, units, func): + """Convert ``pos`` from the valid ways to input positions to two NumPy arrays + + This is used by the functions getShear(), getConvergence(), getMagnification(), and + getLensing() for both PowerSpectrum and NFWHalo. + """ + # Check for PositionD or PositionI: + if isinstance(pos, Position): + pos = [ pos.x, pos.y ] + + elif len(pos) == 0: + raise TypeError("Unable to parse the input pos argument for %s."%func) + + # Check for list of Position: + # The only other options allow pos[0], so if this is invalid, an exception + # will be raised: + elif isinstance(pos[0], Position): + pos = [ np.array([p.x for p in pos], dtype=float), + np.array([p.y for p in pos], dtype=float) ] + + # Now pos must be a tuple of length 2 + elif len(pos) != 2: + raise TypeError("Unable to parse the input pos argument for %s."%func) + + else: + # Check for (x,y): + try: + pos = [ float(pos[0]), float(pos[1]) ] + except TypeError: + # Only other valid option is ( xlist , ylist ) + pos = [ np.array(pos[0], dtype=float), + np.array(pos[1], dtype=float) ] + + # Check validity of units + if isinstance(units, str): + # if the string is invalid, this raises a reasonable error message. + units = AngleUnit.from_name(units) + if not isinstance(units, AngleUnit): + raise GalSimValueError("units must be either an AngleUnit or a string", units, + ('arcsec', 'arcmin', 'degree', 'hour', 'radian')) + + # Convert pos to arcsec + if units != arcsec: + scale = 1. * units / arcsec + # Note that for the next two lines, pos *must* be a list, not a tuple. Assignments to + # elements of tuples is not allowed. + pos[0] *= scale + pos[1] *= scale + + return pos + +def _spline_approx_err(x, f, left, right, splitpoints, i): + # For splines, we can't just do the integral over a small range, since the spline slopes + # are all wrong. Rather we compute a spline function with the current splitpoints and + # just the single point in the trial region and recompute a spline function with that. + # Then we can compute the total error from that approximation. + # (For the error integral, we still use linear.) + + indices = sorted(splitpoints + [i]) + new_tab = _LookupTable(x[indices], f[indices], 'spline') + + xleft, xright = x[left:i+1], x[i:right+1] + fleft, fright = f[left:i+1], f[i:right+1] + f2left = new_tab(xleft) + f2right = new_tab(xright) + return trapz(np.abs(fleft-f2left), xleft), trapz(np.abs(fright-f2right), xright) + +def _spline_approx_split(x, f, left, right, splitpoints): + r"""Split a tabulated function into a two-part piecewise spline approximation by exactly + minimizing \int abs(f(x) - approx(x)) dx. Operates in O(N^2) time. + """ + errs = [_spline_approx_err(x, f, left, right, splitpoints, i) for i in range(left+1, right)] + i = np.argmin(np.sum(errs, axis=1)) + return i+left+1, errs[i] + +def _lin_approx_err(x, f, left, right, i): + r"""Error as \int abs(f(x) - approx(x)) when using ith data point to make piecewise linear + approximation. + """ + xleft, xright = x[left:i+1], x[i:right+1] + fleft, fright = f[left:i+1], f[i:right+1] + xi, fi = x[i], f[i] + mleft = (fi-f[left])/(xi-x[left]) + mright = (f[right]-fi)/(x[right]-xi) + f2left = f[left]+mleft*(xleft-x[left]) + f2right = fi+mright*(xright-xi) + return trapz(np.abs(fleft-f2left), xleft), trapz(np.abs(fright-f2right), xright) + +def _exact_lin_approx_split(x, f, left, right, splitpoints): + r"""Split a tabulated function into a two-part piecewise linear approximation by exactly + minimizing \int abs(f(x) - approx(x)) dx. Operates in O(N^2) time. + """ + errs = [_lin_approx_err(x, f, left, right, i) for i in range(left+1, right)] + i = np.argmin(np.sum(errs, axis=1)) + return i+left+1, errs[i] + +def _lin_approx_split(x, f, left, right, splitpoints): + r"""Split a tabulated function into a two-part piecewise linear approximation by approximately + minimizing \int abs(f(x) - approx(x)) dx. Chooses the split point by exactly minimizing + \int (f(x) - approx(x))^2 dx in O(N) time. + """ + x = x[left:right+1] + f = f[left:right+1] + dx = x[2:] - x[:-2] + # Error contribution on the left. + ff0 = f[1:-1]-f[0] # Only need to search between j=1..(N-1) + xx0 = x[1:-1]-x[0] + mleft = ff0/xx0 # slope + errleft = (np.cumsum(dx*ff0**2) + - 2*mleft*np.cumsum(dx*ff0*xx0) + + mleft**2*np.cumsum(dx*xx0**2)) + # Error contribution on the right. + dx = dx[::-1] # Reversed so that np.cumsum effectively works right-to-left. + ffN = f[-2:0:-1]-f[-1] + xxN = x[-2:0:-1]-x[-1] + mright = ffN/xxN + errright = (np.cumsum(dx*ffN**2) + - 2*mright*np.cumsum(dx*ffN*xxN) + + mright**2*np.cumsum(dx*xxN**2)) + errright = errright[::-1] + + # Get absolute error for the found point. + i = np.argmin(errleft+errright) + return i+left+1, _lin_approx_err(x, f, 0, len(x)-1, i+1) + +
[docs]def thin_tabulated_values(x, f, rel_err=1.e-4, trim_zeros=True, preserve_range=True, + fast_search=True, interpolant='linear'): + """ + Remove items from a set of tabulated f(x) values so that the error in the integral is still + accurate to a given relative accuracy. + + The input ``x`` and ``f`` values can be lists, NumPy arrays, or really anything that can be + converted to a NumPy array. The new lists will be output as numpy arrays. + + Parameters: + x: The ``x`` values in the f(x) tabulation. + f: The ``f`` values in the f(x) tabulation. + rel_err: The maximum relative error to allow in the integral from the removal. + [default: 1.e-4] + trim_zeros: Remove redundant leading and trailing points where f=0? (The last + leading point with f=0 and the first trailing point with f=0 will be + retained). Note that if both trim_leading_zeros and preserve_range are + True, then the only the range of ``x`` *after* zero trimming is preserved. + [default: True] + preserve_range: Should the original range of ``x`` be preserved? (True) Or should the ends + be trimmed to include only the region where the integral is + significant? (False) [default: True] + fast_search: If set to True, then the underlying algorithm will use a relatively fast + O(N) algorithm to select points to include in the thinned approximation. + If set to False, then a slower O(N^2) algorithm will be used. We have + found that the slower algorithm tends to yield a thinned representation + that retains fewer samples while still meeting the relative error + requirement. [default: True] + interpolant: The interpolant to assume for the tabulated values. [default: 'linear'] + + Returns: + a tuple of lists (x_new, y_new) with the thinned tabulation. + """ + if interpolant == 'spline': + split_fn = _spline_approx_split + elif fast_search: + split_fn = _lin_approx_split + else: + split_fn = _exact_lin_approx_split + + x = np.asarray(x, dtype=float) + f = np.asarray(f, dtype=float) + + # Check for valid inputs + if len(x) != len(f): + raise GalSimIncompatibleValuesError("len(x) != len(f)", x=x, f=f) + if rel_err <= 0 or rel_err >= 1: + raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) + if not (np.diff(x) >= 0).all(): + raise GalSimValueError("input x is not sorted.", x) + + # Check for trivial noop. + if len(x) <= 2: + # Nothing to do + return x,f + + total_integ = trapz(abs(f), x, interpolant) + if total_integ == 0: + return np.array([ x[0], x[-1] ]), np.array([ f[0], f[-1] ]) + thresh = total_integ * rel_err + + if trim_zeros: + first = max(f.nonzero()[0][0]-1, 0) # -1 to keep one non-redundant zero. + last = min(f.nonzero()[0][-1]+1, len(x)-1) # +1 to keep one non-redundant zero. + x, f = x[first:last+1], f[first:last+1] + + if not preserve_range: + # Remove values from the front that integrate to less than thresh. + err_integ1 = 0.5 * (abs(f[0]) + abs(f[1])) * (x[1] - x[0]) + k0 = 0 + x_range = x[-1] - x[0] + while k0 < len(x)-2 and err_integ1 < thresh * (x[k0+1]-x[0]) / x_range: + k0 = k0+1 + err_integ1 += 0.5 * (abs(f[k0]) + abs(f[k0+1])) * (x[k0+1] - x[k0]) + # Now the integral from 0 to k0+1 (inclusive) is a bit too large. + # That means k0 is the largest value we can use that will work as the starting value. + + # Remove values from the back that integrate to less than thresh. + k1 = len(x)-1 + err_integ2 = 0.5 * (abs(f[k1-1]) + abs(f[k1])) * (x[k1] - x[k1-1]) + while k1 > k0 and err_integ2 < thresh * (x[-1]-x[k1-1]) / x_range: + k1 = k1-1 + err_integ2 += 0.5 * (abs(f[k1-1]) + abs(f[k1])) * (x[k1] - x[k1-1]) + # Now the integral from k1-1 to len(x)-1 (inclusive) is a bit too large. + # That means k1 is the smallest value we can use that will work as the ending value. + + # Subtract the error so far from thresh + if interpolant == 'spline': + new_integ = trapz(abs(f[k0:k1+1]),x[k0:k1+1], interpolant='spline') + thresh -= np.abs(new_integ-total_integ) + else: + thresh -= trapz(abs(f[:k0]),x[:k0]) + trapz(abs(f[k1:]),x[k1:]) + + x = x[k0:k1+1] # +1 since end of range is given as one-past-the-end. + f = f[k0:k1+1] + + # Check again for noop after trimming endpoints. + if len(x) <= 2: + return x,f + + # Thin interior points. Start with no interior points and then greedily add them back in one at + # a time until relative error goal is met. + # Use a heap to track: + heap = [(-2*thresh, # -err; initialize large enough to trigger while loop below. + 0, # first index of interval + len(x)-1)] # last index of interval + splitpoints = [0,len(x)-1] + while len(heap) > 0: + _, left, right = heapq.heappop(heap) + i, (errleft, errright) = split_fn(x, f, left, right, splitpoints) + splitpoints.append(i) + if i > left+1: + heapq.heappush(heap, (-errleft, left, i)) + if right > i+1: + heapq.heappush(heap, (-errright, i, right)) + if interpolant != 'spline': + # This is a sufficient stopping criterion for linear + if (-sum(h[0] for h in heap) < thresh): + break + else: + # For spline, we also need to recompute the total integral to make sure + # that the realized total error is less than thresh. + if (-sum(h[0] for h in heap) < thresh): + splitpoints = sorted(splitpoints) + current_integ = trapz(f[splitpoints], x[splitpoints], interpolant) + if np.abs(current_integ - total_integ) < thresh: + break + splitpoints = sorted(splitpoints) + return x[splitpoints], f[splitpoints]
+ +
[docs]def old_thin_tabulated_values(x, f, rel_err=1.e-4, preserve_range=False): # pragma: no cover + """ + Remove items from a set of tabulated f(x) values so that the error in the integral is still + accurate to a given relative accuracy. + + The input ``x`` and ``f`` values can be lists, NumPy arrays, or really anything that can be + converted to a NumPy array. The new lists will be output as python lists. + + .. note:: + In Issue #739, Josh wrote `thin_tabulated_values` as a replacement for this function, + which had been buggy -- not actually hitting its target relative accuracy. So on the + same issue, Mike fixed this algorithm to at least work correctly. + + However, we recommend using the above algorithm, since it keeps fewer sample locations + for a given ``rel_err`` than the old algorithm. + + On the other hand, the old algorithm (this one) may be quite a bit faster, so we retain + it here in case there is a use case where it is more appropriate. + + Parameters: + x: The ``x`` values in the f(x) tabulation. + f: The ``f`` values in the f(x) tabulation. + rel_err: The maximum relative error to allow in the integral from the removal. + [default: 1.e-4] + preserve_range: Should the original range of ``x`` be preserved? (True) Or should the ends + be trimmed to include only the region where the integral is + significant? (False) [default: False] + + Returns: + a tuple of lists (x_new, y_new) with the thinned tabulation. + """ + x = np.asarray(x, dtype=float) + f = np.asarray(f, dtype=float) + + # Check for valid inputs + if len(x) != len(f): + raise GalSimIncompatibleValuesError("len(x) != len(f)", x=x, f=f) + if rel_err <= 0 or rel_err >= 1: + raise GalSimRangeError("rel_err must be between 0 and 1", rel_err, 0., 1.) + if not (np.diff(x) >= 0).all(): + raise GalSimValueError("input x is not sorted.", x) + + # Check for trivial noop. + if len(x) <= 2: + # Nothing to do + return x,f + + # Start by calculating the complete integral of |f| + total_integ = trapz(abs(f),x) + if total_integ == 0: + return np.array([ x[0], x[-1] ]), np.array([ f[0], f[-1] ]) + thresh = rel_err * total_integ + x_range = x[-1] - x[0] + + if not preserve_range: + # Remove values from the front that integrate to less than thresh. + err_integ1 = 0.5 * (abs(f[0]) + abs(f[1])) * (x[1] - x[0]) + k0 = 0 + while k0 < len(x)-2 and err_integ1 < thresh * (x[k0+1]-x[0]) / x_range: + k0 = k0+1 + err_integ1 += 0.5 * (abs(f[k0]) + abs(f[k0+1])) * (x[k0+1] - x[k0]) + # Now the integral from 0 to k0+1 (inclusive) is a bit too large. + # That means k0 is the largest value we can use that will work as the starting value. + + # Remove values from the back that integrate to less than thresh. + k1 = len(x)-1 + err_integ2 = 0.5 * (abs(f[k1-1]) + abs(f[k1])) * (x[k1] - x[k1-1]) + while k1 > k0 and err_integ2 < thresh * (x[-1]-x[k1-1]) / x_range: + k1 = k1-1 + err_integ2 += 0.5 * (abs(f[k1-1]) + abs(f[k1])) * (x[k1] - x[k1-1]) + # Now the integral from k1-1 to len(x)-1 (inclusive) is a bit too large. + # That means k1 is the smallest value we can use that will work as the ending value. + + # Subtract the error so far from thresh + thresh -= trapz(abs(f[:k0]),x[:k0]) + trapz(abs(f[k1:]),x[k1:]) + + x = x[k0:k1+1] # +1 since end of range is given as one-past-the-end. + f = f[k0:k1+1] + + # And update x_range for the new values + x_range = x[-1] - x[0] + + # Start a new list with just the first item so far + newx = [ x[0] ] + newf = [ f[0] ] + + k0 = 0 # The last item currently in the new array + k1 = 1 # The current item we are considering to skip or include + while k1 < len(x)-1: + # We are considering replacing all the true values between k0 and k1+1 (non-inclusive) + # with a linear approxmation based on the points at k0 and k1+1. + lin_f = f[k0] + (f[k1+1]-f[k0])/(x[k1+1]-x[k0]) * (x[k0:k1+2] - x[k0]) + # Integrate | f(x) - lin_f(x) | from k0 to k1+1, inclusive. + err_integ = trapz(np.abs(f[k0:k1+2] - lin_f), x[k0:k1+2]) + # If the integral of the difference is < thresh * (dx/x_range), we can skip this item. + if abs(err_integ) < thresh * (x[k1+1]-x[k0]) / x_range: + # OK to skip item k1 + k1 = k1 + 1 + else: + # Also ok to keep if its own relative error is less than rel_err + true_integ = trapz(f[k0:k1+2], x[k0:k1+2]) + if abs(err_integ) < rel_err * abs(true_integ): + # OK to skip item k1 + k1 = k1 + 1 + else: + # Have to include this one. + newx.append(x[k1]) + newf.append(f[k1]) + k0 = k1 + k1 = k1 + 1 + + # Always include the last item + newx.append(x[-1]) + newf.append(f[-1]) + + return newx, newf
+ + +
[docs]def horner(x, coef, dtype=None): + """Evaluate univariate polynomial using Horner's method. + + I.e., take A + Bx + Cx^2 + Dx^3 and evaluate it as + A + x(B + x(C + x(D))) + + Parameters: + x: A numpy array of values at which to evaluate the polynomial. + coef: Polynomial coefficients of increasing powers of x. + dtype: Optionally specify the dtype of the return array. [default: None] + + Returns: + a numpy array of the evaluated polynomial. Will be the same shape as x. + """ + if dtype is None: + dtype = np.result_type( + np.min_scalar_type(x), + np.min_scalar_type(coef) + ) + result = np.empty_like(x, dtype=dtype) + # Make sure everything is an array + if result.dtype == float: + # And if the result is float, it's worth making sure x, coef are also float and + # contiguous, so we can use the faster c++ implementation. + x = np.ascontiguousarray(x, dtype=float) + coef = np.ascontiguousarray(coef, dtype=float) + else: + x = np.asarray(x) + coef = np.asarray(coef) + if len(coef.shape) != 1: + raise GalSimValueError("coef must be 1-dimensional", coef) + _horner(x, coef, result) + return result
+ +
[docs]def _horner(x, coef, result): + """Equivalent to `horner`, but ``x``, ``coef``, and ``result`` must be contiguous arrays. + + In particular, ``result`` must be already allocated as an array in which to put the answer. + This is the thing that is returned from the regular `horner`. + + Parameters: + x: A numpy array of values at which to evaluate the polynomial. + coef: Polynomial coefficients of increasing powers of x. + result: Numpy array into which to write the result. Must be same shape as x. + """ + if result.dtype == float: + _x = x.__array_interface__['data'][0] + _coef = coef.__array_interface__['data'][0] + _result = result.__array_interface__['data'][0] + _galsim.Horner(_x, x.size, _coef, coef.size, _result) + else: + coef = np.trim_zeros(coef, trim='b') # trim only from the back + if len(coef) == 0: + result.fill(0) + return + result.fill(coef[-1]) + for c in coef[-2::-1]: + result *= x + if c != 0: result += c
+ +
[docs]def horner2d(x, y, coefs, dtype=None, triangle=False): + """Evaluate bivariate polynomial using nested Horner's method. + + Parameters: + x: A numpy array of the x values at which to evaluate the polynomial. + y: A numpy array of the y values at which to evaluate the polynomial. + coefs: 2D array-like of coefficients in increasing powers of x and y. + The first axis corresponds to increasing the power of y, and the second to + increasing the power of x. + dtype: Optionally specify the dtype of the return array. [default: None] + triangle: If True, then the coefs are only non-zero in the upper-left triangle + of the array. [default: False] + + Returns: + a numpy array of the evaluated polynomial. Will be the same shape as x and y. + """ + if dtype is None: + dtype = np.result_type( + np.min_scalar_type(x), + np.min_scalar_type(y), + np.min_scalar_type(coefs) + ) + result = np.empty_like(x, dtype=dtype) + temp = np.empty_like(x, dtype=dtype) + # Make sure everything is an array + if result.dtype == float: + # And if the result is float, it's worth making sure x, coef are also float, + # so we can use the faster c++ implementation. + x = np.ascontiguousarray(x, dtype=float) + y = np.ascontiguousarray(y, dtype=float) + coefs = np.ascontiguousarray(coefs, dtype=float) + else: + x = np.asarray(x) + y = np.asarray(y) + coefs = np.asarray(coefs) + + if x.shape != y.shape: + raise GalSimIncompatibleValuesError("x and y are not the same shape", x=x, y=y) + if len(coefs.shape) != 2: + raise GalSimValueError("coefs must be 2-dimensional", coefs) + if triangle and coefs.shape[0] != coefs.shape[1]: + raise GalSimIncompatibleValuesError("coefs must be square if triangle is True", + coefs=coefs, triangle=triangle) + _horner2d(x, y, coefs, result, temp, triangle) + return result
+ +
[docs]def _horner2d(x, y, coefs, result, temp, triangle=False): + """Equivalent to `horner2d`, but ``x``, ``y``, ``coefs``, ``result``, and ``temp`` + must be contiguous arrays. + + In particular, ``result`` must be already allocated as an array in which to put the answer. + This is the thing that is returned from the regular `horner`. In addition, ``temp`` must + be allocated for the function to use as temporary work space. + + Parameters: + x: A numpy array of the x values at which to evaluate the polynomial. + y: A numpy array of the y values at which to evaluate the polynomial. + coefs: 2D array-like of coefficients in increasing powers of x and y. + The first axis corresponds to increasing the power of y, and the second to + increasing the power of x. + result: Numpy array into which to write the result. Must be same shape as x. + temp: Numpy array to hold temporary results. Must be the same shape as x. + triangle: If True, then the coefs are only non-zero in the upper-left triangle + of the array. [default: False] + """ + if result.dtype == float: + # Note: the c++ implementation doesn't need to care about triangle. + # It is able to trivially account for the zeros without special handling. + _x = x.__array_interface__['data'][0] + _y = y.__array_interface__['data'][0] + _coefs = coefs.__array_interface__['data'][0] + _result = result.__array_interface__['data'][0] + _temp = temp.__array_interface__['data'][0] + _galsim.Horner2D(_x, _y, x.size, _coefs, coefs.shape[0], coefs.shape[1], _result, _temp) + else: + if triangle: + result.fill(coefs[-1][0]) + for k, coef in enumerate(coefs[-2::-1]): + result *= x + _horner(y, coef[:k+2], temp) + result += temp + else: + _horner(y, coefs[-1], result) + for coef in coefs[-2::-1]: + result *= x + _horner(y, coef, temp) + result += temp
+ + +def horner3d(x, y, u, coefs): + result = horner2d(y, u, coefs[-1]) + for coef in coefs[-2::-1]: + result *= x + result += horner2d(y, u, coef) + return result + + +def horner4d(x, y, u, v, coefs): + result = horner3d(y, u, v, coefs[-1]) + for coef in coefs[-2::-1]: + result *= x + result += horner3d(y, u, v, coef) + return result + + +
[docs]def deInterleaveImage(image, N, conserve_flux=False,suppress_warnings=False): + """ + The routine to do the opposite of what `interleaveImages` routine does. It generates a + (uniform) dither sequence of low resolution images from a high resolution image. + + Many pixel level detector effects, such as interpixel capacitance, persistence, charge + diffusion etc. can be included only on images drawn at the native pixel scale, which happen to + be undersampled in most cases. Nyquist-sampled images that also include the effects of detector + non-idealities can be obtained by drawing multiple undersampled images (with the detector + effects included) that are offset from each other by a fraction of a pixel. If the offsets are + uniformly spaced, then images can be combined using `interleaveImages` into a Nyquist-sampled + image. + + Drawing multiple low resolution images of a light profile can be a lot slower than drawing a + high resolution image of the same profile, even if the total number of pixels is the same. A + uniformly offset dither sequence can be extracted from a well-resolved image that is drawn by + convolving the surface brightness profile explicitly with the native pixel response and setting + a lower sampling scale (or higher sampling rate) using the ``pixel_scale`` argument in + `GSObject.drawImage` routine and setting the ``method`` parameter to 'no_pixel'. + + Here is an example script using this routine: + + Example:: + + >>> n = 2 + >>> gal = galsim.Gaussian(sigma=2.8) + >>> gal_pix = galsim.Convolve([gal,galsim.Pixel(scale=1.0)]) + >>> img = gal_pix.drawImage(gal_pix,scale=1.0/n,method='no_pixel') + >>> im_list, offsets = galsim.utilities.deInterleaveImage(img,N=n) + >>> for im in im_list: + >>> im.applyNonlinearity(lambda x: x-0.01*x**2) #detector effects + >>> img_new = galsim.utilities.interleaveImages(im_list,N=n,offsets) + + Parameters: + image: Input image from which lower resolution images are extracted. + N: Number of images extracted in either directions. It can be of type + 'int' if equal number of images are extracted in both directions or a + list or tuple of two integers, containing the number of images in x + and y directions respectively. + conserve_flux: Should the routine output images that have, on average, same total + pixel values as the input image (True) or should the pixel values + summed over all the images equal the sum of pixel values of the input + image (False)? [default: False] + suppress_warnings: Suppresses the warnings about the pixel scale of the output, if True. + [default: False] + + Returns: + a list of images (`Image`) and offsets (`PositionD`) to reconstruct the input image using + `interleaveImages`. + """ + if isinstance(N,int): + n1,n2 = N,N + else: + try: + n1,n2 = N + except (TypeError, ValueError): + raise TypeError("N must be an integer or a tuple of two integers") from None + + if not isinstance(image, Image): + raise TypeError("image must be an instance of galsim.Image") + + y_size,x_size = image.array.shape + if x_size%n1 or y_size%n2: + raise GalSimIncompatibleValuesError( + "The value of N is incompatible with the dimensions of the image to be deinterleaved", + N=N, image=image) + + im_list, offsets = [], [] + for i in range(n1): + for j in range(n2): + # The tricky part - going from array indices to Image coordinates (x,y) + # DX[i'] = -(i+0.5)/n+0.5 = -i/n + 0.5*(n-1)/n + # i = -n DX[i'] + 0.5*(n-1) + dx,dy = -(i+0.5)/n1+0.5,-(j+0.5)/n2+0.5 + offset = _PositionD(dx,dy) + img_arr = image.array[j::n2,i::n1].copy() + img = Image(img_arr) + if conserve_flux is True: + img *= n1*n2 + im_list.append(img) + offsets.append(offset) + + wcs = image.wcs + if wcs is not None and wcs._isUniform: + jac = wcs.jacobian() + for img in im_list: + img_wcs = JacobianWCS(jac.dudx*n1,jac.dudy*n2,jac.dvdx*n1,jac.dvdy*n2) + ## Since pixel scale WCS is not equal to its jacobian, checking if img_wcs is a pixel + ## scale + img_wcs_decomp = img_wcs.getDecomposition() + if img_wcs_decomp[1].g==0: + img.wcs = PixelScale(img_wcs_decomp[0]) + else: + img.wcs = img_wcs + ## Preserve the origin so that the interleaved image has the same bounds as the image + ## that is being deinterleaved. + img.setOrigin(image.origin) + + elif suppress_warnings is False: + galsim_warn("Individual images could not be assigned a WCS automatically.") + + return im_list, offsets
+ + +
[docs]def interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False, + catch_offset_errors=True): + """ + Interleaves the pixel values from two or more images and into a single larger image. + + This routine converts a list of images taken at a series of (uniform) dither offsets into a + single higher resolution image, where the value in each final pixel is the observed pixel + value from exactly one of the original images. It can be used to build a Nyquist-sampled image + from a set of images that were observed with pixels larger than the Nyquist scale. + + In the original observed images, the integration of the surface brightness over the pixels is + equivalent to convolution by the pixel profile and then sampling at the centers of the pixels. + This procedure simulates an observation sampled at a higher resolution than the original images, + while retaining the original pixel convolution. + + Such an image can be obtained in a fairly simple manner in simulations of surface brightness + profiles by convolving them explicitly with the native pixel response and setting a lower + sampling scale (or higher sampling rate) using the ``pixel_scale`` argument in + `GSObject.drawImage` routine and setting the ``method`` parameter to 'no_pixel'. + + However, pixel level detector effects can be included only on images drawn at the native pixel + scale, which happen to be undersampled in most cases. Nyquist-sampled images that also include + the effects of detector non-idealities can be obtained by drawing multiple undersampled images + (with the detector effects included) that are offset from each other by a fraction of a pixel. + + This is similar to other procedures that build a higher resolution image from a set of low + resolution images, such as MultiDrizzle and IMCOM. A disadvantage of this routine compared to + ther others is that the images must be offset in equal steps in each direction. This is + difficult to acheive with real observations but can be precisely acheived in a series of + simulated images. + + An advantage of this procedure is that the noise in the final image is not correlated as the + pixel values are each taken from just a single input image. Thus, this routine preserves the + noise properties of the pixels. + + Here's an example script using this routine: + + Example:: + + >>> n = 2 + >>> gal = galsim.Gaussian(sigma=2.8) + >>> DX = numpy.arange(0.0,1.0,1./n) + >>> DX -= DX.mean() + >>> im_list, offsets = [], [] + >>> for dx in DX: + ... offset = galsim.PositionD(dx,0.0) + ... offsets.append(offset) + ... im = galsim.Image(16,16) + ... gal.drawImage(image=im,offset=offset,scale=1.0) + ... im.applyNonlinearity(lambda x: x - 0.01*x**2) # detector effects + ... im_list.append(im) + >>> img = galsim.utilities.interleaveImages(im_list=im_list,N=(n,1),offsets=offsets) + + Parameters: + im_list: A list containing the `galsim.Image` instances to be interleaved. + N: Number of images to interleave in either directions. It can be of + type ``int`` if equal number of images are interleaved in both + directions or a list or tuple of two integers, containing the number + of images in x and y directions respectively. + offsets: A list containing the offsets as galsim.PositionD instances + corresponding to every image in ``im_list``. The offsets must be + spaced equally and must span an entire pixel area. The offset + values must be symmetric around zero, hence taking positive and + negative values, with upper and lower limits of +0.5 and -0.5 + (limit values excluded). + add_flux: Should the routine add the fluxes of all the images (True) or + average them (False)? [default: True] + suppress_warnings: Suppresses the warnings about the pixel scale of the output, if + True. [default: False] + catch_offset_errors: Checks for the consistency of ``offsets`` with ``N`` and raises + errors if inconsistencies found (True). Recommended, but could slow + down the routine a little. [default: True] + + Returns: + the interleaved `Image` + """ + if isinstance(N,int): + n1,n2 = N,N + else: + try: + n1,n2 = N + except (TypeError, ValueError): + raise TypeError("N must be an integer or a tuple of two integers") from None + + if len(im_list)<2: + raise GalSimValueError("im_list must have at least two instances of galsim.Image", im_list) + + if (n1*n2 != len(im_list)): + raise GalSimIncompatibleValuesError( + "N is incompatible with the number of images in im_list", N=N, im_list=im_list) + + if len(im_list)!=len(offsets): + raise GalSimIncompatibleValuesError( + "im_list and offsets must be lists of same length", im_list=im_list, offsets=offsets) + + for offset in offsets: + if not isinstance(offset, PositionD): + raise TypeError("offsets must be a list of galsim.PositionD instances") + + if not isinstance(im_list[0], Image): + raise TypeError("im_list must be a list of galsim.Image instances") + + # These should be the same for all images in im_list. + y_size, x_size = im_list[0].array.shape + wcs = im_list[0].wcs + + for im in im_list[1:]: + if not isinstance(im, Image): + raise TypeError("im_list must be a list of galsim.Image instances") + + if im.array.shape != (y_size,x_size): + raise GalSimIncompatibleValuesError( + "All galsim.Image instances in im_list must be of the same size", im_list=im_list) + + if im.wcs != wcs: + raise GalSimIncompatibleValuesError( + "All galsim.Image instances in im_list must have the same WCS", im_list=im_list) + + img_array = np.zeros((n2*y_size,n1*x_size)) + # The tricky part - going from (x,y) Image coordinates to array indices + # DX[i'] = -(i+0.5)/n+0.5 = -i/n + 0.5*(n-1)/n + # i = -n DX[i'] + 0.5*(n-1) + for k in range(len(offsets)): + dx, dy = offsets[k].x, offsets[k].y + + i = int(round((n1-1)*0.5-n1*dx)) + j = int(round((n2-1)*0.5-n2*dy)) + + if catch_offset_errors is True: + err_i = (n1-1)*0.5-n1*dx - round((n1-1)*0.5-n1*dx) + err_j = (n2-1)*0.5-n2*dy - round((n2-1)*0.5-n2*dy) + tol = 1.e-6 + if abs(err_i)>tol or abs(err_j)>tol: + raise GalSimIncompatibleValuesError( + "offsets must be a list of galsim.PositionD instances with x values " + "spaced by 1/{0} and y values by 1/{1} around 0.".format(n1,n2), + N=N, offsets=offsets) + + if i<0 or j<0 or i>=n1 or j>=n2: + raise GalSimIncompatibleValuesError( + "offsets must be a list of galsim.PositionD instances with x values " + "spaced by 1/{0} and y values by 1/{1} around 0.".format(n1,n2), + N=N, offsets=offsets) + else: + # If we're told to just trust the offsets, at least make sure the slice will be + # the right shape. + i = i%n1 + j = j%n2 + + img_array[j::n2,i::n1] = im_list[k].array + + img = Image(img_array) + if not add_flux: + # Fix the flux normalization + img /= 1.0*len(im_list) + + # Assign an appropriate WCS for the output + if wcs is not None and wcs._isUniform: + jac = wcs.jacobian() + dudx, dudy, dvdx, dvdy = jac.dudx, jac.dudy, jac.dvdx, jac.dvdy + img_wcs = JacobianWCS(1.*dudx/n1,1.*dudy/n2,1.*dvdx/n1,1.*dvdy/n2) + ## Since pixel scale WCS is not equal to its jacobian, checking if img_wcs is a pixel scale + img_wcs_decomp = img_wcs.getDecomposition() + if img_wcs_decomp[1].g==0: ## getDecomposition returns scale,shear,angle,flip + img.wcs = PixelScale(img_wcs_decomp[0]) + else: + img.wcs = img_wcs + + elif not suppress_warnings: + galsim_warn("Interleaved image could not be assigned a WCS automatically.") + + # Assign a possibly non-trivial origin and warn if individual image have different origins. + orig = im_list[0].origin + img.setOrigin(orig) + if any(im.origin != orig for im in im_list[1:]): + if not suppress_warnings: + galsim_warn("Images in im_list have multiple values for origin. Assigning the " + "origin of the first Image instance in im_list to the interleaved image.") + + return img
+ +
[docs]@contextmanager +def printoptions(*args, **kwargs): + """A context manager for using different numpy printoptions temporarily + + From http://stackoverflow.com/questions/2891790/pretty-printing-of-numpy-array + + Usage:: + + with printoptions(threshold=len(long_arr)): + print(long_arr) # Doesn't omit values in the middle of the array + print(long_arr) # If the array is long enough, will use ... in the middle. + + .. note:: + It seems Numpy finally added this feature in version 1.15. So this is probably + equivalent to using ``numpy.printoptions`` instead of ``galsim.utilities.printoptions``. + """ + original = np.get_printoptions() + np.set_printoptions(*args, **kwargs) + # contextmanager exception handling is tricky. Don't forget to wrap the yield: + # http://preshing.com/20110920/the-python-with-statement-by-example/ + try: + yield + finally: + np.set_printoptions(**original)
+ + +_pickle_shared = False + +
[docs]@contextmanager +def pickle_shared(): + """A context manager to flag that one wishes to include object state from shared memory in + pickle objects. + + Example:: + + obj = galsim_obj_with_shared_state() # e.g., galsim.phase_screens.AtmosphericScreen + pickle.dump(obj, file) + + # restart python, unloading shared state + obj = pickle.load(file) # fails due to missing shared state. + + obj = galsim_obj_with_shared_state() + with pickle_shared(): + pickle.dump(obj, filename) + + # restart python, again unloading shared state + obj = pickle.load(file) # loads both obj and required shared state. + """ + global _pickle_shared + original = _pickle_shared + _pickle_shared = True + try: + yield + finally: + _pickle_shared = original
+ + +
[docs]def listify(arg): + """Turn argument into a list if not already iterable. + + Parameters: + arg: An argument that may be a lit or a single item + + Returns: + [arg] if arg was not already a list, otherwise arg. + """ + return [arg] if not hasattr(arg, '__iter__') else arg
+ + +
[docs]def dol_to_lod(dol, N=None, scalar_string=True): + """Generate list of dicts from dict of lists (with broadcasting). + Specifically, generate "scalar-valued" kwargs dictionaries from a kwarg dictionary with values + that are length-N lists, or possibly length-1 lists or scalars that should be broadcasted up to + length-N lists. + + Parameters: + dol: A dict of lists + N: The length of the lists if known in advance. [default: None, which will + determine the maximum length of the lists for you] + scalar_string: Whether strings in the input list should be treated as scalars (and thus + broadcast to each item in the output) or not (in which case, they will + be treated as lists of characters) [default: True] + + Returns: + A list of dicts + """ + if N is None: + if scalar_string: + lens = [len(v) for v in dol.values() + if hasattr(v, '__len__') + and not isinstance(v, str)] + else: + lens = [len(v) for v in dol.values() + if hasattr(v, '__len__')] + N = max(lens) if lens != [] else 1 + # Loop through broadcast range + for i in range(N): + out = {} + for k, v in dol.items(): + if scalar_string and isinstance(v, str): + out[k] = v + continue + try: + out[k] = v[i] + except IndexError: # It's list-like, but too short. + if len(v) != 1: + raise GalSimIncompatibleValuesError( + "Cannot broadcast kwargs of different non-length-1 lengths.", dol=dol) from None + out[k] = v[0] + except TypeError: # Value is not list-like, so broadcast it in its entirety. + out[k] = v + except Exception: + raise GalSimIncompatibleValuesError( + "Cannot broadcast kwarg {0}={1}".format(k, v), dol=dol) + yield out
+ +
[docs]def structure_function(image): + r"""Estimate the angularly-averaged structure function of a 2D random field. + + The angularly-averaged structure function D(r) of the 2D field phi is defined as: + + .. math:: + D(|r|) = \langle |phi(x) - phi(x+r)|^2 \rangle + + where the x and r on the RHS are 2D vectors, but the :math:`|r|` on the LHS is just a scalar + length. + + The image must have its ``scale`` attribute defined. It will be used in the calculations to + set the scale of the radial distances. + + Parameters: + image: `Image` containing random field realization. + + Returns: + A python callable mapping a separation length r to the estimate of the structure + function D(r). + """ + array = image.array + ny, nx = array.shape + scale = image.scale + + # The structure function can be derived from the correlation function B(r) as: + # D(r) = 2 * [B(0) - B(r)] + + corr = np.fft.ifft2(np.abs(np.fft.fft2(np.fft.fftshift(array)))**2).real / (nx * ny) + # Check that the zero-lag correlation function is equal to the variance before doing the + # ifftshift. + #assert (corr[0, 0] / np.var(array) - 1.0) < 1e-6 + corr = np.fft.ifftshift(corr) + + x = scale * (np.arange(nx) - nx//2) + y = scale * (np.arange(ny) - ny//2) + tab = LookupTable2D(x, y, corr) + thetas = np.arange(0., 2*np.pi, 100) # Average over these angles. + + return lambda r: 2*(tab(0.0, 0.0) - np.mean(tab(r*np.cos(thetas), r*np.sin(thetas))))
+ +
[docs]def merge_sorted(arrays): + r"""Merge 2 or more numpy arrays into a single merged array. + + Each of the input arrays must be already sorted. + + This is equivalent to np.unique(np.concatenate(arrays)), but much faster. + + Parameters: + arrays: A list of arrays to merge. + + Returns: + A single numpy.array with the merged values. + """ + try: + return _galsim.MergeSorted(list(arrays)) + except Exception as e: + # Probably the inputs are not sorted. Try to give the user more information. + for i,a in enumerate(arrays): + if not np.all(np.diff(a)>=0): + raise GalSimValueError("Not all arrays input to merge_sorted are sorted." + + "The first such case is at index %s"%i, + value=a) from None + else: + # That wasn't it. Just reraise the exception, converted to a GalSimError. + raise GalSimError(str(e)) from None
+ +
[docs]def combine_wave_list(*args): + """Combine wave_list attributes of all objects in obj_list while respecting blue_limit and + red_limit attributes. Should work with any combination of `SED`, `Bandpass`, and + `ChromaticObject` instances. + + Parameters: + obj_list: List of `SED`, `Bandpass`, or `ChromaticObject` instances. + + Returns: + wave_list, blue_limit, red_limit + """ + if len(args) == 1: + if isinstance(args[0], (list, tuple)): + args = args[0] + else: + raise TypeError("Single input argument must be a list or tuple") + + if len(args) == 0: + return np.array([], dtype=float), 0.0, np.inf + + if len(args) == 1: + obj = args[0] + return obj.wave_list, getattr(obj, 'blue_limit', 0.0), getattr(obj, 'red_limit', np.inf) + + blue_limit = np.max([getattr(obj, 'blue_limit', 0.0) for obj in args]) + red_limit = np.min([getattr(obj, 'red_limit', np.inf) for obj in args]) + if blue_limit > red_limit: + raise GalSimError("Empty wave_list intersection.") + + waves = [np.asarray(obj.wave_list) for obj in args] + waves = [w[(blue_limit <= w) & (w <= red_limit)] for w in waves] + # Make sure both limits are included in final list + if any(len(w) > 0 for w in waves): + waves.append([blue_limit, red_limit]) + wave_list = merge_sorted(waves) + + return wave_list, blue_limit, red_limit
+ +
[docs]def functionize(f): + """Decorate a function ``f`` which accepts scalar positional or keyword arguments, to accept + arguments that can be either scalars or _functions_. If the arguments include univariate + (N-variate) functions, then the output will be a univariate (N-variate) function. While it's + okay to mix scalar and N-variate function arguments, it is an error to mix N-variate and + M-variate function arguments. + + Example:: + + >>> def f(x, y): # Function of two scalars. + ... return x + y + >>> decorated = functionize(f) # Function of two scalars, functions, or a mix. + >>> result = f(2, 3) # 5 + >>> result = f(2, lambda u: u) # Generates a TypeError + >>> result = decorated(2, 3) # Scalar args returns a scalar + >>> result = decorated(2, lambda u: u) # Univariate argument leads to a univariate output. + >>> print(result(5)) # 7 + >>> result = decorated(2, lambda u,v: u*v) # Bivariate argument leads to a bivariate output. + >>> print(result(5, 7)) # 2 + (5*7) = 37 + + We can use arguments that accept keyword arguments too:: + + >>> def f2(u, v=None): + ... if v is None: + ... v = 6.0 + ... return u / v + >>> result = decorated(2, f2) + >>> print(result(12)) # 2 + (12./6) = 4.0 + >>> print(result(12, v=4)) # 2 + (12/4) = 5 + + Note that you can also use python's decorator syntax:: + + >>> @functionize + >>> def f(x, y): + ... return x + y + + Parameters: + f: The function to be decorated. + + Returns: + The decorated function. + """ + @functools.wraps(f) + def ff(*args, **kwargs): + # First check if any of the arguments are callable... + if not any(hasattr(arg, '__call__') for arg in args+tuple(kwargs.values())): + return f(*args, **kwargs) # ... if not, then keep output type a scalar ... + else: + def fff(*inner_args, **inner_kwargs): # ...else the output type is a function: fff. + new_args = [arg + if not hasattr(arg, '__call__') + else arg(*inner_args, **inner_kwargs) + for arg in args] + new_kwargs = dict([(k, v) + if not hasattr(v, '__call__') + else (k, v(*inner_args, **inner_kwargs)) + for k, v in kwargs.items()]) + return f(*new_args, **new_kwargs) + return fff + return ff
+ +
[docs]def binomial(a, b, n): + """Return xy coefficients of (ax + by)^n ordered by descending powers of a. + + Example:: + + # (x + y)^3 = 1 x^3 + 3 x^2 y + 3 x y^2 + 1 y^3 + >>> print(binomial(1, 1, 3)) + array([ 1., 3., 3., 1.]) + + + # (2 x + y)^3 = 8 x^3 + 12 x^2 y + 6 x y^2 + 1 y^3 + >>> print(binomial(2, 1, 3)) + array([ 8., 12., 6., 1.]) + + Parameters: + a: First scalar in binomial to be expanded. + b: Second scalar in binomial to be expanded. + n: Exponent of expansion. + + Returns: + Array of coefficients in expansion. + """ + b_over_a = float(b)/float(a) + def generate(): + c = a**n + yield c + for i in range(n): # pragma: no branch (It never actually gets past the last yield.) + c *= b_over_a * (n-i)/(i+1) + yield c + return np.fromiter(generate(), float, n+1)
+ +
[docs]def unweighted_moments(image, origin=None): + """Computes unweighted 0th, 1st, and 2nd moments in image coordinates. Respects image bounds, + but ignores any scale or wcs. + + Parameters: + image: `Image` from which to compute moments + origin: Optional origin in image coordinates used to compute Mx and My + [default: galsim.PositionD(0, 0)]. + + Returns: + Dict with entries for [M0, Mx, My, Mxx, Myy, Mxy] + """ + if origin is None: + origin = _PositionD(0,0) + a = image.array.astype(float) + offset = image.origin - origin + xgrid, ygrid = np.meshgrid(np.arange(image.array.shape[1]) + offset.x, + np.arange(image.array.shape[0]) + offset.y) + M0 = np.sum(a) + Mx = np.sum(xgrid * a) / M0 + My = np.sum(ygrid * a) / M0 + Mxx = np.sum(((xgrid-Mx)**2) * a) / M0 + Myy = np.sum(((ygrid-My)**2) * a) / M0 + Mxy = np.sum((xgrid-Mx) * (ygrid-My) * a) / M0 + return dict(M0=M0, Mx=Mx, My=My, Mxx=Mxx, Myy=Myy, Mxy=Mxy)
+ +
[docs]def unweighted_shape(arg): + """Computes unweighted second moment size and ellipticity given either an image or a dict of + unweighted moments. + + The size is: + rsqr = Mxx+Myy + The ellipticities are: + e1 = (Mxx-Myy) / rsqr + e2 = 2*Mxy / rsqr + + Parameters: + arg: Either a `galsim.Image` or the output of unweighted_moments(image). + + Returns: + Dict with entries for [rsqr, e1, e2] + """ + if isinstance(arg, Image): + arg = unweighted_moments(arg) + rsqr = arg['Mxx'] + arg['Myy'] + return dict(rsqr=rsqr, e1=(arg['Mxx']-arg['Myy'])/rsqr, e2=2*arg['Mxy']/rsqr)
+ +
[docs]def rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False): + """Select some number of random choices from a list, with replacement, using a supplied RNG. + + ``n`` random choices with replacement are made assuming that those choices should range from 0 + to ``n_choices-1``, so they can be used as indices in a list/array. If ``weight`` is supplied, + then it should be an array of length ``n_choices`` that ranges from 0-1, and can be used to + make weighted choices from the list. + + Parameters: + n: Number of random selections to make. + n_choices: Number of entries from which to choose. + rng: RNG to use. Must a `galsim.BaseDeviate` instance. + weight: Optional list of weight factors to use for weighting the selection of + random indices. + + Returns: + a NumPy array of length n containing the integer-valued indices that were selected. + """ + # Make sure we got a proper RNG. + if not isinstance(rng, BaseDeviate): + raise TypeError("The rng provided to rand_with_replacement() must be a BaseDeviate") + ud = UniformDeviate(rng) + + # Sanity check the requested number of random indices. + # Note: we do not require that the type be an int, as long as the value is consistent with + # an integer value (i.e., it could be a float 1.0 or 1). + if n != int(n) or n < 1: + raise GalSimValueError("n must be an integer >= 1.", n) + if n_choices != int(n_choices) or n_choices < 1: + raise GalSimValueError("n_choices must be an integer >= 1.", n_choices) + + # Sanity check the input weight. + if weight is not None: + # We need some sanity checks here in case people passed in weird values. + if len(weight) != n_choices: + raise GalSimIncompatibleValuesError( + "Array of weights has wrong length", weight=weight, n_choices=n_choices) + if (np.any(np.isnan(weight)) or np.any(np.isinf(weight)) or + np.min(weight)<0 or np.max(weight)>1): + raise GalSimRangeError("Supplied weights include values outside [0,1] or inf/NaN.", + weight, 0., 1.) + + # We first make a random list of integer indices. + index = np.zeros(n) + ud.generate(index) + if _n_rng_calls: + # Here we use the undocumented kwarg (for internal use by config) to track the number of + # RNG calls. + n_rng_calls = n + index = (n_choices*index).astype(int) + + # Then we account for the weights, if possible. + if weight is not None: + # If weight factors are available, make sure the random selection uses the weights. + test_vals = np.zeros(n) + # Note that the weight values by definition have a maximum of 1, as enforced above. + ud.generate(test_vals) + if _n_rng_calls: + n_rng_calls += n + # The ones with mask==True are the ones we should replace. + mask = test_vals > weight[index] + while np.any(mask): + # Update the index and test values for those that failed. We have to do this by + # generating random numbers into new arrays, because ud.generate() does not enable + # us to directly populate a sub-array like index[mask] or test_vals[mask]. + n_fail = mask.astype(int).sum() + # First update the indices that failed. + new_arr = np.zeros(n_fail) + ud.generate(new_arr) + index[mask] = (n_choices*new_arr).astype(int) + # Then update the test values that failed. + new_test_vals = np.zeros(n_fail) + ud.generate(new_test_vals) + test_vals[mask] = new_test_vals + if _n_rng_calls: + n_rng_calls += 2*n_fail + # Finally, update the test array used to determine whether any galaxies failed. + mask = test_vals > weight[index] + + if _n_rng_calls: + return index, n_rng_calls + else: + return index
+ + +
[docs]def check_share_file(filename, subdir): + """Find `SED` or `Bandpass` file, possibly adding share_dir/subdir. + + Parameters: + filename: The file name to look for + subdir: The subdirectory of `galsim.meta_data.share_dir` where this file might be. + + Returns: + True, correct_filename if the file was found + False, '' if not + """ + if os.path.isfile(filename): + return True, filename + + new_filename = os.path.join(meta_data.share_dir, subdir, filename) + if os.path.isfile(new_filename): + return True, new_filename + else: + return False, ''
+ + +
[docs]class OrderedWeakRef(weakref.ref): + """Assign an arbitrary ordering to weakref.ref so that it can be part of a heap. + """ + def __lt__(self, other): + return id(self) < id(other)
+ + +
[docs]def nCr(n, r): + """Combinations. I.e., the number of ways to choose ``r`` distiguishable things from ``n`` + distinguishable things. + + Parameters: + n The number of options to choose from. + r The number of items to choose + + Returns: + nCr, the (n,r) binomial coefficient. + + .. note:: + In Python 3, the factorial function was improved such that doing this the direct way + of calculating n! / (r! (n-r)!) seems to be the fastest algorith. In Python 2, for + largish values of n, a more complicated algorithm that avoided large integers was + faster. This function uses the direct method for both -- we don't bother to check the + version of Python to potentially select a different algorithm in the two cases. + """ + if 0 <= r <= n: + return math.factorial(n) // (math.factorial(r) * math.factorial(n-r)) + else: + return 0
+ +
[docs]def set_omp_threads(num_threads, logger=None): + """Set the number of OpenMP threads to use in the C++ layer. + + :param num_threads: The target number of threads to use (If None or <=0, then try to use the + numer of cpus.) + :param logger: If desired, a logger object for logging any warnings here. (default: None) + + :returns: The number of threads OpenMP reports that it will use. Typically this + matches the input, but OpenMP reserves the right not to comply with + the requested number of threads. + """ + # This function was copied shamelessly from TreeCorr's function of the same name. + + input_num_threads = num_threads # Save the input value. + + # If num_threads is auto, get it from cpu_count + if num_threads is None or num_threads <= 0: + num_threads = multiprocessing.cpu_count() + if logger: + logger.debug('multiprocessing.cpu_count() = %d',num_threads) + + # Tell OpenMP to use this many threads + if logger: + logger.debug('Telling OpenMP to use %d threads',num_threads) + + # Cf. comment in get_omp_threads. Do it here too. + var = "OMP_PROC_BIND" + if var not in os.environ: + os.environ[var] = "false" + num_threads = _galsim.SetOMPThreads(num_threads) + + # Report back appropriately. + if logger: + logger.debug('OpenMP reports that it will use %d threads',num_threads) + if num_threads > 1: + logger.info('Using %d threads.',num_threads) + elif input_num_threads is not None and input_num_threads != 1: + # Only warn if the user specifically asked for num_threads != 1. + logger.warning("Unable to use multiple threads, since OpenMP is not enabled.") + + return num_threads
+ +
[docs]def get_omp_threads(): + """Get the current number of OpenMP threads to be used in the C++ layer. + + :returns: num_threads + """ + # Some OMP implemenations have a bug where if omp_get_max_threads() is called + # (which is what this function does), it sets something called thread affinity. + # The upshot of that is that multiprocessing (i.e. not even just omp threading) is confined + # to a single hardware thread. Yeah, it's idiotic, but that seems to be the case. + # The only solution found by Eli, who looked into it pretty hard, is to set the env + # variable OMP_PROC_BIND to "false". This seems to stop the bad behavior. + # So we do it here always before calling GetOMPThreads. + # If this breaks someone valid use of this variable, let us know and we can try to + # come up with another solution, but without this lots of multiprocessing breaks. + var = "OMP_PROC_BIND" + if var not in os.environ: + os.environ[var] = "false" + return _galsim.GetOMPThreads()
+ +
[docs]@contextmanager +def single_threaded(*, num_threads=1): + """A context manager that turns off (or down) OpenMP threading e.g. during multiprocessing. + + Usage: + + .. code:: + + with single_threaded(): + # Code where you don't want to use any OpenMP threads in the C++ layer + # E.g. starting a multiprocessing pool, where you don't want each process + # to use multiple threads, potentially ending up with n_cpu^2 threads + # running at once, which would generally be bad for performance. + + .. note:: + + This is especaily important when your compiler is gcc and you are using the + "fork" context in multiprocessing. There is a bug in gcc that can cause an + OpenMP parallel block to hang after forking. + cf. `make it possible to use OMP on both sides of a fork <https://patchwork.ozlabs.org/project/gcc/patch/CAPJVwBkOF5GnrMr=4d1ehEKRGkY0tHzJzfXAYBguawu9y5wxXw@mail.gmail.com/#712883>`_ + for more discussion about this issue. + + It can also be used to set a particular number of threads other than 1, using the + optional parameter ``num_threads``, although the original intent of this class is + to leave that as 1 (the default). + + Parameters: + num_threads: The number of threads you want during the context [default: 1] + """ + # Get the current number of threads here, so we can set it back when we're done. + orig_num_threads = get_omp_threads() + temp_num_threads = num_threads + + # If threadpoolctl is installed, use that too, since it will set blas libraries to + # be single threaded too. This makes it so you don't need to set the environment + # variables OPENBLAS_NUM_THREAD=1 or MKL_NUM_THREADS=1, etc. + try: + import threadpoolctl + except ImportError: + tpl = None + else: # pragma: no cover (Not installed on GHA currently.) + tpl = threadpoolctl.threadpool_limits(num_threads) + + set_omp_threads(temp_num_threads) + with warnings.catch_warnings(): + # Starting in python 3.12, there is a deprecation warning about using fork when + # a process is multithreaded. Unfortunately, this applies even to processes that + # are currently single threaded, but used multi-threading previously. + # So if a user is doing something in an explicitly single-threaded context, + # suppress this DeprecationWarning. + warnings.filterwarnings("ignore", category=DeprecationWarning) + yield + + set_omp_threads(orig_num_threads) + if tpl is not None: # pragma: no cover + tpl.unregister()
+ + + +# The rest of these are only used by the tests in GalSim. But we make them available +# for other code bases who may want to use them as well. + +
[docs]def check_pickle(obj, func = lambda x : x, irreprable=False, random=None): + """Check that the object is picklable. + + Also check some related desirable properties that we want for (almost) all galsim objects: + + 1. pickle.loads(pickle.dumps(obj)) recovers something equivalent to the original. + 2. obj != object() and object() != obj. (I.e. it doesn't == things it shouldn't.) + 3. hash(obj) is the same for two equivalent objects, if it is hashable. + 4. copy.copy(obj) returns an equivalent copy unless random=True. + 5. copy.deepcopy(obj) returns an equivalent copy. + 6. eval(repr(obj)) returns an equivalent copy unless random or irreprable=True. + 7. repr(obj) makes something if irreprable=False. + + Parameters: + obj: The object to test + func: A function of obj to use to test for equivalence. [default: lambda x: x] + irreprable: Whether to skip the eval(repr(obj)) test. [default: False] + random: Whether the obj has some intrinsic randomness. [default: False, unless + it has an rng attribute or it is a galsim.BaseDeviate] + """ + # In case the repr uses these: + import galsim + import coord + import astropy + from numpy import array, uint16, uint32, int16, int32, float32, float64, complex64, complex128, ndarray + from astropy.units import Unit + import astropy.io.fits + + print('Try pickling ',str(obj)) + + obj2 = pickle.loads(pickle.dumps(obj)) + assert obj2 is not obj + f1 = func(obj) + f2 = func(obj2) + assert f2 == f1, f"func(obj) = {f1}\nfunc(obj2) = {f2}\nrepr(obj) = {repr(obj)}\nrepr(obj2)={repr(obj2)}" + + # Check that == works properly if the other thing isn't the same type. + assert f1 != object() + assert object() != f1 + + # Test the hash values are equal for two equivalent objects. + if isinstance(obj, Hashable): + assert hash(obj) == hash(obj2), f"hash(obj) = {hash(obj)}, hash(obj2) = {hash(obj2)}" + + obj3 = copy.copy(obj) + assert obj3 is not obj + if random is None: + random = hasattr(obj, 'rng') or isinstance(obj, BaseDeviate) or 'rng' in repr(obj) + if not random: # Things with an rng attribute won't be identical on copy. + f3 = func(obj3) + assert f3 == f1 + elif isinstance(obj, BaseDeviate): + f1 = func(obj) # But BaseDeviates will be ok. Just need to remake f1. + f3 = func(obj3) + assert f3 == f1, f"func(obj) = {f1}\nfunc(obj3) = {f3}" + + obj4 = copy.deepcopy(obj) + assert obj4 is not obj + f4 = func(obj4) + if random: f1 = func(obj) + # But everything should be identical with deepcopy. + assert f4 == f1, f"func(obj) = {f1}\nfunc(obj4) = {f4}" + + # Also test that the repr is an accurate representation of the object. + # The gold standard is that eval(repr(obj)) == obj. So check that here as well. + # A few objects we don't expect to work this way in GalSim; when testing these, we set the + # `irreprable` kwarg to true. Also, we skip anything with random deviates since these don't + # respect the eval/repr roundtrip. + + if not random and not irreprable: + # A further complication is that the default numpy print options do not lead to sufficient + # precision for the eval string to exactly reproduce the original object, and start + # truncating the output for relatively small size arrays. So we temporarily bump up the + # precision and truncation threshold for testing. + with printoptions(precision=20, threshold=np.inf): + obj5 = eval(repr(obj)) + f5 = func(obj5) + assert f5 == f1, f"func(obj) = {f1}\nfunc(obj5) = {f5}\nrepr(obj) = {repr(obj)}\nrepr(obj5)={repr(obj5)}" + else: + # Even if we're not actually doing the test, still make the repr to check for syntax errors. + repr(obj)
+ + # Historical note: + # We used to have a test here where we perturbed the construction arguments to make sure + # that objects that should be different really are different. However, that used + # `__getinitargs__`, which we don't use anymore, so we removed this section. + # This means that this inequality test has to be done manually via check_all_diff. + # See releases v2.3 or earlier for the old way we did this. + + +
[docs]def check_all_diff(objs, check_hash=True): + """Test that all objects test as being unequal. + + It checks all pairs of objects in the list and asserts that obj1 != obj2. + + If check_hash=True, then it also checks that their hashes are different. + + Parameters: + objs: A list of objects to test. + check_hash: Whether to also check the hash values. + """ + # Check that all objects are unique. + # Would like to use `assert len(objs) == len(set(objs))` here, but this requires that the + # elements of objs are hashable (and that they have unique hashes!, which is what we're trying + # to test!. So instead, we just loop over all combinations. + for i, obji in enumerate(objs): + assert obji == obji + assert not (obji != obji) + # Could probably start the next loop at `i+1`, but we start at 0 for completeness + # (and to verify a != b implies b != a) + for j, objj in enumerate(objs): + if i == j: + continue + assert obji != objj, ("Found equivalent objects {0} == {1} at indices {2} and {3}" + .format(obji, objj, i, j)) + + if not check_hash: + return + # Now check that all hashes are unique (if the items are hashable). + if not isinstance(objs[0], Hashable): + return + hashes = [hash(obj) for obj in objs] + if not (len(hashes) == len(set(hashes))): # pragma: no cover + for k, v in Counter(hashes).items(): + if v <= 1: + continue + print("Found multiple equivalent object hashes:") + for i, obj in enumerate(objs): + if hash(obj) == k: + print(i, repr(obj)) + assert len(hashes) == len(set(hashes))
+ + +
[docs]def timer(f): + """A decorator that reports how long a function took to run. + + In GalSim we decorate all of our tests with this to try to watch for long-running tests. + """ + @functools.wraps(f) + def f2(*args, **kwargs): + t0 = time.time() + result = f(*args, **kwargs) + t1 = time.time() + fname = repr(f).split()[1] + print('time for %s = %.2f' % (fname, t1-t0)) + return result + return f2
+ + +
[docs]class CaptureLog: + """A context manager that saves logging output into a string that is accessible for + checking in unit tests. + + After exiting the context, the attribute ``output`` will have the logging output. + + Sample usage: + + >>> with CaptureLog() as cl: + ... cl.logger.info('Do some stuff') + >>> assert cl.output == 'Do some stuff' + + """ + def __init__(self, level=3): + logging_levels = { 0: logging.CRITICAL, + 1: logging.WARNING, + 2: logging.INFO, + 3: logging.DEBUG } + self.logger = logging.getLogger('CaptureLog') + self.logger.setLevel(logging_levels[level]) + self.stream = StringIO() + self.handler = logging.StreamHandler(self.stream) + self.logger.addHandler(self.handler) + + def __enter__(self): + return self + + def __exit__(self, type, value, traceback): + self.handler.flush() + self.output = self.stream.getvalue().strip() + self.handler.close()
+ + +# Context to make it easier to profile bits of the code +
[docs]class Profile: + """A context manager that profiles a snippet of code. + + Sample usage: + + >>> with Profile(): + ... slow_function() + + Parameters: + sortby: What parameter to sort the results by. [default: tottime] + nlines: How many lines of output to report. [default: 30] + reverse: Whether to reverse the order of the output lines to put the most important + items at the bottom rather than the top. [default: False] + filename: If desired, a file to output the full profile information in pstats format. + [default: None] + """ + def __init__(self, sortby='tottime', nlines=30, reverse=False, filename=None): + self.sortby = sortby + self.nlines = nlines + self.reverse = reverse + self.filename = filename + + def __enter__(self): + self.pr = cProfile.Profile() + self.pr.enable() + return self + + def __exit__(self, type, value, traceback): + self.pr.disable() + if self.filename: # pragma: no cover + self.pr.dump_stats(self.filename) + ps = pstats.Stats(self.pr).sort_stats(self.sortby) + if self.reverse: # pragma: no cover + ps = ps.reverse_order() + ps.print_stats(self.nlines)
+ + +def least_squares(fun, x0, args=(), kwargs={}, max_iter=1000, tol=1e-9, lambda_init=1.0): + """Perform a non-linear least squares fit using the Levenberg-Marquardt algorithm. + + Drop in replacement for scipy.optimize.least_squares when using default options, + though many fewer options available in general. + + Parameters: + fun: Function which computes vector of residuals, with the signature + fun(params, *args, **kwargs) -> np.ndarray. + x0: Initial guess for the parameters. + args: Additional arguments to pass to the function. + kwargs: Additional keyword arguments to pass to the function. + max_iter: Maximum number of iterations. [default: 1000] + tol: Tolerance for convergence. [default: 1e-9] + lambda_init: Initial damping factor for Levenberg-Marquardt. [default: 1.0] + + Returns: + A named tuple with fields: + x: The final parameter values. + cost: The final cost (sum of squared residuals). + """ + # JM: This is a tweaked version of a ChatGPT-generated implementation of + # Levenberg-Marquardt (cross-checked against the wikipedia page + # https://en.wikipedia.org/wiki/Levenberg%E2%80%93Marquardt_algorithm). + + from collections import namedtuple + params = np.array(x0) + lambda_ = lambda_init + + for _ in range(max_iter): # pragma: no branch + residuals = fun(params, *args, **kwargs) + + # Jacobian matrix + J = np.zeros((len(residuals), len(params))) + for j in range(len(params)): + perturbation = np.zeros(len(params)) + perturbation[j] = tol + J[:, j] = (fun(params + perturbation, *args, **kwargs) - residuals) / tol + + # Regular least squares param update + JTJ = np.dot(J.T, J) + JTr = np.dot(J.T, residuals) + + # Levenberg-Marquardt adjustment + A = JTJ + lambda_ * np.eye(len(JTJ)) + delta_params = np.linalg.solve(A, JTr) + + new_params = params - delta_params + new_residuals = fun(new_params, *args, **kwargs) + + if np.linalg.norm(new_residuals) < np.linalg.norm(residuals): + params = new_params + residuals = new_residuals + lambda_ /= 3 # reduce damping + else: + lambda_ *= 3 # increase damping + + if np.linalg.norm(delta_params) < tol: + break + + cost = 0.5 * np.sum(residuals**2) + + # Create a result object similar to scipy.optimize.OptimizeResult + Result = namedtuple('Result', ['x', 'cost']) + result = Result(x=params, cost=cost) + + return result +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/vonkarman.html b/docs/_build/html/_modules/galsim/vonkarman.html new file mode 100644 index 00000000000..c91d0b1acab --- /dev/null +++ b/docs/_build/html/_modules/galsim/vonkarman.html @@ -0,0 +1,461 @@ + + + + + + galsim.vonkarman — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.vonkarman

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'VonKarman' ]
+
+import numpy as np
+import astropy.units as u
+
+from . import _galsim
+from .gsobject import GSObject
+from .gsparams import GSParams
+from .utilities import lazy_property, doc_inherit
+from .position import _PositionD
+from .angle import arcsec, AngleUnit
+from .errors import GalSimError, convert_cpp_errors, galsim_warn
+from .errors import GalSimIncompatibleValuesError
+
+
+
[docs]class VonKarman(GSObject): + r"""Class describing a von Karman surface brightness profile, which represents a long exposure + atmospheric PSF. The difference between the von Karman profile and the related `Kolmogorov` + profile is that the von Karman profile includes a parameter for the outer scale of atmospheric + turbulence, which is a physical scale beyond which fluctuations in the refractive index stop + growing, typically between 10 and 100 meters. Quantitatively, the von Karman phase fluctuation + power spectrum at spatial frequency f is proportional to + + .. math:: + + P(f) = r_0^{-5/3} \left(f^2 + L_0^{-2}\right)^{-11/6} + + where :math:`r_0` is the Fried parameter which sets the overall turbulence amplitude and + :math:`L_0` is the outer scale in meters. + The Kolmogorov power spectrum proportional to :math:`r_0^{-5/3} f^{-11/3}` is recovered + as :math:`L_0 \rightarrow \infty`. + + For more information, we recommend the following references: + + Martinez et al. 2010 A&A vol. 516 + Conan 2008 JOSA vol. 25 + + .. note:: + + If one blindly follows the math for converting the von Karman power spectrum into a PSF, one + finds that the PSF contains a delta-function at the origin with fractional flux of: + + .. math:: + + F_\mathrm{delta} = e^{-0.086 (r_0/L_0)^{-5/3}} + + In almost all cases of interest this evaluates to something tiny, often on the order of + :math:`10^{-100}` or smaller. By default, GalSim will ignore this delta function entirely + since it usually doesn't make any difference, but can complicate some calculations like + drawing using method='real_space' or by formally requiring huge Fourier transforms for + drawing using method='fft'. If for some reason you want to keep the delta function + though, then you can pass the do_delta=True argument to the VonKarman initializer. + + Parameters: + lam: Wavelength, either as an astropy Quantity or a float in nanometers. + r0: Fried parameter at specified wavelength ``lam``, either as an astropy + Quantity or a float in meters. Exactly one of r0 and r0_500 should be + specified. + r0_500: Fried parameter at 500 nm, either as an astropy Quantity or a float in + meters. Exactly one of r0 and r0_500 should be specified. + L0: Outer scale, either as an astropy Quantity or a float in meters. + [default: 25.0 m] + flux: The flux (in photons/cm^2/s) of the profile. [default: 1] + scale_unit: Units assumed when drawing this profile or evaluating xValue, kValue, + etc. Should be a `galsim.AngleUnit` or a string that can be used to + construct one (e.g., 'arcsec', 'radians', etc.). + [default: galsim.arcsec] + force_stepk: By default, VonKarman will derive a value of stepk from a computed + real-space surface brightness profile and gsparams settings. Although + this profile is cached for future instantiations of identical VonKarman + objects, it is relatively slow to compute for the first instance and + can dominate the compute time when drawing many VonKaman's with + different parameters using method 'fft', 'sb', or 'no_pixel', a + situation that may occur, e.g., in a fitting context. This keyword + enables a user to bypass the real-space profile computation by directly + specifying a stepk value. Note that if the ``.half_light_radius`` + property is queried, or the object is drawn using method 'phot' or + 'real_space', then the real-space profile calculation is performed (if + not cached) at that point. [default: 0.0, which means do not force a + value of stepk] + do_delta: Include delta-function at origin? (not recommended; see above). + [default: False] + suppress_warning: For some combinations of r0 and L0, it may become impossible to satisfy + the gsparams.maxk_threshold criterion (see above). In that case, the + code will emit a warning alerting the user that they may have entered a + non-physical regime. However, this warning can be suppressed with this + keyword. [default: False] + gsparams: An optional `GSParams` argument. [default: None] + """ + _req_params = { "lam" : (float, u.Quantity) } + _opt_params = { + "L0" : (float, u.Quantity), + "flux" : float, + "scale_unit" : str, + "do_delta" : bool + } + _single_params = [ { + "r0" : (float, u.Quantity), + "r0_500" : (float, u.Quantity) + } ] + + _has_hard_edges = False + _is_axisymmetric = True + #_is_analytic_x = True # = not do_delta defined below. + _is_analytic_k = True + + def __init__(self, lam, r0=None, r0_500=None, L0=25.0, flux=1, scale_unit=arcsec, + force_stepk=0.0, do_delta=False, suppress_warning=False, gsparams=None): + if isinstance(lam, u.Quantity): + lam = lam.to_value(u.nm) + if isinstance(r0, u.Quantity): + r0 = r0.to_value(u.m) + if isinstance(r0_500, u.Quantity): + r0_500 = r0_500.to_value(u.m) + if isinstance(L0, u.Quantity): + L0 = L0.to_value(u.m) + + # We lose stability if L0 gets too large. This should be close enough to infinity for + # all practical purposes though. + if L0 > 1e10: + L0 = 1e10 + + if r0 is not None and r0_500 is not None: + raise GalSimIncompatibleValuesError( + "Only one of r0 and r0_500 may be specified", + r0=r0, r0_500=r0_500) + if r0 is None and r0_500 is None: + raise GalSimIncompatibleValuesError( + "Either r0 or r0_500 must be specified", + r0=r0, r0_500=r0_500) + if r0_500 is not None: + r0 = r0_500 * (lam/500.)**1.2 + + if isinstance(scale_unit, str): + self._scale_unit = AngleUnit.from_name(scale_unit) + else: + self._scale_unit = scale_unit + self._scale = self._scale_unit/arcsec + + self._lam = float(lam) + self._r0 = float(r0) + self._L0 = float(L0) + self._flux = float(flux) + self._do_delta = bool(do_delta) + self._gsparams = GSParams.check(gsparams) + self._suppress = bool(suppress_warning) + self._force_stepk = force_stepk + self._sbvk # Make this now, so we get the warning if appropriate. + + @lazy_property + def _sbvk(self): + with convert_cpp_errors(): + sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, self._flux, + self._scale, self._do_delta, self._gsparams._gsp, + self._force_stepk) + + self._delta = sbvk.getDelta() + if not self._suppress: + if self._delta > self._gsparams.maxk_threshold: + galsim_warn("VonKarman delta-function component is larger than maxk_threshold. " + "Please see docstring for information about this component and how " + "to toggle it.") + if self._do_delta: + with convert_cpp_errors(): + sbvk = _galsim.SBVonKarman(self._lam, self._r0, self._L0, + self._flux-self._delta, self._scale, + self._do_delta, self._gsparams._gsp, + self._force_stepk) + return sbvk + + @lazy_property + def _sbp(self): + # Add in a delta function with appropriate amplitude if requested. + if self._do_delta: + sbvk = self._sbvk + sbdelta = _galsim.SBDeltaFunction(self._delta, self._gsparams._gsp) + return _galsim.SBAdd([sbvk, sbdelta], self._gsparams._gsp) + else: + return self._sbvk + + @property + def lam(self): + """The input lam value. + """ + return self._lam + + @property + def r0(self): + """The input r0 value. + """ + return self._r0 + + @property + def r0_500(self): + """The input r0_500 value. + """ + return self._r0*(self._lam/500.)**(-1.2) + + @property + def L0(self): + """The input L0 value. + """ + return self._L0 + + @property + def scale_unit(self): + """The input scale_units. + """ + return self._scale_unit + + @property + def force_stepk(self): + """Forced value of stepk or 0.0. + """ + return self._force_stepk + + @property + def do_delta(self): + """Whether to include the delta function at the center. + """ + return self._do_delta + + @property + def _is_analytic_x(self): + return not self._do_delta + + @property + def delta_amplitude(self): + """The amplitude of the delta function at the center. + """ + self._sbvk # This is where _delta is calculated. + return self._delta + + @property + def half_light_radius(self): + """The half-light radius. + """ + return self._sbvk.getHalfLightRadius() + + def _structure_function(self, rho): + return self._sbvk.structureFunction(rho) + + def __eq__(self, other): + return (self is other or + (isinstance(other, VonKarman) and + self.lam == other.lam and + self.r0 == other.r0 and + self.L0 == other.L0 and + self.flux == other.flux and + self.scale_unit == other.scale_unit and + self.force_stepk == other.force_stepk and + self.do_delta == other.do_delta and + self.gsparams == other.gsparams)) + + def __hash__(self): + return hash(("galsim.VonKarman", self.lam, self.r0, self.L0, self.flux, self.scale_unit, + self.force_stepk, self.do_delta, self.gsparams)) + + def __repr__(self): + out = "galsim.VonKarman(lam=%r, r0=%r, L0=%r"%(self.lam, self.r0, self.L0) + out += ", flux=%r"%self.flux + if self.scale_unit != arcsec: + out += ", scale_unit=%r"%self.scale_unit + if self.force_stepk: + out += ", force_stepk=%r"%self.force_stepk + if self.do_delta: + out += ", do_delta=True" + if self._suppress: + out += ", suppress_warning=True" + out += ", gsparams=%r"%self.gsparams + out += ")" + return out + + def __str__(self): + return "galsim.VonKarman(lam=%r, r0=%r, L0=%r)"%(self.lam, self.r0, self.L0) + + def __getstate__(self): + d = self.__dict__.copy() + d.pop('_sbp',None) + d.pop('_sbvk',None) + return d + + def __setstate__(self, d): + self.__dict__ = d + + @property + def _maxk(self): + return self._sbvk.maxK() + + @property + def _stepk(self): + return self._sbvk.stepK() + + @property + def _max_sb(self): + return self._sbvk.xValue(_PositionD(0,0)._p) + + def _xValue(self, pos): + return self._sbvk.xValue(pos._p) + + def _kValue(self, kpos): + return self._sbp.kValue(kpos._p) + + def _drawReal(self, image, jac=None, offset=(0.,0.), flux_scaling=1.): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + dx,dy = offset + self._sbp.draw(image._image, image.scale, _jac, dx, dy, flux_scaling) + + def _shoot(self, photons, rng): + self._sbp.shoot(photons._pa, rng._rng) + + def _drawKImage(self, image, jac=None): + _jac = 0 if jac is None else jac.__array_interface__['data'][0] + self._sbp.drawK(image._image, image.scale, _jac) + +
[docs] @doc_inherit + def withFlux(self, flux): + return VonKarman(lam=self.lam, r0=self.r0, L0=self.L0, flux=flux, + scale_unit=self.scale_unit, + force_stepk=self.force_stepk, do_delta=self.do_delta, + suppress_warning=self._suppress, gsparams=self.gsparams)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/wcs.html b/docs/_build/html/_modules/galsim/wcs.html new file mode 100644 index 00000000000..98e99fbba70 --- /dev/null +++ b/docs/_build/html/_modules/galsim/wcs.html @@ -0,0 +1,3051 @@ + + + + + + galsim.wcs — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.wcs

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+__all__ = [ 'BaseWCS', 'PixelScale', 'ShearWCS', 'JacobianWCS',
+            'OffsetWCS', 'OffsetShearWCS', 'AffineTransform', 
+            'UVFunction', 'RaDecFunction', ]
+
+import numpy as np
+import math
+import pickle
+import types, marshal, base64
+
+from .position import Position, PositionD, _PositionI, _PositionD
+from .celestial import CelestialCoord
+from .shear import Shear
+from .errors import GalSimError, GalSimIncompatibleValuesError, GalSimNotImplementedError
+from .errors import GalSimValueError
+from ._utilities import doc_inherit, lazy_property, math_eval
+from .angle import AngleUnit, radians, arcsec
+
+
[docs]class BaseWCS: + """The base class for all other kinds of WCS transformations. + + All the functions the user will typically need are defined here. Most subclasses just + define helper functions to implement each particular WCS definition. So this base + class defines the common interface for all WCS classes. + + There are several types of WCS classes that we implement. The basic class hierarchy is:: + + `BaseWCS` + --- `EuclideanWCS` + --- `UniformWCS` + --- `LocalWCS` + --- `CelestialWCS` + + These base classes are not constructible. They do not have __init__ defined. + + 1. `LocalWCS` classes are those which really just define a pixel size and shape. + They implicitly have the origin in image coordinates correspond to the origin + in world coordinates. They are primarily designed to handle local transformations + at the location of a single galaxy, where it should usually be a good approximation + to consider the pixel shape to be constant over the size of the galaxy. + + Currently we define the following `LocalWCS` classes:: + + - `PixelScale` + - `ShearWCS` + - `JacobianWCS` + + 2. `UniformWCS` classes have a constant pixel size and shape, but they have an arbitrary origin + in both image coordinates and world coordinates. A `LocalWCS` class can be turned into a + non-local `UniformWCS` class when an image has its bounds changed, e.g. by the commands + `Image.setCenter`, `Image.setOrigin` or `Image.shift`. + + Currently we define the following non-local, `UniformWCS` classes:: + + - `OffsetWCS` + - `OffsetShearWCS` + - `AffineTransform` + + 3. `EuclideanWCS` classes use a regular Euclidean coordinate system for the world coordinates, + using `PositionD` for the world positions. We use the notation (u,v) for the world + coordinates and (x,y) for the image coordinates. + + Currently we define the following non-uniform, `EuclideanWCS` class:: + + - `UVFunction` + + 4. `CelestialWCS` classes are defined with their world coordinates on the celestial sphere + in terms of right ascension (RA) and declination (Dec). The pixel size and shape are + always variable. We use `CelestialCoord` for the world coordinates, which helps + facilitate the spherical trigonometry that is sometimes required. + + Currently we define the following `CelestialWCS` classes: (All but the first are defined + in the file fitswcs.py.) + + - `RaDecFunction` + - `AstropyWCS` -- requires astropy.wcs python module to be installed + - `PyAstWCS` -- requires starlink.Ast python module to be installed + - `WcsToolsWCS` -- requires wcstools command line functions to be installed + - `GSFitsWCS` -- native code, but has less functionality than the above + + There are also a few factory functions in fitswcs.py intended to act like class initializers: + + - `FitsWCS` tries to read a fits file using one of the above classes and returns an instance of + whichever one it found was successful. It should always be successful, since its final + attempt uses `AffineTransform`, which has reasonable defaults when the WCS key words are not + in the file, but of course this will only be a very rough approximation of the true WCS. + + - `TanWCS` constructs a simple tangent plane projection WCS directly from the projection + parameters instead of from a fits header. + + - `FittedSIPWCS` constructs a TAN-SIP WCS by fitting to a list of reference celestial and image + coordinates. + + Some things you can do with a WCS instance: + + - Convert positions between image coordinates and world coordinates (sometimes referred + to as sky coordinates):: + + >>> world_pos = wcs.toWorld(image_pos) + >>> image_pos = wcs.toImage(world_pos) + + Note: the transformation from world to image coordinates is not guaranteed to be + implemented. If it is not implemented for a particular WCS class, a NotImplementedError + will be raised. + + The ``image_pos`` parameter should be a `PositionD`. However, ``world_pos`` will + be a `CelestialCoord` if the transformation is in terms of celestial coordinates + (if ``wcs.isCelestial() == True``). Otherwise, it will be a `PositionD` as well. + + - Convert a `GSObject` that is defined in world coordinates to the equivalent profile defined + in terms of image coordinates (or vice versa):: + + >>> image_profile = wcs.toImage(world_profile) + >>> world_profile = wcs.toWorld(image_profile) + + For non-uniform WCS types (for which ``wcs.isUniform() == False``), these need either an + ``image_pos`` or ``world_pos`` parameter to say where this conversion should happen:: + + >>> image_profile = wcs.toImage(world_profile, image_pos=image_pos) + + - Construct a local linear approximation of a WCS at a given location:: + + >>> local_wcs = wcs.local(image_pos = image_pos) + >>> local_wcs = wcs.local(world_pos = world_pos) + + If ``wcs.toWorld(image_pos)`` is not implemented for a particular WCS class, then a + NotImplementedError will be raised if you pass in a ``world_pos`` argument. + + The returned ``local_wcs`` is usually a `JacobianWCS` instance, but see the doc string for + `local` for more details. + + - Construct a full affine approximation of a WCS at a given location:: + + >>> affine_wcs = wcs.affine(image_pos = image_pos) + >>> affine_wcs = wcs.affine(world_pos = world_pos) + + This preserves the transformation near the location of ``image_pos``, but it is linear, so + the transformed values may not agree as you get farther from the given point. + + The returned ``affine_wcs`` is always an `AffineTransform` instance. + + - Get some properties of the pixel size and shape:: + + >>> area = local_wcs.pixelArea() + >>> min_linear_scale = local_wcs.minLinearScale() + >>> max_linear_scale = local_wcs.maxLinearScale() + >>> jac = local_wcs.jacobian() + >>> # Use jac.dudx, jac.dudy, jac.dvdx, jac.dvdy + + Non-uniform WCS types also have these functions, but for them, you must supply either + ``image_pos`` or ``world_pos``. So the following are equivalent:: + + >>> area = wcs.pixelArea(image_pos) + >>> area = wcs.local(image_pos).pixelArea() + + - Query some overall attributes of the WCS transformation:: + + >>> wcs.isLocal() # is this a local WCS? + >>> wcs.isUniform() # does this WCS have a uniform pixel size/shape? + >>> wcs.isCelestial() # are the world coordinates on the celestial sphere? + >>> wcs.isPixelScale() # is this either a PixelScale or an OffsetWCS? + """ +
[docs] def toWorld(self, *args, **kwargs): + """Convert from image coordinates to world coordinates. + + There are essentially four overloaded versions of this function here. + + 1. The first converts a `Position` from image coordinates to world coordinates. + It returns the corresponding position in world coordinates as a `PositionD` if the WCS + is a `EuclideanWCS`, or a `CelestialCoord` if it is a `CelestialWCS`:: + + >>> world_pos = wcs.toWorld(image_pos) + + Equivalent to `posToWorld`. + + 2. The second is nearly the same, but takes x and y values directly and returns + either u, v or ra, dec, depending on the kind of wcs being used. For this version, + x and y may be numpy arrays, in which case the returned values are also numpy + arrays:: + + >>> u, v = wcs.toWorld(x, y) # For EuclideanWCS types + >>> ra, dec = wcs.toWorld(x, y, units=units) # For CelestialWCS types + + Equivalent to `xyTouv` or `xyToradec`. + + 3. The third converts a surface brightness profile (a `GSObject`) from image + coordinates to world coordinates, returning the profile in world coordinates + as a new `GSObject`. For non-uniform WCS transforms, you must provide either + ``image_pos`` or ``world_pos`` to say where the profile is located, so the right + transformation can be performed. And optionally, you may provide a flux scaling + to be performed at the same time:: + + >>> world_profile = wcs.toWorld(image_profile, image_pos=None, world_pos=None, + flux_ratio=1, offset=(0,0)) + + Equivalent to `profileToWorld`. + + 4. The fourth converts a shear measurement (a `Shear`) from image coordinates to world + coordinates. As above, for non-uniform WCS transforms, you must provide either + ``image_pos`` or ``world_pos``. If the wcs is a CelestialWCS, then the returned + shear follows the convention used by TreeCorr (among others) for shear on a sphere, + namely in the local coordinates where north is up and west is to the right. + + >>> world_shear = wcs.toWorld(image_shear, image_pos=None, world_pos=None) + + Equivalent to `shearToWorld`. + """ + if len(args) == 1: + if isinstance(args[0], (gsobject.GSObject, chrom.ChromaticObject)): + return self.profileToWorld(*args, **kwargs) + elif isinstance(args[0], Shear): + return self.shearToWorld(*args, **kwargs) + else: + return self.posToWorld(*args, **kwargs) + elif len(args) == 2: + if self._isCelestial: + return self.xyToradec(*args, **kwargs) + else: + return self.xyTouv(*args, **kwargs) + else: + raise TypeError("toWorld() takes either 1 or 2 positional arguments")
+ +
[docs] def posToWorld(self, image_pos, color=None, **kwargs): + """Convert a position from image coordinates to world coordinates. + + This is equivalent to ``wcs.toWorld(image_pos)``. + + Parameters: + image_pos: The position in image coordinates + color: For color-dependent WCS's, the color term to use. [default: None] + project_center: (Only valid for `CelestialWCS`) A `CelestialCoord` to use for + projecting the result onto a tangent plane world system rather + than returning a `CelestialCoord`. [default: None] + projection: If project_center != None, the kind of projection to use. See + `CelestialCoord.project` for the valid options. [default: 'gnomonic'] + + Returns: + world_pos + """ + if color is None: color = self._color + if not isinstance(image_pos, Position): + raise TypeError("image_pos must be a PositionD or PositionI argument") + return self._posToWorld(image_pos, color=color, **kwargs)
+ +
[docs] def profileToWorld(self, image_profile, image_pos=None, world_pos=None, color=None, + flux_ratio=1., offset=(0,0)): + """Convert a profile from image coordinates to world coordinates. + + This is equivalent to ``wcs.toWorld(image_profile, ...)``. + + Parameters: + image_profile: The profile in image coordinates to transform. + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term to use. [default: None] + flux_ratio: An optional flux scaling to be applied at the same time. + [default: 1] + offset: An optional offset to be applied at the same time. [default: 0,0] + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._profileToWorld( + image_profile, flux_ratio, PositionD(offset))
+ +
[docs] def shearToWorld(self, image_shear, image_pos=None, world_pos=None, color=None): + """Convert a shear measured in image coordinates to world coordinates + + This is equivalent to ``wcs.toWorld(shear, ...)``. + + Specifically, the input shear is taken to be defined such that +e1 means elongated along + the x-axis, -e1 is along the y-axis, +e2 is along the y=x line, and -e2 is along y=-x. + + If the WCS is a EuclideanWCS, then the returned shear is converted to the equivalent + shear in u-v coordinates. I.e. +e1 is along the u-axis, etc. + + If the WCS is a CelestialWCS, then the returned shear is converted to sky coordinates where + North is up and West is to the right (as appropriate for looking up into the sky from Earth). + I.e. +e1 is E-W, -e1 is N-S, +e2 is NW-SE, and -e2 is NE-SW. This is the convention used + by many, but not all, codes and catalogs that use or report shears on the spherical sky. + + Parameters: + image_shear: The shear in image coordinates to convert. + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term to use. [default: None] + + Returns: + world_shear: The shear in world coordinates. + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._shearToWorld(image_shear)
+ +
[docs] def toImage(self, *args, **kwargs): + """Convert from world coordinates to image coordinates + + There are essentially three overloaded versions of this function here. + + 1. The first converts a position from world coordinates to image coordinates. + If the WCS is a `EuclideanWCS`, the argument may be either a `PositionD` or `PositionI` + argument. If it is a `CelestialWCS`, then the argument must be a `CelestialCoord`. + It returns the corresponding position in image coordinates as a `PositionD`:: + + >>> image_pos = wcs.toImage(world_pos) + + Equivalent to `posToImage`. + + 2. The second is nearly the same, but takes either u and v values or ra and dec values + (depending on the kind of wcs being used) directly and returns x and y values. + For this version, the inputs may be numpy arrays, in which case the returned values + are also numpy arrays:: + + >>> x, y = wcs.toImage(u, v) # For EuclideanWCS types + >>> x, y = wcs.toImage(ra, dec, units=units) # For CelestialWCS types + + Equivalent to `uvToxy` or `radecToxy`. + + 3. The third converts a surface brightness profile (a `GSObject`) from world + coordinates to image coordinates, returning the profile in image coordinates + as a new `GSObject`. For non-uniform WCS transforms, you must provide either + ``image_pos`` or ``world_pos`` to say where the profile is located so the right + transformation can be performed. And optionally, you may provide a flux scaling + to be performed at the same time:: + + >>> image_profile = wcs.toImage(world_profile, image_pos=None, world_pos=None, + flux_ratio=1, offset=(0,0)) + + Equivalent to `profileToImage`. + + 4. The fourth converts a shear measurement (a `Shear`) from image coordinates to world + coordinates. As above, for non-uniform WCS transforms, you must provide either + ``image_pos`` or ``world_pos``. If the wcs is a CelestialWCS, then the returned + shear follows the convention used by TreeCorr (among others) for shear on a sphere, + namely in the local coordinates where north is up and west is to the right. + + >>> world_shear = wcs.toWorld(image_shear, image_pos=None, world_pos=None) + + Equivalent to `shearToImage`. + """ + if len(args) == 1: + if isinstance(args[0], (gsobject.GSObject, chrom.ChromaticObject)): + return self.profileToImage(*args, **kwargs) + elif isinstance(args[0], Shear): + return self.shearToImage(*args, **kwargs) + else: + return self.posToImage(*args, **kwargs) + elif len(args) == 2: + if self._isCelestial: + return self.radecToxy(*args, **kwargs) + else: + return self.uvToxy(*args, **kwargs) + else: + raise TypeError("toImage() takes either 1 or 2 positional arguments")
+ +
[docs] def posToImage(self, world_pos, color=None): + """Convert a position from world coordinates to image coordinates. + + This is equivalent to ``wcs.toImage(world_pos)``. + + Parameters: + world_pos: The world coordinate position + color: For color-dependent WCS's, the color term to use. [default: None] + """ + if color is None: color = self._color + if self._isCelestial and not isinstance(world_pos, CelestialCoord): + raise TypeError("world_pos must be a CelestialCoord argument") + elif not self._isCelestial and not isinstance(world_pos, Position): + raise TypeError("world_pos must be a PositionD or PositionI argument") + return self._posToImage(world_pos, color=color)
+ +
[docs] def profileToImage(self, world_profile, image_pos=None, world_pos=None, color=None, + flux_ratio=1., offset=(0,0)): + """Convert a profile from world coordinates to image coordinates. + + This is equivalent to ``wcs.toImage(world_profile, ...)``. + + Parameters: + world_profile: The profile in world coordinates to transform. + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term to use. [default: None] + flux_ratio: An optional flux scaling to be applied at the same time. + [default: 1] + offset: An optional offset to be applied at the same time. [default: 0,0] + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._profileToImage( + world_profile, flux_ratio, PositionD(offset))
+ +
[docs] def shearToImage(self, world_shear, image_pos=None, world_pos=None, color=None): + """Convert a shear measured in world coordinates to image coordinates + + This is equivalent to ``wcs.toImage(shear, ...)``. + + This reverses the process described in `shearToWorld`. + + Parameters: + world_shear: The shear in world coordinates to convert. + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term to use. [default: None] + + Returns: + image_shear: The shear in image coordinates. + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._shearToImage(world_shear)
+ +
[docs] def pixelArea(self, image_pos=None, world_pos=None, color=None): + """Return the area of a pixel in arcsec**2 (or in whatever units you are using for + world coordinates if it is a `EuclideanWCS`). + + For non-uniform WCS transforms, you must provide either ``image_pos`` or ``world_pos`` + to say where the pixel is located. + + Parameters: + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term for which to evaluate the + pixel area. [default: None] + + Returns: + the pixel area in arcsec**2. + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._pixelArea()
+ +
[docs] def minLinearScale(self, image_pos=None, world_pos=None, color=None): + """Return the minimum linear scale of the transformation in any direction. + + This is basically the semi-minor axis of the Jacobian. Sometimes you need a + linear scale size for some calculation. This function returns the smallest + scale in any direction. The function maxLinearScale() returns the largest. + + For non-uniform WCS transforms, you must provide either ``image_pos`` or ``world_pos`` + to say where the pixel is located. + + Parameters: + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term for which to evaluate the + scale. [default: None] + + Returns: + the minimum pixel area in any direction in arcsec. + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._minScale()
+ +
[docs] def maxLinearScale(self, image_pos=None, world_pos=None, color=None): + """Return the maximum linear scale of the transformation in any direction. + + This is basically the semi-major axis of the Jacobian. Sometimes you need a + linear scale size for some calculation. This function returns the largest + scale in any direction. The function minLinearScale() returns the smallest. + + For non-uniform WCS transforms, you must provide either ``image_pos`` or ``world_pos`` + to say where the pixel is located. + + Parameters: + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term for which to evaluate the + scale. [default: None] + + Returns: + the maximum pixel area in any direction in arcsec. + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._maxScale()
+ +
[docs] def isPixelScale(self): + """Return whether the WCS transformation is a simple `PixelScale` or `OffsetWCS`. + + These are the simplest two WCS transformations. `PixelScale` is local and `OffsetWCS` + is non-local. If an `Image` has one of these WCS transformations as its WCS, then + ``im.scale`` works to read and write the pixel scale. If not, ``im.scale`` will raise a + TypeError exception. + + ``wcs.isPixelScale()`` is shorthand for ``isinstance(wcs, (galsim.PixelScale, + galsim.OffsetWCS))``. + """ + return self._isPixelScale
+ + @property + def _isPixelScale(self): + return False # Overridden by PixelScale and OffsetWCS + +
[docs] def isLocal(self): + """Return whether the WCS transformation is a local, linear approximation. + + ``wcs.isLocal()`` is shorthand for ``isinstance(wcs, galsim.LocalWCS)``. + """ + return self._isLocal
+ + @property + def _isLocal(self): + return False # Overridden by LocalWCS + +
[docs] def isUniform(self): + """Return whether the pixels in this WCS have uniform size and shape. + + ``wcs.isUniform()`` is shorthand for ``isinstance(wcs, galsim.UniformWCS)``. + """ + return self._isUniform
+ + @property + def _isUniform(self): + return False # Overridden by UniformWCS + +
[docs] def isCelestial(self): + """Return whether the world coordinates are `CelestialCoord` (i.e. ra,dec). + + ``wcs.isCelestial()`` is shorthand for ``isinstance(wcs, galsim.CelestialWCS)``. + """ + return self._isCelestial
+ + @property + def _isCelestial(self): + return False # Overridden by CelestialWCS + +
[docs] def local(self, image_pos=None, world_pos=None, color=None): + """Return the local linear approximation of the WCS at a given point. + + Parameters: + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term for which to evaluate the + local WCS. [default: None] + + Returns: + a `LocalWCS` instance. + """ + if color is None: color = self._color + if world_pos is not None: + if image_pos is not None: + raise GalSimIncompatibleValuesError( + "Only one of image_pos or world_pos may be provided", + image_pos=image_pos, world_pos=world_pos) + image_pos = self.posToImage(world_pos, color) + if image_pos is not None and not isinstance(image_pos, Position): + raise TypeError("image_pos must be a PositionD or PositionI argument") + return self._local(image_pos, color)
+ +
[docs] def jacobian(self, image_pos=None, world_pos=None, color=None): + """Return the local `JacobianWCS` of the WCS at a given point. + + This is basically the same as local(), but the return value is guaranteed to be a + `JacobianWCS`, which can be useful in some situations, since you can access the values + of the 2x2 Jacobian matrix directly:: + + >>> jac = wcs.jacobian(image_pos) + >>> x,y = np.meshgrid(np.arange(0,32,1), np.arange(0,32,1)) + >>> u = jac.dudx * x + jac.dudy * y + >>> v = jac.dvdx * x + jac.dvdy * y + >>> # ... use u,v values to work directly in world coordinates. + + If you do not need the extra functionality, then you should use local() + instead, since it may be more efficient. + + Parameters: + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term for which to evaluate the + local jacobian. [default: None] + + Returns: + a `JacobianWCS` instance. + """ + if color is None: color = self._color + return self.local(image_pos, world_pos, color=color)._toJacobian()
+ +
[docs] def affine(self, image_pos=None, world_pos=None, color=None): + """Return the local `AffineTransform` of the WCS at a given point. + + This returns a linearized version of the current WCS at a given point. It + returns an `AffineTransform` that is locally approximately the same as the WCS in + the vicinity of the given point. + + It is similar to jacobian(), except that this preserves the offset information + between the image coordinates and world coordinates rather than setting both + origins to (0,0). Instead, the image origin is taken to be ``image_pos``. + + For non-celestial coordinate systems, the world origin is taken to be + ``wcs.toWorld(image_pos)``. In fact, ``wcs.affine(image_pos)`` is really just + shorthand for:: + + >>> wcs.jacobian(image_pos).withOrigin(image_pos, wcs.toWorld(image_pos)) + + For celestial coordinate systems, there is no well-defined choice for the + origin of the Euclidean world coordinate system. So we just take (u,v) = (0,0) + at the given position. So, ``wcs.affine(image_pos)`` is equivalent to:: + + >>> wcs.jacobian(image_pos).withOrigin(image_pos) + + You can use the returned `AffineTransform` to access the relevant values of the 2x2 + Jacobian matrix and the origins directly:: + + >>> affine = wcs.affine(image_pos) + >>> x,y = np.meshgrid(np.arange(0,32,1), np.arange(0,32,1)) + >>> u = affine.dudx * (x-affine.x0) + jac.dudy * (y-affine.y0) + affine.u0 + >>> v = affine.dvdx * (x-affine.x0) + jac.dvdy * (y-affine.y0) + affine.v0 + >>> # ... use u,v values to work directly in sky coordinates. + + As usual, you may provide either ``image_pos`` or ``world_pos`` as you prefer to + specify the location at which to approximate the WCS. + + Parameters: + image_pos: The image coordinate position (for non-uniform WCS types) + world_pos: The world coordinate position (for non-uniform WCS types) + color: For color-dependent WCS's, the color term for which to evaluate the + local affine transform. [default: None] + + Returns: + an `AffineTransform` instance + """ + if color is None: color = self._color + jac = self.jacobian(image_pos, world_pos, color=color) + # That call checked that only one of image_pos or world_pos is provided. + if world_pos is not None: + image_pos = self.toImage(world_pos, color=color) + elif image_pos is None: + # Both are None. Must be a local WCS + image_pos = _PositionD(0,0) + + if self._isCelestial: + return jac.withOrigin(image_pos) + else: + if world_pos is None: + world_pos = self.toWorld(image_pos, color=color) + return jac.withOrigin(image_pos, world_pos, color=color)
+ +
[docs] def shiftOrigin(self, origin, world_origin=None, color=None): + """Shift the origin of the current WCS function, returning the new WCS. + + This function creates a new WCS instance (always a non-local WCS) that shifts the + origin by the given amount. In other words, it treats the image position ``origin`` + the same way the current WCS treats (x,y) = (0,0). + + If the current WCS is a local WCS, this essentially declares where on the image + you want the origin of the world coordinate system to be. i.e. where is (u,v) = (0,0). + So, for example, to set a WCS that has a constant pixel size with the world coordinates + centered at the center of an image, you could write:: + + >>> wcs = galsim.PixelScale(scale).shiftOrigin(im.center) + + This is equivalent to the following:: + + >>> wcs = galsim.OffsetWCS(scale, origin=im.center) + + For non-local WCS types, the origin defines the location in the image coordinate system + should mean the same thing as (x,y) = (0,0) does for the current WCS. The following + example should work regardless of what kind of WCS this is:: + + >>> world_pos1 = wcs.toWorld(PositionD(0,0)) + >>> wcs2 = wcs.shiftOrigin(new_origin) + >>> world_pos2 = wcs2.toWorld(new_origin) + >>> # world_pos1 should be equal to world_pos2 + + Furthermore, if the current WCS is a `EuclideanWCS` (wcs.isCelestial() == False) you may + also provide a ``world_origin`` argument which defines what (u,v) position you want to + correspond to the new origin. Continuing the previous example:: + + >>> wcs3 = wcs.shiftOrigin(new_origin, new_world_origin) + >>> world_pos3 = wcs3.toWorld(new_origin) + >>> # world_pos3 should be equal to new_world_origin + + Parameters: + origin: The image coordinate position to use for what is currently treated + as (0,0). + world_origin: The world coordinate position to use at the origin. Only valid if + wcs.isCelestial() == False. [default: None] + color: For color-dependent WCS's, the color term to use in the connection + between the current origin and world_origin. [default: None] + + Returns: + the new shifted WCS + """ + if color is None: color = self._color + if not isinstance(origin, Position): + raise TypeError("origin must be a PositionD or PositionI argument") + return self._shiftOrigin(origin, world_origin, color)
+ + def withOrigin(self, origin, world_origin=None, color=None): + from .deprecated import depr + depr('withOrigin', 2.3, 'shiftOrigin') + return self.shiftOrigin(origin, world_origin, color) + +
[docs] def fixColor(self, color): + """Fix the color to a particular value. + + This changes a color-dependent WCS into the corresponding color-independent WCS + for the given color. + + Parameters: + color: The value of the color term to use. + + Returns: + the new color-independent WCS + """ + ret = self.copy() + ret._color = color + return ret
+ +
[docs] def writeToFitsHeader(self, header, bounds): + """Write this WCS function to a FITS header. + + This is normally called automatically from within the galsim.fits.write() function. + + The code will attempt to write standard FITS WCS keys so that the WCS will be readable + by other software (e.g. ds9). It may not be able to do so accurately, in which case a + linearized version will be used instead. (Specifically, it will use the local affine + transform with respect to the image center.) + + However, this is not necessary for the WCS to survive a round trip through the FITS + header, as it will also write GalSim-specific key words that should allow it to + reconstruct the WCS correctly. + + .. note: + For `UVFunction` and `RaDecFunction`, if the functions are real python functions + (rather than a string that is converted to a function), then the mechanism we use to + convert the function to a string that can be written to the header has a few + limitations. + + 1. It apparently only works for cpython implementations. + 2. It probably won't work to write from one version of python and read from another. + (At least for major version differences.) + 3. If the function uses globals, you'll need to make sure the globals are present + when you read it back in as well, or it probably won't work. + 4. It looks really ugly in the header. + 5. We haven't thought much about the security implications of this, so beware using + GalSim to open FITS files from untrusted sources. + + Parameters: + header: A FitsHeader (or dict-like) object to write the data to. + bounds: The bounds of the image. + """ + # First write the XMIN, YMIN values + header["GS_XMIN"] = (bounds.xmin, "GalSim image minimum x coordinate") + header["GS_YMIN"] = (bounds.ymin, "GalSim image minimum y coordinate") + + if bounds.xmin != 1 or bounds.ymin != 1: + # ds9 always assumes the image has an origin at (1,1), so we always write the + # WCS to the file with this convention. We'll convert back when we read it + # in if necessary. + delta = _PositionI(1-bounds.xmin, 1-bounds.ymin) + bounds = bounds.shift(delta) + wcs = self.shiftOrigin(delta) + else: + wcs = self + + wcs._writeHeader(header, bounds) + + if hasattr(self, 'header'): + # Store the items that are in self.header in the header if they weren't already put + # there by the call to wcs._writeHeader() call. (We don't want to overwrite the WCS.) + for key in self.header: + if (key not in header and key.strip() != '' and + key.strip() != 'COMMENT' and key.strip() != 'HISTORY'): + header[key] = self.header[key]
+ +
[docs] def makeSkyImage(self, image, sky_level, color=None): + """Make an image of the sky, correctly accounting for the pixel area, which might be + variable over the image. + + Note: This uses finite differences of the wcs mapping to calculate the area of each + pixel in world coordinates. It is usually pretty accurate everywhere except + within a few arcsec of the north or south poles. + + Parameters: + image: The image onto which the sky values will be put. + sky_level: The sky level in ADU/arcsec^2 (or whatever your world coordinate + system units are, if not arcsec). + color: For color-dependent WCS's, the color term to use for making the + sky image. [default: None] + """ + if color is None: color = self._color + self._makeSkyImage(image, sky_level, color)
+ + + # A lot of classes will need these checks, so consolidate them here + def _set_origin(self, origin, world_origin=None): + if origin is None: + self._origin = _PositionD(0,0) + else: + if not isinstance(origin, Position): + raise TypeError("origin must be a PositionD or PositionI argument") + self._origin = origin + if world_origin is None: + self._world_origin = _PositionD(0,0) + else: + if not isinstance(world_origin, Position): + raise TypeError("world_origin must be a PositionD argument") + self._world_origin = world_origin
+ + +
[docs]def readFromFitsHeader(header, suppress_warning=True): + """Read a WCS function from a FITS header. + + This is normally called automatically from within the `galsim.fits.read` function, but + you can also call it directly as:: + + wcs, origin = galsim.wcs.readFromFitsHeader(header) + + If the file was originally written by GalSim using one of the galsim.fits.write() functions, + then this should always succeed in reading back in the original WCS. It may not end up + as exactly the same class as the original, but the underlying world coordinate system + transformation should be preserved. + + .. note:: + For `UVFunction` and `RaDecFunction`, if the functions that were written to the FITS + header were real python functions (rather than a string that is converted to a function), + then the mechanism we use to write to the header and read it back in has some limitations: + + 1. It apparently only works for cpython implementations. + 2. It probably won't work to write from one version of python and read from another. + (At least for major version differences.) + 3. If the function uses globals, you'll need to make sure the globals are present + when you read it back in as well, or it probably won't work. + 4. It looks really ugly in the header. + 5. We haven't thought much about the security implications of this, so beware using + GalSim to open FITS files from untrusted sources. + + If the file was not written by GalSim, then this code will do its best to read the + WCS information in the FITS header. Depending on what kind of WCS is encoded in the + header, this may or may not be successful. + + If there is no WCS information in the header, then this will default to a pixel scale + of 1. + + In addition to the wcs, this function will also return the image origin that the WCS + is assuming for the image. If the file was originally written by GalSim, this should + correspond to the original image origin. If not, it will default to (1,1). + + Parameters: + header: The fits header with the WCS information. + suppress_warning: Whether to suppress a warning that the WCS could not be read from the + FITS header, so the WCS defaulted to either a `PixelScale` or + `AffineTransform`. [default: True] + + Returns: + a tuple (wcs, origin) of the wcs from the header and the image origin. + """ + from .fits import FitsHeader + from .fitswcs import FitsWCS + if not isinstance(header, FitsHeader): + header = FitsHeader(header) + xmin = header.get("GS_XMIN", 1) + ymin = header.get("GS_YMIN", 1) + origin = _PositionI(xmin, ymin) + wcs_name = header.get("GS_WCS", None) + if wcs_name is not None: + gdict = globals().copy() + exec('import galsim', gdict) + wcs_type = eval('galsim.' + wcs_name, gdict) + wcs = wcs_type._readHeader(header) + else: + # If we aren't told which type to use, this should find something appropriate + wcs = FitsWCS(header=header, suppress_warning=suppress_warning) + + if xmin != 1 or ymin != 1: + # ds9 always assumes the image has an origin at (1,1), so convert back to actual + # xmin, ymin if necessary. + delta = _PositionI(xmin-1, ymin-1) + wcs = wcs.shiftOrigin(delta) + + return wcs, origin
+ + +######################################################################################### +# +# Our class hierarchy is: +# +# BaseWCS +# --- EuclideanWCS +# --- UniformWCS +# --- LocalWCS +# --- CelestialWCS +# +# Here we define the rest of these classes (besides BaseWCS that is), and implement some +# functionality that is common among the subclasses of these when possible. +# +######################################################################################### + + +
[docs]class EuclideanWCS(BaseWCS): + """A EuclideanWCS is a `BaseWCS` whose world coordinates are on a Euclidean plane. + We usually use the notation (u,v) to refer to positions in world coordinates, and + they use the class `PositionD`. + """ + + # All EuclideanWCS classes must define origin and world_origin. + # Sometimes it is convenient to access x0,y0,u0,v0 directly. + @property + def x0(self): + """The x component of self.origin. + """ + return self.origin.x + + @property + def y0(self): + """The y component of self.origin. + """ + return self.origin.y + + @property + def u0(self): + """The x component of self.world_origin (aka u). + """ + return self.world_origin.x + + @property + def v0(self): + """The y component of self.world_origin (aka v). + """ + return self.world_origin.y + +
[docs] def xyTouv(self, x, y, color=None): + """Convert x,y from image coordinates to world coordinates. + + This is equivalent to ``wcs.toWorld(x,y)``. + + It is also equivalent to ``wcs.posToWorld(galsim.PositionD(x,y))`` when x and y are scalars; + however, this routine allows x and y to be numpy arrays, in which case, the calculation + will be vectorized, which is often much faster than using the pos interface. + + Parameters: + x: The x value(s) in image coordinates + y: The y value(s) in image coordinates + color: For color-dependent WCS's, the color term to use. [default: None] + + Returns: + ra, dec + """ + if color is None: color = self._color + return self._xyTouv(x, y, color=color)
+ +
[docs] def uvToxy(self, u, v, color=None): + """Convert u,v from world coordinates to image coordinates. + + This is equivalent to ``wcs.toWorld(u,v)``. + + It is also equivalent to ``wcs.posToImage(galsim.PositionD(u,v))`` when u and v are scalars; + however, this routine allows u and v to be numpy arrays, in which case, the calculation + will be vectorized, which is often much faster than using the pos interface. + + Parameters: + u: The u value(s) in world coordinates + v: The v value(s) in world coordinates + color: For color-dependent WCS's, the color term to use. [default: None] + """ + if color is None: color = self._color + return self._uvToxy(u, v, color)
+ + # Simple. Just call _u, _v. + def _posToWorld(self, image_pos, color): + x = image_pos.x - self.x0 + y = image_pos.y - self.y0 + return _PositionD(self._u(x,y,color), self._v(x,y,color)) + self.world_origin + + def _xyTouv(self, x, y, color): + x = x - self.x0 # Not -=, since don't want to modify the input arrays in place. + y = y - self.y0 + u = self._u(x,y,color) + v = self._v(x,y,color) + u += self.u0 + v += self.v0 + return u,v + + # Also simple if _x,_y are implemented. However, they are allowed to raise a + # NotImplementedError. + def _posToImage(self, world_pos, color): + u = world_pos.x - self.u0 + v = world_pos.y - self.v0 + return _PositionD(self._x(u,v,color),self._y(u,v,color)) + self.origin + + def _uvToxy(self, u, v, color): + u = u - self.u0 + v = v - self.v0 + x = self._x(u,v,color) + y = self._y(u,v,color) + x += self.x0 + y += self.y0 + return x, y + + # Each subclass has a function _newOrigin, which just calls the constructor with new + # values for origin and world_origin. This function figures out what those values + # should be to match the desired behavior of shiftOrigin. + def _shiftOrigin(self, origin, world_origin, color): + # Current u,v are: + # u = ufunc(x-x0, y-y0) + u0 + # v = vfunc(x-x0, y-y0) + v0 + # where ufunc, vfunc represent the underlying wcs transformations. + # + # The _newOrigin call is expecting new values for the (x0,y0) and (u0,v0), so + # we need to figure out how to modify the parameters given the current values. + # + # Use (x1,y1) and (u1,v1) for the new values that we will pass to _newOrigin. + # Use (x2,y2) and (u2,v2) for the values passed as arguments. + # + # If world_origin is None, then we want to do basically the same thing as in the + # non-uniform case, except that we also need to pass the function the current value of + # wcs.world_pos to keep it from resetting the world_pos back to None. + + if world_origin is None: + if not self._isLocal: + origin += self.origin + world_origin = self.world_origin + return self._newOrigin(origin, world_origin) + + # But if world_origin is given, it isn't quite as simple. + # + # u' = ufunc(x-x1, y-y1) + u1 + # v' = vfunc(x-x1, y-y1) + v1 + # + # We want to have: + # u'(x2,y2) = u2 + # ufunc(x2-x1, y2-y1) + u1 = u2 + # + # We don't have access to ufunc directly, just u, so + # (u(x2-x1+x0, y2-y1+y0) - u0) + u1 = u2 + # + # If we take + # x1 = x2 + # y1 = y2 + # + # Then + # u(x0,y0) - u0 + u1 = u2 + # => u1 = u0 + u2 - u(x0,y0) + # + # And similarly, + # v1 = v0 + v2 - v(x0,y0) + + else: + if not isinstance(world_origin, Position): + raise TypeError("world_origin must be a PositionD or PositionI argument") + if not self._isLocal: + world_origin += self.world_origin - self._posToWorld(self.origin, color=color) + return self._newOrigin(origin, world_origin) + + # If the class doesn't define something else, then we can approximate the local Jacobian + # from finite differences for the derivatives. This will be overridden by UniformWCS. + def _local(self, image_pos, color): + + if image_pos is None: + raise TypeError("origin must be a PositionD or PositionI argument") + + # Calculate the Jacobian using finite differences for the derivatives. + x0 = image_pos.x - self.x0 + y0 = image_pos.y - self.y0 + + # Use dx,dy = 1 pixel for numerical derivatives + dx = 1 + dy = 1 + + xlist = np.array([ x0+dx, x0-dx, x0, x0 ], dtype=float) + ylist = np.array([ y0, y0, y0+dy, y0-dy ], dtype=float) + u = self._u(xlist,ylist,color) + v = self._v(xlist,ylist,color) + + dudx = 0.5 * (u[0] - u[1]) / dx + dudy = 0.5 * (u[2] - u[3]) / dy + dvdx = 0.5 * (v[0] - v[1]) / dx + dvdy = 0.5 * (v[2] - v[3]) / dy + + return JacobianWCS(dudx, dudy, dvdx, dvdy) + + # The naive way to make the sky image is to loop over pixels and call pixelArea(pos) + # for that position. This is extremely slow. Here, we use the fact that the _u and _v + # functions might work with numpy arrays. If they do, this function is quite fast. + # If not, we still get some gain from calculating u,v for each pixel and sharing some + # of those calculations for multiple finite difference derivatives. But the latter + # option is still pretty slow, so it's much better to have the _u and _v work with + # numpy arrays! + def _makeSkyImage(self, image, sky_level, color): + b = image.bounds + nx = b.xmax-b.xmin+1 + 2 # +2 more than in image to get row/col off each edge. + ny = b.ymax-b.ymin+1 + 2 + x,y = np.meshgrid( np.linspace(b.xmin-1,b.xmax+1,nx), + np.linspace(b.ymin-1,b.ymax+1,ny) ) + x -= self.x0 + y -= self.y0 + u = self._u(x.ravel(),y.ravel(),color) + v = self._v(x.ravel(),y.ravel(),color) + u = np.reshape(u, x.shape) + v = np.reshape(v, x.shape) + # Use the finite differences to estimate the derivatives. + dudx = 0.5 * (u[1:ny-1,2:nx] - u[1:ny-1,0:nx-2]) + dudy = 0.5 * (u[2:ny,1:nx-1] - u[0:ny-2,1:nx-1]) + dvdx = 0.5 * (v[1:ny-1,2:nx] - v[1:ny-1,0:nx-2]) + dvdy = 0.5 * (v[2:ny,1:nx-1] - v[0:ny-2,1:nx-1]) + + area = np.abs(dudx * dvdy - dvdx * dudy) + image.array[:,:] = area * sky_level + + # Each class should define the __eq__ function. Then __ne__ is obvious. + def __ne__(self, other): return not self.__eq__(other)
+ + +
[docs]class UniformWCS(EuclideanWCS): + """A UniformWCS is a `EuclideanWCS` which has a uniform pixel size and shape. + """ + @property + def _isUniform(self): + return True + + # These can also just pass through to the _localwcs attribute. + def _u(self, x, y, color=None): + return self._local_wcs._u(x,y) + def _v(self, x, y, color=None): + return self._local_wcs._v(x,y) + def _x(self, u, v, color=None): + return self._local_wcs._x(u,v) + def _y(self, u, v, color=None): + return self._local_wcs._y(u,v) + + # For UniformWCS, the local WCS is an attribute. Just return it. + def _local(self, image_pos, color): + return self._local_wcs + + # UniformWCS transformations can be inverted easily, so might as well provide that function. +
[docs] def inverse(self): + """Return the inverse transformation, i.e. the transformation that swaps the roles of + the "image" and "world" coordinates. + """ + return self._inverse()
+ + # We'll override this for LocalWCS classes. Non-local UniformWCS classes can use that function + # do the inversion. + def _inverse(self): + return self._local_wcs._inverse()._newOrigin(self.world_origin, self.origin) + + # This is very simple if the pixels are uniform. + def _makeSkyImage(self, image, sky_level, color): + image.fill(sky_level * self.pixelArea()) + + # Just check if the locals match and if the origins match. + def __eq__(self, other): + return (self is other or + (isinstance(other, self.__class__) and + self._local_wcs == other._local_wcs and + self.origin == other.origin and + self.world_origin == other.world_origin))
+ + +
[docs]class LocalWCS(UniformWCS): + """A LocalWCS is a `UniformWCS` in which (0,0) in image coordinates is at the same place + as (0,0) in world coordinates + """ +
[docs] def withOrigin(self, origin, world_origin=None, color=None): + """Recenter the current WCS function at a new origin location, returning the new WCS. + + This function creates a new WCS instance (a non-local WCS) with the same local + behavior as the current WCS, but with the given origin. In other words, you are + declaring where on the image you want the new origin of the world coordinate + system to be. i.e. where is (u,v) = (0,0). + + So, for example, to set a WCS that has a constant pixel size with the world coordinates + centered at the center of an image, you could write:: + + >>> wcs = galsim.PixelScale(scale).withOrigin(im.center) + + This is equivalent to the following:: + + >>> wcs = galsim.OffsetWCS(scale, origin=im.center) + + You may also provide a ``world_origin`` argument which defines what (u,v) position you + want to correspond to the new origin. + + >>> wcs2 = galsim.PixelScale(scale).withOrigin(new_origin, new_world_origin) + >>> world_pos2 = wcs2.toWorld(new_origin) + >>> assert world_pos2 == new_world_origin + + .. note:: + + This is equivalent to `shiftOrigin`, but for for local WCS's, the shift is also + the new location of the origin, so `withOrigin` is a convenient alternate name + for this action. Indeed the `shiftOrigin` function used to be named `withOrigin`, + but that name was confusing for non-local WCS's, as the action in that case is really + shifting the origin, not setting the new value. + + Parameters: + origin: The image coordinate position to use as the origin. + world_origin: The world coordinate position to use as the origin. [default: None] + color: For color-dependent WCS's, the color term to use in the connection + between the current origin and world_origin. [default: None] + + Returns: + the new recentered WCS + """ + if not isinstance(origin, Position): + raise TypeError("origin must be a PositionD or PositionI argument") + if not isinstance(world_origin, (Position, type(None))): + raise TypeError("world_origin must be a PositionD or PositionI argument") + return self._newOrigin(origin, world_origin)
+ + @property + def _isLocal(self): + return True + + # The origins are definitionally (0,0) for these. So just define them here. + @property + def origin(self): + """The image coordinate position to use as the origin. + """ + return _PositionD(0,0) + + @property + def world_origin(self): + """The world coordinate position to use as the origin. + """ + return _PositionD(0,0) + + # For LocalWCS, there is no origin to worry about. + def _posToWorld(self, image_pos, color): + x = image_pos.x + y = image_pos.y + return _PositionD(self._u(x,y),self._v(x,y)) + + def _xyTouv(self, x, y, color): + return self._u(x,y), self._v(x,y) + + # For LocalWCS, there is no origin to worry about. + def _posToImage(self, world_pos, color): + u = world_pos.x + v = world_pos.y + return _PositionD(self._x(u,v),self._y(u,v)) + + def _uvToxy(self, u, v, color): + return self._x(u,v), self._y(u,v) + + # For LocalWCS, this is of course trivial. + def _local(self, image_pos, color): + return self
+ + +
[docs]class CelestialWCS(BaseWCS): + """A CelestialWCS is a `BaseWCS` whose world coordinates are on the celestial sphere. + We use the `CelestialCoord` class for the world coordinates. + """ + + @property + def _isCelestial(self): + return True + + # CelestialWCS classes still have origin, but not world_origin. + @property + def x0(self): + """The x coordinate of self.origin. + """ + return self.origin.x + + @property + def y0(self): + """The y coordinate of self.origin. + """ + return self.origin.y + +
[docs] def xyToradec(self, x, y, units=None, color=None): + """Convert x,y from image coordinates to world coordinates. + + This is equivalent to ``wcs.toWorld(x,y, units=units)``. + + It is also equivalent to ``wcs.posToWorld(galsim.PositionD(x,y)).rad`` when x and y are + scalars if units is 'radians'; however, this routine allows x and y to be numpy arrays, + in which case, the calculation will be vectorized, which is often much faster than using + the pos interface. + + Parameters: + x: The x value(s) in image coordinates + y: The y value(s) in image coordinates + units: (Only valid for `CelestialWCS`, in which case it is required) + The units to use for the returned ra, dec values. + color: For color-dependent WCS's, the color term to use. [default: None] + + Returns: + ra, dec + """ + if color is None: color = self._color + if units is None: + raise TypeError("units is required for CelestialWCS types") + elif isinstance(units, str): + units = AngleUnit.from_name(units) + elif not isinstance(units, AngleUnit): + raise GalSimValueError("units must be either an AngleUnit or a string", units, + AngleUnit.valid_names) + return self._xyToradec(x, y, units, color)
+ +
[docs] def radecToxy(self, ra, dec, units, color=None): + """Convert ra,dec from world coordinates to image coordinates. + + This is equivalent to ``wcs.toImage(ra,dec, units=units)``. + + It is also equivalent to ``wcs.posToImage(galsim.CelestialCoord(ra * units, dec * units))`` + when ra and dec are scalars; however, this routine allows ra and dec to be numpy arrays, + in which case, the calculation will be vectorized, which is often much faster than using + the pos interface. + + Parameters: + ra: The ra value(s) in world coordinates + dec: The dec value(s) in world coordinates + units: The units to use for the input ra, dec values. + color: For color-dependent WCS's, the color term to use. [default: None] + """ + if color is None: color = self._color + if isinstance(units, str): + units = AngleUnit.from_name(units) + elif not isinstance(units, AngleUnit): + raise GalSimValueError("units must be either an AngleUnit or a string", units, + AngleUnit.valid_names) + return self._radecToxy(ra, dec, units, color)
+ + # This is a bit simpler than the EuclideanWCS version, since there is no world_origin. + def _shiftOrigin(self, origin, world_origin, color): + # We want the new wcs to have wcs.toWorld(x2,y2) match the current wcs.toWorld(0,0). + # So, + # + # u' = ufunc(x-x1, y-y1) # In this case, there are no u0,v0 + # v' = vfunc(x-x1, y-y1) + # + # u'(x2,y2) = u(0,0) v'(x2,y2) = v(0,0) + # + # x2 - x1 = 0 - x0 y2 - y1 = 0 - y0 + # => x1 = x0 + x2 y1 = y0 + y2 + if world_origin is not None: + raise TypeError("world_origin is invalid for CelestialWCS classes") + origin += self.origin + return self._newOrigin(origin) + + # If the class doesn't define something else, then we can approximate the local Jacobian + # from finite differences for the derivatives of ra and dec. Very similar to the + # version for EuclideanWCS, but convert from dra, ddec to du, dv locally at at the given + # position. + def _local(self, image_pos, color): + + if image_pos is None: + raise TypeError("origin must be a PositionD or PositionI argument") + + x0 = image_pos.x - self.x0 + y0 = image_pos.y - self.y0 + # Use dx,dy = 1 pixel for numerical derivatives + dx = 1 + dy = 1 + + xlist = np.array([ x0, x0+dx, x0-dx, x0, x0 ], dtype=float) + ylist = np.array([ y0, y0, y0, y0+dy, y0-dy ], dtype=float) + ra, dec = self._radec(xlist,ylist,color) + # Wrap ra to be near ra[0] + ra[ra < ra[0]-np.pi] += 2*np.pi + ra[ra > ra[0]+np.pi] -= 2*np.pi + + # Note: our convention is that ra increases to the left! + # i.e. The u,v plane is the tangent plane as seen from Earth with +v pointing + # north, and +u pointing west. + # That means the du values are the negative of dra. + cosdec = np.cos(dec[0]) + dudx = -0.5 * (ra[1] - ra[2]) / dx * cosdec + dudy = -0.5 * (ra[3] - ra[4]) / dy * cosdec + dvdx = 0.5 * (dec[1] - dec[2]) / dx + dvdy = 0.5 * (dec[3] - dec[4]) / dy + + # These values are all in radians. Convert to arcsec as per our usual standard. + factor = radians / arcsec + return JacobianWCS(dudx*factor, dudy*factor, dvdx*factor, dvdy*factor) + + # This is similar to the version for EuclideanWCS, but uses dra, ddec. + # Again, it is much faster if the _radec function works with numpy arrays. + def _makeSkyImage(self, image, sky_level, color): + b = image.bounds + nx = b.xmax-b.xmin+1 + 2 # +2 more than in image to get row/col off each edge. + ny = b.ymax-b.ymin+1 + 2 + x,y = np.meshgrid( np.linspace(b.xmin-1,b.xmax+1,nx), + np.linspace(b.ymin-1,b.ymax+1,ny) ) + x -= self.x0 + y -= self.y0 + ra, dec = self._radec(x.ravel(),y.ravel(),color) + ra = np.reshape(ra, x.shape) + dec = np.reshape(dec, x.shape) + + # Use the finite differences to estimate the derivatives. + cosdec = np.cos(dec[1:ny-1,1:nx-1]) + dudx = -0.5 * (ra[1:ny-1,2:nx] - ra[1:ny-1,0:nx-2]) + dudy = -0.5 * (ra[2:ny,1:nx-1] - ra[0:ny-2,1:nx-1]) + # Check for discontinuities in ra. ra can jump by 2pi, so when it does + # add (or subtract) pi to dudx, which is dra/2 + dudx[dudx > 1] -= np.pi + dudx[dudx < -1] += np.pi + dudy[dudy > 1] -= np.pi + dudy[dudy < -1] += np.pi + # Now account for the cosdec factor + dudx *= cosdec + dudy *= cosdec + dvdx = 0.5 * (dec[1:ny-1,2:nx] - dec[1:ny-1,0:nx-2]) + dvdy = 0.5 * (dec[2:ny,1:nx-1] - dec[0:ny-2,1:nx-1]) + + area = np.abs(dudx * dvdy - dvdx * dudy) + factor = radians / arcsec + image.array[:,:] = area * sky_level * factor**2 + + + # Simple. Just call _radec. + def _posToWorld(self, image_pos, color, project_center=None, projection='gnomonic'): + x = image_pos.x - self.x0 + y = image_pos.y - self.y0 + ra, dec = self._radec(x,y,color) + coord = CelestialCoord(ra*radians, dec*radians) + if project_center is None: + return coord + else: + u,v = project_center.project(coord, projection=projection) + return _PositionD(u/arcsec, v/arcsec) + + def _xyToradec(self, x, y, units, color): + x = x - self.x0 # Not -=, since don't want to modify the input arrays in place. + y = y - self.y0 + ra, dec = self._radec(x,y,color) + ra *= radians / units + dec *= radians / units + return ra, dec + + # Also simple if _xy is implemented. However, it is allowed to raise a NotImplementedError. + def _posToImage(self, world_pos, color): + ra = world_pos.ra.rad + dec = world_pos.dec.rad + x, y = self._xy(ra,dec,color) + return _PositionD(x,y) + self.origin + + def _radecToxy(self, ra, dec, units, color): + ra = ra * (units / radians) + dec = dec * (units / radians) + x, y = self._xy(ra,dec,color) + x += self.origin.x + y += self.origin.y + return x, y + + # Each class should define the __eq__ function. Then __ne__ is obvious. + def __ne__(self, other): return not self.__eq__(other)
+ + + +######################################################################################### +# +# Local WCS classes are those where (x,y) = (0,0) corresponds to (u,v) = (0,0). +# +# We have the following local WCS classes: +# +# PixelScale +# ShearWCS +# JacobianWCS +# +# They must define the following: +# +# origin attribute or property returning the origin +# world_origin attribute or property returning the world origin +# _u function returning u(x,y) +# _v function returning v(x,y) +# _x function returning x(u,v) +# _y function returning y(u,v) +# _profileToWorld function converting image_profile to world_profile +# _profileToImage function converting world_profile to image_profile +# _pixelArea function returning the pixel area +# _minScale function returning the minimum linear pixel scale +# _maxScale function returning the maximum linear pixel scale +# _toJacobian function returning an equivalent JacobianWCS +# _writeHeader function that writes the WCS to a fits header. +# _readHeader static function that reads the WCS from a fits header. +# _newOrigin function returning a non-local WCS corresponding to this WCS +# copy return a copy +# __eq__ check if this equals another WCS +# __repr__ convert to string +# +######################################################################################### + +
[docs]class PixelScale(LocalWCS): + """This is the simplest possible WCS transformation. It only involves a unit conversion + from pixels to arcsec (or whatever units you want to take for your world coordinate system). + + The conversion functions are: + + u = x * scale + v = y * scale + + A PixelScale is initialized with the command:: + + >>> wcs = galsim.PixelScale(scale) + + Parameters: + scale: The pixel scale, typically in units of arcsec/pixel. + """ + _req_params = { "scale" : float } + + def __init__(self, scale): + self._color = None + self._scale = float(scale) + + # Help make sure PixelScale is read-only. + @property + def scale(self): + """The pixel scale + """ + return self._scale + + @lazy_property + def _invscale(self): + return 1./self._scale + + @property + def _isPixelScale(self): + return True + + def _u(self, x, y, color=None): + return x * self._scale + + def _v(self, x, y, color=None): + return y * self._scale + + def _x(self, u, v, color=None): + return u * self._invscale + + def _y(self, u, v, color=None): + return v * self._invscale + + def _profileToWorld(self, image_profile, flux_ratio, offset): + # In the usual case of GSObject, it's more efficient to use the _Transform version. + # else, it's a ChromaticObject, and we need to use the regular Transform function. + Transform = (transform._Transform if isinstance(image_profile, gsobject.GSObject) + else transform.Transform) + if self._scale == 1.: + j = None + else: + j = np.array(((self._scale, 0.), (0., self._scale))) + return Transform(image_profile, j, flux_ratio=self._invscale**2 * flux_ratio, + offset=(offset.x, offset.y)) + + def _profileToImage(self, world_profile, flux_ratio, offset): + Transform = (transform._Transform if isinstance(world_profile, gsobject.GSObject) + else transform.Transform) + if self._scale == 1.: + j = None + else: + j = np.array(((self._invscale, 0.), (0., self._invscale))) + return Transform(world_profile, j, flux_ratio=self._scale**2 * flux_ratio, + offset=(offset.x, offset.y)) + + def _shearToWorld(self, image_shear): + # These are trivial for PixelScale. + return image_shear + + def _shearToImage(self, world_shear): + return world_shear + + def _pixelArea(self): + return self._scale**2 + + def _minScale(self): + return self._scale + + def _maxScale(self): + return self._scale + + def _inverse(self): + return PixelScale(self._invscale) + + def _toJacobian(self): + return JacobianWCS(self._scale, 0., 0., self._scale) + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("PixelScale", "GalSim WCS name") + header["GS_SCALE"] = (self.scale, "GalSim image scale") + return self.affine()._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + scale = header["GS_SCALE"] + return PixelScale(scale) + + def _newOrigin(self, origin, world_origin): + return OffsetWCS(self._scale, origin, world_origin) + + def copy(self): + return PixelScale(self._scale) + + def __eq__(self, other): + return (self is other or + (isinstance(other, PixelScale) and + self.scale == other.scale)) + + def __repr__(self): return "galsim.PixelScale(%r)"%self.scale + def __hash__(self): return hash(repr(self))
+ + +
[docs]class ShearWCS(LocalWCS): + """This WCS is a uniformly sheared coordinate system. + + The shear is given as the shape that a round object has when observed in image coordinates. + + The conversion functions in terms of (g1,g2) are therefore: + + x = (u + g1 u + g2 v) / scale / sqrt(1-g1**2-g2**2) + y = (v - g1 v + g2 u) / scale / sqrt(1-g1**2-g2**2) + + or, writing this in the usual way of (u,v) as a function of (x,y): + + u = (x - g1 x - g2 y) * scale / sqrt(1-g1**2-g2**2) + v = (y + g1 y - g2 x) * scale / sqrt(1-g1**2-g2**2) + + A ShearWCS is initialized with the command:: + + >>> wcs = galsim.ShearWCS(scale, shear) + + Parameters: + scale: The pixel scale, typically in units of arcsec/pixel. + shear: The shear, which should be a `Shear` instance. + + The Shear transformation conserves object area, so if the input ``scale == 1`` then the + transformation represented by the ShearWCS will conserve object area also. + """ + _req_params = { "scale" : float, "shear" : Shear } + + def __init__(self, scale, shear): + self._color = None + self._scale = float(scale) + self._shear = shear + self._g1 = shear.g1 + self._g2 = shear.g2 + self._gsq = self._g1**2 + self._g2**2 + self._gfactor = 1. / math.sqrt(1. - self._gsq) + + # Help make sure ShearWCS is read-only. + @property + def scale(self): + """The pixel scale. + """ + return self._scale + + @property + def shear(self): + """The applied `Shear`. + """ + return self._shear + + def _u(self, x, y, color=None): + u = x * (1.-self._g1) - y * self._g2 + u *= self._gfactor * self._scale + return u + + def _v(self, x, y, color=None): + v = y * (1.+self._g1) - x * self._g2 + v *= self._gfactor * self._scale + return v + + def _x(self, u, v, color=None): + x = u * (1.+self._g1) + v * self._g2 + x *= self._gfactor / self._scale + return x + + def _y(self, u, v, color=None): + y = v * (1.-self._g1) + u * self._g2 + y *= self._gfactor / self._scale + return y + + def _profileToWorld(self, image_profile, flux_ratio, offset): + return image_profile.dilate(self._scale).shear(-self.shear).shift(offset) * flux_ratio + + def _profileToImage(self, world_profile, flux_ratio, offset): + return world_profile.dilate(1./self._scale).shear(self.shear).shift(offset) * flux_ratio + + def _shearToWorld(self, image_shear): + # This isn't worth customizing. Just use the jacobian. + return self._toJacobian()._shearToWorld(image_shear) + + def _shearToImage(self, world_shear): + return self._toJacobian()._shearToImage(world_shear) + + def _pixelArea(self): + return self._scale**2 + + def _minScale(self): + # min stretch is (1-|g|) / sqrt(1-|g|^2) + return self._scale * (1. - math.sqrt(self._gsq)) * self._gfactor + + def _maxScale(self): + # max stretch is (1+|g|) / sqrt(1-|g|^2) + return self._scale * (1. + math.sqrt(self._gsq)) * self._gfactor + + def _inverse(self): + return ShearWCS(1./self._scale, -self._shear) + + def _toJacobian(self): + return JacobianWCS( + (1.-self._g1) * self._scale * self._gfactor, + -self._g2 * self._scale * self._gfactor, + -self._g2 * self._scale * self._gfactor, + (1.+self._g1) * self._scale * self._gfactor) + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("ShearWCS", "GalSim WCS name") + header["GS_SCALE"] = (self.scale, "GalSim image scale") + header["GS_G1"] = (self.shear.g1, "GalSim image shear g1") + header["GS_G2"] = (self.shear.g2, "GalSim image shear g2") + return self.affine()._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + scale = header["GS_SCALE"] + g1 = header["GS_G1"] + g2 = header["GS_G2"] + return ShearWCS(scale, Shear(g1=g1, g2=g2)) + + def _newOrigin(self, origin, world_origin): + return OffsetShearWCS(self._scale, self._shear, origin, world_origin) + + def copy(self): + return ShearWCS(self._scale, self._shear) + + def __eq__(self, other): + return (self is other or + (isinstance(other, ShearWCS) and + self.scale == other.scale and + self.shear == other.shear)) + + def __repr__(self): return "galsim.ShearWCS(%r, %r)"%(self.scale,self.shear) + def __hash__(self): return hash(repr(self))
+ + +
[docs]class JacobianWCS(LocalWCS): + """This WCS is the most general local linear WCS implementing a 2x2 Jacobian matrix. + + The conversion functions are: + + u = dudx x + dudy y + v = dvdx x + dvdy y + + A JacobianWCS has attributes dudx, dudy, dvdx, dvdy that you can access directly if that + is convenient. You can also access these as a NumPy array directly with:: + + >>> J = jac_wcs.getMatrix() + + Also, JacobianWCS has another method that other WCS classes do not have. The call:: + + >>> scale, shear, theta, flip = jac_wcs.getDecomposition() + + will return the equivalent expansion, shear, rotation and possible flip corresponding to + this transformation. See the docstring for that method for more information. + + A JacobianWCS is initialized with the command:: + + >>> wcs = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy) + + Parameters: + dudx: du/dx + dudy: du/dy + dvdx: dv/dx + dvdy: dv/dy + """ + _req_params = { "dudx" : float, "dudy" : float, "dvdx" : float, "dvdy" : float } + + def __init__(self, dudx, dudy, dvdx, dvdy): + self._color = None + self._dudx = float(dudx) + self._dudy = float(dudy) + self._dvdx = float(dvdx) + self._dvdy = float(dvdy) + self._det = dudx * dvdy - dudy * dvdx + + # Help make sure JacobianWCS is read-only. + @property + def dudx(self): + """du/dx + """ + return self._dudx + + @property + def dudy(self): + """du/dy + """ + return self._dudy + + @property + def dvdx(self): + """dv/dx + """ + return self._dvdx + + @property + def dvdy(self): + """dv/dy + """ + return self._dvdy + + def _u(self, x, y, color=None): + return self._dudx * x + self._dudy * y + + def _v(self, x, y, color=None): + return self._dvdx * x + self._dvdy * y + + def _x(self, u, v, color=None): + # J = ( dudx dudy ) + # ( dvdx dvdy ) + # J^-1 = (1/det) ( dvdy -dudy ) + # ( -dvdx dudx ) + return (self._dvdy * u - self._dudy * v)*self._invdet + + def _y(self, u, v, color=None): + return (-self._dvdx * u + self._dudx * v)*self._invdet + + def _profileToWorld(self, image_profile, flux_ratio, offset): + # In the usual case of GSObject, it's more efficient to use the _Transform version. + # else, it's a ChromaticObject, and we need to use the regular Transform function. + Transform = (transform._Transform if isinstance(image_profile, gsobject.GSObject) + else transform.Transform) + j = np.array(((self._dudx, self._dudy), (self._dvdx, self._dvdy))) + return Transform(image_profile, j, flux_ratio=flux_ratio*abs(self._invdet), + offset=(offset.x, offset.y)) + + def _profileToImage(self, world_profile, flux_ratio, offset): + Transform = (transform._Transform if isinstance(world_profile, gsobject.GSObject) + else transform.Transform) + j = np.array(((self._dvdy, -self._dudy), (-self._dvdx, self._dudx))) * self._invdet + return Transform(world_profile, j, flux_ratio=flux_ratio*abs(self._det), + offset=(offset.x, offset.y)) + + def _shearToWorld(self, image_shear): + # Code from https://github.com/rmjarvis/DESWL/blob/y3a1-v23/psf/run_piff.py#L691 + e1 = image_shear.e1 + e2 = image_shear.e2 + + M = np.array([[1+e1, e2], [e2, 1-e1]]) + J = self.getMatrix() + M = J.dot(M).dot(J.T) + + e1 = (M[0,0] - M[1,1]) / (M[0,0] + M[1,1]) + e2 = (2.*M[0,1]) / (M[0,0] + M[1,1]) + + return Shear(e1=e1, e2=e2) + + def _shearToImage(self, world_shear): + # Same as above but inverse J matrix. + return self._inverse()._shearToWorld(world_shear) + + @lazy_property + def _invdet(self): + try: + return 1./self._det + except ZeroDivisionError: + raise GalSimError("Transformation is singular") + + def _pixelArea(self): + return abs(self._det) + +
[docs] def getMatrix(self): + """Get the Jacobian as a NumPy matrix: + + numpy.array( [[ dudx, dudy ], + [ dvdx, dvdy ]] ) + """ + return np.array([[ self._dudx, self._dudy ], + [ self._dvdx, self._dvdy ]], dtype=float)
+ +
[docs] def getDecomposition(self): + """Get the equivalent expansion, shear, rotation and possible flip corresponding to + this Jacobian transformation. + + A non-singular real matrix can always be decomposed into a symmetric positive definite + matrix times an orthogonal matrix: + + M = P Q + + In our case, P includes an overall scale and a shear, and Q is a rotation and possibly + a flip of (x,y) -> (y,x). + + ( dudx dudy ) = scale/sqrt(1-g1^2-g2^2) ( 1+g1 g2 ) ( cos(theta) -sin(theta) ) F + ( dvdx dvdy ) ( g2 1-g1 ) ( sin(theta) cos(theta) ) + + where F is either the identity matrix, ( 1 0 ), or a flip matrix, ( 0 1 ). + ( 0 1 ) ( 1 0 ) + + If there is no flip, then this means that the effect of:: + + >>> prof.transform(dudx, dudy, dvdx, dvdy) + + is equivalent to:: + + >>> prof.rotate(theta).shear(shear).expand(scale) + + in that order. (Rotation and shear do not commute.) + + The decomposition is returned as a tuple: (scale, shear, theta, flip), where scale is a + float, shear is a `Shear`, theta is an `Angle`, and flip is a bool. + """ + # First we need to see whether or not the transformation includes a flip. The evidence + # for a flip is that the determinant is negative. + if self._det == 0.: + raise GalSimError("Transformation is singular") + elif self._det < 0.: + flip = True + scale = math.sqrt(-self._det) + dudx = self._dudy + dudy = self._dudx + dvdx = self._dvdy + dvdy = self._dvdx + else: + flip = False + scale = math.sqrt(self._det) + dudx = self._dudx + dudy = self._dudy + dvdx = self._dvdx + dvdy = self._dvdy + + # A small bit of algebraic manipulations yield the following two equations that let us + # determine theta: + # + # (dudx + dvdy) = 2 scale/sqrt(1-g^2) cos(t) + # (dvdx - dudy) = 2 scale/sqrt(1-g^2) sin(t) + + C = dudx + dvdy + S = dvdx - dudy + theta = math.atan2(S,C) * radians + + # The next step uses the following equations that you can get from a bit more algebra: + # + # cost (dudx - dvdy) - sint (dudy + dvdx) = 2 scale/sqrt(1-g^2) g1 + # sint (dudx - dvdy) + cost (dudy + dvdx) = 2 scale/sqrt(1-g^2) g2 + + factor = C*C+S*S # factor = (2 scale/sqrt(1-g^2))^2 + C /= factor # C is now cost / (2 scale/sqrt(1-g^2)) + S /= factor # S is now sint / (2 scale/sqrt(1-g^2)) + + g1 = C*(dudx-dvdy) - S*(dudy+dvdx) + g2 = S*(dudx-dvdy) + C*(dudy+dvdx) + + return scale, Shear(g1=g1, g2=g2), theta, flip
+ + def _minScale(self): + # min scale is scale * (1-|g|) / sqrt(1-|g|^2) + # We could get this from the decomposition, but some algebra finds that this + # reduces to the following calculation: + # NB: The unit tests test for the equivalence with the above formula. + h1 = math.sqrt( (self._dudx + self._dvdy)**2 + (self._dudy - self._dvdx)**2 ) + h2 = math.sqrt( (self._dudx - self._dvdy)**2 + (self._dudy + self._dvdx)**2 ) + return 0.5 * abs(h1 - h2) + + def _maxScale(self): + # min scale is scale * (1+|g|) / sqrt(1-|g|^2) + # which is equivalent to the following: + # NB: The unit tests test for the equivalence with the above formula. + h1 = math.sqrt( (self._dudx + self._dvdy)**2 + (self._dudy - self._dvdx)**2 ) + h2 = math.sqrt( (self._dudx - self._dvdy)**2 + (self._dudy + self._dvdx)**2 ) + return 0.5 * (h1 + h2) + + def _inverse(self): + return JacobianWCS(self._dvdy*self._invdet, -self._dudy*self._invdet, + -self._dvdx*self._invdet, self._dudx*self._invdet) + + def _toJacobian(self): + return self + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("JacobianWCS", "GalSim WCS name") + return self.affine()._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + dudx = header.get("CD1_1",1.) + dudy = header.get("CD1_2",0.) + dvdx = header.get("CD2_1",0.) + dvdy = header.get("CD2_2",1.) + return JacobianWCS(dudx, dudy, dvdx, dvdy) + + def _newOrigin(self, origin, world_origin): + return AffineTransform(self._dudx, self._dudy, self._dvdx, self._dvdy, origin, + world_origin) + + def copy(self): + return JacobianWCS(self._dudx, self._dudy, self._dvdx, self._dvdy) + + def __eq__(self, other): + return (self is other or + (isinstance(other, JacobianWCS) and + self.dudx == other.dudx and + self.dudy == other.dudy and + self.dvdx == other.dvdx and + self.dvdy == other.dvdy)) + + def __repr__(self): return "galsim.JacobianWCS(%r, %r, %r, %r)"%( + self.dudx,self.dudy,self.dvdx,self.dvdy) + def __hash__(self): return hash(repr(self))
+ + +######################################################################################### +# +# Non-local UniformWCS classes are those where (x,y) = (0,0) does not (necessarily) +# correspond to (u,v) = (0,0). +# +# We have the following non-local UniformWCS classes: +# +# OffsetWCS +# OffsetShearWCS +# AffineTransform +# +# They must define the following: +# +# origin attribute or property returning the origin +# world_origin attribute or property returning the world origin +# _local_wcs property returning a local WCS with the same pixel shape +# _writeHeader function that writes the WCS to a fits header. +# _readHeader static function that reads the WCS from a fits header. +# _newOrigin function returning the saem WCS, but with new origin, world_origin +# copy return a copy +# __repr__ convert to string +# +######################################################################################### + + +
[docs]class OffsetWCS(UniformWCS): + """This WCS is similar to `PixelScale`, except the origin is not necessarily (0,0) in both + the image and world coordinates. + + The conversion functions are: + + u = (x-x0) * scale + u0 + v = (y-y0) * scale + v0 + + An OffsetWCS is initialized with the command:: + + >>> wcs = galsim.OffsetWCS(scale, origin=None, world_origin=None) + + Parameters: + scale: The pixel scale, typically in units of arcsec/pixel. + origin: Optional origin position for the image coordinate system. + If provided, it should be a `PositionD` or `PositionI`. + [default: PositionD(0., 0.)] + world_origin: Optional origin position for the world coordinate system. + If provided, it should be a `PositionD`. + [default: galsim.PositionD(0., 0.)] + """ + _req_params = { "scale" : float } + _opt_params = { "origin" : PositionD, "world_origin": PositionD } + + def __init__(self, scale, origin=None, world_origin=None): + self._color = None + self._set_origin(origin, world_origin) + self._scale = scale + self._local_wcs = PixelScale(scale) + + @property + def scale(self): + """The pixel scale. + """ + return self._scale + + @property + def origin(self): + """The image coordinate position to use as the origin. + """ + return self._origin + + @property + def world_origin(self): + """The world coordinate position to use as the origin. + """ + return self._world_origin + + @property + def _isPixelScale(self): + return True + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("OffsetWCS", "GalSim WCS name") + header["GS_SCALE"] = (self.scale, "GalSim image scale") + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") + header["GS_U0"] = (self.world_origin.x, "GalSim world origin u") + header["GS_V0"] = (self.world_origin.y, "GalSim world origin v") + return self.affine()._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + scale = header["GS_SCALE"] + x0 = header["GS_X0"] + y0 = header["GS_Y0"] + u0 = header["GS_U0"] + v0 = header["GS_V0"] + return OffsetWCS(scale, _PositionD(x0,y0), _PositionD(u0,v0)) + + def _newOrigin(self, origin, world_origin): + return OffsetWCS(self._scale, origin, world_origin) + + def copy(self): + return OffsetWCS(self._scale, self.origin, self.world_origin) + + def __repr__(self): return "galsim.OffsetWCS(%r, %r, %r)"%( + self.scale, self.origin, self.world_origin) + def __hash__(self): return hash(repr(self))
+ + +
[docs]class OffsetShearWCS(UniformWCS): + """This WCS is a uniformly sheared coordinate system with image and world origins + that are not necessarily coincident. + + The conversion functions are: + + x = ( (1+g1) (u-u0) + g2 (v-v0) ) / scale / sqrt(1-g1**2-g2**2) + x0 + y = ( (1-g1) (v-v0) + g2 (u-u0) ) / scale / sqrt(1-g1**2-g2**2) + y0 + + u = ( (1-g1) (x-x0) - g2 (y-y0) ) * scale / sqrt(1-g1**2-g2**2) + u0 + v = ( (1+g1) (y-y0) - g2 (x-x0) ) * scale / sqrt(1-g1**2-g2**2) + v0 + + An OffsetShearWCS is initialized with the command:: + + >>> wcs = galsim.OffsetShearWCS(scale, shear, origin=None, world_origin=None) + + Parameters: + scale: The pixel scale, typically in units of arcsec/pixel. + shear: The shear, which should be a `Shear` instance. + origin: Optional origin position for the image coordinate system. + If provided, it should be a `PositionD` or `PositionI`. + [default: PositionD(0., 0.)] + world_origin: Optional origin position for the world coordinate system. + If provided, it should be a `PositionD`. + [default: PositionD(0., 0.)] + """ + _req_params = { "scale" : float, "shear" : Shear } + _opt_params = { "origin" : PositionD, "world_origin": PositionD } + + def __init__(self, scale, shear, origin=None, world_origin=None): + self._color = None + self._set_origin(origin, world_origin) + # The shear stuff is not too complicated, but enough so that it is worth + # encapsulating in the ShearWCS class. So here, we just create one of those + # and we'll pass along any shear calculations to that. + self._local_wcs = ShearWCS(scale, shear) + + @property + def scale(self): + """The pixel scale. + """ + return self._local_wcs.scale + + @property + def shear(self): + """The applied `Shear`. + """ + return self._local_wcs.shear + + @property + def origin(self): + """The image coordinate position to use as the origin. + """ + return self._origin + + @property + def world_origin(self): + """The world coordinate position to use as the origin. + """ + return self._world_origin + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("OffsetShearWCS", "GalSim WCS name") + header["GS_SCALE"] = (self.scale, "GalSim image scale") + header["GS_G1"] = (self.shear.g1, "GalSim image shear g1") + header["GS_G2"] = (self.shear.g2, "GalSim image shear g2") + header["GS_X0"] = (self.origin.x, "GalSim image origin x coordinate") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y coordinate") + header["GS_U0"] = (self.world_origin.x, "GalSim world origin u coordinate") + header["GS_V0"] = (self.world_origin.y, "GalSim world origin v coordinate") + return self.affine()._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + scale = header["GS_SCALE"] + g1 = header["GS_G1"] + g2 = header["GS_G2"] + x0 = header["GS_X0"] + y0 = header["GS_Y0"] + u0 = header["GS_U0"] + v0 = header["GS_V0"] + return OffsetShearWCS(scale, Shear(g1=g1, g2=g2), _PositionD(x0,y0), _PositionD(u0,v0)) + + def _newOrigin(self, origin, world_origin): + return OffsetShearWCS(self.scale, self.shear, origin, world_origin) + + def copy(self): + return OffsetShearWCS(self.scale, self.shear, self.origin, self.world_origin) + + def __repr__(self): + return "galsim.OffsetShearWCS(%r, %r, %r, %r)"%( + self.scale, self.shear, self.origin, self.world_origin) + def __hash__(self): return hash(repr(self))
+ + +
[docs]class AffineTransform(UniformWCS): + """This WCS is the most general linear transformation. It involves a 2x2 Jacobian + matrix and an offset. You can provide the offset in terms of either the ``image_pos`` + (x0,y0) where (u,v) = (0,0), or the ``world_pos`` (u0,v0) where (x,y) = (0,0). + Or, in fact, you may provide both, in which case the ``image_pos`` (x0,y0) corresponds + to the ``world_pos`` (u0,v0). + + The conversion functions are: + + u = dudx (x-x0) + dudy (y-y0) + u0 + v = dvdx (x-x0) + dvdy (y-y0) + v0 + + An AffineTransform has attributes dudx, dudy, dvdx, dvdy, x0, y0, u0, v0 that you can + access directly if that is convenient. + + An AffineTransform is initialized with the command:: + + >>> wcs = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=None, world_origin=None) + + Parameters: + dudx: du/dx + dudy: du/dy + dvdx: dv/dx + dvdy: dv/dy + origin: Optional origin position for the image coordinate system. + If provided, it should be a `PositionD` or `PositionI`. + [default: PositionD(0., 0.)] + world_origin: Optional origin position for the world coordinate system. + If provided, it should be a `PositionD`. + [default: PositionD(0., 0.)] + """ + _req_params = { "dudx" : float, "dudy" : float, "dvdx" : float, "dvdy" : float } + _opt_params = { "origin" : PositionD, "world_origin": PositionD } + + def __init__(self, dudx, dudy, dvdx, dvdy, origin=None, world_origin=None): + self._color = None + self._set_origin(origin, world_origin) + # As with OffsetShearWCS, we store a JacobianWCS, rather than reimplement everything. + self._local_wcs = JacobianWCS(dudx, dudy, dvdx, dvdy) + + @property + def dudx(self): + """du/dx + """ + return self._local_wcs.dudx + + @property + def dudy(self): + """du/dy + """ + return self._local_wcs.dudy + + @property + def dvdx(self): + """dv/dx + """ + return self._local_wcs.dvdx + + @property + def dvdy(self): + """dv/dy + """ + return self._local_wcs.dvdy + + @property + def origin(self): + """The image coordinate position to use as the origin. + """ + return self._origin + + @property + def world_origin(self): + """The world coordinate position to use as the origin. + """ + return self._world_origin + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("AffineTransform", "GalSim WCS name") + return self._writeLinearWCS(header, bounds) + + def _writeLinearWCS(self, header, bounds): + header["CTYPE1"] = ("LINEAR", "name of the world coordinate axis") + header["CTYPE2"] = ("LINEAR", "name of the world coordinate axis") + header["CRVAL1"] = (self.u0, "world coordinate at reference pixel = u0") + header["CRVAL2"] = (self.v0, "world coordinate at reference pixel = v0") + header["CRPIX1"] = (self.x0, "image coordinate of reference pixel = x0") + header["CRPIX2"] = (self.y0, "image coordinate of reference pixel = y0") + header["CD1_1"] = (self.dudx, "CD1_1 = dudx") + header["CD1_2"] = (self.dudy, "CD1_2 = dudy") + header["CD2_1"] = (self.dvdx, "CD2_1 = dvdx") + header["CD2_2"] = (self.dvdy, "CD2_2 = dvdy") + return header + + @staticmethod + def _readHeader(header): + # We try to make this work to produce a linear WCS, no matter what kinds of key words + # are in the header. + if 'CD1_1' in header: + # The others should be too, but use get with a default to be safe + dudx = header.get("CD1_1",1.) + dudy = header.get("CD1_2",0.) + dvdx = header.get("CD2_1",0.) + dvdy = header.get("CD2_2",1.) + else: + dudx = header.get("CDELT1",1.) + dudy = 0. + dvdx = 0. + dvdy = header.get("CDELT2",1.) + x0 = header.get("CRPIX1",0.) + y0 = header.get("CRPIX2",0.) + u0 = header.get("CRVAL1",0.) + v0 = header.get("CRVAL2",0.) + + return AffineTransform(dudx, dudy, dvdx, dvdy, _PositionD(x0,y0), _PositionD(u0,v0)) + + def _newOrigin(self, origin, world_origin): + return AffineTransform(self.dudx, self.dudy, self.dvdx, self.dvdy, + origin, world_origin) + + def copy(self): + return AffineTransform(self.dudx, self.dudy, self.dvdx, self.dvdy, + self.origin, self.world_origin) + + def __repr__(self): + return ("galsim.AffineTransform(%r, %r, %r, %r, origin=%r, world_origin=%r)")%( + self.dudx, self.dudy, self.dvdx, self.dvdy, self.origin, self.world_origin) + def __hash__(self): return hash(repr(self))
+ + +######################################################################################### +# +# Non-uniform WCS classes are those where the pixel size and shape are not necessarily +# constant across the image. There are two varieties of these, EuclideanWCS and CelestialWCS. +# +# Here, we have the following non-uniform WCS classes: (There are more in fitswcs.py) +# +# UVFunction is a EuclideanWCS +# RaDecFunction is a CelestialWCS +# +# They must define the following: +# +# origin attribute or property returning the origin +# _writeHeader function that writes the WCS to a fits header. +# _readHeader static function that reads the WCS from a fits header. +# _newOrigin function returning the saem WCS, but with new origin +# copy return a copy +# __eq__ check if this equals another WCS +# __repr__ convert to string +# +# Non-uniform EuclideanWCS classes must define the following: +# +# world_origin attribute or property returning the world origin +# _u function returning u(x,y) +# _v function returning v(x,y) +# _x function returning x(u,v) (May raise a NotImplementedError) +# _y function returning y(u,v) (May raise a NotImplementedError) +# +# CelestialWCS classes must define the following: +# +# _radec function returning (ra, dec) in _radians_ at position (x,y) +# +# Ideally, the above functions would work with NumPy arrays as inputs. +# +######################################################################################### + + +# Some helper functions for serializing arbitrary functions. Used by both UVFunction and +# RaDecFunction. +def _writeFuncToHeader(func, letter, header): + if isinstance(func, str): + # If we have the string version, then just write that + s = func + first_key = 'GS_'+letter+'_STR' + + elif func is not None: + # Otherwise things get more interesting. We have to serialize a python function. + # I got the starting point for this code from: + # http://stackoverflow.com/questions/1253528/ + # In particular, marshal can serialize arbitrary code. (!) + if type(func) == types.FunctionType: + code = marshal.dumps(func.__code__) + name = func.__name__ + defaults = func.__defaults__ + closure = func.__closure__ + + # Functions may also have something called closure cells. If there are any, we need + # to include them as well. Help for this part came from: + # http://stackoverflow.com/questions/573569/ + if closure: + closure_list = [] + for c in closure: + if isinstance(c.cell_contents, types.ModuleType): + # Can't really pickle the modules. e.g. math if they use math functions. + # The modules just need to be loaded on the other side. But we still need + # to make a cell for the module closure item, so just use its name and + # mark it as a module so we can recover it correctly. + closure_list.append( 'module_'+c.cell_contents.__name__ ) + else: + closure_list.append( c.cell_contents ) + else: + closure_list = None + all = (0,code,name,defaults,closure_list) + else: + # For things other than regular functions, we can try to pickle it directly, but + # it might not work. Let pickle raise the appropriate error if it fails. + + # The first item in the tuple is what I'm calling a type_code to indicate what to + # do with the results of unpickling. So far I just have 0 = function, 1 = other, + # but this could be extended if we find a good reason to. + all = (1,func) + + # Now we can use pickle to serialize the full thing. + s = pickle.dumps(all) + + # Fits can't handle arbitrary strings. Shrink to a base-64 alphabet that is printable. + # (This is like UUencoding for those of you who remember that...) + s = base64.b64encode(s).decode() + first_key = 'GS_'+letter+'_FN' + else: + # Nothing to write. + return + + # Fits header strings cannot be more than 68 characters long, so split it up. + fits_len = 68 + n = (len(s)-1)//fits_len + 1 + s_array = [ s[i*fits_len:(i+1)*fits_len] for i in range(n) ] + + # The total number of string splits is stored in fits key GS_U_N. + header["GS_" + letter + "_N"] = n + for i in range(n): + # Use key names like GS_U0000, GS_U00001, etc. for the function versions + # and like GS_SU000, GS_SU001, etc. for the string versions. + if i == 0: key = first_key + else: key = 'GS_%s%04d'%(letter,i) + header[key] = s_array[i] + +def _makecell(value): # pragma: no cover + # (codecov gets confused, because the lambda function is never called.) + # This is a little trick to make a closure cell. + # We make a function that has the given value in closure, then then get the + # first (only) closure item, which will be the closure cell we need. + return (lambda : value).__closure__[0] + +def _readFuncFromHeader(letter, header): + # This undoes the process of _writeFuncToHeader. See the comments in that code for details. + if 'GS_'+letter+'_STR' in header: + # Read in a regular string + n = header["GS_" + letter + "_N"] + s = '' + for i in range(n): + if i == 0: key = 'GS_'+letter+'_STR' + else: key = 'GS_%s%04d'%(letter,i) + s += header[key] + return s + elif 'GS_'+letter+'_FN' in header: + # Read in an encoded function + n = header["GS_" + letter + "_N"] + s = '' + for i in range(n): + if i == 0: key = 'GS_'+letter+'_FN' + else: key = 'GS_%s%04d'%(letter,i) + s += header[key] + s = base64.b64decode(s) + all = pickle.loads(s) + type_code = all[0] + if type_code == 0: + code_str, name, defaults, closure_items = all[1:] + code = marshal.loads(code_str) + if closure_items is None: + closure = None + else: + closure = [] + for value in closure_items: + if isinstance(value,str) and value.startswith('module_'): + module_name = value[7:] + closure.append(_makecell(__import__(module_name))) + else: + closure.append(_makecell(value)) + closure = tuple(closure) + func = types.FunctionType(code, globals(), name, defaults, closure) + return func + else: + return all[1] + else: + return None + +
[docs]class UVFunction(EuclideanWCS): + """This WCS takes two arbitrary functions for u(x,y) and v(x,y). + + The ufunc and vfunc parameters may be: + - python functions that take (x,y) arguments + - python objects with a __call__ method that takes (x,y) arguments + - strings which can be parsed with eval('lambda x,y: '+str) + + You may also provide the inverse functions x(u,v) and y(u,v) as xfunc and yfunc. + These are not required, but if you do not provide them, then any operation that requires + going from world to image coordinates will raise a NotImplementedError. + + Note: some internal calculations will be faster if the functions can take NumPy arrays + for x,y and output arrays for u,v. Usually this does not require any change to your + function, but it is worth keeping in mind. For example, if you want to do a sqrt, you + may be better off using ``numpy.sqrt`` rather than ``math.sqrt``. + + A UVFunction is initialized with the command:: + + >>> wcs = galsim.UVFunction(ufunc, vfunc, origin=None, world_origin=None) + + Parameters: + ufunc: The function u(x,y) + vfunc: The function v(x,y) + xfunc: The function x(u,v) (optional) + yfunc: The function y(u,v) (optional) + origin: Optional origin position for the image coordinate system. + If provided, it should be a `PositionD` or `PositionI`. + [default: PositionD(0., 0.)] + world_origin Optional origin position for the world coordinate system. + If provided, it should be a `PositionD`. + [default: PositionD(0., 0.)] + uses_color: If True, then the functions take three parameters (x,y,c) or (u,v,c) + where the third term is some kind of color value. (The exact meaning + of "color" here is user-defined. You just need to be consistent with + the color values you use when using the wcs.) [default: False] + """ + _req_params = { "ufunc" : str, "vfunc" : str } + _opt_params = { "xfunc" : str, "yfunc" : str, + "origin" : PositionD, "world_origin": PositionD } + + def __init__(self, ufunc, vfunc, xfunc=None, yfunc=None, origin=None, world_origin=None, + uses_color=False): + self._color = None + self._set_origin(origin, world_origin) + + # Keep these to use in copies, etc. + self._orig_ufunc = ufunc + self._orig_vfunc = vfunc + self._orig_xfunc = xfunc + self._orig_yfunc = yfunc + self._uses_color = uses_color + + # Turn these into the real functions + self._initialize_funcs() + + def _initialize_funcs(self): + global galsim # Because if a user's function used galsim, it's probably at global scope. + import galsim + if isinstance(self._orig_ufunc, str): + if self._uses_color: + self._ufunc = math_eval('lambda x,y,c : ' + self._orig_ufunc) + else: + self._ufunc = math_eval('lambda x,y : ' + self._orig_ufunc) + else: + self._ufunc = self._orig_ufunc + + if isinstance(self._orig_vfunc, str): + if self._uses_color: + self._vfunc = math_eval('lambda x,y,c : ' + self._orig_vfunc) + else: + self._vfunc = math_eval('lambda x,y : ' + self._orig_vfunc) + else: + self._vfunc = self._orig_vfunc + + if isinstance(self._orig_xfunc, str): + if self._uses_color: + self._xfunc = math_eval('lambda u,v,c : ' + self._orig_xfunc) + else: + self._xfunc = math_eval('lambda u,v : ' + self._orig_xfunc) + else: + self._xfunc = self._orig_xfunc + + if isinstance(self._orig_yfunc, str): + if self._uses_color: + self._yfunc = math_eval('lambda u,v,c : ' + self._orig_yfunc) + else: + self._yfunc = math_eval('lambda u,v : ' + self._orig_yfunc) + else: + self._yfunc = self._orig_yfunc + + @property + def ufunc(self): + """The input ufunc + """ + return self._ufunc + + @property + def vfunc(self): + """The input vfunc + """ + return self._vfunc + + @property + def xfunc(self): + """The input xfunc + """ + return self._xfunc + + @property + def yfunc(self): + """The input yfunc + """ + return self._yfunc + + @property + def origin(self): + """The image coordinate position to use as the origin. + """ + return self._origin + + @property + def world_origin(self): + """The world coordinate position to use as the origin. + """ + return self._world_origin + + def _u(self, x, y, color=None): + if self._uses_color: + try: + return self._ufunc(x,y,color) + except Exception as e: + # If that didn't work, we have to do it manually for each position. :( (SLOW!) + try: + return np.array([self._ufunc(x1,y1,color) for [x1,y1] in zip(x,y)]) + except Exception: + raise e from None # Raise the original if this fails, since it's probably more relevant. + else: + try: + return self._ufunc(x,y) + except Exception as e: + try: + return np.array([self._ufunc(x1,y1) for [x1,y1] in zip(x,y)]) + except Exception: + raise e from None + + def _v(self, x, y, color=None): + if self._uses_color: + try: + return self._vfunc(x,y,color) + except Exception as e: + try: + return np.array([self._vfunc(x1,y1,color) for [x1,y1] in zip(x,y)]) + except Exception: + raise e from None + else: + try: + return self._vfunc(x,y) + except Exception as e: + try: + return np.array([self._vfunc(x1,y1) for [x1,y1] in zip(x,y)]) + except Exception: + raise e from None + + def _x(self, u, v, color=None): + if self._xfunc is None: + raise GalSimNotImplementedError( + "World -> Image direction not implemented for this UVFunction") + else: + if self._uses_color: + try: + return self._xfunc(u,v,color) + except Exception as e: + try: + return np.array([self._xfunc(u1,v1,color) for [u1,v1] in zip(u,v)]) + except Exception: + raise e from None + else: + try: + return self._xfunc(u,v) + except Exception as e: + try: + return np.array([self._xfunc(u1,v1) for [u1,v1] in zip(u,v)]) + except Exception: + raise e from None + + def _y(self, u, v, color=None): + if self._yfunc is None: + raise GalSimNotImplementedError( + "World -> Image direction not implemented for this UVFunction") + else: + if self._uses_color: + try: + return self._yfunc(u,v,color) + except Exception as e: + try: + return np.array([self._yfunc(u1,v1,color) for [u1,v1] in zip(u,v)]) + except Exception: + raise e from None + else: + try: + return self._yfunc(u,v) + except Exception as e: + try: + return np.array([self._yfunc(u1,v1) for [u1,v1] in zip(u,v)]) + except Exception: + raise e from None + + def _newOrigin(self, origin, world_origin): + return UVFunction(self._orig_ufunc, self._orig_vfunc, self._orig_xfunc, self._orig_yfunc, + origin, world_origin, self._uses_color) + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("UVFunction", "GalSim WCS name") + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") + header["GS_U0"] = (self.world_origin.x, "GalSim world origin u") + header["GS_V0"] = (self.world_origin.y, "GalSim world origin v") + header["GS_COLOR"] = (int(self._uses_color), "GalSim wcs uses color?") + + _writeFuncToHeader(self._orig_ufunc, 'U', header) + _writeFuncToHeader(self._orig_vfunc, 'V', header) + _writeFuncToHeader(self._orig_xfunc, 'X', header) + _writeFuncToHeader(self._orig_yfunc, 'Y', header) + + return self.affine(bounds.true_center)._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + x0 = header["GS_X0"] + y0 = header["GS_Y0"] + u0 = header["GS_U0"] + v0 = header["GS_V0"] + uses_color = bool(header["GS_COLOR"]) + ufunc = _readFuncFromHeader('U', header) + vfunc = _readFuncFromHeader('V', header) + xfunc = _readFuncFromHeader('X', header) + yfunc = _readFuncFromHeader('Y', header) + return UVFunction(ufunc, vfunc, xfunc, yfunc, _PositionD(x0,y0), + _PositionD(u0,v0), uses_color) + + def copy(self): + return UVFunction(self._orig_ufunc, self._orig_vfunc, self._orig_xfunc, self._orig_yfunc, + self.origin, self.world_origin, self._uses_color) + + def __eq__(self, other): + return (self is other or + (isinstance(other, UVFunction) and + self._orig_ufunc == other._orig_ufunc and + self._orig_vfunc == other._orig_vfunc and + self._orig_xfunc == other._orig_xfunc and + self._orig_yfunc == other._orig_yfunc and + self.origin == other.origin and + self.world_origin == other.world_origin and + self._uses_color == other._uses_color)) + + def __repr__(self): + return ("galsim.UVFunction(%r, %r, %r, %r, %r, %r, %r)")%( + self._orig_ufunc, self._orig_vfunc, self._orig_xfunc, self._orig_yfunc, + self.origin, self.world_origin, self._uses_color) + + def __hash__(self): return hash(repr(self)) + + def __getstate__(self): + d = self.__dict__.copy() + del d['_ufunc'] + del d['_vfunc'] + del d['_xfunc'] + del d['_yfunc'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self._initialize_funcs()
+ + +
[docs]class RaDecFunction(CelestialWCS): + """This WCS takes an arbitrary function for the Right Ascension (ra) and Declination (dec). + + In many cases, it can be more convenient to calculate both ra and dec in a single function, + since there will typically be intermediate values that are common to both, so it may be more + efficient to just calculate those once and thence calculate both ra and dec. Thus, we + provide the option to provide either a single function or two separate functions. + + The function parameters used to initialize an RaDecFunction may be: + - a python functions that take (x,y) arguments + - a python object with a __call__ method that takes (x,y) arguments + - a string which can be parsed with eval('lambda x,y: '+str) + + The return values, ra and dec, should be given in _radians_. + + The first argument is called ``ra_func``, but if ``dec_func`` is omitted, then it is assumed + to calculate both ra and dec. The two values should be returned as a tuple (ra,dec). + + We don't want a function that returns `Angle` instances, because we want to allow for the + possibility of using NumPy arrays as inputs and outputs to speed up some calculations. The + function isn't _required_ to work with NumPy arrays, but it is possible that some things + will be faster if it does. If it were expected to return `Angle` instances, then it definitely + couldn't work with arrays. + + An RaDecFunction is initialized with either of the following commands:: + + >>> wcs = galsim.RaDecFunction(radec_func, origin=None) + >>> wcs = galsim.RaDecFunction(ra_func, dec_func, origin=None) + + Parameters: + ra_func: If ``dec_func`` is also given: A function ra(x,y) returning ra in radians. + If ``dec_func=None``: A function returning a tuple (ra,dec), both in radians. + dec_func: Either a function dec(x,y) returning dec in radians, or None (in which + case ``ra_func`` is expected to return both ra and dec. [default: None] + origin: Optional origin position for the image coordinate system. + If provided, it should be a `PositionD` or `PositionI`. + [default: PositionD(0., 0.)] + """ + _req_params = { "ra_func" : str, "dec_func" : str } + _opt_params = { "origin" : PositionD } + + def __init__(self, ra_func, dec_func=None, origin=None): + self._color = None + self._set_origin(origin) + + # Keep these to use in copies, etc. + self._orig_ra_func = ra_func + self._orig_dec_func = dec_func + + # Turn these into the real functions + self._initialize_funcs() + + def _initialize_funcs(self): + global galsim # Because if a user's function used galsim, it's probably at global scope. + import galsim + if self._orig_dec_func is None: + if isinstance(self._orig_ra_func, str): + self._radec_func = math_eval('lambda x,y : ' + self._orig_ra_func) + else: + self._radec_func = self._orig_ra_func + else: + if isinstance(self._orig_ra_func, str): + ra_func = math_eval('lambda x,y : ' + self._orig_ra_func) + else: + ra_func = self._orig_ra_func + if isinstance(self._orig_dec_func, str): + dec_func = math_eval('lambda x,y : ' + self._orig_dec_func) + else: + dec_func = self._orig_dec_func + self._radec_func = lambda x,y : (ra_func(x,y), dec_func(x,y)) + + @property + def radec_func(self): + """The input radec_func + """ + return self._radec_func + + @property + def origin(self): + """The image coordinate position to use as the origin. + """ + return self._origin + + def _radec(self, x, y, color=None): + try: + return self._radec_func(x,y) + except Exception as e: + try: + world = [ self._radec(x1,y1) for (x1,y1) in zip(x,y) ] + except Exception: + raise e from None # Raise the original one if this fails, since it's probably more relevant. + ra = np.array([ w[0] for w in world ]) + dec = np.array([ w[1] for w in world ]) + return ra, dec + + def _xy(self, ra, dec, color=None): + raise GalSimNotImplementedError( + "World -> Image direction not implemented for RaDecFunction") + + def _newOrigin(self, origin): + return RaDecFunction(self._orig_ra_func, self._orig_dec_func, origin) + + def _writeHeader(self, header, bounds): + header["GS_WCS"] = ("RaDecFunction", "GalSim WCS name") + header["GS_X0"] = (self.origin.x, "GalSim image origin x") + header["GS_Y0"] = (self.origin.y, "GalSim image origin y") + + _writeFuncToHeader(self._orig_ra_func, 'R', header) + _writeFuncToHeader(self._orig_dec_func, 'D', header) + + return self.affine(bounds.true_center)._writeLinearWCS(header, bounds) + + @staticmethod + def _readHeader(header): + x0 = header["GS_X0"] + y0 = header["GS_Y0"] + ra_func = _readFuncFromHeader('R', header) + dec_func = _readFuncFromHeader('D', header) + return RaDecFunction(ra_func, dec_func, _PositionD(x0,y0)) + + def copy(self): + return RaDecFunction(self._orig_ra_func, self._orig_dec_func, self.origin) + + def __eq__(self, other): + return (self is other or + (isinstance(other, RaDecFunction) and + self._orig_ra_func == other._orig_ra_func and + self._orig_dec_func == other._orig_dec_func and + self.origin == other.origin)) + + def __repr__(self): + return "galsim.RaDecFunction(%r, %r, %r)"%( + self._orig_ra_func, self._orig_dec_func, self.origin) + + def __hash__(self): return hash(repr(self)) + + def __getstate__(self): + d = self.__dict__.copy() + del d['_radec_func'] + return d + + def __setstate__(self, d): + self.__dict__ = d + self._initialize_funcs()
+ +
[docs]def compatible(wcs1, wcs2): + """ + A utility to check the compatibility of two WCS. In particular, if two WCS are consistent with + each other modulo a shifted origin, we consider them to be compatible, even though they are not + equal. + """ + if wcs1._isUniform and wcs2._isUniform: + return wcs1.jacobian() == wcs2.jacobian() + else: + return wcs1 == wcs2.shiftOrigin(wcs1.origin, wcs1.world_origin)
+ + +# Put these at the bottom to avoid circular import errors. +from . import transform +from . import gsobject +from . import chromatic as chrom +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/galsim/zernike.html b/docs/_build/html/_modules/galsim/zernike.html new file mode 100644 index 00000000000..b35e13d2483 --- /dev/null +++ b/docs/_build/html/_modules/galsim/zernike.html @@ -0,0 +1,1642 @@ + + + + + + galsim.zernike — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for galsim.zernike

+# Copyright (c) 2012-2023 by the GalSim developers team on GitHub
+# https://github.com/GalSim-developers
+#
+# This file is part of GalSim: The modular galaxy image simulation toolkit.
+# https://github.com/GalSim-developers/GalSim
+#
+# GalSim is free software: redistribution and use in source and binary forms,
+# with or without modification, are permitted provided that the following
+# conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this
+#    list of conditions, and the disclaimer given in the accompanying LICENSE
+#    file.
+# 2. Redistributions in binary form must reproduce the above copyright notice,
+#    this list of conditions, and the disclaimer given in the documentation
+#    and/or other materials provided with the distribution.
+#
+
+import numpy as np
+from numbers import Real
+
+from .utilities import LRU_Cache, binomial, horner2d, horner4d, nCr, lazy_property
+from .integ import gq_annulus_points
+from .errors import GalSimValueError, GalSimRangeError, GalSimIncompatibleValuesError
+
+# Some utilities for working with Zernike polynomials
+
+# Start off with the Zernikes up to j=15
+_noll_n = [0,0,1,1,2,2,2,3,3,3,3,4,4,4,4,4]
+_noll_m = [0,0,1,-1,0,-2,2,-1,1,-3,3,0,2,-2,4,-4]
+
[docs]def noll_to_zern(j): + """Convert linear Noll index to tuple of Zernike indices. + j is the linear Noll coordinate, n is the radial Zernike index and m is the azimuthal Zernike + index. + + c.f. https://oeis.org/A176988 + + Parameters: + j: Zernike mode Noll index + + Returns: + (n, m) tuple of Zernike indices + """ + while len(_noll_n) <= j: + n = _noll_n[-1] + 1 + _noll_n.extend( [n] * (n+1) ) + if n % 2 == 0: + _noll_m.append(0) + m = 2 + else: + m = 1 + # pm = +1 if m values go + then - in pairs. + # pm = -1 if m values go - then + in pairs. + pm = +1 if (n//2) % 2 == 0 else -1 + while m <= n: + _noll_m.extend([ pm * m , -pm * m ]) + m += 2 + + return _noll_n[j], _noll_m[j]
+ +def _zern_norm(n, m): + r"""Normalization coefficient for zernike (n, m). + + Defined such that \int_{unit disc} Z(n1, m1) Z(n2, m2) dA = \pi if n1==n2 and m1==m2 else 0.0 + """ + if m == 0: + return np.sqrt(1./(n+1)) + else: + return np.sqrt(1./(2.*n+2)) + + +def _zern_rho_coefs(n, m): + """Compute coefficients of radial part of Zernike (n, m). + """ + kmax = (n-abs(m)) // 2 + A = [0]*(n+1) + val = nCr(n,kmax) # The value for k = 0 in the equation below. + for k in range(kmax): + # val = (-1)**k * _nCr(n-k, k) * _nCr(n-2*k, kmax-k) + # The above formula is faster as a recurrence relation: + A[n-2*k] = val + # Don't use *= since the factor is not an integer, but the result is. + val = -val * (kmax-k)*(n-kmax-k) // ((n-k)*(k+1)) + A[n-2*kmax] = val + return A + + +def _zern_coef_array(n, m, obscuration, shape): + """Assemble coefficient array for evaluating Zernike (n, m) as the real part of a + bivariate polynomial in abs(rho)^2 and rho, where rho is a complex array indicating position on + a unit disc. + + Parameters: + n: Zernike radial coefficient + m: Zernike azimuthal coefficient + obscuration: Linear obscuration fraction. + shape: Output array shape + + Returns: + 2D array of coefficients in |r|^2 and r, where r = u + 1j * v, and u, v are unit + disk coordinates. + """ + out = np.zeros(shape, dtype=np.complex128) + if 0 < obscuration < 1: + coefs = np.array(_annular_zern_rho_coefs(n, m, obscuration), dtype=np.complex128) + elif obscuration == 0: + coefs = np.array(_zern_rho_coefs(n, m), dtype=np.complex128) + else: + raise GalSimRangeError("Invalid obscuration.", obscuration, 0., 1.) + coefs /= _zern_norm(n, m) + if m < 0: + coefs *= -1j + for i, c in enumerate(coefs[abs(m)::2]): + out[i, abs(m)] = c + return out + + +def __noll_coef_array(jmax, obscuration): + """Assemble coefficient array for evaluating Zernike (n, m) as the real part of a + bivariate polynomial in abs(rho)^2 and rho, where rho is a complex array indicating position on + a unit disc. + + Parameters: + jmax: Maximum Noll coefficient + obscuration: Linear obscuration fraction. + + Returns: + 2D array of coefficients in |r|^2 and r, where r = u + 1j * v, and u, v are unit + disk coordinates. + """ + maxn = noll_to_zern(jmax)[0] + shape = (maxn//2+1, maxn+1, jmax) # (max power of |rho|^2, max power of rho, noll index-1) + + out = np.zeros(shape, dtype=np.complex128) + for j in range(1,jmax+1): + n,m = noll_to_zern(j) + coef = _zern_coef_array(n,m,obscuration,shape[0:2]) + out[:,:,j-1] = coef + return out +_noll_coef_array = LRU_Cache(__noll_coef_array) + + +def _xy_contribution(rho2_power, rho_power, shape): + """Convert (rho, |rho|^2) bivariate polynomial coefficients to (x, y) bivariate polynomial + coefficients. + """ + # rho = (x + iy), so multiplying an xy coefficient by rho, and again by rho is equivalent to: + # + # 1 0 0 0 i 0 0 0 -1 + # 0 0 0 -> 1 0 0 -> 0 2i 0 + # 0 0 0 0 0 0 1 0 0 + # + # where the rows indicate powers of x and the columns indicate powers of y. + # So the last array above indicates (x + iy)^2 = (x^2 + 2ixy - y^2) + # Similarly, multiplying by |rho|^2 = x^2 + y^2 is equivalent to + # + # 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 + # 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + # 0 0 0 0 0 -> 1 0 0 0 0 -> 0 0 2 0 0 + # 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + # 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 + # + # and so on. We can apply these operations repeatedly to effect arbitrary powers of rho or + # |rho|^2. + out = np.zeros(shape, dtype=np.complex128) + out[0,0] = 1 + while rho2_power >= 1: + new = np.zeros_like(out) + for (i, j) in zip(*np.nonzero(out)): + val = out[i, j] + new[i+2, j] += val + new[i, j+2] += val + rho2_power -= 1 + out = new + while rho_power >= 1: + new = np.zeros_like(out) + for (i, j) in zip(*np.nonzero(out)): + val = out[i, j] + new[i+1, j] += val + new[i, j+1] += 1j*val + rho_power -= 1 + out = new + return out + + +def _rrsq_to_xy(coefs, shape): + """Convert coefficient array from rho, |rho|^2 to x, y. + """ + new_coefs = np.zeros(shape, dtype=np.float64) + + # Now we loop through the elements of coefs and compute their contribution to new_coefs + for (i, j) in zip(*np.nonzero(coefs)): + new_coefs += (coefs[i, j]*_xy_contribution(i, j, shape)).real + return new_coefs + + +def _xycoef_gradx(coefs, shape): + """Calculate x/y coefficient array of x-derivative of given x/y coefficient array. + """ + # d/dx (x+y) = 1 looks like + # + # 0 1 1 0 + # 1 0 -> 0 0 + # + # d/dx (x^2 + y^2) = 2 x looks like + # + # 0 0 1 0 0 0 + # 0 0 0 -> 2 0 0 + # 1 0 0 0 0 0 + # + # d/dx (x^2 + xy + y^2) = 2x + y looks like + # + # 0 0 1 0 1 0 + # 0 1 0 -> 2 0 0 + # 1 0 0 0 0 0 + + gradx = np.zeros(shape, dtype=np.float64) + for (i, j) in zip(*np.nonzero(coefs)): + if i > 0: + gradx[i-1, j] = coefs[i, j]*i + + return gradx + + +def _xycoef_grady(coefs, shape): + """Calculate x/y coefficient array of y-derivative of given x/y coefficient array. + """ + # see above + grady = np.zeros(shape, dtype=np.float64) + for (i, j) in zip(*np.nonzero(coefs)): + if j > 0: + grady[i, j-1] = coefs[i, j]*j + + return grady + + +def __noll_coef_array_xy(jmax, obscuration): + """Assemble coefficient array for evaluating Zernike (n, m) as a bivariate polynomial in + x and y. + + Parameters: + jmax: Maximum Noll coefficient + obscuration: Linear obscuration fraction. + + Returns: + 2D array of coefficients in x and y. + """ + maxn, _ = noll_to_zern(jmax) + j_full = (maxn+1)*(maxn+2)//2 + if jmax < j_full: # full row calculation may already be in cache + return _noll_coef_array_xy(j_full, obscuration)[..., :jmax] + + shape = (maxn+1, maxn+1, jmax) # (max power of x, max power of y, noll index) + + nca = _noll_coef_array(jmax, obscuration) + + out = np.zeros(shape, dtype=np.float64) + for j in range(1, jmax+1): + out[:,:,j-1] = _rrsq_to_xy(nca[:,:,j-1], shape=shape[0:2]) + return out +_noll_coef_array_xy = LRU_Cache(__noll_coef_array_xy) + + +def __noll_coef_array_xy_gradx(jmax, obscuration): + """Assemble coefficient array for evaluating the x-derivative of Zernike (n, m) as a bivariate + polynomial in x and y. + + Parameters: + jmax: Maximum Noll coefficient + obscuration: Linear obscuration fraction. + + Returns: + 2D array of coefficients in x and y. + """ + maxn, _ = noll_to_zern(jmax) + shape = (maxn+1, maxn+1, jmax) # (max power of x, max power of y, noll index) + + nca = _noll_coef_array(jmax, obscuration) + + out = np.zeros(shape, dtype=np.float64) + for j in range(1, jmax+1): + out[:,:,j-1] = _xycoef_gradx(_rrsq_to_xy(nca[:,:,j-1], shape=shape[0:2]), shape=shape[0:2]) + return out[:-1, :-1, :] +_noll_coef_array_xy_gradx = LRU_Cache(__noll_coef_array_xy_gradx) + + +def __noll_coef_array_xy_grady(jmax, obscuration): + """Assemble coefficient array for evaluating the y-derivative of Zernike (n, m) as a bivariate + polynomial in x and y. + + Parameters: + jmax: Maximum Noll coefficient + obscuration: Linear obscuration fraction. + + Returns: + 2D array of coefficients in x and y. + """ + maxn = noll_to_zern(jmax)[0] + shape = (maxn+1, maxn+1, jmax) # (max power of x, max power of y, noll index-1) + + nca = _noll_coef_array(jmax, obscuration) + + out = np.zeros(shape, dtype=np.float64) + for j in range(1, jmax+1): + out[:,:,j-1] = _xycoef_grady(_rrsq_to_xy(nca[:,:,j-1], shape=shape[0:2]), shape=shape[0:2]) + return out[:-1, :-1, :] +_noll_coef_array_xy_grady = LRU_Cache(__noll_coef_array_xy_grady) + + +def __noll_coef_array_gradx(j, obscuration): + if j == 1: + return np.zeros((1,1), dtype=np.float64) + n, _ = noll_to_zern(j) + # Gradient of Zernike with radial coefficient n has radial coefficient n-1. + # Next line computes the largest j for which radial coefficient is n-1. + jgrad = n*(n+1)//2 + # Solve for gradient coefficients in terms of non-gradient coefficients. + return np.linalg.lstsq( + _noll_coef_array_xy(jgrad, obscuration).reshape(-1, jgrad), + _noll_coef_array_xy_gradx(j, obscuration).reshape(-1, j), + rcond=-1. + )[0] +_noll_coef_array_gradx = LRU_Cache(__noll_coef_array_gradx) + + +def __noll_coef_array_grady(j, obscuration): + if j == 1: + return np.zeros((1,1), dtype=np.float64) + n, _ = noll_to_zern(j) + # Gradient of Zernike with radial coefficient n has radial coefficient n-1. + # Next line computes the largest j for which radial coefficient is n-1. + jgrad = n*(n+1)//2 + # Solve for gradient coefficients in terms of non-gradient coefficients. + return np.linalg.lstsq( + _noll_coef_array_xy(jgrad, obscuration).reshape(-1, jgrad), + _noll_coef_array_xy_grady(j, obscuration).reshape(-1, j), + rcond=-1. + )[0] +_noll_coef_array_grady = LRU_Cache(__noll_coef_array_grady) + + +# Following 3 functions from +# +# "Zernike annular polynomials for imaging systems with annular pupils" +# Mahajan (1981) JOSA Vol. 71, No. 1. + +# Mahajan's h-function normalization for annular Zernike coefficients. +def __h(m, j, eps): + if m == 0: # Equation (A5) + return (1-eps**2)/(2*(2*j+1)) + else: # Equation (A14) + num = -(2*(2*j+2*m-1)) * _Q(m-1, j+1, eps)[0] + den = (j+m)*(1-eps**2) * _Q(m-1, j, eps)[0] + return num/den * _h(m-1, j, eps) +_h = LRU_Cache(__h) + + +# Mahajan's Q-function for annular Zernikes. +def __Q(m, j, eps): + if m == 0: # Equation (A4) + return _annular_zern_rho_coefs(2*j, 0, eps)[::2] + else: # Equation (A13) + num = 2*(2*j+2*m-1) * _h(m-1, j, eps) + den = (j+m)*(1-eps**2)*_Q(m-1, j, eps)[0] + summation = np.zeros((j+1,), dtype=float) + for i in range(j+1): + qq = _Q(m-1, i, eps) + qq = qq*qq[0] # Don't use *= here since it modifies the cache! + summation[:i+1] += qq/_h(m-1, i, eps) + return summation * num / den +_Q = LRU_Cache(__Q) + + +def __annular_zern_rho_coefs(n, m, eps): + """Compute coefficients of radial part of annular Zernike (n, m), with fractional linear + obscuration eps. + """ + out = np.zeros((n+1,), dtype=float) + m = abs(m) + if m == 0: # Equation (18) + norm = 1./(1-eps**2) + # R[n, m=0, eps](r^2) = R[n, m=0, eps=0]((r^2 - eps^2)/(1 - eps^2)) + # Implement this by retrieving R[n, 0] coefficients of (r^2)^k and + # multiplying in the binomial (in r^2) expansion of ((r^2 - eps^2)/(1 - eps^2))^k + coefs = _zern_rho_coefs(n, 0) + for i, coef in enumerate(coefs): + if i % 2 == 1: continue + j = i // 2 + more_coefs = (norm**j) * binomial(-eps**2, 1, j) + out[0:i+1:2] += coef*more_coefs + elif m == n: # Equation (25) + norm = 1./np.sqrt(np.sum((eps**2)**np.arange(n+1))) + out[n] = norm + else: # Equation (A1) + j = (n-m)//2 + norm = np.sqrt((1-eps**2)/(2*(2*j+m+1) * _h(m,j,eps))) + out[m::2] = norm * _Q(m, j, eps) + return out +_annular_zern_rho_coefs = LRU_Cache(__annular_zern_rho_coefs) + + +
[docs]def describe_zernike(j): + """Create a human-readable string describing the jth (unit circle) Zernike mode as a bivariate + polynomial in x and y. + + Parameters: + j: Zernike mode Noll index + + Returns: + string description of jth mode. + """ + Z = Zernike([0]*j+[1]) + n, m = noll_to_zern(j) + var = (1 if m==0 else 2)*(n+1) + arr = Z._coef_array_xy/np.sqrt(var) + first = True + out = "sqrt({}) * (".format(var) + + for (i, k), val in np.ndenumerate(arr): + if val != 0: + if not first: + out += " + " + first = False + ival = int(np.round(val)) + if ival != 1: + out += str(int(np.round(val))) + if i >= 1: + out += "x" + if i >= 2: + out += "^"+str(i) + if k >= 1: + out += "y" + if k >= 2: + out += "^"+str(k) + out += ")" + # Clean up some special cases + out = out.replace("(-1x", "(-x") + out = out.replace("(-1y", "(-y") + out = out.replace("+ -", "- ") + if out == "sqrt(1) * ()": + out = "sqrt(1) * (1)" + return out
+ + +
[docs]class Zernike: + r"""A class to represent a Zernike polynomial series + (http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials). + + Zernike polynomials form an orthonormal basis over the unit circle. The convention used here is + for the normality constant to equal the area of integration, which is pi for the unit circle. + I.e., + + .. math:: + \int_\mathrm{unit circle} Z_i Z_j dA = \pi \delta_{i, j}. + + Two generalizations of the unit circle Zernike polynomials are also available in this class: + annular Zernike polynomials, and polynomials defined over non-unit-radius circles. + + Annular Zernikes are orthonormal over an annulus instead of a circle (see Mahajan, J. Opt. Soc. + Am. 71, 1, (1981)). Similarly, the non-unit-radius polynomials are orthonormal over a region + with outer radius not equal to 1. Taken together, these generalizations yield the + orthonormality condition: + + .. math:: + \int_\mathrm{annulus} Z_i Z_j dA = + \pi \left(R_\mathrm{outer}^2 - R_\mathrm{inner}^2\right) \delta_{i, j} + + where :math:`0 <= R_\mathrm{inner} < R_\mathrm{outer}` indicate the inner and outer radii of + the annulus over which the polynomials are orthonormal. + + The indexing convention for i and j above is that from Noll, J. Opt. Soc. Am. 66, 207-211(1976). + Note that the Noll indices begin at 1; there is no Z_0. Because of this, the series + coefficients argument ``coef`` effectively begins with ``coef[1]`` (``coef[0]`` is ignored). + This convention is used consistently throughout GalSim, e.g., `OpticalPSF`, `OpticalScreen`, + `zernikeRotMatrix`, and `zernikeBasis`. + + As an example, the first few Zernike polynomials in terms of Cartesian coordinates x and y are + + ========== =========================== + Noll index Polynomial + ========== =========================== + 1 1 + 2 2 x + 3 2 y + 4 sqrt(3) (2 (x^2 + y^2) - 1) + ========== =========================== + + A few mathematical convenience operations are additionally available. Zernikes can be added, + subtracted, or multiplied together, or multiplied by scalars. Note, however, that two + Zernikes can only be combined this way if they have matching ``R_outer`` and ``R_inner`` + attributes. Zernike gradients, Laplacians and the determinant of the Hessian matrix are also + available as properties that return new `Zernike` objects. + + Parameters: + coef: Zernike series coefficients. Note that coef[i] corresponds to Z_i under the + Noll index convention, and coef[0] is ignored. (I.e., coef[1] is 'piston', + coef[4] is 'defocus', ...) + R_outer: Outer radius. [default: 1.0] + R_inner: Inner radius. [default: 0.0] + """ + def __init__(self, coef, R_outer=1.0, R_inner=0.0): + self.coef = np.asarray(coef, dtype=float) + if len(self.coef) <= 1: + self.coef = np.array([0, 0], dtype=float) + self.R_outer = float(R_outer) + self.R_inner = float(R_inner) + + @classmethod + def _from_coef_array_xy(cls, coef_array_xy, R_outer=1.0, R_inner=0.0): + """Construct a Zernike from a 2D array of Cartesian polynomial + coefficients. + """ + ret = Zernike.__new__(Zernike) + ret._coef_array_xy = coef_array_xy + ret.R_outer = R_outer + ret.R_inner = R_inner + return ret + +
[docs] def __add__(self, rhs): + """Add two Zernikes. + + Requires that each operand's ``R_outer`` and ``R_inner`` attributes are the same. + """ + if not isinstance(rhs, Zernike): + raise TypeError("Cannot add Zernike to type {}".format(type(rhs))) + if self.R_outer != rhs.R_outer: + raise ValueError("Cannot add Zernikes with inconsistent R_outer") + if self.R_inner != rhs.R_inner: + raise ValueError("Cannot add Zernikes with inconsistent R_inner") + if 'coef' in self.__dict__: + n = max(len(self.coef), len(rhs.coef)) + newCoef = np.zeros(n, dtype=float) + newCoef[:len(self.coef)] = self.coef + newCoef[:len(rhs.coef)] += rhs.coef + return Zernike(newCoef, R_outer=self.R_outer, R_inner=self.R_inner) + else: + s1 = self._coef_array_xy.shape + s2 = rhs._coef_array_xy.shape + sout = max(s1[0], s2[0]), max(s1[1], s2[1]) + coef_array_xy = np.zeros(sout, dtype=float) + coef_array_xy[:s1[0], :s1[1]] = self._coef_array_xy + coef_array_xy[:s2[0], :s2[1]] += rhs._coef_array_xy + return Zernike._from_coef_array_xy( + coef_array_xy, + R_outer=self.R_outer, + R_inner=self.R_inner + )
+ +
[docs] def __sub__(self, rhs): + """Subtract two Zernikes. + + Requires that each operand's ``R_outer`` and ``R_inner`` attributes are the same. + """ + return self + (-rhs)
+ +
[docs] def __neg__(self): + """Negate a Zernike. + """ + if 'coef' in self.__dict__: + ret = Zernike(-self.coef, R_outer=self.R_outer, R_inner=self.R_inner) + if '_coef_array_xy' in self.__dict__: + ret._coef_array_xy = -self._coef_array_xy + else: + ret = Zernike._from_coef_array_xy( + -self._coef_array_xy, + R_outer=self.R_outer, + R_inner=self.R_inner + ) + return ret
+ +
[docs] def __mul__(self, rhs): + """Multiply two Zernikes, or multiply a Zernike by a scalar. + + If both operands are Zernikes, then the ``R_outer`` and ``R_inner`` attributes of each must + be the same. + """ + if isinstance(rhs, Real): + if 'coef' in self.__dict__: + ret = Zernike(rhs*self.coef, self.R_outer, self.R_inner) + if '_coef_array_xy' in self.__dict__: + ret._coef_array_xy = rhs*self._coef_array_xy + else: + ret = Zernike._from_coef_array_xy( + rhs*self._coef_array_xy, + R_outer=self.R_outer, + R_inner=self.R_inner + ) + return ret + elif isinstance(rhs, Zernike): + if self.R_outer != rhs.R_outer: + raise ValueError("Cannot multiply Zernikes with inconsistent R_outer") + if self.R_inner != rhs.R_inner: + raise ValueError("Cannot multiply Zernikes with inconsistent R_inner") + n1, _ = noll_to_zern(len(self.coef)-1) + n2, _ = noll_to_zern(len(rhs.coef)-1) + nTarget = n1+n2 # Maximum possible radial degree is sum of input radial degrees + jTarget = (nTarget+1)*(nTarget+2)//2 # Largest Noll index with above radial degree + + # To multiply, we first convolve in 2D the xy coefficients of each polynomial + sxy = self._coef_array_xy + rxy = rhs._coef_array_xy + shape = (sxy.shape[0]+rxy.shape[0]-1, + sxy.shape[1]+rxy.shape[1]-1) + newXY = np.zeros(shape, dtype=float) + if sxy.shape[0]*sxy.shape[1] > rxy.shape[0]*rxy.shape[1]: + sxy, rxy = rxy, sxy + for (i, j), c in np.ndenumerate(sxy): + newXY[i:i+rxy.shape[0], j:j+rxy.shape[1]] += c*rxy + + return Zernike._from_coef_array_xy( + newXY, + R_outer=self.R_outer, + R_inner=self.R_inner + ) + else: + raise TypeError("Cannot multiply Zernike by type {}".format(type(rhs)))
+ +
[docs] def __rmul__(self, rhs): + """Equivalent to obj * rhs. See `__mul__` for details.""" + return self*rhs
+ + def __truediv__(self, rhs): + if not isinstance(rhs, Real): + raise TypeError("Cannot multiply Zernike by type {}".format(type(rhs))) + return self*(1./rhs) + + @lazy_property + def coef(self): + """Zernike series coefficients. + + Note that coef[i] corresponds to Z_i under the Noll index convention, and coef[0] is + ignored. (I.e., coef[1] is 'piston', coef[4] is 'defocus', ...). + """ + + # The strategy is to use the orthonormality of the Zernike polynomials and + # integrate over the annulus. In particular, + # int_annulus xy(x,y) Zernike_j(x,y) dx dy = area_annulus a_j + # defines coefficients a_j in the expansion + # xy(x, y) = \sum_j a_j Zernike_j(x,y). + + # We can use Gaussian Quadrature over an annulus to do the integration. + + # First determine the number of quadrature points needed to integrate up to + # the maximum possible radial degree. + xy = self._coef_array_xy + nTarget = max(*xy.shape)-1 # Maximum radial degree + jTarget = (nTarget+1)*(nTarget+2)//2 # Largest Noll index with above radial degree + + nRings = nTarget//2+1 + nSpokes = 2*nTarget+1 + x, y, weights = gq_annulus_points(self.R_outer, self.R_inner, nRings, nSpokes) + val = horner2d(x, y, xy, dtype=float) + basis = zernikeBasis( + jTarget, x, y, R_outer=self.R_outer, R_inner=self.R_inner + ) + area = np.pi*(self.R_outer**2 - self.R_inner**2) + return np.dot(basis, val*weights/area) + + @lazy_property + def hessian(self): + """The determinant of the Hessian matrix of this Zernike polynomial expressed as a new + Zernike polynomial. The Hessian matrix is the matrix of second derivatives, to the + determinant is d^2Z/dx^2 * d^Z/dy^2 - (d^Z/dxdy)^2, and is an expression of the local + curvature of the Zernike polynomial. + """ + dxx = self.gradX.gradX + dxy = self.gradX.gradY + dyy = self.gradY.gradY + return dxx*dyy - dxy*dxy + + @lazy_property + def laplacian(self): + """The Laplacian of this Zernike polynomial expressed as a new Zernike polynomial. The + Laplacian is d^2Z/dx^2 + d^2Z/dy^2 (the trace of the Hessian matrix), and is an expression + of the local divergence of the Zernike polynomial. + """ + return self.gradX.gradX + self.gradY.gradY + + @lazy_property + def _coef_array_xy(self): + arr = _noll_coef_array_xy(len(self.coef)-1, self.R_inner/self.R_outer).dot(self.coef[1:]) + + if self.R_outer != 1.0: + n = arr.shape[0] + norm = np.power(1./self.R_outer, np.arange(1,n)) + arr[0,1:] *= norm + for i in range(1,n): + arr[i,0:-i] *= norm[i-1:] + return arr + + @lazy_property + def gradX(self): + """The x-derivative of this Zernike as a new Zernike object. + """ + j = len(self.coef)-1 + ncagx = _noll_coef_array_gradx(j, self.R_inner/self.R_outer).dot(self.coef[1:]) + newCoef = np.empty((len(ncagx)+1,), dtype=float) + newCoef[0] = 0.0 + newCoef[1:] = ncagx + # Handle R_outer with + # df/dx = df/d(x/R) * d(x/R)/dx = df/d(x/R) * 1/R + newCoef /= self.R_outer + return Zernike(newCoef, R_outer=self.R_outer, R_inner=self.R_inner) + + @lazy_property + def gradY(self): + """The y-derivative of this Zernike as a new Zernike object. + """ + j = len(self.coef)-1 + ncagy = _noll_coef_array_grady(j, self.R_inner/self.R_outer).dot(self.coef[1:]) + newCoef = np.empty((len(ncagy)+1,), dtype=float) + newCoef[0] = 0.0 + newCoef[1:] = ncagy + # Handle R_outer with + # df/dy = df/d(y/R) * d(y/R)/dy = df/d(y/R) * 1/R + newCoef /= self.R_outer + return Zernike(newCoef, R_outer=self.R_outer, R_inner=self.R_inner) + +
[docs] def __call__(self, x, y): + """Evaluate this Zernike polynomial series at Cartesian coordinates x and y. + Synonym for `evalCartesian`. + + Parameters: + x: x-coordinate of evaluation points. Can be list-like. + y: y-coordinate of evaluation points. Can be list-like. + Returns: + Series evaluations as numpy array. + """ + return self.evalCartesian(x, y)
+ +
[docs] def evalCartesian(self, x, y): + """Evaluate this Zernike polynomial series at Cartesian coordinates x and y. + + Parameters: + x: x-coordinate of evaluation points. Can be list-like. + y: y-coordinate of evaluation points. Can be list-like. + + Returns: + Series evaluations as numpy array. + """ + return horner2d(x, y, self._coef_array_xy, dtype=float)
+ +
[docs] def evalPolar(self, rho, theta): + """Evaluate this Zernike polynomial series at polar coordinates rho and theta. + + Parameters: + rho: radial coordinate of evaluation points. Can be list-like. + theta: azimuthal coordinate in radians (or as `Angle` object) of evaluation points. + Can be list-like. + + Returns: + Series evaluations as numpy array. + """ + x = rho * np.cos(theta) + y = rho * np.sin(theta) + return self.evalCartesian(x, y)
+ +
[docs] def evalCartesianGrad(self, x, y): + """Evaluate the gradient of this Zernike polynomial series at cartesian coordinates + x and y. + + Parameters: + x: x-coordinate of evaluation points. Can be list-like. + y: y-coordinate of evaluation points. Can be list-like. + Returns: + Tuple of arrays for x-gradient and y-gradient. + """ + return self.gradX.evalCartesian(x, y), self.gradY.evalCartesian(x, y)
+ +
[docs] def rotate(self, theta): + """Return new Zernike polynomial series rotated by angle theta. + + For example:: + + >>> Z = Zernike(coefs) + >>> Zrot = Z.rotate(theta) + >>> Z.evalPolar(r, th) == Zrot.evalPolar(r, th + theta) + + Parameters: + theta: angle in radians. + + Returns: + A new Zernike object. + """ + M = zernikeRotMatrix(len(self.coef)-1, theta) + return Zernike(M.dot(self.coef), self.R_outer, self.R_inner)
+ + def __eq__(self, other): + return (self is other or + (isinstance(other, Zernike) and + np.array_equal(self.coef, other.coef) and + self.R_outer == other.R_outer and + self.R_inner == other.R_inner)) + + def __hash__(self): + return hash(("galsim.Zernike", tuple(self.coef), self.R_outer, self.R_inner)) + + def __repr__(self): + out = "galsim.zernike.Zernike(" + out += repr(self.coef) + if self.R_outer != 1.0: + out += ", R_outer={!r}".format(self.R_outer) + if self.R_inner != 0.0: + out += ", R_inner={!r}".format(self.R_inner) + out += ")" + return out
+ + +
[docs]def zernikeRotMatrix(jmax, theta): + """Construct Zernike basis rotation matrix. This matrix can be used to convert a set of Zernike + polynomial series coefficients expressed in one coordinate system to an equivalent set of + coefficients expressed in a rotated coordinate system. + + For example:: + + >>> Z = Zernike(coefs) + >>> R = zernikeRotMatrix(jmax, theta) + >>> rotCoefs = R.dot(coefs) + >>> Zrot = Zernike(rotCoefs) + >>> Z.evalPolar(r, th) == Zrot.evalPolar(r, th + theta) + + Note that not all values of ``jmax`` are allowed. For example, jmax=2 raises an Exception, + since a non-zero Z_2 coefficient will in general rotate into a combination of Z_2 and Z_3 + coefficients, and therefore the needed rotation matrix requires jmax=3. If you run into this + kind of Exception, raising jmax by 1 will eliminate it. + + Also note that the returned matrix is intended to multiply a vector of Zernike coefficients + ``coef`` indexed following the Noll (1976) convention, which starts at 1. Since python + sequences start at 0, we adopt the convention that ``coef[0]`` is unused, and ``coef[i]`` + corresponds to the coefficient multiplying Z_i. As a result, the size of the returned matrix + is [jmax+1, jmax+1]. + + Parameters: + jmax: Maximum Zernike index (in the Noll convention) over which to construct matrix. + theta: angle of rotation in radians. + + Returns: + Matrix of size [jmax+1, jmax+1]. + """ + # Use formula from Tatulli (2013) arXiv:1302.7106v1 + + # Note that coefficients mix if and only if they have the same radial index n and the same + # absolute azimuthal index m. This means that to construct a rotation matrix, we need for both + # m's in a pair {(n, m), (n, -m)} to be represented, which places constraints on size. + # Specifically, if the final Zernike indicated by size includes only one part of the pair, then + # the rotation would mix coefficients into the element (size+1). We simply disallow this here. + + n_jmax, m_jmax = noll_to_zern(jmax) + # If m_jmax is zero, then we don't need to check the next element to ensure we have a complete + # rotation matrix. + if m_jmax != 0: + n_jmaxp1, m_jmaxp1 = noll_to_zern(jmax+1) + if n_jmax == n_jmaxp1 and abs(m_jmaxp1) == abs(m_jmax): + raise GalSimValueError("Cannot construct Zernike rotation matrix for this jmax.", jmax) + + R = np.zeros((jmax+1, jmax+1), dtype=np.float64) + R[0, 0] = 1.0 + for i in range(jmax): + ni, mi = noll_to_zern(i+1) + for j in range(max(0, i-1), min(i+2, jmax)): + nj, mj = noll_to_zern(j+1) + if ni != nj: + continue + if abs(mi) != abs(mj): + continue + if mi == mj: + R[i+1, j+1] = np.cos(mj * theta) + else: + R[i+1, j+1] = np.sin(mj * theta) + return R
+ + +
[docs]def zernikeBasis(jmax, x, y, R_outer=1.0, R_inner=0.0): + """Construct basis of Zernike polynomial series up to Noll index ``jmax``, evaluated at a + specific set of points ``x`` and ``y``. + + Useful for fitting Zernike polynomials to data, e.g.:: + + >>> x, y, z = myDataToFit() + >>> basis = zernikeBasis(11, x, y) + >>> coefs, _, _, _ = np.linalg.lstsq(basis.T, z) + >>> resids = Zernike(coefs).evalCartesian(x, y) - z + + or equivalently:: + + >>> resids = basis.T.dot(coefs).T - z + + Note that since we follow the Noll indexing scheme for Zernike polynomials, which begins at 1, + but python sequences are indexed from 0, the length of the leading dimension in the result is + ``jmax+1`` instead of ``jmax``. We somewhat arbitrarily fill the 0th slice along the first + dimension with 0s (result[0, ...] == 0) so that it doesn't impact the results of + ``np.linalg.lstsq`` as in the example above. + + Parameters: + jmax: Maximum Noll index to use. + x: x-coordinates (can be list-like, congruent to y) + y: y-coordinates (can be list-like, congruent to x) + R_outer: Outer radius. [default: 1.0] + R_inner: Inner radius. [default: 0.0] + + Returns: + [jmax+1, x.shape] array. Slicing over first index gives basis vectors corresponding + to individual Zernike polynomials. + """ + R_outer = float(R_outer) + R_inner = float(R_inner) + eps = R_inner / R_outer + + noll_coef = _noll_coef_array_xy(jmax, eps) + out = np.zeros(tuple((jmax+1,)+x.shape), dtype=float) + out[1:] = np.array([horner2d(x/R_outer, y/R_outer, nc, dtype=float) + for nc in noll_coef.transpose(2,0,1)]) + return out
+ + +
[docs]def zernikeGradBases(jmax, x, y, R_outer=1.0, R_inner=0.0): + """Construct bases of Zernike polynomial series gradients up to Noll index ``jmax``, evaluated + at a specific set of points ``x`` and ``y``. + + Note that since we follow the Noll indexing scheme for Zernike polynomials, which begins at 1, + but python sequences are indexed from 0, the length of the leading dimension in the result is + ``jmax+1`` instead of ``jmax``. We somewhat arbitrarily fill the 0th slice along the first + dimension with 0s (result[0, ...] == 0). + + Parameters: + jmax: Maximum Noll index to use. + x: x-coordinates (can be list-like, congruent to y) + y: y-coordinates (can be list-like, congruent to x) + R_outer: Outer radius. [default: 1.0] + R_inner: Inner radius. [default: 0.0] + + Returns: + [2, jmax+1, x.shape] array. The first index selects the gradient in the x/y direction, + slicing over the second index gives basis vectors corresponding to individual Zernike + polynomials. + """ + + R_outer = float(R_outer) + R_inner = float(R_inner) + eps = R_inner / R_outer + + noll_coef_x = _noll_coef_array_xy_gradx(jmax, eps) + dzkdx = np.zeros(tuple((jmax + 1,) + x.shape), dtype=float) + dzkdx[1:] = np.array([ + horner2d(x/R_outer, y/R_outer, nc, dtype=float)/R_outer + for nc in noll_coef_x.transpose(2, 0, 1) + ]) + + noll_coef_y = _noll_coef_array_xy_grady(jmax, eps) + dzkdy = np.zeros(tuple((jmax + 1,) + x.shape), dtype=float) + dzkdy[1:] = np.array([ + horner2d(x/R_outer, y/R_outer, nc, dtype=float)/R_outer + for nc in noll_coef_y.transpose(2, 0, 1) + ]) + return np.array([dzkdx, dzkdy])
+ + +
[docs]class DoubleZernike: + r"""The Cartesian product of two (annular) Zernike polynomial series. Each + double Zernike term is the product of two single Zernike polynomials over + separate annuli: + + .. math:: + DZ_{k,j}(u, v, x, y) = Z_k(u, v) Z_j(x, y) + + The double Zernike's therefore satisfy the orthonormality condition: + + .. math:: + \int_{annuli} DZ_{k,j} DZ_{k',j'} = A_1 A_2 \delta_{k, k'} \delta_{j, j'} + + where :math:`A_1` and :math:`A_2` are the areas of the two annuli. + + Double Zernikes series are useful for representing the field and pupil + dependent wavefronts of a telescope. We adopt the typical convention that + the first index (the ``k`` index) corresponds to the field-dependence, while + the second index (the ``j`` index) corresponds to the pupil-dependence. + + Parameters: + coef: [kmax, jmax] array of double Zernike coefficients. Like the + single Zernike class, the 0th index of either axis is + ignored. + uv_outer: Outer radius of the first annulus. [default: 1.0] + uv_inner: Inner radius of the first annulus. [default: 0.0] + xy_outer: Outer radius of the second annulus. [default: 1.0] + xy_inner: Inner radius of the second annulus. [default: 0.0] + """ + def __init__( + self, + coef, + *, + uv_outer=1.0, # "field" + uv_inner=0.0, + xy_outer=1.0, # "pupil" + xy_inner=0.0 + ): + self.coef = np.asarray(coef, dtype=float) + self.uv_outer = uv_outer + self.uv_inner = uv_inner + self.xy_outer = xy_outer + self.xy_inner = xy_inner + self._xy_series = [ + Zernike(col, R_outer=uv_outer, R_inner=uv_inner) for col in coef.T + ] + + @lazy_property + def _kmax(self): + """Maximum Noll index of the field dependence.""" + if 'coef' in self.__dict__: + return self.coef.shape[0] - 1 + else: + sh = self._coef_array_uvxy.shape + nuv = max(sh[0], sh[1]) - 1 # Max radial degree of uv + return (nuv+1)*(nuv+2)//2 + + @lazy_property + def _jmax(self): + """Maximum Noll index of the pupil dependence.""" + if 'coef' in self.__dict__: + return self.coef.shape[1] - 1 + else: + sh = self._coef_array_uvxy.shape + nxy = max(sh[2], sh[3]) - 1 # Max radial degree of xy + return (nxy+1)*(nxy+2)//2 + + @lazy_property + def _nuv(self): + """Maximum radial degree of the field dependence.""" + return noll_to_zern(self._kmax)[0] + + @lazy_property + def _nxy(self): + """Maximum radial degree of the pupil dependence.""" + return noll_to_zern(self._jmax)[0] + + @classmethod + def _from_uvxy( + cls, + uvxy, + *, + uv_outer=1.0, # "field" + uv_inner=0.0, + xy_outer=1.0, # "pupil" + xy_inner=0.0 + ): + """Construct a DoubleZernike from a 4D array of Cartesian polynomial + coefficients. + """ + ret = DoubleZernike.__new__(DoubleZernike) + ret._coef_array_uvxy = np.asarray(uvxy, dtype=float) + ret.uv_outer = uv_outer + ret.uv_inner = uv_inner + ret.xy_outer = xy_outer + ret.xy_inner = xy_inner + return ret + + def _call_old(self, u, v, x=None, y=None): + # Original implementation constructing "single" Zernike from + # coefficients directly. Retained mostly for testing purposes. + assert np.ndim(u) == np.ndim(v) + assert np.shape(u) == np.shape(v) + if x is None: # uv only + if np.ndim(u) == 0: # uv scalar + return Zernike( + [z(u, v) for z in self._xy_series], + R_outer=self.xy_outer, + R_inner=self.xy_inner + ) + else: # uv vector + return [ + Zernike( + [z(u[i], v[i]) for z in self._xy_series], + R_outer=self.xy_outer, + R_inner=self.xy_inner + ) for i in range(len(u)) + ] + else: # uv and xy + assert np.ndim(x) == np.ndim(y) + assert np.shape(x) == np.shape(y) + if np.ndim(u) == 0: # uv scalar + return self._call_old(u, v)(x, y) # defer to Zernike.__call__ + else: # uv vector + # Note that we _don't_ defer to Zernike.__call__, as doing so + # would yield the outer product of uv and xy, which is not what + # we want. + zs = self._call_old(u, v) + if np.ndim(x) == 0: # xy scalar + return np.array([z(x, y) for z in zs]) + else: # xy vector + assert np.shape(x) == np.shape(u) + return np.array([z(x[i], y[i]) for i, z in enumerate(zs)]) + + def _compute_coef(self, kmax, jmax): + # Same strategy as Zernike; take advantage of orthonormality to + # determine Noll coefficients from Cartesian coefficients. + + # Determine number of GQ points + uv_rings = self._nuv//2+1 + uv_spokes = 2*self._nuv+1 + xy_rings = self._nxy//2+1 + xy_spokes = 2*self._nxy+1 + + # Compute GQ points and weights on double annulus + u, v, uv_w = gq_annulus_points(self.uv_outer, self.uv_inner, uv_rings, uv_spokes) + x, y, xy_w = gq_annulus_points(self.xy_outer, self.xy_inner, xy_rings, xy_spokes) + nu = len(u) + nx = len(x) + u = np.repeat(u, nx) + v = np.repeat(v, nx) + uv_w = np.repeat(uv_w, nx) + x = np.tile(x, nu) + y = np.tile(y, nu) + xy_w = np.tile(xy_w, nu) + weights = uv_w * xy_w + + # Evaluate uvxy polynomial at GQ points + vals = horner4d(u, v, x, y, self._coef_array_uvxy) + + # Project into Zernike basis + basis = doubleZernikeBasis( + kmax, jmax, + u, v, x, y, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + area = np.pi**2 * (self.uv_outer**2 - self.uv_inner**2) * (self.xy_outer**2 - self.xy_inner**2) + return np.dot(basis, vals*weights/area) + + + @lazy_property + def coef(self): + """DoubleZernike coefficients. + + The coefficients are stored in a 2D array, where the first index + corresponds to the ``uv`` dependence and the second index corresponds to + the ``xy`` dependence. The indices are Noll indices. As an example, when + describing a telescope wavefront the (1, 4) tuple corresponds to a + constant (over the field) pupil defocus. The (2, 5) term is a linear + (over the field) astigmatism, etc. + """ + return self._compute_coef(self._kmax, self._jmax) + + @lazy_property + def _coef_array_uvxy(self): + uv_shape = self._xy_series[0]._coef_array_xy.shape + xy_shape = Zernike([0]*self._jmax+[1])._coef_array_xy.shape + out_shape = uv_shape + xy_shape + out = np.zeros(out_shape, dtype=float) + # Now accumulate one uv term at a time + for j, zk in enumerate(self._xy_series): + coef_array_uv = zk._coef_array_xy + coef_array_xy = Zernike( + [0]*j+[1], + R_outer=self.xy_outer, + R_inner=self.xy_inner, + )._coef_array_xy + term = np.multiply.outer(coef_array_uv, coef_array_xy) + sh = term.shape + out[:sh[0], :sh[1], :sh[2], :sh[3]] += term + return out + + def __call__(self, u, v, x=None, y=None): + """Evaluate this DoubleZernike polynomial series at Cartesian + coordinates u, v, x, y. + + Parameters: + u, v: float or array-like. Coordinates on first annulus. + x, y: float or array-like. Coordinates on second annulus. + + Returns: + float or array-like. Value of the DoubleZernike polynomial series. + """ + # Cases: + # uv only: + # if uv scalar, then return Zernike + # if uv vector, then return list of Zernike + # uv and xy: + # if uv scalar: + # if xy scalar, then return scalar + # if xy vector, then return vector + # if uv vector: + # if xy scalar, then return vector + # if xy vector, then return vector, uv and xy must be congruent + assert np.ndim(u) == np.ndim(v) + assert np.shape(u) == np.shape(v) + if (x is None) != (y is None): + raise GalSimIncompatibleValuesError( + "Must provide both x and y, or neither.", + x=x, y=y + ) + if x is None: + if np.ndim(u) == 0: + a_ij = np.zeros(self._coef_array_uvxy.shape[2:4]) + for i, j in np.ndindex(a_ij.shape): + a_ij[i, j] = horner2d( + u, v, self._coef_array_uvxy[..., i, j], dtype=float + ) + return Zernike._from_coef_array_xy( + a_ij, + R_outer=self.xy_outer, + R_inner=self.xy_inner + ) + else: + return [ + self.__call__(u_, v_) for u_, v_ in zip(u, v) + ] + else: + assert np.ndim(x) == np.ndim(y) + assert np.shape(x) == np.shape(y) + if np.ndim(u) == 0: # uv scalar + return self.__call__(u, v)(x, y) + else: # uv vector + if np.ndim(x) == 0: # xy scalar + return np.array([z(x, y) for z in self.__call__(u, v)]) + else: # xy vector + assert np.shape(x) == np.shape(u) + return horner4d(u, v, x, y, self._coef_array_uvxy) + + def __neg__(self): + """Negate a DoubleZernike.""" + if 'coef' in self.__dict__: + ret = DoubleZernike( + -self.coef, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + if '_coef_array_uvxy' in self.__dict__: + ret._coef_array_uvxy = -self._coef_array_uvxy + else: + ret = DoubleZernike._from_uvxy( + -self._coef_array_uvxy, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + return ret + + def __add__(self, rhs): + """Add two DoubleZernikes. + + Requires that each operand's xy and uv domains are the same. + """ + if not isinstance(rhs, DoubleZernike): + raise TypeError("Cannot add DoubleZernike to type {}".format(type(rhs))) + if self.uv_outer != rhs.uv_outer: + raise ValueError("Cannot add DoubleZernikes with inconsistent uv_outer") + if self.uv_inner != rhs.uv_inner: + raise ValueError("Cannot add DoubleZernikes with inconsistent uv_inner") + if self.xy_outer != rhs.xy_outer: + raise ValueError("Cannot add DoubleZernikes with inconsistent xy_outer") + if self.xy_inner != rhs.xy_inner: + raise ValueError("Cannot add DoubleZernikes with inconsistent xy_inner") + if 'coef' in self.__dict__: + kmax = max(self._kmax, rhs._kmax) + jmax = max(self._jmax, rhs._jmax) + newCoef = np.zeros((kmax+1, jmax+1), dtype=float) + newCoef[:self._kmax+1, :self._jmax+1] = self.coef + newCoef[:rhs._kmax+1, :rhs._jmax+1] += rhs.coef + return DoubleZernike( + newCoef, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + else: + s1 = self._coef_array_uvxy.shape + s2 = rhs._coef_array_uvxy.shape + sh = [max(s1[i], s2[i]) for i in range(4)] + coef_array_uvxy = np.zeros(sh, dtype=float) + coef_array_uvxy[:s1[0], :s1[1], :s1[2], :s1[3]] = self._coef_array_uvxy + coef_array_uvxy[:s2[0], :s2[1], :s2[2], :s2[3]] += rhs._coef_array_uvxy + return DoubleZernike._from_uvxy( + coef_array_uvxy, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + + def __sub__(self, rhs): + """Subtract two DoubleZernikes. + + Requires that each operand's xy and uv domains are the same. + """ + return self + (-rhs) + + def __mul__(self, rhs): + """Multiply two DoubleZernikes, or multiply a DoubleZernike by a scalar. + + If both operands are DoubleZernikes, then the domains for both annuli + must be the same. + """ + if isinstance(rhs, Real): + if 'coef' in self.__dict__: + ret = DoubleZernike( + rhs*self.coef, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + if '_coef_array_uvxy' in self.__dict__: + ret._coef_array_uvxy = rhs*self._coef_array_uvxy + else: + ret = DoubleZernike._from_uvxy( + rhs*self._coef_array_uvxy, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + return ret + elif isinstance(rhs, DoubleZernike): + if self.uv_outer != rhs.uv_outer: + raise ValueError("Cannot multiply DoubleZernikes with inconsistent uv_outer") + if self.uv_inner != rhs.uv_inner: + raise ValueError("Cannot multiply DoubleZernikes with inconsistent uv_inner") + if self.xy_outer != rhs.xy_outer: + raise ValueError("Cannot multiply DoubleZernikes with inconsistent xy_outer") + if self.xy_inner != rhs.xy_inner: + raise ValueError("Cannot multiply DoubleZernikes with inconsistent xy_inner") + # Multiplication is a 4d convolution of the Cartesian coefficients. + # Easiest to get it right just by hand... + uvxy1 = self._coef_array_uvxy + uvxy2 = rhs._coef_array_uvxy + sh1 = uvxy1.shape + sh2 = uvxy2.shape + outshape = tuple([d0+d1-1 for d0, d1 in zip(sh1, sh2)]) + uvxy = np.zeros(outshape) + for (i, j, k, l), c in np.ndenumerate(uvxy1): + uvxy[i:i+sh2[0], j:j+sh2[1], k:k+sh2[2], l:l+sh2[3]] += c*uvxy2 + return DoubleZernike._from_uvxy( + uvxy, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + else: + raise TypeError("Cannot multiply DoubleZernike by type {}".format(type(rhs))) + + def __rmul__(self, rhs): + """Equivalent to obj * rhs. See `__mul__` for details.""" + return self*rhs + + def __truediv__(self, rhs): + if not isinstance(rhs, Real): + raise TypeError("Cannot multiply Zernike by type {}".format(type(rhs))) + return self*(1./rhs) + + def __eq__(self, rhs): + if not isinstance(rhs, DoubleZernike): + return False + return ( + np.array_equal(self.coef, rhs.coef) and + self.uv_outer == rhs.uv_outer and + self.uv_inner == rhs.uv_inner and + self.xy_outer == rhs.xy_outer and + self.xy_inner == rhs.xy_inner + ) + + def __hash__(self): + return hash(( + "galsim.DoubleZernike", + tuple(self.coef.ravel()), + self.coef.shape, + self.uv_outer, + self.uv_inner, + self.xy_outer, + self.xy_inner + )) + + def __repr__(self): + out = "galsim.zernike.DoubleZernike(" + out += repr(self.coef) + if self.uv_outer != 1.0: + out += ", uv_outer={}".format(self.uv_outer) + if self.uv_inner != 0.0: + out += ", uv_inner={}".format(self.uv_inner) + if self.xy_outer != 1.0: + out += ", xy_outer={}".format(self.xy_outer) + if self.xy_inner != 0.0: + out += ", xy_inner={}".format(self.xy_inner) + out += ")" + return out + + @lazy_property + def gradX(self): + """The gradient of the DoubleZernike in the x direction.""" + uvxy = self._coef_array_uvxy + gradx = np.zeros_like(uvxy) + for (i, j, k, l), c in np.ndenumerate(uvxy): + if k > 0: + if c != 0: + gradx[i, j, k-1, l] = c*k + return DoubleZernike._from_uvxy( + gradx, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + ) + + @lazy_property + def gradY(self): + """The gradient of the DoubleZernike in the y direction.""" + uvxy = self._coef_array_uvxy + grady = np.zeros_like(uvxy) + for (i, j, k, l), c in np.ndenumerate(uvxy): + if l > 0: + if c != 0: + grady[i, j, k, l-1] = c*l + return DoubleZernike._from_uvxy( + grady, + xy_outer=self.xy_outer, xy_inner=self.xy_inner, + uv_outer=self.uv_outer, uv_inner=self.uv_inner + ) + + @lazy_property + def gradU(self): + """The gradient of the DoubleZernike in the u direction.""" + uvxy = self._coef_array_uvxy + gradu = np.zeros_like(uvxy) + for (i, j, k, l), c in np.ndenumerate(uvxy): + if i > 0: + if c != 0: + gradu[i-1, j, k, l] = c*i + return DoubleZernike._from_uvxy( + gradu, + xy_outer=self.xy_outer, xy_inner=self.xy_inner, + uv_outer=self.uv_outer, uv_inner=self.uv_inner + ) + + @lazy_property + def gradV(self): + """The gradient of the DoubleZernike in the v direction.""" + uvxy = self._coef_array_uvxy + gradv = np.zeros_like(uvxy) + for (i, j, k, l), c in np.ndenumerate(uvxy): + if j > 0: + if c != 0: + gradv[i, j-1, k, l] = c*j + return DoubleZernike._from_uvxy( + gradv, + xy_outer=self.xy_outer, xy_inner=self.xy_inner, + uv_outer=self.uv_outer, uv_inner=self.uv_inner + ) + + @lazy_property + def mean_xy(self): + """Mean value over the xy annulus, returned as a Zernike in the uv + domain.""" + + # For any Zernike series, the expectation value is just the coefficient + # of the Z1 term. All the other terms have zero expectation. For the + # double Zernike series, the uv dependence of the xy expectation + # value is contained in the first column of the coefficient array. + + if 'coef' in self.__dict__: + c = self.coef[:, 1] + else: + c = self._compute_coef(self._kmax, 1)[:, 1] + return Zernike(c, R_outer=self.uv_outer, R_inner=self.uv_inner) + + @lazy_property + def mean_uv(self): + """Mean value over the uv annulus, returned as a Zernike in the xy + domain.""" + + # See comment in mean_xy. + + if 'coef' in self.__dict__: + c = self.coef[1] + else: + c = self._compute_coef(1, self._jmax)[1] + return Zernike(c, R_outer=self.xy_outer, R_inner=self.xy_inner) + + def rotate(self, *, theta_uv=0.0, theta_xy=0.0): + """Rotate the DoubleZernike by the given angle(s). + + For example: + + >>> DZrot = DZ.rotate(theta_xy=th) + >>> DZ(u, v, r*np.cos(ph), r*np.sin(ph)) == DZrot(u, v, r*np.cos(ph+th), r*np.sin(ph+th)) + + or: + + >>> DZrot = DZ.rotate(theta_uv=th) + >>> DZ(r*np.cos(ph), r*np.sin(ph), x, y) == DZrot(r*np.cos(ph+th), r*np.sin(ph+th), x, y) + + Parameters: + theta_uv: Rotation angle (in radians) in the uv plane. + theta_xy: Rotation angle (in radians) in the xy plane. + + Returns: + The rotated DoubleZernike. + """ + M_uv = zernikeRotMatrix(self._kmax, theta_uv) + M_xy = zernikeRotMatrix(self._jmax, theta_xy) + coef = M_uv @ self.coef @ M_xy.T + return DoubleZernike( + coef, + uv_outer=self.uv_outer, uv_inner=self.uv_inner, + xy_outer=self.xy_outer, xy_inner=self.xy_inner + )
+ + +
[docs]def doubleZernikeBasis( + kmax, jmax, u, v, x, y, *, uv_outer=1.0, uv_inner=0.0, xy_outer=1.0, xy_inner=0.0 +): + """Construct basis of DoubleZernike polynomial series up to Noll indices + (kmax, jmax), evaluated at (u, v, x, y). + + Parameters: + kmax: Maximum Noll index for first annulus. + jmax: Maximum Noll index for second annulus. + u, v: Coordinates in the first annulus. + x, y: Coordinates in the second annulus. + uv_outer: Outer radius of the first annulus. + uv_inner: Inner radius of the first annulus. + xy_outer: Outer radius of the second annulus. + xy_inner: Inner radius of the second annulus. + + Returns: + [kmax+1, jmax+1, u.shape] array. Slicing over the first two dimensions + gives the basis vectors corresponding to individual DoubleZernike terms. + + """ + return np.einsum( + 'k...,j...->kj...', + zernikeBasis(kmax, u, v, R_outer=uv_outer, R_inner=uv_inner), + zernikeBasis(jmax, x, y, R_outer=xy_outer, R_inner=xy_inner) + )
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_modules/index.html b/docs/_build/html/_modules/index.html new file mode 100644 index 00000000000..bfce97f017d --- /dev/null +++ b/docs/_build/html/_modules/index.html @@ -0,0 +1,216 @@ + + + + + + Overview: module code — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ +

All modules for which code is available

+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/_sources/arbitrary.rst.txt b/docs/_build/html/_sources/arbitrary.rst.txt new file mode 100644 index 00000000000..0536926d38c --- /dev/null +++ b/docs/_build/html/_sources/arbitrary.rst.txt @@ -0,0 +1,35 @@ +Arbitrary Profiles +================== + +If none of the above classes seem appropriate, it is possible to define any arbitrary +profile by an image and then interpolating between the pixel positions to define the +surface brightness at any arbitrary location. Similarly, one can define the image in +Fourier space, or use shapelets, which are a different complete basis set, instead of +using pixels. + +Interpolated Images +------------------- + +.. autoclass:: galsim.InterpolatedImage + :members: + :show-inheritance: + +.. autofunction:: galsim._InterpolatedImage + +Interpolated Fourier-space Images +--------------------------------- + +.. autoclass:: galsim.InterpolatedKImage + :members: + :show-inheritance: + +.. autofunction:: galsim._InterpolatedKImage + +Shapelet Decomposition +---------------------- + +.. autoclass:: galsim.Shapelet + :members: + :show-inheritance: + + diff --git a/docs/_build/html/_sources/bandpass.rst.txt b/docs/_build/html/_sources/bandpass.rst.txt new file mode 100644 index 00000000000..d466e19caab --- /dev/null +++ b/docs/_build/html/_sources/bandpass.rst.txt @@ -0,0 +1,7 @@ +Bandpass Filters +================ + +.. autoclass:: galsim.Bandpass + :members: + + .. automethod:: galsim.Bandpass.__call__ diff --git a/docs/_build/html/_sources/bessel.rst.txt b/docs/_build/html/_sources/bessel.rst.txt new file mode 100644 index 00000000000..9dad01e90f5 --- /dev/null +++ b/docs/_build/html/_sources/bessel.rst.txt @@ -0,0 +1,27 @@ +Bessel Functions +================ + +These are probably not super useful for most users. They should all be equivalent to +the scipy bessel functions. However, in C++ we wanted to avoid dependencies that would +have given us these Bessel functions, so we implemented our own. The Python interface +is mostly to enable unit tests that these C++ function are correct. + +.. autofunction:: galsim.bessel.j0 +.. autofunction:: galsim.bessel.j1 +.. autofunction:: galsim.bessel.jv +.. autofunction:: galsim.bessel.jn +.. autofunction:: galsim.bessel.kv +.. autofunction:: galsim.bessel.kn +.. autofunction:: galsim.bessel.yv +.. autofunction:: galsim.bessel.yn +.. autofunction:: galsim.bessel.iv +.. autofunction:: galsim.bessel.j0_root +.. autofunction:: galsim.bessel.jv_root + +The next few are not really related to Bessel functions, but they are also exposed +from the C++ layer. + +.. autofunction:: galsim.bessel.si +.. autofunction:: galsim.bessel.ci +.. autofunction:: galsim.bessel.sinc +.. autofunction:: galsim.bessel.gammainc diff --git a/docs/_build/html/_sources/bounds.rst.txt b/docs/_build/html/_sources/bounds.rst.txt new file mode 100644 index 00000000000..b0c28c88ce3 --- /dev/null +++ b/docs/_build/html/_sources/bounds.rst.txt @@ -0,0 +1,18 @@ +Bounding boxes +============== + +.. autoclass:: galsim.Bounds + :members: + +.. autoclass:: galsim.BoundsI + :members: + :show-inheritance: + +.. autoclass:: galsim.BoundsD + :members: + :show-inheritance: + +.. autofunction:: galsim._BoundsI + +.. autofunction:: galsim._BoundsD + diff --git a/docs/_build/html/_sources/catalog.rst.txt b/docs/_build/html/_sources/catalog.rst.txt new file mode 100644 index 00000000000..cfdb2b8da5b --- /dev/null +++ b/docs/_build/html/_sources/catalog.rst.txt @@ -0,0 +1,13 @@ +Catalogs and Input Dictionaries +=============================== + +.. autoclass:: galsim.Catalog + :members: + +.. autoclass:: galsim.OutputCatalog + :members: + +.. autoclass:: galsim.Dict + :members: + + diff --git a/docs/_build/html/_sources/cd.rst.txt b/docs/_build/html/_sources/cd.rst.txt new file mode 100644 index 00000000000..bbe14a25e8f --- /dev/null +++ b/docs/_build/html/_sources/cd.rst.txt @@ -0,0 +1,23 @@ +Charge Deflection Model +======================= + +We include in GalSim a basic implementation of the Antilogus charge deflection model. +Probably at this point, there are better implementations of this model, so this might not +be very useful for most users. However, if you just want a quick and dirty way to simulate +the so-called "Brighter-Fatter Effect", you can use this. + +.. note:: + A better implementation of brighter-fatter is now available as part of the `SiliconSensor` + model. It does not use the Antilogus model at all, but rather tries to simulate the + underlying physics of the effect. + +.. autoclass:: galsim.cdmodel.BaseCDModel + :members: + + .. automethod:: galsim.cdmodel.BaseCDModel.__init__ + +.. autoclass:: galsim.cdmodel.PowerLawCD + :members: + :show-inheritance: + + .. automethod:: galsim.cdmodel.PowerLawCD.__init__ diff --git a/docs/_build/html/_sources/chromatic.rst.txt b/docs/_build/html/_sources/chromatic.rst.txt new file mode 100644 index 00000000000..1df6671c044 --- /dev/null +++ b/docs/_build/html/_sources/chromatic.rst.txt @@ -0,0 +1,59 @@ + +Wavelength-dependent Profiles +############################# + +Real astronomical objects emit photons at a range of wavelengths according to a potentially +complicated spectral energy distribution (SED). These photons then may be affected differently +by the atmosphere and optics as part of the point-spread function (PSF). Then they typically +pass through a bandpass filter with a variable transmission as a function of wavelength. +Finally, there may be other wavelength-dependent effects when converting from photons to +electrons in the sensor. + +GalSim supplies a number of tools to simulate these chromatic effects. +An `SED` is used to define the SED of the objects. There are a variety of options as to the units +of the input SED function; e.g. photons/cm^2/nm/sec, ergs/cm^2/Hz/s, etc. There are also ways +to adjust the normalization of the SED to give a particular observed magnitude when observed +through a particular `Bandpass`. And there is a dimensionless option, which may be appropriate +for defining chromatic PSFs. + +The `Bandpass` class represents a spectral throughput function, which could be an +entire imaging system throughput response function (reflection off of mirrors, transmission through +filters, lenses and the atmosphere, and quantum efficiency of detectors), or individual pieces +thereof. Both a `Bandpass` and the `SED` are necessary to compute the relative contribution of +each wavelength of a `ChromaticObject` to a drawn image. + +Then there are a number of kinds of `ChromaticObject` to define the wavelength dependence of an +object's surface brightness profile. The simplest one is when the spatial and spectral +dependencies are separable; i.e. every part of the profile has the same SED. In this case, +one forms the `ChromaticObject` simply by multiplying a `GSObject` by an `SED`:: + + >>> obj = galsim.Sersic(n=2.3, half_light_radius=3.5) + >>> sed = galsim.SED('CWW_Sbc_ext.sed', wave_type'Ang', flux_type='flambda') + >>> chromatic_object = obj * sed + +Other more complicated kinds of chromatic profiles are subclasses of `ChromaticObject` and +have their own initialization arguments. See the listings below. + +To draw any kind of `ChromaticObject`, you call its :meth:`~ChromaticObject.drawImage` +method, which works largely the same as :meth:`~GSObject:drawImage`, but requires a +`Bandpass` argument to define what bandpass is being used for the observation:: + + >>> gband = galsim.Bandpass(lambda w:1.0, wave_type='nm', blue_limit=410, red_limit=550) + >>> image = chromatic_obj.drawImage(gband) + +The transformation methods of `ChromaticObject`, like :meth:`~ChromaticObject.dilate` and +:meth:`~ChromaticObject.shift`, can also accept as an argument a function of wavelength (in +nanometers) that returns a wavelength-dependent dilation, shift, etc. These can be used to +implement chromatic PSFs. For example, a diffraction limited PSF might look like:: + + >>> psf500 = galsim.Airy(lam_over_diam=2.0) + >>> chromatic_psf = ChromaticObject(psf500).dilate(lambda w:(w/500)**1.0) + + +.. toctree:: + :maxdepth: 2 + + sed + bandpass + chromaticobject + spectral diff --git a/docs/_build/html/_sources/chromaticobject.rst.txt b/docs/_build/html/_sources/chromaticobject.rst.txt new file mode 100644 index 00000000000..5ee05fe9017 --- /dev/null +++ b/docs/_build/html/_sources/chromaticobject.rst.txt @@ -0,0 +1,70 @@ +Chromatic Profiles +================== + +The `ChromaticObject` class and its various subclasses Define wavelength-dependent surface +brightness profiles. + +Implementation is done by constructing `GSObject` instances as functions of wavelength. +The `ChromaticObject.drawImage` method then integrates over wavelength while also multiplying by a +throughput function (a `galsim.Bandpass` instance). + +These classes can be used to simulate galaxies with color gradients, observe a given galaxy through +different filters, or implement wavelength-dependent point spread functions. + +So far photon-shooting a `ChromaticObject` is not implemented, but there is ongoing work to +include this option in GalSim, as the current FFT drawing method is very slow. So these are not +yet particularly useful for large image simulations, especially ones including many faint sources. + + +.. autoclass:: galsim.ChromaticObject + :members: + + .. automethod:: galsim.ChromaticObject.__mul__ + +.. autoclass:: galsim.ChromaticAtmosphere + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticOpticalPSF + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticAiry + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticRealGalaxy + :members: + :show-inheritance: + +.. autoclass:: galsim.InterpolatedChromaticObject + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticSum + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticConvolution + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticDeconvolution + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticAutoConvolution + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticAutoCorrelation + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticTransformation + :members: + :show-inheritance: + +.. autoclass:: galsim.ChromaticFourierSqrtProfile + :members: + :show-inheritance: diff --git a/docs/_build/html/_sources/composite.rst.txt b/docs/_build/html/_sources/composite.rst.txt new file mode 100644 index 00000000000..b7bb7494a34 --- /dev/null +++ b/docs/_build/html/_sources/composite.rst.txt @@ -0,0 +1,37 @@ + +Composite Profiles +================== + +The above profiles can be combined in various ways including sums and convolutions. + +Sums of GSObjects +----------------- + +.. autoclass:: galsim.Sum + :members: + :show-inheritance: + +.. autofunction:: galsim.Add + +Convolutions of GSObjects +------------------------- + +.. autoclass:: galsim.Convolution + :members: + :show-inheritance: + +.. autofunction:: galsim.Convolve + +.. autoclass:: galsim.AutoConvolution + :members: + :show-inheritance: + +.. autofunction:: galsim.AutoConvolve + +.. autoclass:: galsim.AutoCorrelation + :members: + :show-inheritance: + +.. autofunction:: galsim.AutoCorrelate + + diff --git a/docs/_build/html/_sources/config.rst.txt b/docs/_build/html/_sources/config.rst.txt new file mode 100644 index 00000000000..68da812c142 --- /dev/null +++ b/docs/_build/html/_sources/config.rst.txt @@ -0,0 +1,97 @@ + +The Config Module +################# + +The basic configuration method is to use a dictionary which can be parsed in python. +Within that structure, each field can either be a value, another dictionary which is then +further parsed, or occasionally a list of items (which can be either values or dictionaries). +The hierarchy can go as deep as necessary. + +Our example config files are all yaml files, which are read using the executable ``galsim``. +This is a nice format for config files, but it is not required. Anything that can represent a +dictionary will do. For example, the executable ``galsim`` also reads in and processes json-style +config files if you prefer. + +If you would like a kind of tutorial that goes through typical uses of the config files, there +are a series of demo config files in the ``GalSim/examples`` directory. +See `Tutorials` for more information. + +For a concrete example of what a config file looks like, here is +:gh-link:`demo1.yaml ` +(the first file in the aforementioned tutorial) stripped of most of the comments to make it easier +to see the essence of the structure: + +.. code-block:: yaml + + gal : + type : Gaussian + sigma : 2 # arcsec + flux : 1.e5 # total counts in all pixels + + psf : + type : Gaussian + sigma : 1 # arcsec + + image : + pixel_scale : 0.2 # arcsec / pixel + noise : + type : Gaussian + sigma : 30 # standard deviation of the counts in each pixel + + output : + dir : output_yaml + file_name : demo1.fits + +This file defines a dictionary, which in python would look like:: + + config = { + 'gal' : { + 'type' : 'Gaussian', + 'sigma' : 2., + 'flux' : 1.e5 + }, + 'psf' : { + 'type' : 'Gaussian', + 'sigma' : 1. + }, + 'image' : { + 'pixel_scale' : 0.2, + 'noise' : { + 'type' : 'Gaussian', + 'sigma' : 30. + } + }, + 'output' : { + 'dir' : 'output_yaml', + 'file_name' : 'demo1.fits' + } + } + +As you can see, there are several top level fields (``gal``, ``psf``, ``image``, and ``output``) +that define various aspects of the simulation. There are others as well that we will describe +below, but most simulations will want to include at least these four. + +Most fields have a ``type`` item that defines what the other items in the field mean. +(The ``image`` and ``output`` fields here have implicit types ``Single`` and ``Fits``, +which are the default, so may be omitted.) +For instance, a Gaussian surface brightness profile is defined by the parameters +``sigma`` and ``flux``. + +Most types have some optional items that take reasonable defaults if you omit them. +E.g. the flux is not relevant for a PSF, so it may be omitted in the ``psf`` field, in which +case the default of ``flux=1`` is used. + + +.. toctree:: + :maxdepth: 1 + + config_top + config_objects + config_stamp + config_image + config_input + config_output + config_values + config_special + config_galsim + config_process diff --git a/docs/_build/html/_sources/config_galsim.rst.txt b/docs/_build/html/_sources/config_galsim.rst.txt new file mode 100644 index 00000000000..fda4ec92344 --- /dev/null +++ b/docs/_build/html/_sources/config_galsim.rst.txt @@ -0,0 +1,76 @@ +The galsim Executable +===================== + +The normal way to run a GalSim simulation using a config file is ``galsim config.yaml``, where +``config.yaml`` is the name of the config file to be parsed. For instance, to run demo1 (given +above), you would type:: + + galsim demo1.yaml + +Changing or adding parameters +----------------------------- + +Sometimes it is convenient to be able to change some of the configuration parameters from the +command line, rather than edit the config file. For instance, you might want to make a number +of simulations, which are nearly identical but differ in one or two specific attribute. + +To enable this, you can provide the changed (or new) parameters on the command line after the +name of the config file. E.g. +to make several simulations that are identical except for the flux of the galaxy and the output +file, one could do:: + + galsim demo1.yaml gal.flux=1.e4 output.file_name=demo1_1e4.fits + galsim demo1.yaml gal.flux=2.e4 output.file_name=demo1_2e4.fits + galsim demo1.yaml gal.flux=3.e4 output.file_name=demo1_3e4.fits + galsim demo1.yaml gal.flux=4.e4 output.file_name=demo1_4e4.fits + +Notice that the ``.`` is used to separate levels within the config hierarchy. +So ``gal.flux`` represents ``config['gal']['flux']``. + +Splitting up a config job +------------------------- + +For large simulations, one will typically want to split the job up into multiple smaller jobs, +each of which can be run on a single node or core. The natural way to split this up is by +parceling some number of output files into each sub-job. We make this splitting very easy using +the command line options ``-n`` and ``-j``. The total number of jobs you want should be given +with ``-n``, and each separate job should be given a different ``-j``. So to divide a run across +5 machines, you would run one of the following commands on each of the 5 different machines +(or more typically send these 5 commands as jobs in a queue system):: + + galsim config.yaml -n 5 -j 1 + galsim config.yaml -n 5 -j 2 + galsim config.yaml -n 5 -j 3 + galsim config.yaml -n 5 -j 4 + galsim config.yaml -n 5 -j 5 + +Other command line options +-------------------------- + +There are few other command line options that we describe here for completeness. + +* ``-h`` or ``--help`` gives the help message. This is really the definitive information about the + ``galsim`` executable, so if that message disagrees with anything here, you should trust that + information over what is written here. +* ``-v {0,1,2,3}`` or ``--verbosity {0,1,2,3}`` sets how verbose the logging output should be. + The default is ``-v 1``, which provides some modest amount of output about each file being built. + ``-v 2`` give more information about the progress within each output file, including one line of + information about each object that is drawn. + ``-v 3`` (debug mode) gives a lot of output and should be reserved for diagnosing runtime problems. + ``-v 0`` turns off all logging output except for error messages. +* ``-l LOG_FILE`` or ``--log_file LOG_FILE`` gives a file name for writing the logging output. If + omitted, the default is to write to stdout. +* ``-f {yaml,json}`` or ``--file_type {yaml,json}`` defines what type of configuration file to parse. + The default is to determine this from the file name extension, so it is not normally needed, + but if you have non-standard file names, you might need to set this. + * ``-m MODULE`` or ``--module MODULE`` gives a python module to import before parsing the config + file. This has been superseded by the ``modules`` top level field, which is normally more + convenient. However, this option is still allowed for backwards compatibility. +* ``-p`` or ``--profile`` turns on profiling information that gets output at the end of the run + (or when multi-processing, at the end of execution of a process). This can be useful for + diagnosing where a simulation is spending most of its computation time. +* ``-n NJOBS`` or ``--njobs NJOBS`` sets the total number of jobs that this run is a part of. Used in conjunction with -j (--job). +* ``-j JOB`` or ``--job JOB`` sets the job number for this particular run. Must be in [1,njobs]. Used in conjunction with -n (--njobs). +* ``-x`` or ``--except_abort`` aborts the whole job whenever any file raises an exception rather than continuing on. (new in version 1.5) +* ``--version`` shows the version of GalSim. + diff --git a/docs/_build/html/_sources/config_image.rst.txt b/docs/_build/html/_sources/config_image.rst.txt new file mode 100644 index 00000000000..92c86e72e20 --- /dev/null +++ b/docs/_build/html/_sources/config_image.rst.txt @@ -0,0 +1,444 @@ + +Config Image Field +================== + +The ``image`` field defines some properties about how to draw the images. + +Image Field Attributes +---------------------- + +Some attributes that are allowed for all image types are: + +* ``pixel_scale`` = *float_value* (default = 1.0) The pixel scale, typically taken to be arcsec/pixel. Most size parameters for the profiles are taken to be specified in arcsec. If you would rather specify everything in pixels, just leave off the pixel_scale (or set it to 1.0) and then 1 pixel = 1 arcsec, so everything should work the way you expect. Or if you want all your units to be degrees or radians or something else, then just set this pixel scale in the same units. +* ``sky_level`` = *float_value* (default = 0.0; only one of ``sky_level`` and ``sky_level_pixel`` is allowed) The background level of the image in ADU/arcsec^2 +* ``sky_level_pixel`` = *float_value* (default = 0.0; only one of ``sky_level`` and ``sky_level_pixel`` is allowed) The background level of the image in ADU/pixel +* ``index_convention`` = *str_value* (default = 'FITS') The convention for what to call the lower left pixel of the image. The standard FITS convention is to call this pixel (1,1). However, this can be counter-intuitive to people used to C or python indexing. So if ``index_convention`` is 'C' or 'python' or '0', then the image origin will be considered (0,0) instead. (While unnecessary to specify explicitly since it is the default, the (1,1) convention may be called 'FITS', 'Fortran' or '1'.) +* ``dtype`` = *str_value* (default = 'np.float32') The data type to use for the output image. Allowed values include all the Python and numpy types that are allowed for an `Image`. +* ``random_seed`` = *int_value* or *list* (optional) The random seed to use for random numbers in the simulation. + + * The typical use case is that this is a simple integer value. Then, internally we scramble this value in a deterministic way and use a sequence of seeds based on the scrambled value so that the output is deterministic even when using multiple processes to build each image. + * If ``random_seed`` is a list, then the first one will be converted as described above, but the later ones will not. Rather, it will respect your specification and evaluate the seed for each object as you specifiy. This will create multiple random number generators, according to the multiple seed specifications. This is normally used to have one random number behave normally for noise and such, and another one (or more) repeat with some other cadence (e.g. repeat for each image in an exposure to make sure you generate the same PSFs for multiple CCDs in an exposure). See `Demo 7` and `Demo 13` for examples of this. Whenever you want to use an rng other than the first one, add ``rng_num`` to the field and set it to the number of the rng you want to use in this list. + + .. note:: + We use 0-based indexing for ``rng_num``, so the first item in the ``random_seed`` list is ``rng_num=0``, the second one is ``rng_num=1``, etc. + + * The default behavior, if ``random_seed`` is not given, is to get a seed from the system (/dev/urandom if possible, otherwise based on the time). + +* ``nproc`` = *int_value* (default = 1) Specify the number of processors to use when drawing images. If nproc <= 0, then this means to try to automatically figure out the number of cpus and use that. +* ``timeout`` = *float_value* (default = 900) Specify the number of seconds to allow for each job when multiprocessing before the multiprocessing queue times out. The default is generally appropriate to prevent jobs from hanging forever from some kind of multiprocessing snafu, but if your jobs are expected to take more than 15 minutes per image, you might need to increase this. +* ``wcs`` See `WCS Field` below. +* ``bandpass`` See `Bandpass Field` below. +* ``sensor`` See `Sensor Field` below. + +Image Types +----------- + +The default image type is 'Single', which means that the image contains just a single +postage stamp. Other types are possible (and common) that draw more than one postage stamp +on a full image in different ways. Each type define extra attributes that are either +allowed or required. +The image types defined by GalSim are: + +* 'Single' The image contains a single object at the center (unless it has been shifted of course -- see shift attribute above). + + * ``size`` = *int_value* (optional) If you want square images for each object (common), you just need to set this one value and the images will be ``size`` x ``size``. The default is for GalSim to automatically determine a good size for the image that will encompass most of the flux of the object. + * ``xsize`` = *int_value* (default = ``size``) If you want non-square images, you can specify ``xsize`` and ``ysize`` separately instead. It is an error for only one of them to be non-zero. + * ``ysize`` = *int_value* (default = ``size``) + * ``world_pos`` = *pos_value* The position of the object in world coordinates. For the Single image type, this is unconnected to the object rendering on the image, which is always at the center. However, it may be provided as something that other calculations need to access. e.g. shear from a PowerSpectrum or NFWHalo. + * ``image_pos`` = *pos_value* The nominal position on the image at which to center of the object. For the Single image type, the object is always placed as close as possible to the center of the image (unless an explicit offset is specified), but the bounds will be adjusted so that position is equal to ``image_pos``. + +* 'Tiled' The image consists of a tiled array of postage stamps. + + * ``nx_tiles`` = *int_value* (required) + * ``ny_tiles`` = *int_value* (required) + * ``stamp_size`` = *int_value* (either ``stamp_size`` or both ``stamp_xsize`` and ``stamp_ysize`` are required) The size of square stamps on which to draw the objects. + * ``stamp_xsize`` = *int_value* (either ``stamp_size`` or both ``stamp_xsize`` and ``stamp_ysize`` are required) The xsize of the stamps on which to draw the objects. + * ``stamp_ysize`` = *int_value* (either ``stamp_size`` or both ``stamp_xsize`` and ``stamp_ysize`` are required) The ysize of the stamps on which to draw the objects. + * ``border`` = *int_value* (default = 0) number of pixels between tiles. Note: the border value may be negative, in which case the tiles will overlap each other. + * ``xborder`` = *int_value* (default = ``border``) number of pixels between tiles in the x direction + * ``yborder`` = *int_value* (default = ``border``) number of pixels between tiles in the y direction + * ``order`` = *str_value* (default = 'row') Which order to fill the stamps. 'row' means to proceed row by row starting at the bottom row (each row is filled from left to right). 'column' means to fill the columns from left to right (each column is filled from bottom to top). 'random' means to place the tiles in a random order. + +* 'Scattered' The image consists of a large contiguous area on which postage stamps of each object are placed at arbitrary positions, possibly overlapping each other (in which case the fluxes are added together for the final pixel value). + + * ``size`` = *int_value* (either ``size`` or both ``xsize`` and ``ysize`` are required) + * ``xsize`` = *int_value* (either ``size`` or both ``xsize`` and ``ysize`` are required) + * ``ysize`` = *int_value* (either ``size`` or both ``xsize`` and ``ysize`` are required) + * ``nobjects`` = *int_value* (default if using an input catalog and the output type is 'Fits' is the number of entries in the input catalog; otherwise required) + * ``stamp_size`` = *int_value* (optional) The ``stamp_size`` attribute works like the ``size`` attribute for 'Single'. + * ``stamp_xsize`` = *int_value* (default = ``stamp_size``) + * ``stamp_ysize`` = *int_value* (default = ``stamp_size``) + * ``world_pos`` = *pos_value* (only one of ``world_pos`` and ``image_pos`` is allowed) The position in world coordinates relative to the center of the image at which to center of the object. + * ``image_pos`` = *pos_value* (only one of ``world_pos`` and ``image_pos`` is allowed; default if neither is given is to use type 'XY' with ``x`` = 'Random' from 1 .. ``xsize``, ``y`` = 'Random' from 1 .. ``ysize``) The position on the image at which to center of the object. + +Custom Image Types +------------------ + +To define your own image type, you will need to write an importable Python module +(typically a file in the current directory where you are running ``galsim``, but it could also +be something you have installed in your Python distro) with a class that will be used +to build the image. + +The class should be a subclass of `galsim.config.ImageBuilder`, which is the class used for +the default 'Single' type. There are a number of class methods, and you only need to override +the ones for which you want different behavior than that of the 'Single' type. + +.. autoclass:: galsim.config.ImageBuilder + :members: + +The ``base`` parameter is the original full configuration dict that is being used for running the +simulation. The ``config`` parameter is the local portion of the full dict that defines the image +being built, which would typically be ``base['image']``. + +Then, in the Python module, you need to register this function with some type name, which will +be the value of the ``type`` attribute that triggers the use of this Builder object:: + + galsim.config.RegisterImageType('CustomImage', CustomImageLoader()) + +.. autofunction:: galsim.config.RegisterImageType + +Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple image types use the same class instantiated with different +initialization parameters. This is not used by the GalSim image types, but there may be use +cases where it would be useful for custom image types. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_image.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_image + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_image``, +which will read your file and execute the registration command to add the builder to the list +of valid image types. + +Then you can use this as a valid image type: + +.. code-block:: yaml + + image: + type: CustomImage + ... + +We don't currently have any examples of custom images, but it may be helpful to look at the GalSim +implementation of the included image types (click on the ``[source]`` links): + +.. autoclass:: galsim.config.image_scattered.ScatteredImageBuilder + :show-inheritance: + +.. autoclass:: galsim.config.image_tiled.TiledImageBuilder + :show-inheritance: + + +Noise +----- + +Typically, you will want to add noise to the image. The noise attribute should be a dict +with a ``type`` attribute to define what kind of noise should be added. The noise types +that are defined by GalSim are: + +* 'Gaussian' is the simplest kind of noise. Just Gaussian noise across the whole image with a given sigma (or variance). + + * ``sigma`` = *float_value* (either ``sigma`` or ``variance`` is required) The rms of the noise in ADU. + * ``variance`` = *float_value* (either ``sigma`` or ``variance`` is required) The variance of the noise in ADU^2. + +* 'Poisson' adds Poisson noise for the flux value in each pixel, with an optional sky background level. This is the default noise if you don't specify a different noise type. + + * ``sky_level`` = *float_value* (default = 0.0) The sky level in ADU/arcsec^2 to use for the noise. If both this and ``image.sky_level`` are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be ``image.sky_level``. + * ``sky_level_pixel`` = *float_value* (default = 0.0) The sky level in ADU/pixel to use for the noise. If both this and ``image.sky_level_pixel`` are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be ``image.sky_level_pixel``. + +* 'CCD' includes both Poisson noise for the flux value in each pixel (with an optional gain) and an optional Gaussian read noise. + + * ``sky_level`` = *float_value* (default = 0.0) The sky level in ADU/arcsec^2 to use for the noise. If both this and ``image.sky_level`` are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be ``image.sky_level``. + * ``sky_level_pixel`` = *float_value* (default = 0.0) The sky level in ADU/pixel to use for the noise. If both this and ``image.sky_level_pixel`` are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be ``image.sky_level_pixel``. + * ``gain`` = *float_value* (default = 1.0) The CCD gain in e-/ADU. + * ``read_noise`` = *float_value* (default = 0.0) The CCD read noise in e-. + +* 'COSMOS' provides spatially correlated noise of the sort found in the F814W HST COSMOS science images described by Leauthaud et al (2007). The point variance (given by the zero distance correlation function value) may be normalized by the user as required, as well as the dimensions of the correlation function. + + * ``file_name`` = *str_value* (optional) The path and filename of the FITS file containing the correlation function data used to generate the COSMOS noise field. The default is to use the file packaged with GalSim as 'share/acs_I_unrot_sci_20_cf.fits', but this option lets you override this if desired. + * ``cosmos_scale`` = *float_value* (default = 0.03) The ACS coadd images in COSMOS have a pixel scale of 0.03 arcsec, and so the pixel scale ``cosmos_scale`` adopted in the representation of of the correlation function takes a default value of 0.03. If you wish to use other units ensure that ``cosmos_scale`` takes the value corresponding to 0.03 arcsec in your chosen system. + * ``variance`` = *float_value* (default = 0.) Scale the point variance of the noise field to the desired value, equivalent to scaling the correlation function to have this value at zero separation distance. Choosing the default scaling of 0. uses the variance in the original COSMOS noise fields. + +* 'Correlated' is a more general version of COSMOS, allowing any arbitrary correlated noise spectrum to be used. It requires the user to provide the appropriate file giving an image of the correlation function. See `BaseCorrelatedNoise.from_file` for details about how this file must look. + + * ``file_name`` = *str_value* (required) The path and filename of the FITS file containing the correlation function data used to generate the Correlated noise field. + * ``pixel_scale`` = *float_value* (required) The pixel scale of the original image data from which the correlated noise was constructed. + * ``variance`` = *float_value* (default = 0.) Scale the point variance of the noise field to the desired value, equivalent to scaling the correlation function to have this value at zero separation distance. Choosing the default scaling of 0. uses the variance in the file without modification. + +The ``noise`` field can also take the following attributes, which are relevant when using +object types that have some intrinsic noise already, such as 'RealGalaxy': + +* ``whiten`` = *bool_value* (default = False) Whether or not a noise-whitening procedure should be done on the image after it is drawn to make the noise uncorrelated (white noise). This is only relevant when using the ``gal`` type 'RealGalaxy'. Note: After the whitening process, there is white Gaussian noise in the image. We subtract this much noise from the variance of whatever is given in the ``image.noise`` field. However, unless this is ``type = 'Gaussian'``, the final noise field will not precisely match what you request. e.g. 'Poisson' noise would have a portion of the variance be Gaussian rather than Poisson. This probably does not matter in most cases, but if you are whitening, the most coherent noise profile is 'Gaussian', since that works seamlessly. +* ``symmetrize`` = *int_value* (default = None) The order at which to impose N-fold symmetry on the noise in the image after it is drawn, after which there will be correlated Gaussian noise with the desired symmetry in the image (usually much less than must be added to achieve a fully white noise field). Similar caveats apply to this option as to the ``white`` option. + +In addition to the above, you may also define your own custom noise type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of `galsim.config.NoiseBuilder`. This is really an abstract +base class. At least the first two of these methods need to be overridden: + +.. autoclass:: galsim.config.NoiseBuilder + :members: + +Then, as usual, you need to register this type using:: + + galsim.config.RegisterNoiseType('CustomNoise', CustomNoiseBuilder()) + +.. autofunction:: galsim.config.RegisterNoiseType + +and tell the config parser the name of the module to load at the start of processing. + +.. code-block:: yaml + + modules: + - my_custom_noise + +Then you can use this as a valid noise type: + +.. code-block:: yaml + + image: + noise: + type: CustomNoise + ... + +We don't currently have any examples of custom noise types, but it may be helpful to look at the GalSim implementation of the various included noise types (click on the ``[source]`` links: + +.. autoclass:: galsim.config.GaussianNoiseBuilder + :show-inheritance: + +.. autoclass:: galsim.config.PoissonNoiseBuilder + :show-inheritance: + +.. autoclass:: galsim.config.CCDNoiseBuilder + :show-inheritance: + +.. autoclass:: galsim.config.COSMOSNoiseBuilder + :show-inheritance: + + +WCS Field +--------- + +The ``pixel_scale`` attribute mentioned above is the usual way to define the connection between +pixel coordinates and sky coordinates. However, one can define a more complicated relationship, +which is known as a World Coordinate System (WCS) if desired. To do this, use the ``wcs`` +attribute instead of the ``pixel_scale`` attribute. This should be a dict with a ``type`` +attribute that defines what kind of WCS to use. The wcs types that are defined by GalSim are: + +* 'PixelScale' implements a regular square pixel grid. If you do not specify any ``wcs`` item, this is what will be used, and the scale will be the ``image.pixel_scale`` value. + + * ``scale`` = *float_value* (default = ``image.pixel_scale``) The scale size of the pixels. The area is ``scale * scale``. + +* 'Shear' implements a uniform shear of a regular square pixel grid. After the shear, the pixel area will still be ``scale * scale``, but they will be parallelograms (rhombi actually) rather than squares. + + * ``scale`` = *float_value* (required) The pixel scale of the grid before being sheared. + * ``shear`` = *shear_value* (required) The shear to apply. + +* 'Jacobian' or 'Affine' implements an arbitrary affine transform. This is the most general WCS that has a uniform pixel shape. The world (u,v) coordinates are linearly related to the image (i.e. pixel) (x,y) coordinates. + + * ``dudx`` = *float_value* (required) du/dx + * ``dudy`` = *float_value* (required) du/dy + * ``dvdx`` = *float_value* (required) dv/dx + * ``dvdy`` = *float_value* (required) dv/dy + +* 'UVFunction' implements an arbitrary transformation from image coordinates (x,y) to world coordinates (u,v) via two functions u(x,y) and v(x,y). You can also provide the inverse functions x(u,v) and y(u,v). They are not required, but if they are not given, then positions of objects cannot be given in world coordinates via ``image.world_pos``. + + * ``ufunc`` = *str_value* (required) A string that can be turned into the function u(x,y) via the python command ``eval('lambda x,y : ' + ufunc)``. + * ``vfunc`` = *str_value* (required) A string that can be turned into the function v(x,y) via the python command ``eval('lambda x,y : ' + vfunc)``. + * ``xfunc`` = *str_value* (optional) A string that can be turned into the function x(u,v) of the inverse transformation via the python command ``eval('lambda u,v : ' + xfunc)``. + * ``yfunc`` = *str_value* (optional) A string that can be turned into the function y(u,v) of the inverse transformation via the python command ``eval('lambda u,v : ' + yfunc)``. + +* 'RaDecFunction' implements an arbitrary transformation from image coordinates (x,y) to celestial coordinates (ra,dec) via two functions ra(x,y) and dec(x,y). + + * ``ra_func`` = *str_value* (required) A string that can be turned into the function ra(x,y) via the python command ``eval('lambda x,y : ' + rafunc)``. + * ``dec_func`` = *str_value* (required) A string that can be turned into the function dec(x,y) via the python command ``eval('lambda x,y : ' + decfunc)``. + +* 'Fits' reads a WCS from a FITS file. Most common WCS types are implemented, but if the file uses something a bit unusual, the success of the read may depend on what other python packages you have installed. See the documentation of `FitsWCS` for more details. + + * ``file_name`` = *str_value* (required) The name of the FITS file. + * ``dir`` = *str_value* (default = '.') + +* 'Tan' implements a tangent-plane projection of the celestial sphere around a given right ascension and declination. There is an arbitrary Jacobian matrix relating the image coordinates to the coordinates in the tangent plane. + + * ``dudx`` = *float_value* (required) du/dx + * ``dudy`` = *float_value* (required) du/dy + * ``dvdx`` = *float_value* (required) dv/dx + * ``dvdy`` = *float_value* (required) dv/dy + * ``ra`` = *angle_value* (required) the right ascension of the tangent point + * ``dec`` = *angle_value* (required) the declination of the tangent point + * ``unit`` = *str_value* (default = 'arcsec') the units to use for the intermediate (u,v) coordinates. Options are 'arcsec', 'arcmin', 'deg', 'rad', 'hr'. + +In addition, all wcs types can define an origin in either image coordinates, world coordinates, or +both: + +* ``origin`` = *pos_value* (default = (0,0)) Optionally set the image coordinates to use as the origin position, if not (x,y) = (0,0). Special: You can also specify ``origin`` to be 'center', in which case the origin is taken to be the center of the image rather than the corner. +* ``world_origin`` = *pos_value* (default = (0,0)) Optionally set the world coordinates to use as the origin position, if not (u,v) = (0,0). (Not available for the celestial WCS types: 'RaDecFunction', 'Fits', and 'Tan'.) + +In addition to the above, you may also define your own custom WCS type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of `galsim.config.WCSBuilder`. + +.. autoclass:: galsim.config.WCSBuilder + :members: + +Then, as usual, you need to register this type using:: + + galsim.config.RegisterWCSType('CustomWCS', CustomWCSBuilder()) + +.. autofunction:: galsim.config.RegisterWCSType + +If the builder will use a particular input type, you should let GalSim know this by specifying +the ``input_type`` when registering. e.g. if it uses an input FitsHeader, you would write:: + + galsim.config.RegisterWCSType('CustomWCS', CustomWCSBuilder(), input_type='fits_header') + +and tell the config parser the name of the module to load at the start of processing. + +.. code-block:: yaml + + modules: + - my_custom_wcs + +Then you can use this as a valid wcs type: + +.. code-block:: yaml + + image: + wcs: + type: CustomWCS + ... + +For examples of custom wcs types, see :gh-link:`des_wcs.py `, which implements ``DES_SlowLocal`` and ``DES_Local``. +The latter is faster because it uses in input field, 'des_wcs', which saves on I/O time by only loading the files once. DES_Local is used by :gh-link:`meds.yaml `. + +It may also be helpful to look at the GalSim implementation of the various included wcs types (click on +the ``[source]`` links): + +.. autoclass:: galsim.config.SimpleWCSBuilder + :show-inheritance: + +.. autoclass:: galsim.config.OriginWCSBuilder + :show-inheritance: + + +.. autoclass:: galsim.config.TanWCSBuilder + :show-inheritance: + +.. autoclass:: galsim.config.ListWCSBuilder + :show-inheritance: + +Bandpass Field +-------------- + +If you want to draw chromatic objects, then you also need to define the `Bandpass` to use +for the image in a ``image.bandpass`` field. Currently, there is only one defined +type to use for the Bandpass, but the code is written in a modular way to allow for +other types, including custom Bandpass types. + +* 'FileBandpass' is the default type here, and you may omit the type name when using it. + + * ``file_name`` = *str_value* (required) The file to read in. + * ``wave_type`` = *str_value* or *unit_value* (required) The unit of the wavelengths in the file ('nm' or 'Ang' or variations on these -- cf. `Bandpass`) + * ``blue_limit`` = *float_value* or *quantity_value* (optional) Hard cut off on the blue side. + * ``red_limit`` = *float value* or *quantity_value* (optional) Hard cut off on the red side. + * ``zeropoint`` = *float_value* (optional) The zero-point to use. + * ``thin`` = *float_value* (optional) If given, call `Bandpass.thin` on the Bandpass after reading in from the file, using this for the ``rel_err``. + +You may also define your own custom Bandpass type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of `galsim.config.BandpassBuilder`. + +.. autoclass:: galsim.config.BandpassBuilder + :members: + +Then, as usual, you need to register this type using:: + + galsim.config.RegisterBandpassType('CustomBandpass', CustomBandpassBuilder()) + +.. autofunction:: galsim.config.RegisterBandpassType + +and tell the config parser the name of the module to load at the start of processing. + +.. code-block:: yaml + + modules: + - my_custom_bandpass + +Then you can use this as a valid bandpass type: + +.. code-block:: yaml + + image: + bandpass: + type: CustomBandpass + ... + +Sensor Field +------------ + +When drawing with ``method='phot'``, one can use a `Sensor` to model the conversion of photons +to electrons and then the accumulation of these electrons in the pixels. + +The sensor types defined by GalSim are: + +* 'Simple' is the default, which does nothing but accumulate the photons at their (x,y) + positions onto the corresponding pixels in the image. Typically, in this case, you would + omit the ``sensor`` field entirely. + +* 'Silicon' models a silicon-based CCD sensor that converts photons to electrons at a wavelength- + dependent depth (probabilistically) and drifts them down to the wells, properly taking + into account the repulsion of previously accumulated electrons (known as the brighter-fatter + effect). See `SiliconSensor` for details. + + * ``name`` = *str_value* (default = 'lsst_itl_50_8') The naem of the sensor model to use. + See `SiliconSensor` for the other allowed values. + * ``strength`` = *float_value* (default = 1.0) The strength of the brighter-fatter effect + relative to the nominal value in the model. + * ``diffusion_factor`` = *float_value* (default = 1.0) The magnitude of the diffusion + relative to the nominal value in the model. + * ``qdist`` = *int_value* (default = 3) The maximum number of pixels away to calculate the + distortions due to charge accumulation. + * ``nrecalc`` = *int_value* (default = 10000) The number of electrons to accumulate before + recalculating the distortion of the pixel shapes. + * ``treering_func`` = *table_value* (optional) A `LookupTable` giving the tree ring pattern + as a radial function f(r). + * ``treering_center`` = *pos_value* (optional) The position of the center of the tree ring + pattern in image coordinates (which may be off the edge of the image). + * ``transpose`` = *bool_value* (default = False) Whether to transpose the meaning of (x,y) + for the purpose of the brighter-fatter effect. This changes which direction the BFE + causes more elongation. + +You may also define your own custom `Sensor` type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of `galsim.config.SensorBuilder`. + +.. autoclass:: galsim.config.SensorBuilder + :members: + +Then, as usual, you need to register this type using:: + + galsim.config.RegisterSensorType('CustomSensor', CustomSensorBuilder()) + +.. autofunction:: galsim.config.RegisterSensorType + +and tell the config parser the name of the module to load at the start of processing. + +.. code-block:: yaml + + modules: + - my_custom_sensor + +Then you can use this as a valid sensor type: + +.. code-block:: yaml + + image: + sensor: + type: CustomSensor + ... diff --git a/docs/_build/html/_sources/config_input.rst.txt b/docs/_build/html/_sources/config_input.rst.txt new file mode 100644 index 00000000000..f15853d692d --- /dev/null +++ b/docs/_build/html/_sources/config_input.rst.txt @@ -0,0 +1,220 @@ +Config Input Field +================== + +The ``input`` field indicates where to find any files that you want to use in building the images +or how to set up any objects that require initialization. + +Input Types +----------- + +The ``input`` fields defined by GalSim are: + +* ``catalog`` defines an input catalog that has values for each object. Connected with 'Catalog' value type described in `Config Values`. + + * ``file_name`` = *str_value* (required) The name of the file with the input catalog. + * ``dir`` = *str_value* (default = '.') The directory the file is in. + * ``file_type`` = *str_value* (default = automatically determined from extension of ``file_name``) Valid options are: + + * 'ascii' Read from an ASCII file. + + * ``comments`` = *str_value* (default = '#') The character used to indicate the start of a comment in an ASCII catalog. + + * 'fits' Read from a FITS binary table. + + * ``hdu`` = *int_value* (default = 1) Which hdu to use within the FITS file. Note: 0 means the primary HDU, the first extension is 1. + +* ``dict`` defines an input dictionary, such as a YAML or JSON file. Connected with 'Dict' value type described in `Config Values`. + + * ``file_name`` = *str_value* (required) The name of the dictionary file. + * ``dir`` = *str_value* (default = '.') The directory the file is in. + * ``file_type`` = *str_value* (default = automatically determined from extension of ``file_name``) Valid options are: + + * 'yaml' Read from a YAML file. + * 'json' Read from a JSON file. + * 'pickle' Read from a python pickle file. + + * ``key_split`` = *str_value* (default = '.') For specifying keys below the first level of the dictionary, use this string to split the key value into multiple strings. e.g. If ``key_split`` = '.' (the default) then ``key : galaxy_constants.redshift`` would be parsed as ``dict['galaxy_constants']['redshift']``. Usually '.' is an intuitive choice, but if some of your key names have a '.' in them, then this would not work correctly, so you should pick something else. + +* ``fits_header`` lets you read from the header section of a FITS file. Connected with 'FitsHeader' value type described in `Config Values`. + + * ``file_name`` = *str_value* (required) The name of the FITS file. + * ``dir`` = *str_value* (default = '.') The directory the file is in. + * ``hdu`` = *int_value* (optional) Which HDU to read from the input file. Default is 0 (the primary HDU), unless the compression implies that the first extension should be used. + * ``compression`` = *str_value* (optional) The kind of compression if any. The default is to base the compression on the file extension. e.g. 'blah.fits.fz' implies Rice compression. But it can also be specified explicitly. Supported values are 'none', 'rice', 'gzip', 'bzip2', 'gzip_tile', 'hcompress', 'plio'. + * ``text_file`` = *bool_value* (default = False) Whether the input file is actually a text file, rather than a binary FITS file. Normally the file is taken to be a FITS file, but if this is True, then it will read it as a text file containing the header information, such as the .head file output from SCamp. + +* ``real_catalog`` defines a catalog of real galaxy images. Connected with 'RealGalaxy' profile described in `Config Objects`. + + * ``file_name`` = *str_value* (optional) The name of the file with the input catalog. If omitted, it will try to use the standard catalog in the $PREFIX/share/galsim directory. You can download the COSMOS catalog to that directory with the executable ``galsim_download_cosmos``. + * ``sample`` = *str_value* (optional) A string that can be used to specify the sample to use, either "23.5" or "25.2". At most one of ``file_name`` and ``sample`` should be specified. + * ``dir`` = *str_value* (optional) The directory the file is in (along with related image and noise files). + * ``preload`` = *bool_value* (default = False) Whether to preload all the header information from the catalog fits file into memory at the start. If ``preload=True``, the bulk of the I/O time happens at the start of the processing. If ``preload=False``, there is approximately the same total I/O time (assuming you eventually use most of the image files referenced in the catalog), but it is spread over the various RealGalaxy objects that get built. + +* ``cosmos_catalog`` defines an input catalog that has values for each object. Connected with 'COSMOSCatalog' profile described in `Config Objects`. + + * ``file_name`` = *str_value* (optional) The name of the file with the input catalog. If omitted, it will try to use the standard catalog in the $PREFIX/share/galsim directory. You can download the COSMOS catalog to that directory with the executable ``galsim_download_cosmos``. + * ``sample`` = *str_value* (optional) A string that can be used to specify the sample to use, either "23.5" or "25.2". At most one of ``file_name`` and ``sample`` should be specified. + * ``dir`` = *str_value* (optional) The directory the file is in (along with related image and noise files). + * ``preload`` = *bool_value* (default = False) Whether to preload all the header information from the catalog fits file into memory at the start. If ``preload=True``, the bulk of the I/O time happens at the start of the processing. If ``preload=False``, there is approximately the same total I/O time (assuming you eventually use most of the image files referenced in the catalog), but it is spread over the various RealGalaxy objects that get built. + * ``use_real`` = *bool_value* (default = True) Whether load the RealGalaxy catalog. If this is True, you can request either real or parametric galaxies from the catalog. But if you are only going to use parametric galaxies, then you may set this to False, and it will not bother to load the real galaxies. + * ``exclusion_level`` = *str_value* (default='marginal') Level of additional cuts to make on the galaxies based on the quality of postage stamp definition and/or parametric fit quality [beyond the minimal cuts imposed when making the catalog - see Mandelbaum et al. (2012, MNRAS, 420, 1518) for details]. Valid options are: + + * "none": No cuts. + * "bad_stamp": Apply cuts to eliminate galaxies that have failures in postage stamp definition. These cuts may also eliminate a small subset of the good postage stamps as well. + * "bad_fits": Apply cuts to eliminate galaxies that have failures in the parametric fits. These cuts may also eliminate a small subset of the good parametric fits as well. + * "marginal": Apply the above cuts, plus ones that eliminate some more marginal cases. + + * ``min_hlr`` *float_value* (optional) Exclude galaxies whose fitted half-light radius is smaller than this value (in arcsec). + * ``max_hlr`` *float_value* (optional) Exclude galaxies whose fitted half-light radius is larger than this value (in arcsec). + * ``min_flux`` *float_value* (optional) Exclude galaxies whose fitted flux is smaller than this value (in arcsec). + * ``max_flux`` *float_value* (optional) Exclude galaxies whose fitted flux is larger than this value (in arcsec). + +* ``galaxy_sample`` is a generalization of ``cosmos_catalog`` that provides similar functionality for arbitrary input sample catalogs. Connected with 'SampleGalaxy' profile described in `Config Objects`. + + * ``file_name`` = *str_value* (required) The name of the file with the input catalog. + * ``dir`` = *str_value* (optional) The directory the file is in (along with related image and noise files). + * ``preload`` = *bool_value* (default = False) Whether to preload all the header information from the catalog fits file into memory at the start. If ``preload=True``, the bulk of the I/O time happens at the start of the processing. If ``preload=False``, there is approximately the same total I/O time (assuming you eventually use most of the image files referenced in the catalog), but it is spread over the various RealGalaxy objects that get built. + * ``orig_exptime`` = *float_value* (default = 1.) The exposure time (in seconds) of the original observations. + * ``orig_area`` = *float_value* (default = 1.) The effective collecting area (in cm^2) of the original observations. + * ``use_real`` = *bool_value* (default = True) Whether load the RealGalaxy catalog. If this is True, you can request either real or parametric galaxies from the catalog. But if you are only going to use parametric galaxies, then you may set this to False, and it will not bother to load the real galaxies. + * ``exclusion_level`` = *str_value* (default='none') Level of additional cuts to make on the galaxies based on the quality of postage stamp definition and/or parametric fit quality. Valid options are: + + * "none": No cuts. + * "bad_stamp": Apply cuts to eliminate galaxies that have failures in postage stamp definition. These cuts may also eliminate a small subset of the good postage stamps as well. + * "bad_fits": Apply cuts to eliminate galaxies that have failures in the parametric fits. These cuts may also eliminate a small subset of the good parametric fits as well. + * "marginal": Apply the above cuts, plus ones that eliminate some more marginal cases. + + * ``min_hlr`` = *float_value* (optional) Exclude galaxies whose fitted half-light radius is smaller than this value (in arcsec). + * ``max_hlr`` = *float_value* (optional) Exclude galaxies whose fitted half-light radius is larger than this value (in arcsec). + * ``min_flux`` = *float_value* (optional) Exclude galaxies whose fitted flux is smaller than this value (in arcsec). + * ``max_flux`` = *float_value* (optional) Exclude galaxies whose fitted flux is larger than this value (in arcsec). + * ``cut_ratio`` = *float_value* (optional) For the "bad_stamp" exclusions, cut out any stamps with average adjacent pixels larger than this fraction of the peak pixel count. + * ``sn_limit`` = *float_value* (default = 10.) For the "bad_stamp" exclusions, cut out any stamps with estimated S/N for an elliptical Gaussian less than this limit. + * ``min_mask_dist`` = *float_value* (default = 10.) For the "bad_stamp" exclusions, remove any stamps that have some masked pixels closer to the center than this minimum distance (in pixels). + +* ``nfw_halo`` defines an NFW halo. Connected with 'NFWHaloShear' and 'NFWHaloMagnification' value types described in `Config Values`. + + * ``mass`` = *float_value* (required) The mass of the halo in units of (Msolar / h). + * ``conc`` = *float_value* (required) The concentration parameter, defined as the virial radius / scale radius. + * ``redshift`` = *float_value* (required) The redshift of the halo. + * ``halo_pos`` = *pos_value* (default = 0,0) The position of the halo in world coordinates relative to the origin of the world coordinate system. (Typically you would want to to set ``wcs.origin`` to 'center' to get the halo in the center of the image.) + * ``omega_m`` = *float_value* (default = 1 - ``omega_lam``) + * ``omega_lam`` = *float_value* (default = 1 - ``omega_m`` or 0.7 if neither is specified) + +* ``power_spectrum`` defines a lensing power spectrum. Connected with 'PowerSpectrumShear' and 'PowerSpectrumMagnification' value types described in `Config Values`. + + * ``e_power_function`` = *str_value* (at least one of ``e_power_function`` and ``b_power_function`` is required) A string describing the function of k to use for the E-mode power function. e.g. ``'k**2'``. Alternatively, it may be a file name from which a tabulated power spectrum is read in. + * ``b_power_function`` = *str_value* (at least one of ``e_power_function`` and ``b_power_function`` is required) A string describing the function of k to use for the B-mode power function. e.g. ``'k**2'``. Alternatively, it may be a file name from which a tabulated power spectrum is read in. + * ``delta2`` = *bool_value* (default = False) Whether the function is really Delta^2(k) = k^2 P(k)/2pi rather than P(k). + * ``units`` = *str_value* (default = 'arcsec') The appropriate units for k^-1. The default is to use our canonical units, arcsec, for all position variables. However, power spectra are often more appropriately defined in terms of radians, so that can be specified here to let GalSim handle the units conversion. Other choices are arcmin or degrees. + * ``grid_spacing`` = *float_value* (required for 'Scattered' image type, automatic for 'Tiled') The distance between grid points on which the power spectrum shears are instantiated. + * ``interpolant`` = *str_value* (default = 'Linear') What to use for interpolating between pixel centers. Options are 'Nearest', 'Linear', 'Cubic', 'Quintic', 'Sinc', or 'LanczosN', where the 'N' after 'Lanczos' should be replaced with the integer order to use for the Lanczos filter. + * ``ngrid`` = *int_value* (optional) The number of grid points to use. The default is to use image_size * scale / grid_spacing. + * ``center`` = *pos_value* (optional) The center point of the grid. The default is to use the image center. + * ``index`` = *str_value* (optional) If set, the power spectrum will only be computed when this index changed. E.g. if ``index`` is 'file_num', then it will only update with each new file, not with each image. + * ``variance`` = *float_value* (optional) If set, rescale the overall variance of the generated shears to this value. + +* ``initial_image`` specifies an initial image (read from an FITS file) to draw onto, rather than create a new blank image. + + * ``file_name`` = *str_value* (required) The name fo the file with the desired initial image. + * ``dir`` = *str_value* (default = '.') The directory the file is in. + * ``read_header`` = *bool_value* (default = False) Whether to read the header as well. + +Another feature of the ``input`` field is that it may optionally be a list. So you can have multiple input catalogs for instance; one for object parameters and one for overall image parameters perhaps. When using values with the type 'InputCatalog', you would specify which catalog to use with ``num``. If you only have a single item for one of the inputs, you can omit the ``num`` parameter when you are using it. + +Custom Input Types +------------------ + +To define your own input type, you will need to write an importable Python module +(typically a file in the current directory where you are running ``galsim``, but it could also +be something you have installed in your Python distro) with a class that will be used +to load whatever information you want loaded:: + + class MyInputData(object): + """A class that knows how to load some information from a file or maybe build some + structure in advance that will be used repeatedly in the simulation. + """ + def __init__(self, ...): + pass + +Next you need to write a Loader class that is a subclass of `galsim.config.InputLoader`, +which the config code will use to build your input object with the right initialization kwargs. + +.. autoclass:: galsim.config.InputLoader + :members: + +The main thing you might want to override is the `galsim.config.InputLoader.getKwargs` function +to determine what kwargs you +want to pass to your initialization function or class based on the parameters in the config dict. +The base class, `galsim.config.InputLoader` uses special class attributes, which most GalSim classes +have defined (for precisely this purpose). If you want to follow that same model, and there is no +special setup to do at the start of each image, then you can just use the ``InputLoader`` itself +rather than defining your own subclass. + +In either case, in your Python module, you need to register this function with some name, +which will be the name of the attribute in the ``input`` field that triggers the use of this +Loader object:: + + galsim.config.RegisterInputType('CustomInput', CustomInputLoader(MyInputData)) + +or:: + + galsim.config.RegisterInputType('CustomInput', InputLoader(MyInputData)) + +.. autofunction:: galsim.config.RegisterInputType + + +In the above functions, the ``base`` parameter is the original full configuration dict that is being +used for running the simulation. The ``config`` parameter is the local portion of the full dict +that defines the object being built, which would in this case be ``base['input']['CustomInput']``. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_input.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_input + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_input``, +which will read your file and execute the registration command to add the builder to the list +of valid input types. + +Then you can use this as a valid value type: + +.. code-block:: yaml + + input: + CustomInput: + ... + +For examples of custom inputs, see :gh-link:`des_wcs.py `, +which is used by :gh-link:`meds.yaml `. + +Also `The DES Module` uses custom inputs for the `DES_PSFEx` and `DES_Shapelet` object types, which are used by :gh-link:`draw_psf.yaml `. + +It may also be helpful to look at the GalSim implementation of our various included input types +(click on the ``[source]`` links): + +.. autoclass:: galsim.config.input_cosmos.SampleLoader + +.. autofunction:: galsim.config.input_cosmos._BuildCOSMOSGalaxy + +.. autoclass:: galsim.config.input_nfw.NFWLoader + +.. autofunction:: galsim.config.input_nfw._GenerateFromNFWHaloShear + +.. autofunction:: galsim.config.input_nfw._GenerateFromNFWHaloMagnification + +.. autoclass:: galsim.config.input_powerspectrum.PowerSpectrumLoader + +.. autofunction:: galsim.config.input_powerspectrum._GenerateFromPowerSpectrumShear + +.. autofunction:: galsim.config.input_powerspectrum._GenerateFromPowerSpectrumMagnification + +.. autofunction:: galsim.config.input_real._BuildRealGalaxy + +.. autofunction:: galsim.config.input_real._BuildRealGalaxyOriginal diff --git a/docs/_build/html/_sources/config_objects.rst.txt b/docs/_build/html/_sources/config_objects.rst.txt new file mode 100644 index 00000000000..809959e9232 --- /dev/null +++ b/docs/_build/html/_sources/config_objects.rst.txt @@ -0,0 +1,449 @@ +Config Objects +============== + +GalSim defines a number of object types, which correspond to the GSObject types in the python code. +Some are designed to be appropriate for describing PSFs and others for describing galaxies. +GalSim does not enforce this distinction in any way; you can use any object type in the ``psf`` +or ``gal`` fields. But normally, the ``psf`` field would use types from the `PSF Types` +below, and the ``gal`` field would use types from the `Galaxy Types`. +There are also some `Generic Types` that can be appropriate for either. + +Each object type sets a number of other items that either must or may be present in the dict +for the object (i.e. in the top level ``psf`` or ``gal`` field or farther down in the dict where +an object is being defined, such is in a 'List' object type). These attributes are given as +bullet items for each type defined below. + +There are also some `Other attributes` that are allowed for any object (or +sometimes just galaxies), regardless of what type they are. + +And finally, it is possible to define your own object type, +which we describe in `Custom Object Types`. + +PSF Types +--------- + +* 'Moffat' A Moffat profile: :math:`I(r) \sim (1 + (r/r_0)^2)^{-\beta}`, where :math:`r_0` is the ``scale_radius``. + + * ``beta`` = *float_value* (required) + * ``scale_radius`` = *float_value* (exactly one of ``scale_radius``, ``fwhm`` or ``half_light_radius`` is required) + * ``half_light_radius`` = *float_value* (exactly one of ``scale_radius``, ``fwhm`` or ``half_light_radius`` is required) + * ``fwhm`` = *float_value* (exactly one of ``scale_radius``, ``fwhm`` or ``half_light_radius`` is required) + * ``trunc`` = *float_value* (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation. + +* 'Airy' A simple Airy disk. (Typically one would convolve this by some model of the atmospheric component of the PSF. cf. 'Convolution' below.) + + * ``lam_over_diam`` = *float_value* (either ``lam_over_diam`` or both ``lam`` and ``diam`` required) Lambda / telescope_diameter converted to units of arcsec (or whatever units you want your profile to use). + * ``lam`` = *float_value* or *quantity_value* (either ``lam_over_diam`` or both ``lam`` and ``diam`` required). This should be the wavelength in nanometers. + * ``diam`` = *float_value* or *quantity_value* (either ``lam_over_diam`` or both ``lam`` and ``diam`` required). This should be the telescope diameter in meters. + * ``obscuration`` = *float_value* (default = 0) The linear size of an obstructing secondary mirror as a fraction of the full mirror size. + * ``scale_unit`` = *str_value* (default = 'arcsec') Units to be used for internal calculations when calculating lam/diam. + +* 'Kolmogorov' A Kolmogorov turbulent spectrum: :math:`T(k) \sim \exp(-D(k)/2)`, where :math:`D(k) = 6.8839 (\lambda k/2\pi r0)^{5/3}`. + + * ``lam_over_r0`` = *float_value* (exactly one of ``lam_over_r0``, ``fwhm`` or ``half_light_radius`` or both ``lam`` and ``r0`` is required) Lambda / r0 converted to units of arcsec (or whatever units you want your profile to use). + * ``lam`` = *float_value* or *quantity_value* (exactly one of ``lam_over_r0``, ``fwhm`` or ``half_light_radius`` or both ``lam`` and ``r0`` is required) The wavelength in nanometers. + * ``r0`` = *float_value* or *quantity_value* (exactly one of ``lam_over_r0``, ``fwhm`` or ``half_light_radius`` or both ``lam`` and ``r0`` is required) The Fried parameter in meters. + * ``r0_500`` = *float_value* or *quantity_value* (optional, in lieu of ``r0``). The Fried parameter in meters at a wavelength of 500 nm. The correct ``r0`` value will be calculated using the standard relation r0 = r0_500 * (lam/500)``1.2. + * ``fwhm`` = *float_value* (exactly one of ``lam_over_r0``, ``fwhm`` or ``half_light_radius`` or both ``lam`` and ``r0`` is required) + * ``half_light_radius`` = *float_value* (exactly one of ``lam_over_r0``, ``fwhm`` or ``half_light_radius`` or both ``lam`` and ``r0`` is required) + * ``scale_unit`` = *str_value* (default = 'arcsec') Units to be used for internal calculations when calculating lam/r0. + +* 'OpticalPSF' A PSF from aberrated telescope optics. + + * ``lam_over_diam`` = *float_value* (either ``lam_over_diam`` or both ``lam`` and ``diam`` required) + * ``lam`` = *float_value* or *quantity_value* (either ``lam_over_diam`` or both ``lam`` and ``diam`` required). This should be the wavelength in nanometers. + * ``diam`` = *float_value* or *quantity_value* (either ``lam_over_diam`` or both ``lam`` and ``diam`` required). This should be the telescope diameter in meters. + * ``defocus`` = *float_value* (default = 0) The defocus value, using the Noll convention for the normalization. (Noll index 4) + * ``astig1`` = *float_value* (default = 0) The astigmatism in the y direction, using the Noll convention for the normalization. (Noll index 5) + * ``astig2`` = *float_value* (default = 0) The astigmatism in the x direction, using the Noll convention for the normalization. (Noll index 6) + * ``coma1`` = *float_value* (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 7) + * ``coma2`` = *float_value* (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 8) + * ``trefoil1`` = *float_value* (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 9) + * ``trefoil2`` = *float_value* (default = 0) The defocus value, using the Noll convention for the normalization. (Noll index 10) + * ``spher`` = *float_value* (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 11) + * ``aberrations`` = *list* (optional) This is an alternative way to specify the above aberrations. You can just give them as a list of values using the Noll convention for the ordering (starting at Noll index 1, since there is no 0). With this syntax, you may go to as high order as you want. + * ``circular_pupil`` = *bool_value* (default = True) Whether the pupil should be circular (True, the default) or square (False). + * ``obscuration`` = *float_value* (default = 0) The linear dimension of a central obscuration as a fraction of the pupil linear dimension. + * ``interpolant`` = *str_value* (default = 'quintic') Which interpolant to use for the constructed InterpolatedImage object describing the PSF profile. + * ``oversampling`` = *float_value* (default = 1.5) How much oversampling of the internal image is needed relative to the Nyquist scale of the corresponding Airy profile. The more aberrated the PSF, the higher this needs to be. + * ``pad_factor`` = *float_value* (default = 1.5) How much padding to put around the edge of the internal image of the PSF. + * ``suppress_warning`` = *bool_value* (default = False) Whether to suppress warnings about possible aliasing problems due to the choices of ``oversampling`` and ``pad_factor``. + * ``max_size`` = *float_value* (optional) If the PSF will only be used to draw images of some size, you can set the OpticalPSF class to not build the internal image of the PSF (much) larger than that. This can help speed up calculations if GalSim natively decides to build a very large image of the PSF, when the wings never actually affect the final image. + * ``nstruts`` = *int_value* (default = 0) How many support struts to include. + * ``strut_thick`` = *float_value* (default = 0.05) How thick the struts should be as a fraction of the pupil diameter. + * ``strut_angle`` = *angle_value* (default = 0 degrees) The counter-clockwise angle between the vertical and one of the struts. The rest will be spaced equally from there. + * ``pupil_plane_im`` = *str_value* (optional) Instead of using strut-related parameters to define the pupil plane geometry, you can use this parameter to specify a file-name containing an image of the pupil plane. + * ``pupil_angle`` = *angle_value* (default = 0 degrees) When specifying a pupil_plane_im, use this parameter to rotate it by some angle defined counter-clockwise with respect to the vertical. + * ``scale_unit`` = *str_value* (default = 'arcsec') Units to be used for internal calculations when calculating lam/diam. + +* 'ChromaticAtmosphere' A chromatic PSF implementing both differential chromatic diffraction (DCR) and wavelength-dependent seeing. See `ChromaticAtmosphere` for valid combinations that can be used to set the zenith and parallactic angles needed for DCR. + * ``base_profile`` = *object* (required) The base profile to use for the profile shape at a given reference wavelength + * ``base_wavelength`` = *float_value* (required) The wavelength at which the PSF has the base profile + * ``alpha`` = *float_value* (default = -0.2) Power law index for wavelength-dependent seeing. + * ``zenith_angle`` = *Angle_value* (optional) The zenith angle. + * ``parallactic_angle`` = *Angle_value* (optional) The parallactic angle. + * ``zenith_coord`` = *CelestialCoord* (optional) The (ra,dec) coordinate of the zenith. + * ``HA`` = *Angle_value* (optional) Hour angle of the observation. + * ``latitude`` = *Angle_value* (optional) Latitude of the observatory. + * ``pressure`` = *float_value* or *quantity_value* (default = 69.328) Air pressure in kPa. + * ``temperature`` = *float_value* or *quantity_value* (default = 293.15) Temperature in K. + * ``H2O_pressure`` = *float_value* or *quantity_value* (default = 1.067) Water vapor pressure in kPa. + +Galaxy Types +------------ + +* 'Exponential' A radial exponential profile: :math:`I(r) \sim \exp(-r/r_0)`, where :math:`r_0` is the ``scale_radius``. + + * ``scale_radius`` = *float_value* (exactly one of ``scale_radius`` or ``half_light_radius`` is required) + * ``half_light_radius`` = *float_value* (exactly one of ``scale_radius`` or ``half_light_radius`` is required) + +* 'Sersic' A Sersic profile: :math:`I(r) \sim \exp(-(r/r_0)^{1/n}) = \exp(-b (r/r_e)^{1/n})`, where :math:`r_0` is the ``scale_radius`` and :math:`r_e` is the ``half_light_radius``. + + * ``n`` = *float_value* (required) + * ``half_light_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) + * ``scale_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) + * ``trunc`` = *float_value* (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation. + * ``flux_untruncated`` = *bool_value* (default = False) Set the profile such that the specified flux corresponds to that of the untruncated profile. Valid only when ``trunc`` > 0; ignored otherwise. + +* 'DeVaucouleurs' A DeVaucouleurs profile: :math:`I(r) \sim \exp(-(r/r_0)^{1/4}) = \exp(-b (r/r_e)^{1/4})` (aka n=4 Sersic). + + * ``scale_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) + * ``half_light_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) + * ``trunc`` = *float_value* (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation. + * ``flux_untruncated`` = *bool_value* (default = False) Set the profile such that the specified flux corresponds to that of the untruncated profile. Valid only when ``trunc`` > 0; ignored otherwise. + +* 'Spergel' A profile based on the Spergel (2010) paper with the form: :math:`I(r) \sim (r/r_0)^\nu * K_\nu(r/r_0)` where :math:`r_0` is the ``scale_radius`` and :math:`K_\nu` is the modified Bessel function of the second kind. + + * ``nu`` = *float_value* (required) + * ``half_light_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) + * ``scale_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) + +* 'RealGalaxy' A real galaxy image, typically taken from a deep HST image, deconvolved by the original PSF. Note that the deconvolution implies that this cannot be draw with photon shooting (``image.draw_method = 'phot'``). This requires that ``input.real_catalog`` be specified and uses the following fields: + + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``real_catalog.nobjects``-1; only one of ``id`` or ``index`` may be specified) Which item in the catalog to use. Special: If ``index`` is either a 'Sequence' or 'Random' and ``last`` or ``max`` (respectively) is not specified, then it is automatically set to ``real_catalog.nobjects-1``. + * ``id`` = *str_value* (only one of ``id`` or ``index`` may be specified) The ID in the catalog of the object to use. + * ``x_interpolant`` = *str_value* (default = 'Quintic') What to use for interpolating between pixel centers. Options are 'Nearest', 'Linear', 'Cubic', 'Quintic', 'Sinc', or 'LanczosN', where the 'N' after 'Lanczos' should be replaced with the integer order to use for the Lanczos filter. + * ``k_interpolant`` = *str_value* (default = 'Quintic') What to use for interpolating between pixel centers in Fourier space, for convolution. Options are 'Nearest', 'Linear', 'Cubic', 'Quintic', 'Sinc', or 'LanczosN', where the 'N' after 'Lanczos' should be replaced with the integer order to use for the Lanczos filter. See docstring for this class for caveats about changing this parameter. + * ``flux`` = *float_value* (default = catalog value) If set, this works as described below. However, 'RealGalaxy' has a different default. If ``flux`` is omitted, the flux of the actual galaxy in the catalog is used. + * ``pad_factor`` = *float_value* (default = 4) Amount of zero-padding to use around the image when creating the InterpolatedImage. See docstring for this class for caveats about changing this parameter. + * ``noise_pad_size`` = *float* (optional) If provided, then the original image is padded to a larger image of this size using the noise profile of the original image. This is important if you are using ``noise.whiten`` or ``noise.symmetrize``. You want to make sure the image has the original noise all the way to the edge of the postage stamp. Otherwise, the edges will have the wrong noise profile. + * ``num`` = *int_value* (default = 0) If ``input.real_catalog`` is a list, this indicates which number catalog to use. + +* 'RealGalaxyOriginal' This is the same as 'RealGalaxy' except that the profile is _not_ deconvolved by the original PSF. So this is the galaxy *as observed* in the original image. This requires that ``input.real_catalog`` be specified and uses the same fields as 'RealGalaxy'. This may be more useful than the deconvolved version. For example, unlike 'RealGalaxy', it can be drawn with photon shooting (``image.draw_method = 'phot'``). +* 'COSMOSGalaxy' Either a real or parametric galaxy from the COSMOS catalog. This requires that ``input.cosmos_catalog`` be specified and uses the following fields: + + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``cosmos_catalog.nobjects``-1) Which item in the catalog to use. Special: If ``index`` is either a 'Sequence' or 'Random' and ``last`` or ``max`` (respectively) is not specified, then it is automatically set to ``real_catalog.nobjects-1``. + * ``gal_type`` = *str_vale* (required, unless ``real_catalog.use_real`` is False, in which case 'parametric') Which type of galaxy to use. Options are 'real' or 'parametric'. + * ``noise_pad_size`` = *float_val* (default = 5) The size of a padding region in arcsec around the HST image when ``gal_type='real'``. Only applies to galaxies whose original potage stamp size is smaller than this value; it effectively sets a minimum stamp size with the HST correlated noise for small galaxies. + * ``deep`` = *bool_value* (default = False) Whether the flux and size should be rescaled to approximate a galaxy catalog with a limiting mag of 25 in F814W, rather than 23.5. + * ``noise_pad_size`` = *float* (optional) If provided, then the original image is padded to a larger image of this size using the noise profile of the original image. This is important if you are using ``noise.whiten`` or ``noise.symmetrize``. You want to make sure the image has the original noise all the way to the edge of the postage stamp. Otherwise, the edges will have the wrong noise profile. + * ``sersic_prec`` = *float_value* (default = 0.05) The desired precision on the Sersic index n in parametric galaxies. GalSim is significantly faster if it gets a smallish number of Sersic values, so it can cache some of the calculations and use them again the next time it gets a galaxy with the same index. If ``sersic_prec`` is 0.0, then use the exact value of index n from the catalog. But if it is >0, then round the index to that precision. + * ``chromatic`` = *bool_value* (default = False) Whether to build chromatic profiles. (Only valid if ``gal_type='parametric'``.) + * ``area`` = *float_value* (default = None, which will use the HST collecting area.) The effective collecting area in cm**2 of the telescope being simulated. Used for rescaling the flux values. + * ``exptime`` = *float_value* (default = 1) The exposure time in seconds. Used for rescaling the flux values. (Note: The processed COSMOS ACS/HST science images have units of counts/second; i.e. they have an effective exposure time of 1 second in terms of their flux levels. The default value corresponds to a 1 second exposure on HST, which will match these processed images.) + * ``num`` = *int_value* (default = 0) If ``input.cosmos_catalog`` is a list, this indicates which number catalog to use. + +* 'SampleGalaxy' Either a real or parametric galaxy from an input galaxy sample. This requires that ``input.galaxy_sample`` be specified and uses the following fields: + + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``cosmos_catalog.nobjects``-1) Which item in the catalog to use. Special: If ``index`` is either a 'Sequence' or 'Random' and ``last`` or ``max`` (respectively) is not specified, then it is automatically set to ``real_catalog.nobjects-1``. + * ``gal_type`` = *str_vale* (required, unless ``real_catalog.use_real`` is False, in which case 'parametric') Which type of galaxy to use. Options are 'real' or 'parametric'. + * ``noise_pad_size`` = *float_val* (default = 5) The size of a padding region in arcsec around the HST image when ``gal_type='real'``. Only applies to galaxies whose original potage stamp size is smaller than this value; it effectively sets a minimum stamp size with the HST correlated noise for small galaxies. + * ``deep`` = *bool_value* (default = False) Whether the flux and size should be rescaled to approximate a galaxy catalog with a limiting mag of 25 in F814W, rather than 23.5. + * ``noise_pad_size`` = *float* (optional) If provided, then the original image is padded to a larger image of this size using the noise profile of the original image. This is important if you are using ``noise.whiten`` or ``noise.symmetrize``. You want to make sure the image has the original noise all the way to the edge of the postage stamp. Otherwise, the edges will have the wrong noise profile. + * ``sersic_prec`` = *float_value* (default = 0.05) The desired precision on the Sersic index n in parametric galaxies. GalSim is significantly faster if it gets a smallish number of Sersic values, so it can cache some of the calculations and use them again the next time it gets a galaxy with the same index. If ``sersic_prec`` is 0.0, then use the exact value of index n from the catalog. But if it is >0, then round the index to that precision. + * ``chromatic`` = *bool_value* (default = False) Whether to build chromatic profiles. (Only valid if ``gal_type='parametric'``.) + * ``area`` = *float_value* (default = None, which will use the HST collecting area.) The effective collecting area in cm**2 of the telescope being simulated. Used for rescaling the flux values. + * ``exptime`` = *float_value* (default = 1) The exposure time in seconds. Used for rescaling the flux values. (Note: The processed COSMOS ACS/HST science images have units of counts/second; i.e. they have an effective exposure time of 1 second in terms of their flux levels. The default value corresponds to a 1 second exposure on HST, which will match these processed images.) + * ``num`` = *int_value* (default = 0) If ``input.galaxy_sample`` is a list, this indicates which number catalog to use. + +* 'InclinedExponential' The 2D projection of a 3D exponential profile: :math:`I(R,z) \sim \mathrm{sech}^2 (z/h_s) * \exp(-R/R_s)` at an arbitrary inclination angle, where :math:`h_s` is the ``scale_height`` and :math:`R_s` is the ``scale_radius`` The base profile is inclined along the y-axis, so if you want a different position angle, you should add a ``rotate`` field. + + * ``inclination`` = *angle_value* (required) The inclination angle, defined such that 0 degrees is face-on and 90 degrees is edge-on. + * ``half_light_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) The half-light radius as an alternative to ``scale_radius``. + * ``scale_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) The scale_radius, R_s. + * ``scale_height`` = *float_value* (exactly one of ``scale_height`` or ``scale_h_over_r`` is required) The scale height, h_s. + * ``scale_h_over_r`` = *float_value* (exactly one of ``scale_height`` or ``scale_h_over_r`` is required) The ratio h_s/R_s as an alternative to ``scale_height``. + +* 'InclinedSersic' Like 'InclinedExponential', but using a `Sersic` profile in the plane of the disc. + + * ``n`` = *float_value* (required) + * ``half_light_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) The half-light radius as an alternative to ``scale_radius``. + * ``scale_radius`` = *float_value* (exactly one of ``half_light_radius`` or ``scale_radius`` is required) The scale radius, R_s. + * ``trunc`` = *float_value* (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation. + * ``flux_untruncated`` = *bool_value* (default = False) Set the profile such that the specified flux corresponds to that of the untruncated profile. Valid only when ``trunc`` > 0; ignored otherwise. + * ``scale_height`` = *float_value* (exactly one of ``scale_height`` or ``scale_h_over_r`` is required) The scale height, h_s. + * ``scale_h_over_r`` = *float_value* (exactly one of ``scale_height`` or ``scale_h_over_r`` is required) The ratio h_s/R_s as an alternative to ``scale_height``. + +* 'DeltaFunction' A delta function profile with a specified flux. This is typically not used for galaxies, but rather for stars in a scene that includes both. So when convolved by the PSF, the stars will have the profile from the PSF, but the correct flux. +* 'RandomKnots' A profile made of a sum of a number of delta functions distributed according to either a Gaussian profile or a given specified profile. This is intended to represent knots of star formation, so it would typically be added to a smooth disk component and have the same size and shape. + + * ``npoints`` = *int_value* (required) How many points to include. + * ``half_light_radius`` = *float_value* (either ``half_light_radius`` or ``profile`` is required) The expectation of the half light radius, setting the overall scale of the Gaussian profile. Note: any given realized profile will not necessarily have exactly this half-light radius. + * ``profile`` = *GSObject* (either ``half_light_radius`` or ``profile`` is required) The profile you want to use for the distribution of knots. + + +Generic Types +------------- + +* 'Gaussian' A circular Gaussian profile: :math:`I(r) \sim \exp(-r^2 / (2 \sigma^2))`. This is not all that appropriate for either PSFs or galaxies, but as it is extremely simple, it is often useful for very basic testing, as many measured properties of the profile have analytic values. + + * ``sigma`` = *float_value* (exactly one of ``sigma``, ``fwhm`` or ``half_light_radius`` is required) + * ``fwhm`` = *float_value* (exactly one of ``sigma``, ``fwhm`` or ``half_light_radius`` is required) + * ``half_light_radius`` = *float_value* (exactly one of ``sigma``, ``fwhm`` or ``half_light_radius`` is required) + +* 'InterpolatedImage' A profile described simply by a provided image (given in a fits file). + + * ``image`` = *str_value* (required) The file name from which to read the image. + * ``x_interpolant`` = *str_value* (default = 'Quintic') What to use for interpolating between pixel centers. Options are 'Nearest', 'Linear', 'Cubic', 'Quintic', 'Sinc', or 'LanczosN', where the 'N' after 'Lanczos' should be replaced with the integer order to use for the Lanczos filter. + * ``k_interpolant`` = *str_value* (default = 'Quintic') What to use for interpolating between pixel centers in Fourier space, for convolution. Options are 'Nearest', 'Linear', 'Cubic', 'Quintic', 'Sinc', or 'LanczosN', where the 'N' after 'Lanczos' should be replaced with the integer order to use for the Lanczos filter. See docstring for this class for caveats about changing this parameter. + * ``normalization`` = *str_value* (default = 'flux') What normalization to assume for the input image. Options are ('flux' or 'f') or ('surface brightness' or 'sb'). + * ``scale`` = *float_value* (default = 'GS_SCALE' entry from the fits header, or 1 if not present) What pixel scale to use for the image pixels. + * ``pad_factor`` = *float_value* (default = 4) Amount of zero-padding to use around the image when creating the SBInterpolatedImage. See docstring for this class for caveats about changing this parameter. + * ``noise_pad_size`` = *float_value* (optional; required if ``noise_pad`` is provided) If non-zero, then the original image is padded to a larger image of this size using the noise specified in ``noise_pad``. + * ``noise_pad`` = *str_value* (optional; required if ``noise_pad_size`` is provided) Either a filename to use for padding the ``image`` with noise according to a noise correlation function, or a variance value to pad with Gaussian noise. + * ``pad_image`` = *str_value* (optional) The name of an image file to use for directly padding the ``image`` (deterministically) rather than padding with noise. + * ``calculate_stepk`` = *bool_value* (default = True) Recalculate optimal Fourier space separation for convolutions? Can lead to significant optimization compared to default values. + * ``calculate_maxk`` = *bool_value* (default = True) Recalculate optimal Fourier space total k range for convolutions? Can lead to significant optimization compared to default values. + * ``use_true_center`` = *bool_value* (default = True) Whether to use the true center of the provided image as the nominal center of the profile (True) or round up to the nearest integer value (False). + * ``hdu`` = *int_value* (default = the primary HDU for uncompressed images, or the first extension for compressed images) Which HDU to use from the input FITS file. + +* 'Box' A rectangular boxcar profile: :math:`I(x,y) \sim H(w/2-|x|) H(h/2-|y|)`, + where :math:`H` is the Heaviside function, :math:`w` is ``width`` and :math:`h` is ``height``. + + * ``width`` = *float_value* (required) The full width of the profile. + * ``height`` = *float_value* (required) The full height of the profile. + +* 'Pixel' A square boxcar profile: :math:`I(x,y) \sim H(s/2-|x|) H(s/2-|y|)`, + where :math:`H` is the Heaviside function and :math:`s` is ``scale``. + This is equivalent to a 'Box' type with ``width`` = ``height`` (called ``scale`` here). Note however, that the default rendering method already correctly accounts for the pixel response, so normally you will not need to use this as part of the PSF (or galaxy). + + * ``scale`` = *float_value* The pixel scale, which is the width and height of the pixel. + +* 'TopHat' A circular tophat profile: :math:`I(r) \sim H(r-|r|)`, where :math:`H` is the + Heaviside function and :math:`r` is ``radius``. + + * ``radius`` = *float_value* The radius of the circular tophat profile. + +* 'Sum' or 'Add' Add several profiles together. + + * ``items`` = *list* (required) A list of profiles to be added. + +* 'Convolution' or 'Convolve' Convolve several profiles together. + + * ``items`` = *list* (required) A list of profiles to be convolved. + +* 'List' Select profile from a list. + + * ``items`` = *list* (required) A list of profiles. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) Which item in the list to select each time. + +* 'Eval' Use Python's ``eval`` function to evaluate a given string as a GSObject. + + * ``str`` = *str_value* (required) The string to evaluate. + +Other Attributes +---------------- + +There are a number of transformation attributes that are always allowed for any object type. +Some of these operations do not commute with each other, so the order is important. +The following transformations will be applied in the order given here, which corresponds +roughly to when they occur to the light packet traveling through the universe. + +The first few, ``flux``, ``dilate``, ``ellip``, and ``rotate``, are typically used to define the +intrinsic profile of the object. +The next two, ``magnify`` and ``shear``, are typically used to define how the profile is +modified by lensing. +The next one, ``shift``, is used to shift the position of the galaxy relative to its nominal +position on the sky. + +* ``flux`` = *float_value* (default = 1.0) Set the flux of the object in ADU. Note that the component ``items`` in a 'Sum' can also have fluxes specified, which can be used as fractional fluxes (e.g. 0.6 for the disk and 0.4 for the bulge). Then the outer level profile can set the real flux for the whole thing. +* ``dilate`` or ``dilation`` = *float_value* (optional) Dilate the profile by a given scale, preserving the flux. +* ``ellip`` = *shear_value* (optional) Shear the profile by a given shear to give the profile some non-round intrinsic shape. +* ``rotate`` or ``rotation`` = *angle_value* (optional) Rotate the profile by a given angle. +* ``scale_flux`` = *float_value* (optional) Factor by which to scale the flux of the galaxy profile. +* ``magnify`` or ``magnification`` = *float_value* (optional) Magnify the profile by a given scale, preserving the surface brightness. +* ``shear`` = *shear_value* (optional) Shear the profile by a given shear. +* ``shift`` = *pos_value* (optional) Shift the centroid of the profile by a given amount relative to the center of the image on which it will be drawn. +* ``skip`` = *bool_value* (default=False) Skip this object. +* ``sed`` = *SED* (optional) If desired, you may set an SED to use for the object. See `SED Field` below for details. + +There are also a few special attributes that are only allowed for the top-level ``gal`` field, +not for objects that are part of an aggregate object like 'Sum', 'Convolution' or 'List' +and not for ``psf``. + +* ``resolution`` = *float_value* (optional) If the base profile allows a ``half_light_radius`` parameter, and the psf is able to calculate a ``half_light_radius``, then it is permissible to specify a resolution: resolution = r_gal / r_psf (where r_gal and r_psf are the half-light radii) in lieu of specifying the ``half_light_radius`` of the galaxy explicitly. This is especially useful if the PSF size is generated randomly. +* ``signal_to_noise`` = *float_value* (optional) You may specify a signal-to-noise value rather than a ``flux``. Our definition of the S/N derives from a weighted integral of the flux in the drawn image: + :math:`S = \sum W(x,y) I(x,y) / \sum W(x,y)` where :math:`W(x,y)` is taken to be a matched filter, so :math:`W(x,y) = I(x,y)`. (Note: This currently requires ``draw_method = 'fft'``. It is a bit trickier to do this for photon shooting, and we have not enabled that yet.) +* ``redshift`` = *float_value* (optional) The redshift of the galaxy. This is required when using 'NFWHaloShear' or 'NFWHaloMagnification'. But note that this is only valid for achromatic objects. For chromatic objects, the redshift should be applied to the SED instead. + + +Custom Object Types +------------------- + +To define your own object type, you will need to write an importable Python module +(typically a file in the current directory where you are running ``galsim``, but it could also +be something you have installed in your Python distro) with a function that will be used +to build a GalSim `GSObject`. + +The build function should have the following functional form: + +.. code-block:: py + + def BuildCustomObject(config, base, ignore, gsparams, logger): + """Build a custom GSObject of some sort + + Parameters: + config: The configuration dict of the object being built + base: The base configuration dict. + ignore: A list of parameters that might be in the config dict, + but which may be ignored. i.e. it is not an error for + these items to be present. + gsparams: An optional dict of items used to build a GSParams object + (may be None). + logger: An optional logger object to log progress (may be None). + + Returns: + gsobject, safe + + The returned gsobject is the built GSObject instance, and safe is a bool + value that indicates whether the object is safe to reuse for future stamps + (e.g. if all the parameters used to build this object are constant and will + not change for later stamps). + """ + # If desired, log some output. + if logger: + logger.debug("Starting work on building CustomObject") + + # The gsparams are passed around using a dict so they can be easily added to. + # At this point, we would typically convert them to a regular GSParams + # instance to use when building the GSObject. + if gsparams: + gsparams = galsim.GSParams( **gsparams ) + + # If you need a random number generator, this is the one to use. + rng = base['rng'] + + # Build the GSObject + # Probably something complicated that you want this function to do. + gsobject = [...] + + safe = False # typically, but set to True if this object is safe to reuse. + return gsobject, safe + +The ``base`` parameter is the original full configuration dict that is being used for running the +simulation. The ``config`` parameter is the local portion of the full dict that defines the object +being built, e.g. ``config`` might be ``base['gal']`` or it might be farther down as an item in +the ``items`` attribute of a 'List' or 'Sum' object. + +Then, in the Python module, you need to register this function with some type name, which will +be the value of the ``type`` attribute that triggers running this function:: + + galsim.config.RegisterObjectType('CustomObject', BuildCustomObject) + +.. autofunction:: galsim.config.RegisterObjectType + +If the builder will use a particular input type, you should let GalSim know this by specifying +the ``input_type`` when registering. E.g. if the builder expects to use an input ``dict`` file +to define some properties that will be used, you would register this fact using:: + + galsim.config.RegisterObjectType('CustomObject', BuildCustomObject, + input_type='dict') + +The input object can be accessed in the build function as e.g.:: + + input_dict = galsim.config.GetInputObj('dict', config, base, 'CustomObject') + ignore = ignore + ['num'] + +The last argument is just used to help give sensible error messages if there is some problem, +but it should typically be the name of the object type being built. When you are using an +input object, the 'num' attribute is reserved for indicating which of possibly several input +objects (``dict`` in this case) to use. You should not also define a num attribute that has +some meaning for this object type. If you are using the ``ignore`` parameter to check for extra +invalid parameters, you would thus want to add 'num' to the list. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_object.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_object + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_object``, +which will read your file and execute the registration command to add the object to the list +of valid object types. + +Then you can use this as a valid object type: + +.. code-block:: yaml + + gal: + type: CustomObject + ... + +For examples of custom objects, see :gh-link:`des_psfex.py ` +and :gh-link:`des_shapelet.py ` +in the galsim.des module, which define custom object types DES_PSFEx and DES_Shapelet. These objects are used by :gh-link:`draw_psf.yaml ` +in the ``GalSim/examples/des`` directory. +It may also be helpful to look at the GalSim implementation of some of the included object builders (click on the ``[source]`` links): + +.. autofunction:: galsim.config.gsobject._BuildAdd + +.. autofunction:: galsim.config.gsobject._BuildConvolve + +.. autofunction:: galsim.config.gsobject._BuildList + +.. autofunction:: galsim.config.gsobject._BuildOpticalPSF + + +SED Field +--------- + +If you want your object to have a non-trivial wavelength dependence, you can include an +``sed`` parameter to define its `SED`. Currently, there is only one defined +type to use for the SED, but the code is written in a modular way to allow for +other types, including custom SED types. + +* 'FileSED' is the default type here, and you may omit the type name when using it. + + * ``file_name`` = *str_value* (required) The file to read in. + * ``wave_type`` = *str_value* or *unit_value* (required) The unit of the wavelengths in the file ('nm' or 'Ang' or variations on these -- cf. `SED`) + * ``flux_type`` = *str_value* (required) The type of spectral density or dimensionless normalization used in the file ('flambda', 'fnu', 'fphotons' or '1' -- cf. `SED`) + * ``redshift`` = *float_value* (optional) If given, shift the spectrum to the given redshift. You can also specify the redshift as an object-level parameter if preferred. + * ``norm_flux_density`` = *float_value* or *quantity_value* (optional) Set a normalization value of the flux density at a specific wavelength. If given, ``norm_wavelength`` is required. + * ``norm_wavelength`` = *float_value* or *quantity_value* (optional) The wavelength to use for the normalization flux density. + * ``norm_flux`` = *float_value* (optional) Set a normalization value of the flux over a specific bandpass. If given, ``norm_bandpass`` is required. + * ``norm_bandpass`` = *Bandpass* (optional) The bandpass to use for the normalization flux. + +You may also define your own custom SED type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of `galsim.config.SEDBuilder`. + +.. autoclass:: galsim.config.SEDBuilder + :members: + +Then, as usual, you need to register this type using:: + + galsim.config.RegisterSEDType('CustomSED', CustomSEDBuilder()) + +.. autofunction:: galsim.config.RegisterSEDType + +and tell the config parser the name of the module to load at the start of processing. + +.. code-block:: yaml + + modules: + - my_custom_sed + +Then you can use this as a valid sed type: + +.. code-block:: yaml + + gal: + ... + sed: + type: CustomSED + ... diff --git a/docs/_build/html/_sources/config_output.rst.txt b/docs/_build/html/_sources/config_output.rst.txt new file mode 100644 index 00000000000..e71306abc31 --- /dev/null +++ b/docs/_build/html/_sources/config_output.rst.txt @@ -0,0 +1,256 @@ +Config Output Field +=================== + +The ``output`` field indicates where to write the output files and what kind of output format +they should be. + +.. note:: + + **Multiprocessing** + + The config processing can use python multiprocessing to split the work among multiple + processes on a single node. This can be done either at the file level or the image level. + If you set output.nproc != 1, then it will parallelize the creation of files, building + and writing each file in a separate process. If you instead set image.nproc != 1, then + the files will be built one at a time, but the work for drawing the objects will be + parallelized across the processes. + + There are tradeoffs between these two kinds of multiprocessing that the user should be + aware of. Python multiprocessing uses pickle to pass information between processes. + In the image-based multiprocessing, each process builds a postage stamp image for each + object and sends that stamp back to the main process to assemble into the final image. + If the objects are all very easy to draw, this communication can end up dominating the + run time as python will pickle the image data to send back to the main process. + + File-based multiprocessing has much less communication between processes, since each image + is fully built and written all in a single process. However, this kind of multiprocessing + often requires more memory, since each process holds a full image to be written to disk + as it is building it. Users should consider this tradeoff carefully when deciding which + kind of multiprocessing (if either) is appropriate for their use case. + + Finally, one last caveat about multiprocessing. Galsim turns off OpenMP threading when + in a multiprocessing context, so you don't, for instance, have 64 processes, each spawning + 64 OpenMP threads at once. This works for OpenMP, but not some other sources of threading + that may be initiated by numpy functions. If you get errors related to being unable to + create threads, you should install (via pip or conda) the ``threadpoolctl`` package. + If this package is installed, GalSim will use it to turn off threading for all of the + possible backends used by numpy. + + +Output Field Attributes +----------------------- + +All output types use the following attributes to specify the location and number +of output files, or aspects of how to build and write the output files. + +* ``file_name`` = *str_value* (default = '\.fits') You would typically want to specify this explicitly, but if you do not, then if the configuration file is called my_test.yaml, the output file would be my_test.fits. +* ``dir`` = *str_value* (default = '.') In which directory should the output file be put. +* ``nfiles`` = *int_value* (default = 1) How many files to build. Note: if ``nfiles`` > 1, then ``file_name`` and/or ``dir`` should not be a simple string. Rather it should be some generated string that provides a different save location for each file. See the section below on setting *str_value*. +* ``nproc`` = *int_value* (default = 1) Specify the number of processors to use when building files. If nproc <= 0, then this means to try to automatically figure out the number of cpus and use that. If you are doing many files, it is often more efficient to split up the processes at this level rather than when drawing the postage stamps (which is what ``image.nproc`` means). +* ``timeout`` = *float_value* (default = 3600) Specify the number of seconds to allow for each job when multiprocessing before the multiprocessing queue times out. The default is generally appropriate to prevent jobs from hanging forever from some kind of multiprocessing snafu, but if your jobs are expected to take more than an hour per output file, you might need to increase this. +* ``skip`` = *bool_value* (default = False) Specify files to skip. This would normally be an evaluated boolean rather than simply True or False of course. e.g. To only do the fifth file, you could use ``skip : { type : Eval, str : 'ffile_num != 4' }``, which may be useful during debugging if you are trying to diagnose a problem in one particular file. +* ``noclobber`` = *bool_value* (default = False) Specify whether to skip building files that already exist. This may be useful if you are running close to the memory limit on your machine with multiprocessing. e.g. You could use ``nproc`` > 1 for a first run using multiprocessing, and then run again with ``nproc`` = 1 and ``noclobber`` = True to clean up any files that failed from insufficient memory during the multiprocessing run. +* ``retry_io`` = *int_value* (default = 0) How many times to retry the write command if there is any kind of failure. Some systems have trouble with multiple concurrent writes to disk, so if you are doing a big parallel job, this can be helpful. If this is > 0, then after an OSError exception on the write command, the code will wait an increasing number of seconds (starting with 1 for the first failure), and then try again up to this many times. + +Output Types +------------ + +The default output type is 'Fits', which means to write a FITS file with the constructed +image in the first HDU. But other types are possible, which are specified as usual with a +``type`` field. Other types may define additional allowed and/or required fields. +The output types defined by GalSim are: + +* 'Fits' A simple fits file. This is the default if ``type`` is not given. +* 'MultiFits' A multi-extension fits file. + + * ``nimages`` = *int_value* (default if using an input catalog and the image type is 'Single' is the number of entries in the input catalog; otherwise required) The number of hdu extensions on which to draw an image. + +* 'DataCube' A fits data cube. + + * ``nimages`` = *int_value* (default if using an input catalog and the image type is 'Single' is the number of entries in the input catalog; otherwise required) The number of images in the data cube (i.e. the third dimension of the cube). + +Custom Output Types +------------------- + +To define your own output type, you will need to write an importable Python module +(typically a file in the current directory where you are running ``galsim``, but it could also +be something you have installed in your Python distro) with a class that will be used +to build the output file. + +The class should be a subclass of ``galsim.config.OutputBuilder``, which is the class used for +the default 'Fits' type. There are a number of class methods, and you only need to override +the ones for which you want different behavior than that of the 'Fits' type. + +.. autoclass:: galsim.config.OutputBuilder + :members: + +The ``base`` parameter is the original full configuration dict that is being used for running the +simulation. The ``config`` parameter is the local portion of the full dict that defines the object +being built, which would typically be ``base['output']``. + +Then, in the Python module, you need to register this function with some type name, which will +be the value of the ``type`` attribute that triggers the use of this Builder object:: + + galsim.config.RegisterOutputType('CustomOutput', CustomOutputBuilder()) + +.. autofunction:: galsim.config.RegisterOutputType + +Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple output types use the same class instantiated with different +initialization parameters. This is not used by the GalSim output types, but there may be use +cases where it would be useful for custom output types. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_output.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_output + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_output``, +which will read your file and execute the registration command to add the builder to the list +of valid output types. + +Then you can use this as a valid output type: + +.. code-block:: yaml + + output: + type: CustomOutput + ... + +For an example of a custom output type, see `MEDSBuilder` in `The DES Module`, +which is used by :gh-link:`meds.yaml `. + +It may also be helpful to look at the GalSim implementation of the included output types +(click on the ``[source]`` links): + +.. autoclass:: galsim.config.output_datacube.DataCubeBuilder + :show-inheritance: + +.. autoclass:: galsim.config.output_multifits.MultiFitsBuilder + :show-inheritance: + +Extra Outputs +------------- + +In addition to the fields for defining the main output file(s), there may also be fields +specifying optional "extra" outputs. Either extra files to be written, or sometimes extra HDUs +to be added to the main FITS files. These extra output fields are dicts that may have a number +of parameters defining how they should be built or where they should be written. + +* ``psf`` will output (typically) noiseless images of the PSF used for each galaxy. + + * ``file_name`` = *str_value* (either ``file_name`` or ``hdu`` is required) Write the psf image to a different file (in the same directory as the main image). + * ``hdu`` = *int_value* (either ``file_name`` or ``hdu`` is required) Write the psf image to another hdu in the main file. (This option is only possible if ``type`` == 'Fits') Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0. + * ``dir`` = *str_value* (default = ``output.dir`` if that is provided, else '.') (Only relevant if ``file_name`` is provided.) + * ``draw_method`` = *str_value* (default = 'auto') The same options are available as for the ``image.draw_method`` item, but now applying to the rendering of the psf images. + * ``shift`` = *pos_value* (optional) A shift to apply to the PSF object. Special: if this is 'galaxy' then apply the same shift as was applied to the galaxy. + * ``offset`` = *pos_value* (optional) An offset to apply when drawing the PSF object. Special: if this is 'galaxy' then apply the same offset as was applied when drawing the galaxy. + * ``signal_to_noise`` = *float_value* (optional) If provided, noise will be added at the same level as the main image, and the flux will be rescaled to result in the provided signal-to-noise. The default is to use flux=1 and not add any noise. + +* ``weight`` will output the weight image (an inverse variance map of the noise properties). + + * ``file_name`` = *str_value* (either ``file_name`` or ``hdu`` is required) Write the weight image to a different file (in the same directory as the main image). + * ``hdu`` = *int_value* (either ``file_name`` or ``hdu`` is required) Write the weight image to another hdu in the main file. (This option is only possible if ``type`` == 'Fits') Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0. + * ``dir`` = *str_value* (default = ``output.dir`` if that is provided, else '.') (Only relevant if ``file_name`` is provided.) + * ``include_obj_var`` = *bool_value* (default = False) Normally, the object variance is not included as a component for the inverse variance map. If you would rather include it, set this to True. + +* ``badpix`` will output the bad-pixel mask image. This will be relevant when we eventually add the ability to add defects to the images. For now the bad-pixel mask will be all 0s. + + * ``file_name`` = *str_value* (either ``file_name`` or ``hdu`` is required) Write the bad pixel mask image to a different file (in the same directory as the main image). + * ``hdu`` = *int_value* (either ``file_name`` or ``hdu`` is required) Write the bad pixel mask image to another hdu in the main file. (This option is only possible if ``type`` == 'Fits') Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0. + * ``dir`` = *str_value* (default = ``output.dir`` if that is provided, else '.') (Only relevant if ``file_name`` is provided.) + +* ``truth`` will output a truth catalog. Note: assuming you are using the ``galsim`` executable to process the config file, the config dict is really read in as an OrderedDict, so the columns in the output catalog will be in the same order as in the YAML file. If you are doing this manually and just use a regular Python dict for config, then the output columns will be in some arbitrary order. + + * ``file_name`` = *str_value* (either ``file_name`` or ``hdu`` is required) Write the bad pixel mask image to a different file (in the same directory as the main image). + * ``hdu`` = *int_value* (either ``file_name`` or ``hdu`` is required) Write the bad pixel mask image to another hdu in the main file. (This option is only possible if ``type`` == 'Fits') Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0. + * ``dir`` = *str_value* (default = ``output.dir`` if that is provided, else '.') (Only relevant if ``file_name`` is provided.) + * ``columns`` = *dict* (required) A dict connecting the names of the output columns to the values that should be output. The values can be specified in a few different ways: + + * A string indicating what current value in the config dict to use. e.g. 'gal.shear.g1' would grab the value of config['gal']['shear']['g1'] that was used for the current object. + * A dict that should be evaluated in the usual way values are evaluated in the config processing. Caveat: Since we do not have a way to indicate what type the return value should be, this functionality is mostly limited to 'Eval' and 'Current' types, which is normally fine, since it would mostly be useful for just doing some extra processing to some current value. + * An implicit Eval string starting with '$', typically using '@' values to get Current values. e.g. to output e1-style shapes for a Shear object that was built with (g1,g2), you could write '$(@gal.ellip).e1' and '$(@gal.ellip).e2'. + * A straight value. Not usually very useful, but allowed. e.g. You might want your truth catalogs to have a consistent format, but some simulations may not define a particular value. You could just output -999 (or anything) for that column in those cases. + +Adding your own Extra Output Type +--------------------------------- + +You can also add your own extra output type in a similar fashion as the other custom types that +you can define. (cf. e.g. [Custom Output Types](#custom-output-types)) As usual, you would +write a custom module that can be imported, which should contain a class for building and +writing the extra output, register it with GalSim, and add the module to the ``modules`` field. + +The class should be a subclass of ``galsim.config.ExtraOutputBuilder``. You may override any +of the following methods. + +.. autoclass:: galsim.config.ExtraOutputBuilder + :members: + +Then, in the Python module, you need to register this function with some type name, which will +be the value of the attribute in the ``output`` field that triggers the use of this Builder object:: + + galsim.config.RegisterExtraOutput('CustomExtraOutput', CustomExtraOutputBuilder()) + +.. autofunction:: galsim.config.RegisterExtraOutput + +Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple output types use the same class instantiated with different +initialization parameters. This is not used by the GalSim output types, but there may be use +cases where it would be useful for custom output types. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_output.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_output + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_output``, +which will read your file and execute the registration command to add the builder to the list +of valid output types. + +Then you can use this as a valid extra output directive: + +.. code-block:: yaml + + output: + custom_extra_output: + ... + +For examples of custom extra outputs, see + +* :gh-link:`blend.yaml ` +* :gh-link:`blendset.yaml ` + +which use custom extra outputs ``deblend`` and ``deblend_meds`` defined in :gh-link:`blend.py `. + +Also, + +* :gh-link:`cgc.yaml ` + +which uses custom extra output ``noise_free`` defined in :gh-link:`noise_free.py `. + +It may also be helpful to look at the GalSim implementation of the included extra output types +(click on the ``[source]`` links): + +.. autoclass:: galsim.config.extra_psf.ExtraPSFBuilder + :show-inheritance: + +.. autoclass:: galsim.config.extra_truth.TruthBuilder + :show-inheritance: + +.. autoclass:: galsim.config.extra_weight.WeightBuilder + :show-inheritance: + +.. autoclass:: galsim.config.extra_badpix.BadPixBuilder + :show-inheritance: diff --git a/docs/_build/html/_sources/config_process.rst.txt b/docs/_build/html/_sources/config_process.rst.txt new file mode 100644 index 00000000000..4473d759d7a --- /dev/null +++ b/docs/_build/html/_sources/config_process.rst.txt @@ -0,0 +1,220 @@ +Config Processing From Python +============================= + +It is also possible to run the config processing from a Python script, rather than using +`the galsim executable`. An example of this can be found in +:gh-link:`demo8 `. + +Running the Whole Script +------------------------ + +The following functions are relevant to running the whole config script from Python: + +.. autofunction:: galsim.config.ReadConfig + +.. autofunction:: galsim.config.CopyConfig + +.. autofunction:: galsim.config.ImportModules + +.. autofunction:: galsim.config.ProcessTemplate + +.. autofunction:: galsim.config.ProcessAllTemplates + +.. autofunction:: galsim.config.Process + + +Building Files +-------------- + +The following functions are relevant to building one or more files as specified by a config +dict: + +.. autofunction:: galsim.config.BuildFiles + +.. autofunction:: galsim.config.BuildFile + +.. autofunction:: galsim.config.GetNFiles + +.. autofunction:: galsim.config.GetNImagesForFile + +.. autofunction:: galsim.config.GetNObjForFile + +.. autofunction:: galsim.config.SetupConfigFileNum + + +Building Images +--------------- + +The following functions are relevant to building one or more images as specified by a config +dict: + +.. autofunction:: galsim.config.BuildImages + +.. autofunction:: galsim.config.BuildImage + +.. autofunction:: galsim.config.SetupConfigImageSize + +.. autofunction:: galsim.config.SetupConfigImageNum + +.. autofunction:: galsim.config.GetNObjForImage + +.. autofunction:: galsim.config.FlattenNoiseVariance + +.. autofunction:: galsim.config.BuildWCS + +.. autofunction:: galsim.config.AddSky + +.. autofunction:: galsim.config.AddNoise + +.. autofunction:: galsim.config.CalculateNoiseVariance + +.. autofunction:: galsim.config.AddNoiseVariance + +.. autofunction:: galsim.config.GetSky + + +Building Stamps +--------------- + +The following functions are relevant to building one or more stamps as specified by a config +dict: + +.. autofunction:: galsim.config.BuildStamps + +.. autofunction:: galsim.config.BuildStamp + +.. autofunction:: galsim.config.SetupConfigStampSize + +.. autofunction:: galsim.config.SetupConfigObjNum + +.. autofunction:: galsim.config.DrawBasic + + +Building Objects +---------------- + +The following functions are relevant to building individual objects as specified by a config +dict: + +.. autofunction:: galsim.config.BuildGSObject + +.. autofunction:: galsim.config.UpdateGSParams + +.. autofunction:: galsim.config.TransformObject + +.. autoclass:: galsim.config.SkipThisObject + + +Generating Values +----------------- + +The following functions are relevant to generating and accessing individual values as specified by +a config dict: + +.. autofunction:: galsim.config.ParseValue + +.. autofunction:: galsim.config.GetCurrentValue + +.. autofunction:: galsim.config.EvaluateCurrentValue + +.. autofunction:: galsim.config.SetDefaultIndex + +.. autofunction:: galsim.config.CheckAllParams + +.. autofunction:: galsim.config.GetAllParams + +.. autofunction:: galsim.config.ParseWorldPos + + +Using Input Fields +------------------ + +The following functions are relevant to processing and using input fields in a config dict: + +.. autofunction:: galsim.config.ProcessInput + +.. autofunction:: galsim.config.ProcessInputNObjects + +.. autofunction:: galsim.config.SetupInput + +.. autofunction:: galsim.config.SetupInputsForImage + +.. autofunction:: galsim.config.GetInputObj + + +Processing Extra Outputs +------------------------ + +The following functions are relevant to processing extra output fields in a config dict: + +.. autofunction:: galsim.config.SetupExtraOutput + +.. autofunction:: galsim.config.SetupExtraOutputsForImage + +.. autofunction:: galsim.config.ProcessExtraOutputsForStamp + +.. autofunction:: galsim.config.ProcessExtraOutputsForImage + +.. autofunction:: galsim.config.WriteExtraOutputs + +.. autofunction:: galsim.config.AddExtraOutputHDUs + +.. autofunction:: galsim.config.CheckNoExtraOutputHDUs + +.. autofunction:: galsim.config.GetFinalExtraOutput + + +Config Utilities +---------------- + +The following functions are used internally by the various ``galsim.config`` functions, +but they might be useful for some users. + +.. autoclass:: galsim.config.LoggerWrapper + :members: + +.. autofunction:: galsim.config.ReadYaml + +.. autofunction:: galsim.config.ReadJson + +.. autofunction:: galsim.config.MergeConfig + +.. autofunction:: galsim.config.ConvertNones + +.. autofunction:: galsim.config.RemoveCurrent + +.. autofunction:: galsim.config.GetLoggerProxy + +.. autofunction:: galsim.config.UpdateNProc + +.. autofunction:: galsim.config.ParseRandomSeed + +.. autofunction:: galsim.config.PropagateIndexKeyRNGNum + +.. autofunction:: galsim.config.SetupConfigRNG + +.. autofunction:: galsim.config.ParseExtendedKey + +.. autofunction:: galsim.config.GetFromConfig + +.. autofunction:: galsim.config.SetInConfig + +.. autofunction:: galsim.config.UpdateConfig + +.. autofunction:: galsim.config.MultiProcess + +.. autofunction:: galsim.config.GetIndex + +.. autofunction:: galsim.config.GetRNG + +.. autofunction:: galsim.config.CleanConfig + +.. autofunction:: galsim.config.SetDefaultExt + +.. autofunction:: galsim.config.RetryIO + +.. autofunction:: galsim.config.MakeImageTasks + +.. autofunction:: galsim.config.MakeStampTasks + +.. autofunction:: galsim.config.RegisterInputConnectedType diff --git a/docs/_build/html/_sources/config_special.rst.txt b/docs/_build/html/_sources/config_special.rst.txt new file mode 100644 index 00000000000..2fa7bee5914 --- /dev/null +++ b/docs/_build/html/_sources/config_special.rst.txt @@ -0,0 +1,207 @@ +Special Fields +============== + +There are a couple of other top level fields that act more in a support role, rather than being +part of the main processing. + +modules +------- + +Almost all aspects of the file building can be customized by the user if the existing GalSim +types do not do precisely what you need. How to do this is described in the pages about +each of the different top-level fields. In all cases, you need to tell GalSim what Python +modules to load at the start of processing to get the implementations of your custom types. +That is what this field is for. + +The ``modules`` field should contain a list of modules that GalSim should import before +processing the rest of the config file. These modules can be either in the current directory +where you are running the code or installed in your Python distro. (Or technically, they +need to be located in a directory in ``sys.path``.) + +See: + +* :gh-link:`meds.yaml ` +* :gh-link:`blendset.yaml ` +* :gh-link:`cgc.yaml ` + +for some examples of this field. + +eval_variables +-------------- + +Sometimes, it can be useful to define some configuration parameters right at the top of the +config file that might be used farther down in the file somewhere to highlight them. +Or sometimes, there are calculations that are needed by several different values in the +config file, which you only want to calculate once. + +You can put such values in a top-level ``eval_variables`` field. They work just like +variables that you define for `Eval ` +items, but they can be placed separately from those evaluations. + +For examples of this field, see: + +* :gh-link:`demo11.yaml ` +* :gh-link:`draw_psf.yaml ` +* :gh-link:`cgc.yaml ` + +template +-------- + +This feature directs the config processing to first load in some other file (or specific +field with that file) and then possibly modify some components of that dict. + +To load in some other config file named ``config.yaml``, you would write:: + + template: config.yaml + +If you only want to load a particular field from that file, say the ``image`` field, you could +write:: + + template: config.yaml:image + +The template field may appear anywhere in the config file. Wherever it appears, the contents +of the other file will be a starting point for that part of the current config dict, +but you can replace +or add values by specifying new values for some of the fields. Fields that are not at +the top level are specified using a ``.`` to proceed down the levels of the config hierarchy. +e.g. ``image.noise.sky_level`` refers to ``config['image']['noise']['sky_level']``. + +For example, if you have a simulation defined in ``my_sim.yaml``, and you want to make another +simulation that is identical, except you want Sersic galaxies instead of Exponential galaxies say, +you could write a new file that looks something like this: + +.. code-block:: yaml + + template : my_sim.yaml + gal: + type : Sersic + n : { type : Random, min : 1, max: 4 } + half_light_radius : + template : my_sim.yaml:gal.half_light_radius + flux : 1000 + output.dir : sersic_sim + +This will load in the file ``my_sim.yaml`` first, then replace the whole ``config['gal']`` field +as well as ``config['output']['dir']`` (leaving the rest of ``config['output']`` unchanged). +The new ``config['gal']`` field will use the same ``half_light_radius`` specification from +the other file (which might be some complicated random variate that you did not want to +duplicate here). + +If the ``template`` field is not at the top level of the config dict, the adjustments should be +made relative to that level of the dictionary: + +.. code-block:: yaml + + psf : + template: cgc.yaml:psf + index_key : obj_num + items.0.ellip.e.max : 0.05 + items.1.nstruts : 1 + items.1.strut_angle : { type : Random } + +Note that the modifications do not start with ``psf.``, since the template processing is being done +within the ``psf`` field. + +Finally, if you want to use a different field from the current config dict as a template, you can +use the colon notation without the file. +E.g. To have a bulge plus disk that have the same kinds of parameters, except that the overall type is a DeVaucouleurs and Exponential respectively, you could do: + +.. code-block:: yaml + + gal: + type: Sum + items: + - + type: DeVaucouleurs + half_light_radius: { type: Random, min: 0.2, max: 0.8 } + flux: { type: Random, min: 100, max: 1000 } + ellip: + type: Eta1Eta2 + eta1: { type: RandomGaussian, sigma: 0.2 } + eta2: { type: RandomGaussian, sigma: 0.2 } + - + template: :gal.items.0 + type: Exponential + +This would generate different values for the size, flux, and shape of each component. But the way those numbers are drawn would be the same for each. + +It is also possible for modules to register a name for a template file, so users can use that name +rather than the actual file location. For instance, if a module has a template yaml file that is +installed with the python code, it will typically be in an obscure location in a Python +site-packages directory somewhere. But the installed module would be able to know this location +and register it by name. E.g. "my_default_sim". Then users who want to +start with that canonical config file and maybe modify a few things can write: + +.. code-block:: yaml + + modules: + - my_module + + template: my_default_sim + + image.random_seed: 12345 + image.nobjects: 500 + output.file_name: objs_500.fits + +To use this feature, the module (i.e. my_module in the example here) should register the name to the correct file name using the `RegisterTemplate` function:: + + module_dir = os.path.dirname(__file__) + default_sim_file = os.path.join(module_dir, 'my_default_sim.yaml') + galsim.config.RegisterTemplate('my_default_sim', default_sim_file) + +.. autofunction:: galsim.config.RegisterTemplate + +See: + +* :gh-link:`rgc.yaml ` +* :gh-link:`cgc_psf.yaml ` + +for more examples of this feature. + +Special Specifications +====================== + +A few specifications may be used almost anywhere in the config to adjust how the values in those +fields are processed. They are automatically propagated to lower levels in the dictionary. +For instance, if you set ``index_key : image_num`` in the ``psf`` field, then all values +generated for any aspect of the psf will be constant for a whole image and only change +when the processing goes on to the next image. + +index_key +--------- + +This specifies the cadence on which to generate a new value for each non-constant value. +There are default cadences for each of the major top-level fields, but if you want to specify +a different cadence for some value or field, then you can override it. + +Options are: + + * 'file_num' Update the values for each new file. This is the default for items in the ``input`` and ``output`` fields. + * 'image_num' Update the values for each new image. This is the default for items in the ``image`` field that apply to the full image (i.e. not including ``random_seed``, ``image_pos``, ``world_pos``, etc.). + * 'obj_num' Update the values for each object. This is the default for the other items in ``image``, and also for items in ``stamp``, ``gal``, and ``psf``. + * 'obj_num_in_file' For this purpose, equivalent to 'obj_num'. (For 'Sequence' value types, there is an important distinction between the two. See its description in `Config Values` for more details.) + +It is also possible for a custom module to add additional valid values here by adding to ``galsim.config.valid_index_keys``, which is a list of strings, which are allowed. + + +rng_index_key +------------- + +Each ``index_key`` has its own random number generator to use for generating values that need an rng object. Normally you want these to match up, but this lets you specify to use the rng for a different key than is used for the actual sequencing. + +For instance, if you set ``rng_index_key = 'image_num'`` for a ``gal`` value, then it will use the rng normally used for image_num items, but it will still generate a new value for each obj_num. + +rng_num +------- + +Normally you specify a single random number seed, which spawns a sequence of rng objects that +update according to the above index keys. So an rng for each object is stored in ``obj_num_rng``, +one for image_num values is in ``image_num_rng``, etc. + +However, you are allowed to specify this seed sequence manually, and in particular, you can +have it be a list of several different sequences which update at different rates, and may +repeat. For instance, this may be useful to have some galaxy properties repeat for several +exposures, while other properties of the observations are different for each exposure. + +You would specify which random number you want to use from such a list using ``rng_num`` in a +field. See the description of ``random_seed`` in `Image Field Attributes` for more information. diff --git a/docs/_build/html/_sources/config_stamp.rst.txt b/docs/_build/html/_sources/config_stamp.rst.txt new file mode 100644 index 00000000000..258bef2fb5b --- /dev/null +++ b/docs/_build/html/_sources/config_stamp.rst.txt @@ -0,0 +1,268 @@ +Config Stamp Field +================== + +The ``stamp`` field defines some properties about how to draw the postage-stamp images of each +object. It is often unneccessary to explicitly include this top-level field. The default +``stamp`` type, called 'Basic', is often what you want. + +Stamp Field Attributes +---------------------- + +Some attributes that are allowed for all stamp types are: + +* ``draw_method`` = *str_value* (default = 'auto') Valid options are: + + * 'auto' The default is normally equivalent to 'fft'. However, if the object being rendered is simple (no convolution) and has hard edges (e.g. a Box or a truncated Moffat or Sersic), then it will switch to 'real_space', since that is often both faster and more accurate in these cases (due to ringing in Fourier space). + * 'fft' This method will convolve by the pixel (as well as convolving the galaxy and PSF if you have both) using a fast Fourier transform. + * 'real_space' This uses real-space integration to integrate over the pixel. This is only possible if there is only _either_ a PSF or galaxy, not both. Also, it cannot involve a Convolution internally. If GalSim is unable to do the real-space integration, this will revert to 'fft'. + * 'phot' Use photon shooting, which treats the profile as a probability distribution, draws photons from that distribution, and then "shoots" them at the image. The flux of each photon is added to whichever pixel it hits. This automatically handles the integration over the pixel, but it cannot be used with Deconvolutions (including RealGalaxy objects) and the result will necessarily have Poisson noise from the finite number of photons being shot. + + * ``max_extra_noise`` = *float_value* (optional) If the image is sky noise dominated, then it is efficient to stop shooting photons when the photon noise of the galaxy is much less than the sky noise. This parameter specifies how much extra noise, as a fraction of the sky noise, is permissible in any pixel. + * ``n_photons`` = *int_value* (optional; default is to assume the object flux is given in photons and use that) Specifies the total number of photons to shoot as a hard, fixed number. If both ``n_photons`` and ``max_extra_noise`` are specified in the options, ``max_extra_noise`` is ignored and a warning is generated. + * ``poisson_flux`` = *bool_value* (default = True, unless ``n_photons`` is given, in which case the default is False) Whether to allow the total object flux to vary according to Poisson statistics for the number of photons being shot. + + * 'no_pixel' This will not integrate the flux over the pixel response. Rather, it just samples the surface brightness distribution at the pixel centers and multiplies by the pixel area. This is appropriate if the PSF already has the pixel response included (e.g. from an observed image of a PSF). + * 'sb' This is similar to 'no_pixel', except that the image values will simply be the sampled surface brightness, not multiplied by the pixel area. This does not correspond to any real observing scenario, but it could be useful if you want to view the surface brightness profile of an object directly, without including the pixel integration. + +* ``offset`` = *pos_value* (optional) An offset in chip coordinates (i.e. pixel units) to apply when drawing the object on the postage stamp. +* ``gsparams`` = *dict* (optional) A dict of (non-default) GSParams items that you want applied to the constructed object. +* ``retry_failures`` = *int_value* (default = 0) How many times to retry the construction of a GSObject if there is any kind of failure. For example, you might have a random shear value that technically may come back with :math:`|g| > 1`, but it should be very rare. So you might set it to retry once or twice in that case. If this is > 0, then after a failure, the code will try again up to this many times. +* ``skip_failures`` = *bool_value* (default = False) Whether to skip an object if some aspect of the processing failes (i.e. an exception is raised). This is similar in spirit to the above ``retry_failures``, but more appropriate when the input is not a random value that sometimes causes problems, but rather an input catalog that might have invalid values. +* ``world_pos`` = *pos_value* or *sky_value* (only one of ``world_pos`` and ``image_pos`` is allowed) The position in world coordinates at which to center the object. This is often defined in the ``image`` field, but it can be overridden in the ``stamp`` field. +* ``image_pos`` = *pos_value* (only one of ``world_pos`` and ``image_pos`` is allowed) The position on the full image at which to center the object. This is often defined in the ``image`` field, but it can be overridden in the ``stamp`` field. Note: the object is always centered as nearly as possible on the postage stamp being drawn (unless an explicit ``offset`` is given), but the ``image_pos`` or ``world_pos`` determines where in the larger image this stamp is placed. +* ``sky_pos`` = *sky_value* (default = ``world_pos``) Normally this is just ``world_pos``, but if you are using a Euclidean WCS, then this allows for the ability to specify a location on the sky in case some other type needs it for a calculation. +* ``skip`` = *bool_value* (default = False) Skip this stamp. +* ``quick_skip`` = *bool_value* (default = False) Skip this stamp before doing any work, even making the rng or calculating the position. (Usually used by some other part of the processing to precalculate objects that are not worth doing for some reason.) +* ``obj_rng`` = *bool_value* (default = True) Whether to make a fresh random number generator for each object. If set to False, all objects will use the same rng, which will be the one used for image-level calculations. +* ``photon_ops`` See `Photon Operators List` below. + +Stamp Types +----------- + +The default stamp type is 'Basic', which constructs a galaxy object based on the ``gal`` field +(if present) and a PSF object from the ``psf`` field (again, if present), convolves them +together, and draws the object onto a postage stamp. This is often what you need, but +there is also a 'Ring' type, and you can define your own custom ``stamp`` type if you want +to customize any aspect of the stamp-building process. + +* 'Basic' The postage stamp contains a single ``gal`` object convolved by a ``psf`` object, assuming both fields are given. If only one of the two is given, that one is drawn. + + * ``size`` = *int_value* (optional) If you want square postage stamps for each object (common), you just need to set this one value and the images will be ``size`` x ``size``. The default is for GalSim to automatically determine a good size for the image that will encompass most of the flux of the object, but note that the ``image`` type may define the stamp size (e.g. 'Tiled'), in which case that will be used. + * ``xsize`` = *int_value* (default = ``size``) If you want non-square postage stamps, you can specify ``xsize`` and ``ysize`` separately instead. It is an error for only one of them to be non-zero. + * ``ysize`` = *int_value* (default = ``size``) + * ``min_flux_frac`` = *float_value* (optional) If the rendered stamp (before noise is applied) has less than this fraction of the nominal flux of the object, reject it and start over (presumably choosing new random values for size, flux, etc.). This counts as a "failure" for the purpose of the ``retry_failures`` count. + * ``min_snr`` = *float_value* (optional) If the measured signal-to-noise ratio (using the optimal matched filter definition of S/N, measured using the signal on the stamp before noise is applied) is less than this, then reject it and start over. This counts as a "failure" for the purpose of the ``retry_failures`` count. + * ``max_snr`` = *float_value* (optional) If the measured signal-to-noise ratio is higher than this, then reject it and start over. This counts as a "failure" for the purpose of the ``retry_failures`` count. + * ``reject`` = *bool_value* (optional) If this evaluates to true, then reject the current stamp and start over. Typically, this would be a custom function that would perform some measurement on the pre-noise image. + See :gh-link:`cgc.yaml ` for an examples of such a custom function. This counts as a "failure" for the purpose of the ``retry_failures`` count. + +* 'Ring' Generate galaxies in a ring for a ring test. (Use num=2 to get pairs of 90 degree rotated galaxies.) + + * ``size``, ``xsize``, ``ysize`` = *int_value* (optional) Same meaning as for 'Basic' type. + * ``num`` = *int_value* (required) How many objects to include in the ring. + * ``full_rotation`` = *angle_value* (default = 180 degrees) What angle should be spanned by the full rotation? The default of 180 degrees is appropriate for the typical case of a rotationally symmetric galaxy (e.g. a sheared Exponential), but if the ``first`` profile does not have rotational symmetry, then you probably want to set this to 360 degrees. + * ``index`` = *int_value* (default = 'Sequence' from 0 to num-1) Which item in the Ring is this. + * ``min_flux_frac`` = *float_value* (optional) Equivalent to Basic, but only applies to the first stamp in the ring. + * ``min_snr`` = *float_value* (optional) Equivalent to Basic, but only applies to the first stamp in the ring. + * ``max_snr`` = *float_value* (optional) Equivalent to Basic, but only applies to the first stamp in the ring. + * ``reject`` = *bool_value* (optional) Equivalent to Basic, but only applies to the first stamp in the ring. + * ``shear`` = *shear_value* (optional) Shear the galaxy profile by a given shear. Normally ``shear`` goes in the ``gal`` field. But for ring simulations, where we rotate the base galaxy by some amount, one typically wants the shear to be applied after the rotation. So any ``shear`` (or other transformation) item that is in the ``gal`` field is applied *before* the ring rotation. Then any ``shear`` (or again, any other transformation) that is in the ``stamp`` field is applied *after* the ring rotation. + + +Custom Stamp Types +------------------ + +To define your own stamp type, you will need to write an importable Python module +(typically a file in the current directory where you are running ``galsim``, but it could also +be something you have installed in your Python distro) with a class that will be used +to build the stamp. + +The class should be a subclass of `galsim.config.StampBuilder`, which is the class used for +the default 'Basic' type. There are a number of class methods, and you only need to override +the ones for which you want different behavior than that of the 'Basic' type. + +.. autoclass:: galsim.config.StampBuilder + :members: + +The ``base`` parameter is the original full configuration dict that is being used for running the +simulation. The ``config`` parameter is the local portion of the full dict that defines the stamp +being built, which would typically be ``base['stamp']``. + +Then, in the Python module, you need to register this function with some type name, which will +be the value of the ``type`` attribute that triggers the use of this Builder object:: + + galsim.config.RegisterStampType('CustomStamp', CustomStampBuilder()) + +.. autofunction: galsim.config.RegisterStampType + +Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple stamp types use the same class instantiated with different +initialization parameters. This is not used by the GalSim stamp types, but there may be use +cases where it would be useful for custom stamp types. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_stamp.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_stamp + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_stamp``, +which will read your file and execute the registration command to add the builder to the list +of valid stamp types. + +Then you can use this as a valid stamp type: + +.. code-block:: yaml + + stamp: + type: CustomStamp + ... + +For examples of custom stamps, see + +* :gh-link:`blend.yaml ` +* :gh-link:`blendset.yaml ` + +which use custom stamp types ``Blend`` and ``BlendSet`` defined in :gh-link:`blend.py `. + +It may also be helpful to look at the GalSim implementation of the include ``Ring`` type: +(click on the ``[source]`` link): + +.. autoclass:: galsim.config.stamp_ring.RingBuilder + :show-inheritance: + +Photon Operators List +--------------------- + +When drawing with ``method='phot'``, there are a number of operators you can apply to the +photon array before accumulating the photons on the sensor. You can specify these using +``photon_ops`` in the ``stamp`` field. This directive should be a list of dicts, each +specifying a `PhotonOp` in the order in which the operators should be applied to the photons. + +The photon operator types defined by GalSim are: + +* 'WavelengthSampler' assigns wavelengths to the photons based on an SED and the current Bandpass. + + * ``sed`` = *SED* (required) The SED to use. To use the galaxy SED (which would be typical), + you can use ``@gal.sed`` for this. + * ``npoints`` = *int_value* (optional) The number of points `DistDeviate` should use for its + interpolation table. + +* 'FRatioAngles' assigns incidence angles (in terms of their tangent, dxdz and dydz) to the + photons randomly given an f/ratio and an obscuration. + + * ``fratio`` = *float_vale* (required) The f/ratio of the telescope. + * ``obscuration`` = *float_value* (default = 0.0) The linear dimension of the central + obscuration as a fraction of the aperture size. + +* 'PhotonDCR' adjusts the positions of the photons according to the effect of differential + chromatic refraction in the atmosphere. There are several ways one can define the + parallactic angle needed to compute the DCR effect. One of the following is required. + + 1. ``zenith_angle`` and ``parallactic_angle`` + 2. ``zenith_angle`` alone, implicitly taking ``parallactic_angle = 0``. + 3. ``zenith_coord`` along with either ``sky_pos`` in the ``stamp`` field or using a + `CelestialWCS` so GalSim can determine the sky position from the image coordinates. + 4. ``HA`` and ``latitude`` along with either ``sky_pos`` in the ``stamp`` field or using a + `CelestialWCS` so GalSim can determine the sky position from the image coordinates. + + * ``base_wavelength`` = *float_value* (required) The wavelength (in nm) for the fiducial + photon positions. + * ``scale_unit`` = *str_value* (default = 'arcsec') The scale unit for the photon positions. + * ``alpha`` = *float_value* (default = 0.0) A power law index for wavelength-dependent + seeing. This should only be used if doing a star-only simulation. It is not correct when + drawing galaxies. + * ``zenith_angle`` = *angle_value* (optional; see above) the angle from the object to zenith. + * ``parallactic_angle`` = *angle_value* (option; see above) the parallactic angle. + * ``zenith_coord`` = *sky_value* (optional; see above) the celestial coordinates of the zenith. + * ``HA`` = *angle_value* (optional; see above) the local hour angle. + * ``latitude`` = *angle_value* (optional; see above) the latitude of the telescope. + * ``pressure`` = *float_value* or *quantity_value* (default = 69.328) the pressure in kPa. + * ``temperature`` = *float_value* or *quantity_value* (default = 293.15) the temperature in Kelvin. + * ``H2O_pressure`` = *float_value* or *quantity_value* (default = 1.067) the water vapor pressure in kPa. + +* 'FocusDepth' adjusts the positions of the photons at the surface of the sensor to account for + the nominal focus being either above or below the sensor surface. The depth value is typically + negative, since the best focus is generally somewhere in the bulk of the sensor (although for + short wavelengths it is often very close to the surface). + + * ``depth`` = *float_value* (required) The distance (in pixels) above the surface where the photons are + nominally in focus. A negative value means the focus in below the surface of the sensor. + +* 'Refraction' adjusts the incidence angles to account for refraction at the surface of the + sensor. + + .. note:: + + If this is combined with FocusDepth, then the order of the two operators is important. + If FocusDepth is before Refraction, then the depth refers to the distance the sensor + would need to move for the bundle to be in focus at the surface. + If FocusDepth is after Refraction, then the depth refers to the physical distance + below the surface of the sensor where the photons actually come to a focus. + + * ``index_ratio`` = *float_value* (required) The ratio of the index of refraction of the + sensor material to that of the air. + +* 'PupilImageSampler' assigns pupil positions to the photons randomly given an image of the + pupil plane. + + * ``diam`` = *float_value* (required) The diameter of the pupil aperture. + * ``lam`` = *float_value* (optional). The wavelength in nanometers. + * ``circular_pupil`` = *bool_value* (default = True) Whether the pupil should be circular (True, the default) or square (False). + * ``obscuration`` = *float_value* (default = 0) The linear dimension of a central obscuration as a fraction of the pupil linear dimension. + * ``oversampling`` = *float_value* (default = 1.5) How much oversampling of the internal image is needed relative to the Nyquist scale of the corresponding Airy profile. The more aberrated the PSF, the higher this needs to be. + * ``pad_factor`` = *float_value* (default = 1.5) How much padding to put around the edge of the internal image of the PSF. + * ``nstruts`` = *int_value* (default = 0) How many support struts to include. + * ``strut_thick`` = *float_value* (default = 0.05) How thick the struts should be as a fraction of the pupil diameter. + * ``strut_angle`` = *angle_value* (default = 0 degrees) The counter-clockwise angle between the vertical and one of the struts. The rest will be spaced equally from there. + * ``pupil_plane_im`` = *str_value* (optional) Instead of using strut-related parameters to define the pupil plane geometry, you can use this parameter to specify a file-name containing an image of the pupil plane. + * ``pupil_angle`` = *angle_value* (default = 0 degrees) When specifying a pupil_plane_im, use this parameter to rotate it by some angle defined counter-clockwise with respect to the vertical. + * ``pupil_plane_scale`` = *float_value* (optional) Sampling interval in meters to use for the pupil plane array. + * ``pupil_plane_size`` = *float_value* (optional) Size in meters to use for the pupil plane array. + +* +* 'PupilAnnulusSampler' assigns pupil positions to the photons randomly within an annular + entrance pupil. + + * ``R_outer`` = *float_value* (required) The outer radius of the pupil annulus in meters. + * ``R_inner`` = *float_value* (default = 0) The inner radius in meters. + +* 'TimeSampler' gives the photons random time values uniformly within some interval. + + * ``t0`` = *float_value* (default = 0) The nominal start time of the observation in seconds. + * ``exptime`` = *float_value* (default = 0) The exposure time in seconds. + +You may also define your own custom `PhotonOp` type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of `galsim.config.PhotonOpBuilder`. + +.. autoclass:: galsim.config.PhotonOpBuilder + :members: + +Then, as usual, you need to register this type using:: + + galsim.config.RegisterPhotonOpType('CustomPhotonOp', CustomPhotonOpBuilder()) + +.. autofunction:: galsim.config.RegisterPhotonOpType + +and tell the config parser the name of the module to load at the start of processing. + +.. code-block:: yaml + + modules: + - my_custom_photon_op + +Then you can use this as a valid photon operator type: + +.. code-block:: yaml + + stamp: + photon_ops: + - + type: CustomPhotonOp + ... diff --git a/docs/_build/html/_sources/config_top.rst.txt b/docs/_build/html/_sources/config_top.rst.txt new file mode 100644 index 00000000000..7b81f071573 --- /dev/null +++ b/docs/_build/html/_sources/config_top.rst.txt @@ -0,0 +1,92 @@ + +Top Level Fields +================ + +At the top level, there are 6 basic fields: + +* ``psf`` defines what kind of PSF profile to use. +* ``gal`` defines what kind of galaxy profile to use. +* ``stamp`` defines parameters related to building the postage stamp image of each object. +* ``image`` defines parameters related to the full images to be drawn. +* ``input`` defines any necessary input files or things that need some kind of initialization. +* ``output`` defines the names and format of the output files. + +None of these are technically required, although it is an error to have _neither_ ``psf`` nor +``gal``. (If you don't want to draw anything but noise, you need to let GalSim know that this is intentional by using ``type: None`` for one of these.) But the most common usage would be to use +``psf``, ``gal``, ``image`` and ``output``. +It is not uncommon for there to be no input files, so you will often omit the ``input`` field. +And sometimes you will omit the ``gal`` field to draw an image with just stars. +Most simulations will use the default ``stamp`` type (called 'Basic'), which involves drawing +a galaxy convolved by a PSF (or just a PSF image if ``gal`` is omitted) on each postage stamp, +so this field will very often be omitted as well. + +We will go through each one in turn. As we do, some values will be called *float_value*, +*int_value*, etc. These can either be a value directly (e.g. *float_value* could just be 1.5), +or they can be a dict that describes how the value should be generated each time (e.g. a random +number or a value read from an input catalog). +See `Config Values` for more information about how to specify these values. + +In addition each value will have one of (required) or (optional) or (default = _something_) to +indicate whether the item is required or if there is some sensible default value. The (optional) +tag usually means that the action in question will not be done at all, rather than done using some +default value. Also, sometimes no item is individually required, but one of several is. + +**psf**: + +The ``psf`` field defines the profile of the point-spread function (PSF). +Any object type is allowed for +the ``psf`` type, although some types are obviously more appropriate to use as a PSF than others. +For a list of all the available object types, see `Config Objects`. + +If this field is omitted, the PSF will effectively be a delta function. I.e. the ideal +galaxy surface brightness profiles will be drawn directly on the image without any convolution. + +**gal**: + +The ``gal`` field defines the profile of the galaxy. +As for the ``psf`` field, any object type is allowed for +the ``gal`` type, although some types are obviously more appropriate to use as a galaxy than others. +For a list of all the available object types, see `Config Objects`. + +Technically, the ``gal`` field is not +fundamental; its usage is defined by the ``stamp`` type. One could for instance define a +``stamp`` type that looked for a ``gal_set`` field instead that might give a list of galaxies +to draw onto a single stamp. However, all of the ``stamp`` types defined natively in GalSim +use the ``gal`` field, so it will be used by most users of the code. + +If this field is omitted, the default ``stamp`` type = 'Basic' will draw the PSF surface brightness +profiles directly according to the ``psf`` field. +Other ``stamp`` types may require this field or may require some other field instead. + +**stamp**: + +The ``stamp`` field defines the relevant properties and parameters of the stamp-building process. +For a list of all the available ``stamp`` types, see `Config Stamp Field`. + +This field is often omitted, in which case the 'Basic' ``stamp`` type will be assumed. + +**image**: + +The ``image`` field defines the relevant properties and parameters of the full image-building +process. +For a list of all the available ``image`` types, see `Config Image Field`. + +If this field is omitted, the 'Single' ``image`` type will be assumed. + +**input**: + +The ``input`` field indicates where to find any files that you want to use in building the images +or how to set up any objects that require initialization. +For a list of all the available ``input`` types, see `Config Input Field`. + +This field is only required if you use `object types ` +or `value types ` that use an input object. +Such types will indicate this requirement in their descriptions. + +**output**: + +The ``output`` field indicates where to write the output files and what kind of output format they +should have. +For a list of all the available ``output`` types, see `Config Output Field`. + + diff --git a/docs/_build/html/_sources/config_values.rst.txt b/docs/_build/html/_sources/config_values.rst.txt new file mode 100644 index 00000000000..c91fa96f8a6 --- /dev/null +++ b/docs/_build/html/_sources/config_values.rst.txt @@ -0,0 +1,961 @@ +Config Values +============= + +There are seven kinds of values that are used within the configuration apparatus, which we +have been designating using the following italic names: + +- *float_value* corresponds to a Python ``float`` value. See `float_value` +- *int_value* corresponds to a Python ``int`` value. See `int_value` +- *bool_value* corresponds to a Python ``bool`` value. See `bool_value` +- *str_value* corresponds to a Python ``str`` value. See `str_value` +- *angle_value* corresponds to a GalSim `Angle` instance. See `angle_value` +- *shear_value* corresponds to a GalSim `Shear` instance. See `shear_value` +- *pos_value* corresponds to a GalSim `PositionD` instance. See `pos_value` +- *sky_value* corresponds to a GalSim `CelestialCoord` instance. See `sky_value` +- *table_value* corresponds to a GalSim `LookupTable` instance. See `table_value` +- *quantity_value* corresponds to an Astropy :class:`~astropy.units.Quantity` instance. See `quantity_value` +- *unit_value* corresponds to an Astropy :class:`~astropy.units.Unit` instance. See `unit_value` + +Each of the Python types can be given as a constant value using the normal Python conventions +for how to specify such a value. The GalSim *angle_value* and *pos_value* also have +direct specification options. In addition, all of them may be a dict with a ``type`` attribute +defining how to generate the value in question. These are defined below for each kind of +value. + +One special ``type`` that any value may use is 'Eval', which uses the Python ``eval`` function +to evaluate a string. The `Eval type` is described in its own section. + +In addition to all of these, you can also write your own `Custom Value Types` +and register them to be used by the config parser. + +float_value +----------- + +Options are: + +* A normal float value (e.g. 1.8) +* Anything that python can convert into a float (e.g. '1.8') +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Catalog' Read the value from an input catalog. This requires that ``input.catalog`` be specified and uses the following fields: + + * ``col`` = *int_value* for ASCII catalog or *str_value* for FITS catalog (required) + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``input_cat.nobjects``-1) + * ``num`` = *int_value* (default = 0) If ``input.catalog`` is a list, this indicates which number catalog to use. + + * 'Dict' Read the value from an input dictionary. This requires that ``input.dict`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) For specifying keys below the first level of the dictionary, the ``key`` string is split using the ``input.dict.key_split`` value (default = '.') into multiple keys. e.g. ``key : galaxy_constants.redshift`` would be parsed as ``dict['galaxy_constants']['redshift']``. + * ``num`` = *int_value* (default = 0) If ``input.dict`` is a list, this indicates which number dictionary to use. + + * 'FitsHeader' Read the value from an input FITS header. This requires that ``input.fits_header`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) + * ``num`` = *int_value* (default = 0) If ``input.fits_header`` is a list, this indicates which number file to use. + + * 'Random' Generate random values uniformly distributed within a range. + + * ``min`` = *float_value* (required) + * ``max`` = *float_value* (required) + + * 'RandomGaussian' Generate random values from a Gaussian deviate. + + * ``sigma`` = *float_value* (required) + * ``mean`` = *float_value* (default = 0) + * ``min`` = *float_value* (optional) Clip the distribution at some minimum. + * ``max`` = *float_value* (optional) Clip the distribution at some maximum. + + * 'RandomPoisson' Generate random values from a Poisson deviate. + + * ``mean`` = *int_value* (required) The mean value of the Poisson distribution. + + * 'RandomBinomial' Generate random values from a Binomial deviate. + + * ``N`` = *int_value* (required) The number of "coin flips" for the distribution. + * ``p`` = *float_value* (default = 0.5) The probability of "heads" for each "coin flip". + + * 'RandomWeibull' Generate random values from a Weibull deviate. + + * ``a`` = *float_value* (required) (Equivalent to k in the Wikipedia article) + * ``b`` = *float_value* (required) (Equivalent to lambda in the Wikipedia article) + + * 'RandomGamma' Generate random values from a Gamma deviate. + + * ``k`` = *float_value* (required) The shape parameter. + * ``theta`` = *float_value* (required) The scale parameter. + + * 'RandomChi2' Generate random values from a Chi-square deviate. + + * ``n`` = *float_value* (required) The number of degreed of freedom. + + * 'RandomDistribution' Generate random values from a given probability distribution. + + * ``function`` = *str_value* (required) A string describing the function of x to use for the probability distribution. e.g. ``x**2.3``. Alternatively, it may be a file name from which a tabulated function (with columns of x, p(x)) is read in. + * ``x_min`` = *float_value* (required unless ``function`` is a file name) The minimum value of x to use for the distribution. (If ``function`` is a file name, the minimum value read in is taken as ``x_min``.) + * ``x_max`` = *float_value* (required unless ``function`` is a file name) The maximum value of x to use for the distribution. (If ``function`` is a file name, the maximum value read in is taken as ``x_max``.) + * ``npoints`` = *int_value* (default = 256) How many points to use for the cumulative probability distribution (CDF), which is used to map from a uniform deviate to the given distribution. More points will be more accurate, but slower. + * ``interpolant`` = *str_value* (default = 'Linear') What to use for interpolating between tabulated points in the CDF. Options are 'Nearest', 'Linear', 'Cubic' or 'Quintic'. (Technically, 'Sinc' and 'LanczosN' are also possible, but they do not make sense here.) + + * 'PowerSpectrumMagnification' Calculate a magnification from a given power spectrum. This requires that ``input.power_spectrum`` be specified and uses the following fields: + + * ``max_mu`` = *float_value* (default = 5) The maximum magnification to allow. If the power spectrum returns a mu value greater than this or less than 0, then use ``max_mu`` instead. This is a sign of strong lensing, and other approximations are probably breaking down at this point anyway, so this keeps the object profile from going crazy. + * ``num`` = *int_value* (default = 0) If ``input.power_spectrum`` is a list, this indicates which number power spectrum to use. + + * 'NFWHaloMagnification' Calculate a magnification from an NFW Halo mass. This requires that ``input.nfw_halo`` be specified and uses the following fields: + + * ``gal.redshift`` = *float_value* (required) Special: The ``redshift`` item must be in the ``gal`` field, not ``magnification``. Or if the galaxy is chromatic, it should be in the galaxy's SED field. + * ``max_mu`` = *float_value* (default = 5) The maximum magnification to allow. If NFWHalo returns a mu value greater than this or less than 0, then use ``max_mu`` instead. This is a sign of strong lensing, and other approximations are probably breaking down at this point anyway, so this keeps the object profile from going crazy. + * ``num`` = *int_value* (default = 0) If ``input.nfw_halo`` is a list, this indicates which number halo to use. + + * 'COSMOSValue' Gets a value from an input COSMOS catalog. This requires that ``input.cosmos_catalog`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) + * ``index`` = *int_value* (required) + * ``num`` = *int_value* (default = 0) If ``input.cosmos_catalog`` is a list, this indicates which number catalog to use. + + * 'SampleValue' Gets a value from an input galaxy sample. This requires that ``input.galaxy_sample`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) + * ``index`` = *int_value* (required) + * ``num`` = *int_value* (default = 0) If ``input.cosmos_catalog`` is a list, this indicates which number catalog to use. + + * 'Sequence' Generate a sequence of values. + + * ``first`` = *float_value* (default = 0) + * ``step`` = *float_value* (default = 1) The step size between items. + * ``repeat`` = *int_value* (default = 1) How many times to repeat the same value before moving on. + * ``last`` = *float_value* (optional; at most one of ``last`` and ``nitems`` is allowed) + + .. note:: + + If ``last`` is provided, once a value passes ``last``, the sequence will + repeat starting with ``first`` again. + + * ``nitems`` = *int_value* (optional; at most one of ``last`` and ``nitems`` is allowed) The number of items in the sequence before starting over again at ``first``. The default is to just keep incrementing forever. + * ``index_key`` = *str_value* (optional; see the option descriptions below for which index is used by default) Which number to use for indexing in the sequence. Valid options are: + + * 'file_num' Index according to the running file number being worked on. This is the default for items in the ``input`` and ``output`` fields. + * 'image_num' Index according to the running image number. This index number does not start back at 0 with each file, but rather keeps incrementing. This is the default for items in the ``image`` field that apply to the full image (i.e. not including ``random_seed``, ``image_pos``, ``world_pos``, etc.). + * 'obj_num' Index according to the running object number. This index number does not start back at 0 with each file or image, but rather keeps incrementing. This is the default for ``image.random_seed``. + * 'obj_num_in_file' Index according to the object number within the current file (i.e. start back at 0 again for each new file). This is the default for items in ``image`` that apply to the object -- ``image_pos``, ``world_pos``, ``offset``, ``stamp_size`` and related, or ``border`` and related -- and also to items in ``psf`` or ``gal``. Resetting the count back to zero at the start of each file is generally what you want when the files have different numbers of objects. E.g., when you are reading from input catalogs that contain different numbers of objects, you normally want to start back at 0 for each new catalog. + + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *float_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. This is especially useful if you need to use a value from some other calculation, but the value is a random variate, so you cannot just reproduce it. You need the actual value returned by the random number generator. + + * ``key`` = *str_value* (required) The key name of the item to use. The nested layers in the dictionary should be separated by '.' characters. e.g. To access the current half-light radius of the galaxy, use 'gal.half_light_radius'. For list items, use the number in the list as a key (using the normal python 0-based counting convention). e.g. for the half-light radius of the third item in a galaxy ``List`` type, use 'gal.items.2.half_light_radius'. + + * 'Sum' The sum of two other *float_value* items. + + * ``items`` = *list* (required) A list of *float_value* items to be added together. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +int_value +--------- + +Options are: + +* A normal int value (e.g. 8) +* Anything that python can convert into an int (e.g. 8.0, '8') + + .. note:: + + float values will silently drop any fractional part, so 8.7 will become 8. + +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Catalog' Read the value from an input catalog. This requires that ``input.catalog`` be specified and uses the following fields: + + * ``col`` = *int_value* for ASCII catalog or *str_value* for FITS catalog (required) + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``input_cat.nobjects``-1) + * ``num`` = *int_value* (default = 0) If ``input.catalog`` is a list, this indicates which number catalog to use. + + * 'Dict' Read the value from an input dictionary. This requires that ``input.dict`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) For specifying keys below the first level of the dictionary, the ``key`` string is split using the ``input.dict.key_split`` value (default = '.') into multiple keys. e.g. ``key : galaxy_constants.redshift`` would be parsed as ``dict['galaxy_constants']['redshift']``. + * ``num`` = *int_value* (default = 0) If ``input.dict`` is a list, this indicates which number dictionary to use. + + * 'FitsHeader' Read the value from an input FITS header. This requires that ``input.fits_header`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) + * ``num`` = *int_value* (default = 0) If ``input.fits_header`` is a list, this indicates which number file to use. + + * 'Random' Generate a random value uniformly distributed within a range. + + * ``min`` = *int_value* (required) + * ``max`` = *int_value* (required) Note: the range includes both ``min`` and ``max``. + + * 'RandomPoisson' Generate random values from a Poisson deviate. + + * ``mean`` = *int_value* (required) The mean value of the Poisson distribution. + + * 'RandomBinomial' Generate random values from a Binomial deviate. + + * ``N`` = *int_value* (required) The number of "coin flips" for the distribution. + * ``p`` = *float_value* (default = 0.5) The probability of "heads" for each "coin flip". + + * 'Sequence' Generate a sequence of values. + + * ``first`` = *int_value* (default = 0) + * ``step`` = *int_value* (default = 1) The step size between items. + * ``repeat`` = *int_value* (default = 1) How many times to repeat the same value before moving on. + * ``last`` = *float_value* (optional; at most one of ``last`` and ``nitems`` is allowed) + + .. note:: + + if ``last`` is provided, once a value passes ``last``, the sequence will + repeat starting with ``first`` again. + + * ``nitems`` = *int_value* (optional; at most one of ``last`` and ``nitems`` is allowed) The number of items in the sequence before starting over again at ``first``. The default is to just keep incrementing forever. + * ``index_key`` = *str_value* (optional) Which number to use for indexing in the sequence. (See the description of this for *float_value* for more details.) + + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *int_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Sum' The sum of two other *int_value* items. + + * ``items`` = *list* (required) A list of *int_value* items to be added together. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +bool_value +---------- + +Options are: + +* A normal bool value (i.e. True or False) +* Anything that python can convert into a bool (e.g. 1, 0.0) +* Some reasonable (case-insensitive) strings: 'true'/'false', 'yes'/'no', '1'/'0' +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Catalog' Read the value from an input catalog. This requires that ``input.catalog`` be specified and uses the following fields: + + * ``col`` = *int_value* for ASCII catalog or *str_value* for FITS catalog (required) + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``input_cat.nobjects``-1) + * ``num`` = *int_value* (default = 0) If ``input.catalog`` is a list, this indicates which number catalog to use. + + * 'Dict' Read the value from an input dictionary. This requires that ``input.dict`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) For specifying keys below the first level of the dictionary, the ``key`` string is split using the ``input.dict.key_split`` value (default = '.') into multiple keys. e.g. ``key : galaxy_constants.redshift`` would be parsed as ``dict['galaxy_constants']['redshift']``. + * ``num`` = *int_value* (default = 0) If ``input.dict`` is a list, this indicates which number dictionary to use. + + * 'FitsHeader' Read the value from an input FITS header. This requires that ``input.fits_header`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) + * ``num`` = *int_value* (default = 0) If ``input.fits_header`` is a list, this indicates which number file to use. + + * 'Random' Generate a random bool value. + + * ``p`` = *float_value* (default = 0.5) The probability of getting True. [New in v1.5] + + * 'RandomBinomial' Generate random values from a Binomial deviate with N=1. + + .. note:: + + The default case with ``p`` = 0.5 is equivalent to the 'Random' type. So this + would normally be used for random booleans with a different probability of True. + + * ``p`` = *float_value* (default = 0.5) The probability of True. + + * 'Sequence' Generate a sequence of values. + + * ``first`` = *bool_value* (default = False) For bool, the only two values in the sequence are False and True, so ``step`` and ``last`` are not needed. + * ``repeat`` = *int_value* (default = 1) How many times to repeat the same value before moving on. + * ``index_key`` = *str_value* (optional) Which number to use for indexing in the sequence. (See the description of this for *float_value* for more details.) + + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *bool_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +str_value +--------- + +Options are: + +* A normal str value (e.g. 'out.fits') +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Catalog' Read the value from an input catalog. This requires that ``input.catalog`` be specified and uses the following fields: + + * ``col`` = *int_value* for ASCII catalog or *str_value* for FITS catalog (required) + * ``index`` = *int_value* (default = 'Sequence' from 0 to ``input_cat.nobjects``-1) + * ``num`` = *int_value* (default = 0) If ``input.catalog`` is a list, this indicates which number catalog to use. + + * 'Dict' Read the value from an input dictionary. This requires that ``input.dict`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) For specifying keys below the first level of the dictionary, the ``key`` string is split using the ``input.dict.key_split`` value (default = '.') into multiple keys. e.g. ``key : galaxy_constants.redshift`` would be parsed as ``dict['galaxy_constants']['redshift']``. + * ``num`` = *int_value* (default = 0) If ``input.dict`` is a list, this indicates which number dictionary to use. + + * 'FitsHeader' Read the value from an input FITS header. This requires that ``input.fits_header`` be specified and uses the following fields: + + * ``key`` = *str_value* (required) + * ``num`` = *int_value* (default = 0) If ``input.fits_header`` is a list, this indicates which number file to use. + + * 'NumberedFile' Build a string that includes a number portion: rootNNNNext. e.g. file0001.fits, file0002.fits, etc. + + * ``root`` = *str_value* (required) The part of the string that comes before the number. + * ``num`` = *int_value* (default = 'Sequence' starting with 0) The number to use in the string. + * ``digits`` = *int_value* (default = 0) How many digits to use (minimum) to write the number. The number will be left-padded with 0s as needed. + * ``ext`` = *str_value* (default = '.fits' for ``output.file_name`` and the ``file_name`` entries for sub-items within output -- ``psf``, ``weight``, ``badpix`` --, and '' for all other uses) An extension to place after the number. + + * 'FormattedStr' Build a string using a format akin to the normal python %-style formatting or C/C++ printf-style formatting. + + * ``format`` = *str_value* (required) The formatting string to use. (e.g. 'image_%f_%d.fits') + * ``items`` = *list* (required) A list of items to insert into the corresponding % items in the format string. The letter after the % indicates what kind of value each item is. So for the above example, the first item in the string should be a *float_value* to put into the %f spot. The second should be an *int_value* to put into the %d spot. + + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *str_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +angle_value +----------- + +These represent `Angle` values. + +Options are: + +* A string consisting of a float followed by one of the following angle units: radians, degrees, hours, arcminutes, arcseconds. These may be abbreviated as rad, deg, hr, arcmin, arcsec. (e.g. '45 deg') +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Radians' or 'Rad' Use a *float_value* as an angle in radians. + + * ``theta`` = *float_value* (required) + + * 'Degrees' or 'Deg' Use a *float_value* as an angle in degrees. + + * ``theta`` = *float_value* (required) + + * 'Random' Generate a random angle uniformly distributed from 0 to 2pi radians. + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *angle_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Sum' The sum of two other *angle_value* items. + + * ``items`` = *list* (required) A list of *angle_value* items to be added together. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +shear_value +----------- + +These represent `Shear` values. + +Options are: + +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'E1E2' Specify as a distortion in cartesian coordinates. + + * ``e1`` = *float_value* (required) + * ``e2`` = *float_value* (required) + + * 'EBeta' Specify as a distortion in polar coordinates. + + * ``e`` = *float_value* (required) + * ``beta`` = *angle_value* (required) + + * 'G1G2' Specify as a reduced shear in cartesian coordinates. + + * ``g1`` = *float_value* (required) + * ``g2`` = *float_value* (required) + + * 'GBeta' Specify as a reduced shear in polar coordinates. + + * ``g`` = *float_value* (required) + * ``beta`` = *angle_value* (required) + + * 'Eta1Eta2' Specify as a conformal shear in cartesian coordinates. + + * ``eta1`` = *float_value* (required) + * ``eta2`` = *float_value* (required) + + * 'EtaBeta' Specify as a conformal shear in polar coordinates. + + * ``eta`` = *float_value* (required) + * ``beta`` = *angle_value* (required) + + * 'QBeta' Specify as an axis ratio and position angle. + + * ``q`` = *float_value* (required) + * ``beta`` = *angle_value* (required) + + * 'PowerSpectrumShear' Calculate a shear from a given power spectrum. This requires that ``input.power_spectrum`` be specified and uses the following field: + + * ``num`` = *int_value* (default = 0) If ``input.power_spectrum`` is a list, this indicates which number power spectrum to use. + + * 'NFWHaloShear' Calculate a shear from an NFW Halo mass. This requires that ``input.nfw_halo`` be specified and uses the following fields: + + * ``gal.redshift`` = *float_value* (required) Special: The ``redshift`` item must be in the ``gal`` field, not ``shear``. Or if the galaxy is chromatic, it should be in the galaxy's SED field. + * ``num`` = *int_value* (default = 0) If ``input.nfw_halo`` is a list, this indicates which number halo to use. + + * 'List' Select items from a list. + + * ``items`` = ``list`` (required) A list of *shear_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Sum' The sum of two other *shear_value* items. + + .. note:: + + Unlike the other kinds of values, shears addition is not commutative. + ``g_a + g_b`` is not the same as ``g_b + g_a``. Thus, the order of the elements + in the ``items`` list matters. The shear effects are applied from last to first, + so the effects should be listed in order from closest to the observer to farthest + along the light path. This is a *somewhat* standard convention for what + ``g_a + g_b`` means when applied to a galaxy. ``g_b`` would be a shear that is + close to the galaxy, and then ``g_a`` would be another shear closer to the + observer (perhaps within the telescope). + + * ``items`` = *list* (required) A list of *shear_value* items to be added together. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +pos_value +--------- + +These represent `PositionD` values, usually for a location on the image. + +Options are: + +* A string consisting of two floats separated by a comma and possibly white space. (e.g. '1.7, 3.0') +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'XY' Specify x and y separately. + + * ``x`` = *float_value* (required) + * ``y`` = *float_value* (required) + + * 'RTheta' Specify using polar coordinate. + + * ``r`` = *float_value* (required) + * ``theta`` = *angle_value* (required) + + * 'RandomCircle' Generate a random value uniformly distributed within a circle of a given radius. + + .. note:: + + This is different from 'RTheta' with each one random, since that would + preferentially pick locations near the center of the circle. + + * ``radius`` = *float_value* (required) The size of the circle within which to draw a random value. + * ``inner_radius`` = *float_value* (default = 0) If desired, an inner circle may be excluded, making this an annulus rather than a full circle. + * ``center`` = *pos_value* (default = 0,0) The center of the circle. + + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *pos_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Sum' The sum of two other *pos_value* items. + + * ``items`` = *list* (required) A list of *pos_value* items to be added together. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +sky_value +--------- + +These represent `CelestialCoord` values for a location in the sky. + +Options are: + +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'RADec' Specify ra and dec separately. + + * ``ra`` = *angle_value* (required) + * ``dec`` = *angle_value* (required) + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +table_value +----------- + +These represent `LookupTable` values to provide some kind of unary function, although in some +cases you may be able to provide a regular function instead, e.g. via an Eval type using a lambda +function. + +Options are: + +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'File' Read a `LookupTable` from a file. cf. `LookupTable.from_file`. + + * ``file_name`` = *str_value* (required) + * ``interpolant`` = *str_value* (default = 'spline') Which interpolant to use. + * ``x_log`` = *bool_value* (default = False) Whether to use log(x) for the abscissae. + * ``f_log`` = *bool_value* (default = False) Whether to use log(f) for the ordinates. + * ``amplitude`` = *float_value* (default = 1.0) An optional scaling to apply to the + ordinates. + + * 'List' Select items from a list. + + * ``items`` = *list* (required) A list of *table_value* items. + * ``index`` = *int_value* (default = 'Sequence' from 0 to len(items)-1) + + * 'Current' Use the current value of some other item in the config file. (See the description of this for *float_value* for more details.) + + * ``key`` = *str_value* (required) The key name of the item to use. + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +quantity_value +-------------- + +These represent `astropy.units.Quantity` values, which are a combination of a float and a unit (specifically, an `astropy.units.Unit`). + +Options are: + +* An `astropy.units.Quantity` value (e.g. '8.7*u.m', where 'u' is the `astropy.units` module) +* A string interpretable by `astropy.units.Quantity` (e.g. '8.7 m') +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Quantity' Specify the value and unit separately. + + * ``value`` = *float_value* (required) + * ``unit`` = *unit_value* (required) + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + +unit_value +---------- + +These represent `astropy.units.Unit` values. + +Options are: + +* An `astropy.units.Unit` value (e.g. 'u.m', where 'u' is the `astropy.units` module) +* A string interpretable by `astropy.units.Unit` (e.g. 'm') +* A dict with: + + * ``type`` = *str* (required) Valid options are: + + * 'Unit' Specify the unit. + + * ``unit`` = *str_value* (required) + + * 'Eval' Evaluate a string. See `Eval type`. + +* A string that starts with '$' or '@'. See `Shorthand Notation`. + + +Eval type +--------- + +Every kind of value has 'Eval' as one of its allowed types. This works a little bit differently +than the other types, so we describe it here in its own section. + +The only required attribute to go along with an 'Eval' is ``str``, which is the string to be +evaluated using the python ``eval`` function. + +For example ``str : '800 * 1.e-9 / 4 * 206265'`` will evaluate to 0.041253. (This example is taken from demo3.yaml.) This might either be easier than doing a calculation +yourself or perhaps be clearer as to how the number was formed. For example, this example +calculates ``lam_over_diam`` using lambda = 800 nm, D = 4 m, converting the result into arcsec. +If you later wanted to change to a 6.5m telescope, it would be very clear what to change, +as opposed to if the value were listed as 0.041253. + +Preset variables +^^^^^^^^^^^^^^^^ + +The 'Eval' type gets even more powerful when you use variables. The file demo10.yaml has some +examples that use the ``pos`` variable, the position of the galaxy relative to the center of the +image, which GalSim will make available for you for any +'Tiled' or 'Scattered' image. The PSF ``fwhm`` is given as +``'0.9 + 0.5 * (world_pos.x**2 + world_pos.y**2) / 100**2'``, which calculates the PSF size as a function of +position on the image. + +Variables that GalSim will provide for you to use: + +* ``image_pos`` = the position of the object on the image in pixels. + + * Available if image ``type`` is 'Tiled' or 'Scattered' + * Available if ``image_pos`` or ``world_pos`` is explicitly given in the ``stamp`` field. + * A `galsim.PositionD` instance + +* ``world_pos`` = the position of the object in world coordinates. + + * Available if image ``type`` is 'Tiled' or 'Scattered' + * Available if ``image_pos`` or ``world_pos`` is explicitly given in the ``stamp`` field. + * A `galsim.PositionD` instance if the wcs is a `galsim.wcs.EuclideanWCS` or a `galsim.CelestialCoord` if the wcs is a `galsim.wcs.CelestialWCS`. + +* ``sky_pos`` = the position of the object in sky coordinates (RA, Dec) + + * Available if ``sky_pos`` is explicitly given in the ``stamp`` field. + * Available if ``world_pos`` is defined (as per above) and the WCS is a CelestialWCS. + * A `galsim.CelestialCoord` instance + +* ``uv_pos`` = the position of the object in a tangent plane projection relative to ``world_center``. + + * Available if either image_pos or world_pos is defined and the wcs is defined. + * A `galsim.PositionD` instance + +* ``image_center`` = the center of the image in pixels. + + * A `galsim.PositionD` instance + +* ``world_center`` = the world position of ``image_center``. + + * Computed automatically from ``image_center`` and the wcs. + * A `galsim.PositionD` instance if the wcs is a `galsim.wcs.EuclideanWCS` or a `galsim.CelestialCoord` if the wcs is a `galsim.wcs.CelestialWCS`. + * May also be provided directly if you want something different for this, by specifically including a ``world_center`` item in the ``image`` field. In this case, it should be a `galsim.CelestialCoord` value. + +* ``image_origin`` = the origin of the image in pixels. This is the position on the image that corresponds to the lower-leftmost pixel + + * A `galsim.PositionI` instance + +* ``image_xsize``, ``image_ysize`` = the size of the image in pixels. +* ``image_bounds`` = the bounds of the current image. + + * A `galsim.BoundsI` instance + +* ``stamp_xsize``, ``stamp_ysize`` = the size of the postage stamp in pixels if available. + + * Not always available, since the postage stamp is allowed to be automatically sized based on the size of final object profile. + +* ``pixel_scale`` = the pixel scale of the current image or postage stamp + + * Only available if the WCS is a simple pixel scale. + +* ``wcs`` = the WCS of the current image or postage stamp + + * A `galsim.BaseWCS` instance + +* ``bandpass`` = the bandpass of the current image if defined. + + * A `galsim.Bandpass` instance + +* ``file_num`` = the number of the file currently being worked on. +* ``image_num`` = the number of the image currently being worked on. + + * If parsing a value in ``input`` or ``output``, this may be set to 0 or the last image number from the previous file (if any). + +* ``obj_num`` = the number of the object currently being worked on. + + * If parsing a value in ``input``, ``output``, or ``image``, this may be set to 0 or the last object number from the previous image (if any). + +* ``start_obj_num`` = the number of the first object in the current file. +* ``rng`` = the random number generator being used for this object. + + * A `galsim.BaseDeviate` instance + * You can convert it to whatever deviate you need. e.g. ``galsim.GaussianDeviate(rng,1.0,0.2)()`` + +Available Modules +^^^^^^^^^^^^^^^^^^ + +Python modules that GalSim will import for you to use: + +* ``math`` +* ``numpy`` or ``np`` +* ``os`` +* ``galsim`` (obviously) +* Anything in the ``modules`` field of your configuration file. + + +User-defined variables +^^^^^^^^^^^^^^^^^^^^^^ + +It is also possible to define your own variables to use in your expression simply by +defining more attributes in addition to ``str``. The first letter of the attribute +declares what type it should be evaluated to. Then the rest of the attribute name is +the name of your variable. + +For example, we do not have a specific type for drawing from a Log-Normal distribution. +If you want the flux, say, to be log-normally distributed, you can write something like +the following: + +.. code-block:: yaml + + flux : + type : Eval + str : '1.e5 * math.exp(normal)' + fnormal : { type : RandomGaussian , sigma : 0.2 } + +The ``f`` at the start of ``fnormal`` indicates that the variable ``normal`` should be +evaluated as a *float_value*. In this case using ``type`` = 'RandomGaussian'. + +Another example appears in demo10.yaml. There, we define the magnitude of the ellipticity as: + +.. code-block:: yaml + + e: + type : Eval + fr : { type : Eval , str : '(world_pos.x**2 + world_pos.y**2)**0.5' } + str : '0.4 * (r/100)**1.5' + +So this declares a float variable ``r`` that evaluates as the radial distance from the center. Then the ellipticity is defined in terms of ``r`` directly rather than via ``world_pos``. + +Initial letters of user-defined variables for 'Eval': + +* 'f' = ``float`` +* 'i' = ``int`` +* 'b' = ``bool`` +* 's' = ``str`` +* 'a' = `galsim.Angle` +* 'p' = `galsim.PositionD` +* 'g' = `galsim.Shear` +* 't' = `galsim.LookupTable` +* 'd' = ``dict`` (This takes a dict as a literal, rather than evaluating it.) +* 'l' = ``list`` (Similarly, this allows for a literal list in the config file.) + + +The eval-variables field +^^^^^^^^^^^^^^^^^^^^^^^^ + +Sometimes it is useful to have the same variable used by multiple Eval calculations. For such cases, Eval will look for a top-level field called ``eval_variables``. If this field is present, then anything defined there will be accessible in all Eval calculations in addition to whatever variables are defined for each specific Eval item. + +This is similar to the functionality that YAML provides where a value can be named by putting a variable name with an ``&`` before it before any value. Then later, you can refer to the value by that name preceded by a ``*``, rather than write the value again. This can lead to more maintainable config files. + +It can be convenient to combine the YAML naming scheme with our ``eval_variables`` setup in the following way: + +.. code-block:: yaml + + eval_variables : + fpixel_scale : &pixel_scale 0.3 + istamp_size : &stamp_size 100 + infiles : &nfiles 30 + [ ... ] + +This can be put near the top of the YAML file to put the important values all in one place with appropriate names. Then in the rest of the file, the variables can be used with the YAML ``*`` notation: + +.. code-block:: yaml + + image: + pixel_scale : *pixel_scale + +or as part of an ``Eval`` item: + +.. code-block:: yaml + + shift : + type : RTheta + r : { type : Eval , str : 'pixel_scale * 0.5' } + theta : { type : Random } + + +Module-defined variables +^^^^^^^^^^^^^^^^^^^^^^^^ + +If you are using any user-defined modules (loaded in the ``modules`` field), then they are +allowed to add to the list of `Preset variables`. The mechanism for this is to append items +to the list ``galsim.config.eval_base_variables``. This list includes all of the variables +that an Eval type will load into the local namespace before evaluating. For instance, +if a user-defined module wants to make available a variable called, say, ``coadd_wcs``, +then the module could add this name to the list of avilable variables as folllows: + +.. code-block:: python + + galsim.config.eval_base_variables.append('coadd_wcs') + +This should be done at module scope, since you only want to add it once. So probably best +to add it when your module is imported. + +Then in some processing step, you could set this variable as + +.. code-block:: python + + base['coadd_wcs'] = coadd_wcs + +Then any subsequent Eval field could use this variable as a local variable, just like the +ones listed in `Preset variables`. + + +Shorthand notation +^^^^^^^^^^^^^^^^^^ + +It can be a bit cumbersome at times to write out a full dict with ``type : Eval`` and the +``str`` item you want to evaluate. To streamline this, we also allow for a shorthand notation +for both Eval and Current types. + +- Any string that starts with '$' is taken to mean an Eval type with the rest of the string + being used as the ``str`` field. +- Any string that starts with '@' is taken to mean a Current type with the rest of the string + being used as the ``key`` field. + +Furthermore, you may use '@' style Current specifications within an Eval string, where the +text after the '@' up to the next white space is used for the key. +So for the above example of a half pixel shift in some random direction, you could write: + +.. code-block:: yaml + + shift: + type : RTheta + r : '$0.5 * @image.pixel_scale' + theta : { type: Random } + +In many situations, this shorthand notation aids readability. However, because there is +no dict, you cannot define any variables with this notation. If you need to define any +variables, you will need to use the regular dict notation. + + +Custom Value Types +------------------ + +To define your own value type, you will need to write an importable Python module +(typically a file in the current directory where you are running ``galsim``, but it could also +be something you have installed in your Python distro) with a function that will be used +to generate the value you want from the parameters in the config dict. + +The generator function should have the following functional form:: + + def GenerateCustomValue(config, base, value_type): + """Generate some kind of custom value given some configuration parameters + + @param config The configuration dict of the value being generated. + @param base The base configuration dict. + @param value_type The desired output type. + + @returns value, safe + + The returned value should be something of type value_type, and safe is a bool + value that indicates whether the value is safe to reuse for future stamps + (i.e. it is a constant value that will not change for later stamps). + """ + # If you need a random number generator, this is the one to use. + rng = base['rng'] + + # Generate the desired value. + # Probably something complicated that you want this function to do. + value = [...] + + safe = False # typically, but set to True if this value is safe to reuse. + return value, safe + +The ``base`` parameter is the original full configuration dict that is being used for running the +simulation. The ``config`` parameter is the local portion of the full dict that defines the value +being generated, e.g. ``config`` might be ``base['gal']['flux']``. + +Normally a generator function can only produce a single kind of output type (float for instance), +in which case you can probably ignore the ``value_type`` parameter. However, if your generator +can be used for multiple kinds of values (int, float and bool maybe), then you might want +to do something different depending on what ``value_type`` is given. + +Then, in the Python module, you need to register this function with some type name, which will +be the value of the ``type`` attribute that triggers running this function. You also need to +give a list of all valid value types that are allowed for this function:: + + galsim.config.RegisterValueType('CustomValue', GenerateCustomValue, [float, int]) + +.. autofunction:: galsim.config.RegisterValueType + +If the generator will use a particular input type, you should let GalSim know this by specifying +the ``input_type`` when registering. E.g. if the generator expects to use an input ``catalog`` +to access some ancillary information for each object, you would register this fact using:: + + galsim.config.RegisterValueType('CustomValue', GenerateCustomValue, [float, int], + input_type='catalog') + +The input object can be accessed in the build function as e.g.:: + + input_cat = galsim.config.GetInputObj('catalog', config, base, 'CustomValue') + +The last argument is just used to help give sensible error messages if there is some problem, +but it should typically be the name of the value type being built. + +Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file ``my_custom_value.py``, then you would use the following top-level ``modules`` field +in the config file: + +.. code-block:: yaml + + modules: + - my_custom_value + +This ``modules`` field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command ``import my_custom_value``, +which will read your file and execute the registration command to add your type to the list +of valid value types. + +Then you can use this as a valid value type: + +.. code-block:: yaml + + gal: + flux: + type: CustomValue + ... + +For examples of custom values, see: + +* :gh-link:`log_normal.py ` +* :gh-link:`hsm_shape_measure.py ` +* :gh-link:`excluded_random.py ` +* :gh-link:`great3_reject ` diff --git a/docs/_build/html/_sources/corr_noise.rst.txt b/docs/_build/html/_sources/corr_noise.rst.txt new file mode 100644 index 00000000000..d195b7b24d2 --- /dev/null +++ b/docs/_build/html/_sources/corr_noise.rst.txt @@ -0,0 +1,56 @@ + +Correlated Noise +================ + +The pixel noise in real astronomical images can be correlated, so we have some functionality +to enable simulating this effect in GalSim. + +* `BaseCorrelatedNoise` defines the correlated noise according to an input `GSObject`. This is + the base class for the other ways of defining correlated noise, and implements most of the + useful methods of these classes. + + .. warning:: + + This `GSObject` profile **must** have two-fold rotational symmetry to represent a physical + correlation function, and this requirement is not enforced by GalSim. Users need to + ensure this fact in their calling code. + +* `CorrelatedNoise` computes the correlated noise of in input `Image`, e.g. a blank patch of sky + in an image similar to the one you want to simulate. +* `UncorrelatedNoise` is a `BaseCorrelatedNoise` that start with no correlations. This can then + be sheared or convolved with other profiles to induce correlations. +* `getCOSMOSNoise` is a function the returns a `BaseCorrelatedNoise` corresponding to the + correlated noise found in the HST COSMOS F814W coadd images used by `RealGalaxy`. + +While adding correlated noise to images is a useful feature, this functionality was originally +implemented in GalSim in order to *remove* correlations. Specifically, the `RealGalaxy` class +treats HST images as surface brightness profiles. These images have correlated noise that resulted +from the drizzle image processing. Furthermore, when sheared or convolved by a PSF, the +noise in these images becomes even more correlated. If uncorrected, these pixel correlations +can lead to biases in weak lensing shear estimates of the rendered galaxies. + +To produce more accurate image simulations using these galaxies as the underlying models (and +especially avoid the weak lensing shear biases), it is often useful to "whiten" the correlated +noise that results form these manipulations. + +* `Image.whitenNoise` takes a `BaseCorrelatedNoise` instance as the estimate of the correlated + noise already in an image, and attempts to add more noise to result in uncorrelated "white" + noise. +* `Image.symmetrizeNoise` similarly adds noise, but only attempts to produce a noise profile with + 4-fold (or generically any order) symmetry, which results in less added noise while still + achieving the goal of not having correlated noise bias weak lensing shear measurements. + +.. autoclass:: galsim.BaseCorrelatedNoise + :members: + :show-inheritance: + +.. autoclass:: galsim.CorrelatedNoise + :members: + :show-inheritance: + +.. autoclass:: galsim.UncorrelatedNoise + :members: + :show-inheritance: + +.. autofunction:: galsim.getCOSMOSNoise + diff --git a/docs/_build/html/_sources/cpp.rst.txt b/docs/_build/html/_sources/cpp.rst.txt new file mode 100644 index 00000000000..7dc12d25b95 --- /dev/null +++ b/docs/_build/html/_sources/cpp.rst.txt @@ -0,0 +1,102 @@ +C++ Layer +######### + +While GalSim is primarily a Python package, much of the implementation is done in C++ +for improved speed for the hard-core numerical calculations. + +If you would like to use the C++-layer functions in your own C++ project, you can do +so with the caveat that these are officially implementation details, so we don't +strictly enforce semantic versioning for the C++-layer API. That is, function signatures +may change on minor version updates (e.g. 2.3.x to 2.4.0). We don't often make huge +changes to the C++ API, so most of the time you will be fine upgrading, but you should +be prepared that you may need to update your code when upgrading GalSim. (We do +guarantee that we won't change the C++ API for bugfix updates, e.g. 2.4.1 to 2.4.2.) + +.. note:: + + While we don't intend to break ABI compatibility on bugfix releases, we don't + currently have any mechanisms in place to ensure ABI compatibility. So it is + possible that you will need to recompile your code when updating to a new + GalSim version. + +The other caveat is that we haven't put much energy into documenting the C++ layer +functions. The following comes from a combination of Doxygen and Breathe to shoehorn +it into the Sphinx structure. But the docs are pretty bare bones in places. Sorry +about that. If you use these and want to pretty up these docs, a PR doing so would be +much appreciated. :) + +When compiling your code, all of the public functionality should be included simply +by using ``#include "GalSim.h"`` with the appropriate ``-I`` directive when compiling +to find the GalSim include directory. The appropriate directory name is accessible +from python by running the command: + +.. code:: + + python -c "import galsim; print(galsim.include_dir)" + + +.. toctree:: + :maxdepth: 1 + + cpp_image + cpp_sb + cpp_bounds + cpp_noise + cpp_photon + cpp_interp + cpp_hsm + cpp_math + + +Linking Your Code +================= + +If you install GalSim using conda (see `Installing With Conda`), then the appropriate +C++ library file is included in the conda packaging. It should be installed into either +the main conda lib directory (e.g. ``/anaconda/lib``) or the one for your conda environment +(e.g. ``/anaconda/envs/myenv/lib``). + +If you don't use conda, then you will need to build the lib file yourself, since we don't +include it in the pip package. +See `Installing the C++ Shared Library` for instructions on installing it. + +There are both versioned and unversioned copies of the library. On OSX, these are +``libgalsim.M.m.dylib`` and ``libgalsim.dylib``. On Linux, they are +``libgalsim.M.m.so`` and ``libgalsim.so``. + +You should link by specifying the appropriate directory with ``-L`` and link with ``-lgalsim``. + +Version control +=============== + +We provide a number of functions to help you ensure that your code remains compatible with +updates to GalSim. + +First, there are 3 MACROS that you can use to make a compile-time assert that you are +coding to the right version: + +.. doxygendefine:: GALSIM_MAJOR + +.. doxygendefine:: GALSIM_MINOR + +.. doxygendefine:: GALSIM_REVISION + +Then there are three functions that return the compiled versions of those same numbers: + +.. doxygenfunction:: galsim::major_version + +.. doxygenfunction:: galsim::minor_version + +.. doxygenfunction:: galsim::revision + +One can also get the full three-number version as a string (e.g. "1.4.2") + +.. doxygenfunction:: galsim::version + +And finally, we provide a fucntion that checks that the header file being included matches +the compiled values in the library being linked to: + +.. doxygenfunction:: galsim::check_version + + + diff --git a/docs/_build/html/_sources/cpp_bounds.rst.txt b/docs/_build/html/_sources/cpp_bounds.rst.txt new file mode 100644 index 00000000000..fdec84accdd --- /dev/null +++ b/docs/_build/html/_sources/cpp_bounds.rst.txt @@ -0,0 +1,8 @@ +Positions and Bounds +==================== + +.. doxygenclass:: galsim::Position + +.. doxygenclass:: galsim::Bounds + +. diff --git a/docs/_build/html/_sources/cpp_hsm.rst.txt b/docs/_build/html/_sources/cpp_hsm.rst.txt new file mode 100644 index 00000000000..cb83726b2c8 --- /dev/null +++ b/docs/_build/html/_sources/cpp_hsm.rst.txt @@ -0,0 +1,25 @@ +HSM Implementation +================== + +Primary Interface +----------------- + +.. doxygenfunction:: galsim::hsm::EstimateShearView + +.. doxygenfunction:: galsim::hsm::FindAdaptiveMomView + +.. doxygenstruct:: galsim::hsm::HSMParams + +.. doxygenclass:: galsim::hsm::HSMError + +Helper Functions +---------------- + +.. doxygenfunction:: galsim::hsm::general_shear_estimator + +.. doxygenfunction:: galsim::hsm::find_ellipmom_2 + +.. doxygenstruct:: galsim::hsm::ShapeData + +.. doxygenstruct:: galsim::hsm::ObjectData + diff --git a/docs/_build/html/_sources/cpp_image.rst.txt b/docs/_build/html/_sources/cpp_image.rst.txt new file mode 100644 index 00000000000..f0a2f16d11e --- /dev/null +++ b/docs/_build/html/_sources/cpp_image.rst.txt @@ -0,0 +1,37 @@ +C++ Images +========== + +Image Classes +------------- + +.. doxygenclass:: galsim::AssignableToImage + +.. doxygenclass:: galsim::BaseImage + +.. doxygenclass:: galsim::ImageAlloc + +.. doxygenclass:: galsim::ImageView + +.. doxygenclass:: galsim::ConstImageView + +Image-related Functionality +--------------------------- + +.. doxygenclass:: galsim::ImageError + +.. doxygenclass:: galsim::ImageBoundsError + +.. doxygenfunction:: galsim::goodFFTSize + +.. doxygenfunction:: galsim::rfft + +.. doxygenfunction:: galsim::irfft + +.. doxygenfunction:: galsim::cfft + +.. doxygenfunction:: galsim::wrapImage + +.. doxygenfunction:: galsim::invertImage + +.. doxygenfunction:: galsim::ClearDepixelizeCache + diff --git a/docs/_build/html/_sources/cpp_interp.rst.txt b/docs/_build/html/_sources/cpp_interp.rst.txt new file mode 100644 index 00000000000..759d0c44e8d --- /dev/null +++ b/docs/_build/html/_sources/cpp_interp.rst.txt @@ -0,0 +1,30 @@ +Interpolation Tools +=================== + +C++ Interpolants +---------------- + +.. doxygenclass:: galsim::Interpolant + +.. doxygenclass:: galsim::Delta + +.. doxygenclass:: galsim::Nearest + +.. doxygenclass:: galsim::Linear + +.. doxygenclass:: galsim::Cubic + +.. doxygenclass:: galsim::Quintic + +.. doxygenclass:: galsim::SincInterpolant + +.. doxygenclass:: galsim::Lanczos + +C++ Lookup Tables +----------------- + +.. doxygenclass:: galsim::Table + +.. doxygenclass:: galsim::TableBuilder + +.. doxygenclass:: galsim::Table2D diff --git a/docs/_build/html/_sources/cpp_math.rst.txt b/docs/_build/html/_sources/cpp_math.rst.txt new file mode 100644 index 00000000000..8519e36015a --- /dev/null +++ b/docs/_build/html/_sources/cpp_math.rst.txt @@ -0,0 +1,82 @@ +Math +==== + +Nonlinear solver +---------------- + +.. doxygenclass:: galsim::Solve + +Bessel and Related Functions +---------------------------- + +.. doxygenfunction:: galsim::math::cyl_bessel_j + +.. doxygenfunction:: galsim::math::cyl_bessel_y + +.. doxygenfunction:: galsim::math::cyl_bessel_k + +.. doxygenfunction:: galsim::math::cyl_bessel_i + +.. doxygenfunction:: galsim::math::j0 + +.. doxygenfunction:: galsim::math::j1 + +.. doxygenfunction:: galsim::math::getBesselRoot0 + +.. doxygenfunction:: galsim::math::getBesselRoot + + +Other mathematical functions +---------------------------- + +.. doxygenfunction:: galsim::math::sincos + +.. doxygenfunction:: galsim::math::gamma_p + +.. doxygenfunction:: galsim::math::sinc + +.. doxygenfunction:: galsim::math::Si + +.. doxygenfunction:: galsim::math::Ci + + +Horner's method for polynomial evaluation +----------------------------------------- + +.. doxygenfunction:: galsim::math::Horner + +.. doxygenfunction:: galsim::math::Horner2D + +C++ Integration Functions +------------------------- + +.. doxygenstruct:: galsim::integ::IntRegion + +.. doxygenfunction:: galsim::integ::int1d(const UF&, double, double, const double, const double) + +.. doxygenfunction:: galsim::integ::int1d(const UF&, IntRegion&, const double, const double) + +.. doxygenfunction:: galsim::integ::int2d(const BF&, double, double, double, double, const double, const double) + +.. doxygenfunction:: galsim::integ::int2d(const BF&, IntRegion&, const YREG&, const double, const double) + +.. doxygenfunction:: galsim::integ::int2d(const BF&, IntRegion&, IntRegion&, const double, const double) + +.. doxygenfunction:: galsim::integ::int3d(const TF&, double, double, double, double, double, double, const double, const double) + +.. doxygenfunction:: galsim::integ::int3d(const TF&, IntRegion&, const YREG&, const ZREG&, const double, const double) + +.. doxygenfunction:: galsim::integ::int3d(const TF&, IntRegion&, IntRegion&, IntRegion&, const double, const double) + +.. doxygenfunction:: galsim::math::hankel_trunc + +.. doxygenfunction:: galsim::math::hankel_inf + +Misc Utilities +-------------- + +.. doxygenfunction:: galsim::math::isNan + +.. doxygenfunction:: galsim::SetOMPThreads + +.. doxygenfunction:: galsim::GetOMPThreads diff --git a/docs/_build/html/_sources/cpp_noise.rst.txt b/docs/_build/html/_sources/cpp_noise.rst.txt new file mode 100644 index 00000000000..d8a1c6c4405 --- /dev/null +++ b/docs/_build/html/_sources/cpp_noise.rst.txt @@ -0,0 +1,28 @@ +Noise-related Functionality +=========================== + +C++ Random Deviates +------------------- + +.. doxygenclass:: galsim::BaseDeviate + +.. doxygenclass:: galsim::UniformDeviate + +.. doxygenclass:: galsim::GaussianDeviate + +.. doxygenclass:: galsim::BinomialDeviate + +.. doxygenclass:: galsim::PoissonDeviate + +.. doxygenclass:: galsim::WeibullDeviate + +.. doxygenclass:: galsim::GammaDeviate + +.. doxygenclass:: galsim::Chi2Deviate + +C++ Correlated Noise +-------------------- + +.. doxygenfunction:: galsim::calculateCovarianceMatrix + + diff --git a/docs/_build/html/_sources/cpp_photon.rst.txt b/docs/_build/html/_sources/cpp_photon.rst.txt new file mode 100644 index 00000000000..7ac214a5b21 --- /dev/null +++ b/docs/_build/html/_sources/cpp_photon.rst.txt @@ -0,0 +1,17 @@ +Photons and Sensor Effects +========================== + +C++ Photon Array +---------------- + +.. doxygenclass:: galsim::PhotonArray + +Silicon Sensor +-------------- + +.. doxygenclass:: galsim::Silicon + +Charge Deflection Correction +---------------------------- + +.. doxygenfunction:: galsim::ApplyCD diff --git a/docs/_build/html/_sources/cpp_sb.rst.txt b/docs/_build/html/_sources/cpp_sb.rst.txt new file mode 100644 index 00000000000..fd1cd1bca1c --- /dev/null +++ b/docs/_build/html/_sources/cpp_sb.rst.txt @@ -0,0 +1,86 @@ +C++ Surface Brightness Profiles +=============================== + + +SBProfile Base Class +-------------------- + +.. doxygenclass:: galsim::SBProfile + + +Simple C++ Profiles +------------------- + +.. doxygenclass:: galsim::SBGaussian + +.. doxygenclass:: galsim::SBDeltaFunction + +.. doxygenclass:: galsim::SBBox + +.. doxygenclass:: galsim::SBTopHat + + +PSF C++ Profiles +---------------- + +.. doxygenclass:: galsim::SBMoffat + +.. doxygenclass:: galsim::SBAiry + +.. doxygenclass:: galsim::SBKolmogorov + +.. doxygenclass:: galsim::SBVonKarman + +.. doxygenclass:: galsim::SBSecondKick + + +Galaxy C++ Profiles +------------------- + +.. doxygenclass:: galsim::SBExponential + +.. doxygenclass:: galsim::SBSersic + +.. doxygenclass:: galsim::SBInclinedExponential + +.. doxygenclass:: galsim::SBInclinedSersic + +.. doxygenclass:: galsim::SBSpergel + + +Arbitrary C++ Profiles +---------------------- + +.. doxygenclass:: galsim::SBInterpolatedImage + +.. doxygenclass:: galsim::SBInterpolatedKImage + +.. doxygenclass:: galsim::SBShapelet + + +Composite C++ Profiles +---------------------- + +.. doxygenclass:: galsim::SBAdd + +.. doxygenclass:: galsim::SBConvolve + +.. doxygenclass:: galsim::SBAutoConvolve + +.. doxygenclass:: galsim::SBAutoCorrelate + + +Transformed C++ Profiles +------------------------ + +.. doxygenclass:: galsim::SBTransform + +.. doxygenclass:: galsim::SBDeconvolve + +.. doxygenclass:: galsim::SBFourierSqrt + + +C++ GSParams +------------ + +.. doxygenstruct:: galsim::GSParams diff --git a/docs/_build/html/_sources/dcr.rst.txt b/docs/_build/html/_sources/dcr.rst.txt new file mode 100644 index 00000000000..05f0e20c31e --- /dev/null +++ b/docs/_build/html/_sources/dcr.rst.txt @@ -0,0 +1,14 @@ +Differential Chromatic Refraction +================================= + +These utilities are used for our various classes and functions that implement differential +chromatic refraction (DCR). + +.. autofunction:: galsim.dcr.air_refractive_index_minus_one + +.. autofunction:: galsim.dcr.get_refraction + +.. autofunction:: galsim.dcr.zenith_parallactic_angles + +.. autofunction:: galsim.dcr.parse_dcr_angles + diff --git a/docs/_build/html/_sources/des.rst.txt b/docs/_build/html/_sources/des.rst.txt new file mode 100644 index 00000000000..405f599ed05 --- /dev/null +++ b/docs/_build/html/_sources/des.rst.txt @@ -0,0 +1,44 @@ + +The DES Module +############## + +The galsim.des module contains some functionality specific developed for the use of GalSim in +simulations of the Dark Energy Survey. However, both PSFEx and MEDS files are used for other +surveys besides DES, so both `DES_PSFEx` and `MEDSBuilder` may be relevant to users outside of +DES. + +.. note:: + To use this module, you must separately ``import galsim.des``. These functions are + not automatically imported when you ``import galsim``. + +DES PSF models +-------------- + +.. autoclass:: galsim.des.DES_PSFEx + :members: + :show-inheritance: + +.. autoclass:: galsim.des.DES_Shapelet + :members: + :show-inheritance: + + +Writing to MEDS Files +--------------------- + +This module defines the `MultiExposureObject` class for representing multiple exposure data for a single object. The `WriteMEDS` function can be used to write a list of `MultiExposureObject` instances to a single MEDS file. + +Importing this module also adds these data structures to the config framework, so that MEDS file output can subsequently be simulated directly using a config file. + +.. autoclass:: galsim.des.MultiExposureObject + :members: + +.. autoclass:: galsim.des.MEDSBuilder + :members: + :show-inheritance: + +.. autoclass:: galsim.des.OffsetBuilder + :members: + :show-inheritance: + +.. autofunction:: galsim.des.WriteMEDS diff --git a/docs/_build/html/_sources/deviate.rst.txt b/docs/_build/html/_sources/deviate.rst.txt new file mode 100644 index 00000000000..570824badc5 --- /dev/null +++ b/docs/_build/html/_sources/deviate.rst.txt @@ -0,0 +1,93 @@ + +Random Deviates +=============== + +GalSim can produce random values according to a variety of probability distributions: + +* `UniformDeviate` implements :math:`p(x) = 1` for :math:`0 \le x < 1`. +* `GaussianDeviate` implements :math:`p(x) = \frac{1}{\sqrt{2\pi\sigma}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}`. +* `PoissonDeviate` implements :math:`p(x) = \frac{e^{-\mu}\mu^x}{x!}` for integer :math:`x > 0`. +* `BinomialDeviate` implements :math:`p(x) = {N \choose x}p^k(1-p)^{N-x}` for integer :math:`0 \le x \le N`. +* `Chi2Deviate` implements :math:`p(x) = \frac{x^{(n/2)-1}e^{-x/2}}{\Gamma(n/2)2^{n/2}}` for :math:`x > 0`. +* `GammaDeviate` implements :math:`p(x) = x^{k-1}\frac{e^{-x/\theta}}{\theta^k\Gamma(k)}` for :math:`x > 0`. +* `WeibullDeviate` implements :math:`p(x) = \frac{a}{b}\left(\frac{x}{b}\right)^{a-1}e^{-\left(\frac{x}{b}\right)^a}` for :math:`x \ge 0`. +* `DistDeviate` implements any arbitrary, user-supplied :math:`p(x)`. + +These are all subclasses of the base class `BaseDeviate`, which implements the underlying +pseudo-random number generator using the Boost libraries Mersenne twister. + +We have fixed the implementation of this to Boost version 1.48.0, the relevant files of which are +bundled with the GalSim distribution, so that random numbers produced by GalSim simulations are +deterministic across different user platforms and operating systems. These Boost files are +included with GalSim, so the user does not need to have Boost installed on their system. + +There are ways to connect various different deviate objects to use the same underlying +`BaseDeviate`, which is often important for producing deterministic simulations given a particular +random number seed. See the docstring of `BaseDeviate` for details. + +.. note:: + + We have put some care into the way we seed the random number generator such that it is + safe to start several random number sequences seeded by sequential seeds. This is already + supposed to be the case for the Boost Mersenne Twister implementation, but we add some extra + (probably overly paranoid) steps to ensure this by seeding one pseudo-rng, skip a few values, + and then use that to seed the actual pseudo-rng that we will use. + + This means you can start the rngs for sequential images or even galaxies with sequential seed + values and there will not be any measurable correlations in the results. This can greatly + ease the ability to split work across multiple processes and still achieve deterministic + results. + +.. autoclass:: galsim.BaseDeviate + :members: + + .. automethod:: galsim.BaseDeviate._seed + .. automethod:: galsim.BaseDeviate._reset + +.. autoclass:: galsim.UniformDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.UniformDeviate.__call__ + +.. autoclass:: galsim.GaussianDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.GaussianDeviate.__call__ + +.. autoclass:: galsim.PoissonDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.PoissonDeviate.__call__ + +.. autoclass:: galsim.BinomialDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.BinomialDeviate.__call__ + +.. autoclass:: galsim.Chi2Deviate + :members: + :show-inheritance: + + .. automethod:: galsim.Chi2Deviate.__call__ + +.. autoclass:: galsim.GammaDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.GammaDeviate.__call__ + +.. autoclass:: galsim.WeibullDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.WeibullDeviate.__call__ + +.. autoclass:: galsim.DistDeviate + :members: + :show-inheritance: + + .. automethod:: galsim.DistDeviate.__call__ diff --git a/docs/_build/html/_sources/errors.rst.txt b/docs/_build/html/_sources/errors.rst.txt new file mode 100644 index 00000000000..d7134f357ca --- /dev/null +++ b/docs/_build/html/_sources/errors.rst.txt @@ -0,0 +1,127 @@ + +Errors and Warnings +################### + +GalSim uses some custom Exception and Warning classes when it finds some exceptional +occurrence: + +`GalSimError` + This is the base class for all other GalSim exceptions. So you can catch this + exception if you want to catch just the exceptions raised by GalSim. It is + roughly analogous to a ``RuntimeError``. + +`GalSimValueError` + This indicates that you provided an invalid value for an argument to a function. + It includes attributes that tell you about what value you provided and sometimes + about the allowed values. + +`GalSimKeyError` + This indicates that you tried to access some dict-like object (e.g. `FitsHeader` + or `Catalog`) with an invalid key. + +`GalSimIndexError` + This indicates that you tried to access some list-like object (e.g. `RealGalaxyCatalog`) + with an invalid index. + +`GalSimRangeError` + This indicates that you provided a value that is outside of the allowed range. It + includes attributes indicating what value you provided and what the allowed range is. + +`GalSimBoundsError` + This indicates that you used a `Position` outside of the allowed `Bounds`. It is + basically a `GalSimRangeError`, but in two dimensions. It includes attributes that + tell you the `Position` and the allowed `Bounds`. + +`GalSimUndefinedBoundsError` + This indicates that you are trying to use an undefined `Bounds` instance in a context + where it must be defined. + +`GalSimImmutableError` + This indicates that you tried to change an immutable `Image` in some way. + +`GalSimIncompatibleValuesError` + This indicates that two or more values that you provided to some function are not + compatible with each other. It includes attributes telling you the two values that + are incompatible. + +`GalSimSEDError` + This indicates that you tried to use an SED in a context where it is required to be + either spectral or dimensionless, and you provided the other kind. + +`GalSimHSMError` + This indicates that the HSM algorithm raised some kind of exception. + +`GalSimFFTSizeError` + This indicates that something you did requires a very large FFT, in particular one + that is larger than the relevant ``gsparams.maximum_fft_size`` parameter. It includes + attributes that tell you both the size that was required and how much memory it would + have used, so you can decide whether you want to adjust some parameters of your + simulation or to adjust the object's `GSParams` options. + +`GalSimConfigError` + This indicates that there was some kind of failure processing a configuration file. + +`GalSimConfigValueError` + This indicates that some parameter in your configuration file is an invalid value. + +`GalSimNotImplementedError` + This indicates that you tried to do something that is not implemented currently. + +`GalSimWarning` + This indicates that you did something that is not necessarily an error, but we think + it is likely that you didn't do something right. + +`GalSimDeprecationWarning` + This indicates that you are using functionality that is currently deprecated. + Your code will generally continue to work until the next major upgrade, but you are + encouraged to update your code to the new syntax. + +.. autoclass:: galsim.GalSimError + +.. autoclass:: galsim.GalSimValueError + :show-inheritance: + +.. autoclass:: galsim.GalSimKeyError + :show-inheritance: + +.. autoclass:: galsim.GalSimIndexError + :show-inheritance: + +.. autoclass:: galsim.GalSimRangeError + :show-inheritance: + +.. autoclass:: galsim.GalSimBoundsError + :show-inheritance: + +.. autoclass:: galsim.GalSimUndefinedBoundsError + :show-inheritance: + +.. autoclass:: galsim.GalSimImmutableError + :show-inheritance: + +.. autoclass:: galsim.GalSimIncompatibleValuesError + :show-inheritance: + +.. autoclass:: galsim.GalSimSEDError + :show-inheritance: + +.. autoclass:: galsim.GalSimHSMError + :show-inheritance: + +.. autoclass:: galsim.GalSimFFTSizeError + :show-inheritance: + +.. autoclass:: galsim.GalSimConfigError + :show-inheritance: + +.. autoclass:: galsim.GalSimConfigValueError + :show-inheritance: + +.. autoclass:: galsim.GalSimNotImplementedError + :show-inheritance: + +.. autoclass:: galsim.GalSimWarning + :show-inheritance: + +.. autoclass:: galsim.GalSimDeprecationWarning + :show-inheritance: diff --git a/docs/_build/html/_sources/fft.rst.txt b/docs/_build/html/_sources/fft.rst.txt new file mode 100644 index 00000000000..6a29526d0eb --- /dev/null +++ b/docs/_build/html/_sources/fft.rst.txt @@ -0,0 +1,30 @@ +Fourier Transforms +================== + +In the C++ layer we use `FFTW `_ for our 2D Fourier transforms. +This package is generally faster than numpy fft functions. So for at least a subset +of the functionality available in the numpy versions, we have implemented python functions +that call out to the backend C++ FFTW functions. + +These should be drop in replacements for np.fft.* functions. e.g.:: + + >>> karray = galsim.fft.fft2(xarray) + +is functionally equivalent to:: + + >>> karray = np.fft.fft2(xarray) + +but should be a bit faster. + +.. note:: + The GalSim versions often only implement the normal use case without many of the + advanced options available with the numpy functions. This is mostly laziness on our part -- + we only implemented the functions that we needed. If your usage requires some option available + in the numpy version, feel free to post a feature request on our GitHub page. + + +.. autofunction:: galsim.fft.fft2 +.. autofunction:: galsim.fft.ifft2 +.. autofunction:: galsim.fft.rfft2 +.. autofunction:: galsim.fft.irfft2 + diff --git a/docs/_build/html/_sources/fits.rst.txt b/docs/_build/html/_sources/fits.rst.txt new file mode 100644 index 00000000000..979bb70eb32 --- /dev/null +++ b/docs/_build/html/_sources/fits.rst.txt @@ -0,0 +1,50 @@ +Interfacing with FITS Files +=========================== + +As many astronomical images are stored as FITS files, GalSim includes functionality for +reading and writing these files with GalSim `Image` instances. + +We include routines for reading and writing an individual `Image` to/from FITS files, and also +routines for handling multiple `Image` instances in a single FITS file. + +We also have a wrapper around the FITS header information to make it work more like a Python +``dict``, called `FitsHeader`. + +.. note:: + These routines are largely wrappers of the astropy.io.fits package. They are still fairly + useful for connecting GalSim objects with the AstroPy API. However, they used to be critically + important for providing a stable API across different PyFITS and then AstroPy versions. + For instance, now the ``astropy.io.fits.Header`` API is very similar to our own `FitsHeader`, + but we used to have many checks for different PyFITS and AstroPy versions to call things in + different ways while maintaining an intuitive front-end user interface. + + +Reading FITS Files +------------------ + +.. autofunction:: galsim.fits.read + +.. autofunction:: galsim.fits.readMulti + +.. autofunction:: galsim.fits.readCube + +.. autofunction:: galsim.fits.readFile + +.. autofunction:: galsim.fits.closeHDUList + +Writing FITS Files +------------------ + +.. autofunction:: galsim.fits.write + +.. autofunction:: galsim.fits.writeMulti + +.. autofunction:: galsim.fits.writeCube + +.. autofunction:: galsim.fits.writeFile + +FITS Headers +------------ + +.. autoclass:: galsim.fits.FitsHeader + :members: diff --git a/docs/_build/html/_sources/gal.rst.txt b/docs/_build/html/_sources/gal.rst.txt new file mode 100644 index 00000000000..83022242328 --- /dev/null +++ b/docs/_build/html/_sources/gal.rst.txt @@ -0,0 +1,57 @@ +Galaxies +======== + +There are a number of profiles that are designed to be used for galaxy profiles. +Again though, there is nothing restricting these classes to be used only for that purpose +if you have another use case for which one would be relevant. + +Exponenatial Profile +-------------------- + +.. autoclass:: galsim.Exponential + :members: + :show-inheritance: + +De Vaucouleurs Profile +---------------------- + +.. autoclass:: galsim.DeVaucouleurs + :members: + :show-inheritance: + +Sersic Profile +-------------- + +.. autoclass:: galsim.Sersic + :members: + :show-inheritance: + +Inclined Exponential Profile +---------------------------- + +.. autoclass:: galsim.InclinedExponential + :members: + :show-inheritance: + +Inclined Sersic Profile +----------------------- + +.. autoclass:: galsim.InclinedSersic + :members: + :show-inheritance: + +Spergel Profile +--------------- + +.. autoclass:: galsim.Spergel + :members: + :show-inheritance: + +Knots of Star Formation +----------------------- + +.. autoclass:: galsim.RandomKnots + :members: + :show-inheritance: + + diff --git a/docs/_build/html/_sources/gsobject.rst.txt b/docs/_build/html/_sources/gsobject.rst.txt new file mode 100644 index 00000000000..bd12dab264a --- /dev/null +++ b/docs/_build/html/_sources/gsobject.rst.txt @@ -0,0 +1,30 @@ + +The GSObject base class +======================= + +This class defines most of the public API methods for how to use one of the various +surface brightness profiles like transforming it, drawing it, etc. + +Note that not all methods are allowed to be called for all subclasses. For instance, +some classes only define the profile in Fourier space, so methods which need to access +the profile in real space may not be implemented. In such cases, a NotImplementedError +will be raised. + + +.. autoclass:: galsim.GSObject + :members: + + .. automethod:: galsim.GSObject.__add__ + .. automethod:: galsim.GSObject.__sub__ + .. automethod:: galsim.GSObject.__mul__ + .. automethod:: galsim.GSObject.__rmul__ + .. automethod:: galsim.GSObject.__div__ + .. automethod:: galsim.GSObject._xValue + .. automethod:: galsim.GSObject._kValue + .. automethod:: galsim.GSObject._shear + .. automethod:: galsim.GSObject._shift + .. automethod:: galsim.GSObject._drawReal + .. automethod:: galsim.GSObject._calculate_nphotons + .. automethod:: galsim.GSObject._shoot + .. automethod:: galsim.GSObject._drawKImage + diff --git a/docs/_build/html/_sources/gsparams.rst.txt b/docs/_build/html/_sources/gsparams.rst.txt new file mode 100644 index 00000000000..78780cd5c17 --- /dev/null +++ b/docs/_build/html/_sources/gsparams.rst.txt @@ -0,0 +1,11 @@ + +The GSParams class +================== + +As mentioned in the `GSObject` docstring, all of the various `GSObject` classes take an optional +``gsparams`` argument, which can be used to specify various numbers that govern the tradeoff +between accuracy and speed for the calculations made in drawing a GSObject. The numbers are +encapsulated in a class called `GSParams`. + +.. autoclass:: galsim.GSParams + :members: diff --git a/docs/_build/html/_sources/history.rst.txt b/docs/_build/html/_sources/history.rst.txt new file mode 100644 index 00000000000..63a1c3b7268 --- /dev/null +++ b/docs/_build/html/_sources/history.rst.txt @@ -0,0 +1,12 @@ + +Revision History +################ + +.. include:: ../CHANGELOG.rst + + +Older Versions +============== + +.. toctree:: + older diff --git a/docs/_build/html/_sources/hsm.rst.txt b/docs/_build/html/_sources/hsm.rst.txt new file mode 100644 index 00000000000..5ae2d23920c --- /dev/null +++ b/docs/_build/html/_sources/hsm.rst.txt @@ -0,0 +1,61 @@ + +The HSM Module +############## + +Routines for adaptive moment estimation and PSF correction. + +This module contains code for estimation of second moments of images, and for carrying out PSF +correction using a variety of algorithms. The algorithms are described in +`Hirata & Seljak (2003) `_, and were +tested/characterized using real data in +`Mandelbaum et al. (2005) `_. +Note that these routines for moment measurement and shear estimation are not accessible via config, +only via python. There are a number of default settings for the code (often governing the tradeoff +between accuracy and speed) that can be adjusting using an optional ``hsmparams`` argument as +described below. + +The moments that are estimated are "adaptive moments" (see the first paper cited above for details); +that is, they use an elliptical Gaussian weight that is matched to the image of the object being +measured. The observed moments can be represented as a Gaussian sigma and a Shear object +representing the shape. + +The PSF correction includes several algorithms, three that are re-implementations of methods +originated by others and one that was originated by Hirata & Seljak: + +- One from `Kaiser, Squires, & Broadhurst (1995) `_, "KSB" + +- One from `Bernstein & Jarvis (2002) `_, "BJ" + +- One that represents a modification by Hirata & Seljak (2003) of methods in Bernstein & Jarvis (2002), "LINEAR" + +- One method from Hirata & Seljak (2003), "REGAUSS" (re-Gaussianization) + +These methods return shear (or shape) estimators, which may not in fact satisfy conditions like +:math:`|e|<=1`, and so they are represented simply as e1/e2 or g1/g2 (depending on the method) +rather than using a Shear object, which IS required to satisfy :math:`|e|<=1`. + +These methods are all based on correction of moments, but with different sets of assumptions. For +more detailed discussion on all of these algorithms, see the relevant papers above. + +Users can find a listing of the parameters that can be adjusted using the ``hsmparams`` keyword, +along with default values, under `galsim.hsm.HSMParams` below. + + +Shape Measurement Functions +=========================== + +.. autofunction:: galsim.hsm.FindAdaptiveMom + +.. autofunction:: galsim.hsm.EstimateShear + +HSM output +========== + +.. autoclass:: galsim.hsm.ShapeData + :members: + +HSM parameters +============== + +.. autoclass:: galsim.hsm.HSMParams + :members: diff --git a/docs/_build/html/_sources/image.rst.txt b/docs/_build/html/_sources/image.rst.txt new file mode 100644 index 00000000000..8a6fa8a1ae9 --- /dev/null +++ b/docs/_build/html/_sources/image.rst.txt @@ -0,0 +1,26 @@ + +Images and Related Concepts +########################### + +The main purpose of GalSim is normally to create images that simulate real astronomical +observations. As such, the `Image` class is the most common class in GalSim that you will +likely be working with. + +An image if fundamentally an array of pixel values plus a bounding box indicating the range +of position values for those pixels, and a definition for how the image coordinates relate +to positions on the sky. + +For the former concept, the `Bounds` class defines which x and y values are contained in +the image, and the `Position` class is used to describe a particular location on an image. + +For the latter concept, the simplest option is to just set a uniform pixel scale, e.g. +in arcsec/pixel. But a wide range of more complicated `World Coordinate Systems` are +also possible. + +.. toctree:: + :maxdepth: 2 + + image_class + bounds + pos + diff --git a/docs/_build/html/_sources/image_class.rst.txt b/docs/_build/html/_sources/image_class.rst.txt new file mode 100644 index 00000000000..971ad95c453 --- /dev/null +++ b/docs/_build/html/_sources/image_class.rst.txt @@ -0,0 +1,30 @@ + +The Image class +=============== + +.. autoclass:: galsim.Image + :members: + + .. automethod:: galsim.Image.__getitem__ + .. automethod:: galsim.Image.__setitem__ + .. automethod:: galsim.Image._wrap + .. automethod:: galsim.Image._view + .. automethod:: galsim.Image._shift + .. automethod:: galsim.Image.__call__ + .. automethod:: galsim.Image._getValue + .. automethod:: galsim.Image._setValue + .. automethod:: galsim.Image._addValue + .. automethod:: galsim.Image._fill + .. automethod:: galsim.Image._invertSelf + +.. autofunction:: galsim._Image + +.. autofunction:: galsim.ImageF +.. autofunction:: galsim.ImageD +.. autofunction:: galsim.ImageI +.. autofunction:: galsim.ImageS +.. autofunction:: galsim.ImageUI +.. autofunction:: galsim.ImageUS +.. autofunction:: galsim.ImageCF +.. autofunction:: galsim.ImageCD + diff --git a/docs/_build/html/_sources/index.rst.txt b/docs/_build/html/_sources/index.rst.txt new file mode 100644 index 00000000000..fe300dfea57 --- /dev/null +++ b/docs/_build/html/_sources/index.rst.txt @@ -0,0 +1,39 @@ +.. GalSim documentation master file, created by + +GalSim: The modular galaxy image simulation toolkit + +.. toctree:: + :maxdepth: 2 + + overview + install + tutorials + + image + sb + chromatic + units + wcs + random + wl + photon + utilities + errors + + config + hsm + des + roman + + cpp + + shared + history + +================== +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/_build/html/_sources/install.rst.txt b/docs/_build/html/_sources/install.rst.txt new file mode 100644 index 00000000000..908f93a304d --- /dev/null +++ b/docs/_build/html/_sources/install.rst.txt @@ -0,0 +1,115 @@ +Installation Instructions +######################### + +GalSim is a Python module that has much of its implementation in C++ for +improved computational efficiency. +It is regularly tested on Python versions 3.7, 3.8, and 3.9 on both linux +and mac os. + +It also seems to work on PyPy (both via conda-forge and the GitHub Actions +setup), although we don't consider this an officially supported system. +If you use GalSim with PyPy and experience any problems, we would appreciate +hearing about them. Please open an issue describing any problems you find. + +System requirements: GalSim currently only supports Linux and Mac OSX. +Possibly other POSIX-compliant systems, but we specifically do not +currently support Windows. + +The usual way to install GalSim is now (starting with version 2.0) simply:: + + pip install galsim + +which will install the latest official release of GalSim. +For complete details, see `Installing With Pip`. + +Another option If you use Anaconda Python is to use ``conda``:: + + conda install -c conda-forge galsim + +For more information, see `Installing With Conda`. + + +.. toctree:: + :maxdepth: 1 + + install_pip.rst + install_conda.rst + +Running tests +============= + +The simplest way to run our test suite by typing:: + + python setup.py test + +This should run all the Python-layer tests with pytest and also compile and +run the C++ test suite. + +There are a number of packages that are used by the tests, but which are not +required for GalSim installation and running. These should be installed +automatically by the above command, but you can install them manually via:: + + pip install -r test_requirements.txt + +(As usually, you may need to add either ``sudo`` or ``--user``.) + +By default, the tests will run in parallel using the pytest plugins +``pytest-xdist`` and ``pytest-timeout`` (to manage how much time each test is +allowed to run). If you want to run the Python tests in serial instead, +you can do this via:: + + python setup.py test -j1 + +You can also use this to modify how many jobs will be spawned for running the +tests. + +Or, you can run the Python tests yourself in the ``tests`` directory by typing:: + + pytest test*.py + +You can also run them with multiple jobs (e.g. for 4 jobs) by typing:: + + pytest -n=4 --timeout=60 test*.py + +You need the ``pytest-xdist`` and ``pytest-timeout`` plugins for this to work. + +.. note:: + + If your system does not have ``pytest`` installed, and you do not want + to install it, you can run all the Python tests with the script ``run_all_tests`` + in the ``tests`` directory. If this finishes without an error, then all the tests + have passed. However, note that this script runs more tests than our normal + test run using ``pytest``, so it may take quite a while to finish. (The "all" in + the file name means run **all** the tests including the slow ones that we normally + skip.) + + + +Running example scripts +======================= + +The ``examples`` directory has a series of demo scripts:: + + demo1.py, demo2.py, ... + +These can be considered a tutorial on getting up to speed with GalSim. Reading +through these in order will introduce you to how to use most of the features of +GalSim in Python. To run these scripts, type (e.g.):: + + python demo1.py + +There are also a corresponding set of config files:: + + demo1.yaml, demo2.yaml, ... + +These files can be run using the executable ``galsim``, and will produce the +same output images as the Python scripts:: + + galsim demo1.yaml + +They are also well commented, and can be considered a parallel tutorial for +learning the config file usage of GalSim. + +All demo scripts are designed to be run in the ``GalSim/examples`` directory. +Some of them access files in subdirectories of the ``examples`` directory, so they +would not work correctly from other locations. diff --git a/docs/_build/html/_sources/install_conda.rst.txt b/docs/_build/html/_sources/install_conda.rst.txt new file mode 100644 index 00000000000..9cb685a76f0 --- /dev/null +++ b/docs/_build/html/_sources/install_conda.rst.txt @@ -0,0 +1,23 @@ +Installing With Conda +===================== + +If you use conda (normally via the Anaconda Python distribution), then all of +the prerequisites and galsim itself are available from the conda-forge channel, +so you can use that as follows:: + + conda install -c conda-forge galsim + +Also, if you prefer to use the defaults channel, then (at least as of this +writing), it had all the items in conda_requirements.txt, except for pybind11. +So if you have conda-forge in your list of channels, but it comes after +defaults, then that should still work and pybind11 will be the only one that +will need the conda-forge channel. + +If you want to install from source (e.g. to work on a development branch), +but use conda for the dependencies, you can do:: + + git clone git@github.com:GalSim-developers/GalSim.git + cd GalSim + conda install --file conda_requirements.txt + python setup.py install + diff --git a/docs/_build/html/_sources/install_pip.rst.txt b/docs/_build/html/_sources/install_pip.rst.txt new file mode 100644 index 00000000000..6459953c734 --- /dev/null +++ b/docs/_build/html/_sources/install_pip.rst.txt @@ -0,0 +1,394 @@ +Installing With Pip +=================== + +Overall summary +--------------- + +The usual way to install GalSim is now (starting with version 2.0) simply:: + + pip install galsim + +which will install the latest official release of GalSim. + +Note that you may need to use sudo with the above command if you are installing +into system directories. If you do not have write privileges for the directory +it is trying to install into, you can use the --user flag to install into a +local directory instead. (Normally something like $HOME/Library/Python/2.7 +or $HOME/.local, depending on your system.) + +This might fail if certain libraries are installed in non-standard locations. +In this case, add the paths for these libraries to both the LIBRARY_PATH and +LD_LIBRARY_PATH environmental variables before running pip:: + + export LIBRARY_PATH=$LIBARY_PATH:/path/to/lib:/other/path/to/lib + export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib:/other/path/to/lib + +If you would rather install from source (e.g. to work on a development branch), +you can do:: + + git clone git@github.com:GalSim-developers/GalSim.git + cd GalSim + pip install -r requirements.txt + python setup.py install + +(again possibly with either sudo or --user). + +Either of these installation methods should handle most of the required +dependencies for you if you do not have them already installed on your machine. +In particular, all of the python dependencies should be automatically installed +for you. See `Installing Python Dependencies` if you have trouble with any of these. + +FFTW is not directly pip installable, so if the above installation fails, +you may need to install it separately. See `Installing FFTW` for more details +about how to do this. + +Eigen is also not pip installable, but it is a header-only library, so it doesn't +require any installation. Therefore, if you don't have it it locally in a place where +setup.py can find it, a version of Eigen will be downloaded directly. +See `Installing Eigen` for more details. + +Finally, while GalSim is officially a Python package, there are some users who +use the compiled C++ library directly, rather than via the Python interface. +See `Installing the C++ Shared Library` for information about this possibility. + +Installing Python Dependencies +------------------------------ + +Normally, all of the python package dependencies will be automatically installed +by pip. The following versions are known to work with GalSim 2.1. In most cases, +other recent (especially later) versions will also work: + +- NumPy (1.16.1) +- Future (0.17.1) +- Astropy (3.0.5) +- PyBind11 (2.2.3) +- LSSTDESC.Coord (1.0.5) + +There are a few others modules are not technically required, but we let pip +install them along with GalSim, because they either add useful functionality +or efficiency to GalSim. These are listed in the requirements.txt file that +pip uses to determine what else to install. But if you install with +``python setup.py install``, then these will not be installed. + +- Starlink (3.10.0) (Improved WCS functionality) +- PyYaml (3.12) (Reads YAML config files) +- Pandas (0.20) (Faster reading of ASCII input files) + +If you want to install these yourself, the quickest way is to do:: + + pip install -r requirements.txt + +If you want more control about which version you get or otherwise want to install +each package individually, you can do:: + + pip install numpy + pip install future + pip install astropy + pip install pybind11 + pip install LSSTDESC.Coord + + pip install starlink-pyast + pip install pyyaml + pip install pandas + +In all cases, you may need to precede the above commands with ``sudo`` or +add ``--user`` to the end as you normally do when pip installing on your system. + + +Installing FFTW +--------------- + +GalSim uses FFTW (The Fastest Fourier Transform in the West) for performing +fast Fourier transforms. + +We require FFTW version >= 3.0. Most tests have been done with FFTW 3.3.7, +so if you have trouble with an earlier version, try upgrading to 3.3.7 or later. + +Installing FFTW yourself +^^^^^^^^^^^^^^^^^^^^^^^^ + +FFTW is available at the URL: + +http://www.fftw.org/download.html + +As of this writing, version 3.3.7 is the current latest release, for which +the following commands should work to download and install it:: + + wget http://www.fftw.org/fftw-3.3.7.tar.gz + tar xfz fftw-3.3.7.tar.gz + cd fftw-3.3.7 + ./configure --enable-shared + make + sudo make install + +If you want to install into a different directory (e.g. because you do not +have sudo privileges on your machine), then specify the alternate directory +with the --prefix flag to configure. E.g.:: + + ./configure --enable-shared --prefix=$HOME + +which will install the library into $HOME/lib and the header file into +$HOME/include. In this case, leave off the sudo from the last line. +Also, you should make sure these directories are in your LD_LIBRARY_PATH +and C_INCLUDE_PATH environment variables, respectively. + +Alternatively, if you do not want to modify your LD_LIBRARY_PATH and/or +C_INCLUDE_PATH, you can instead set an environment variable to tell GalSim +where the files are:: + + export FFTW_DIR=/path/to/fftw/prefix + +E.g. in the above case where prefix is $HOME, you would do:: + + export FFTW_DIR=$HOME + +Probably, you should put this into your shell login file (e.g. .bash_profile) +so it always gets set when you log in. + + +Using an existing installation of FFTW +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If FFTW is already installed on your system, there may be nothing to do. +If it is in a standard location like /usr/local/lib or in some other +directory in your LD_LIBRARY_PATH, then GalSim should find it without +any extra work on your part. + +If it is in a non-standard location, and you do not want to add this path +to your LD_LIBRARY_PATH (or you are on a modern Mac that hides such system +variables from setup.py), then you can instead set the FFTW_DIR environment +variable to tell GalSim where to look when running pip or setup.py:: + + FFTW_DIR=/path/to/fftw/lib pip install galsim + +or:: + + FFTW_DIR=/path/to/fftw/lib python setup.py install + +For instance, if libfftw3.so is located in /opt/cray/pe/lib64, you could use +that with:: + + FFTW_DIR=/opt/cray/pe/lib64 pip install galsim + +If you want to set up your system so that you don't have to type that each +time you re-install galsim, you might want to set it in your .bash_profile +file or similar location. e.g.:: + + export FFTW_DIR=/opt/cray/pe/lib64 + +If you have multiple versions of FFTW installed on your system, this variable +can be used to specify which version you want GalSim to use as this will be +the first location it will check during the installation process. + +Furthermore, setup.py will trust this location even if it cannot load the +library that it finds there. This can be useful when cross-compiling, since +the library it finds might not be loadable on the system doing the compiling. + + +Installing FFTW with conda +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use conda, FFTW can be install with:: + + conda install fftw + +This will put it into the anaconda/lib directory on your system (within your +active environment if appropriate). GalSim knows to look here, so there is +nothing additional you need to do. + + +Installing FFTW with apt-get +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On Linux machines that use apt-get, FFTW can be installed with:: + + apt-get install libfftw3-dev + + +Installing FFTW with fink +^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use fink on a Mac, FFTW can be installed with:: + + fink install fftw3 + +(Make sure to use fftw3, not fftw, since fftw is version 2.) + +This will put it into the /sw/lib directory on your system. GalSim knows to +look here, so there is nothing additional you need to do. + + +Installing FFTW with MacPorts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use MacPorts, FFTW can be installed with:: + + port install fftw-3 + +This will put it into the /opt/local/lib directory on your system. GalSim knows +to look here, so there is nothing additional you need to do. + + +Installing Eigen +---------------- + +GalSim uses Eigen for the C++-layer linear algebra calculations. It is a +header-only library, which means that nothing needs to be compiled to use it. +You can download the header files yourself, but if you do not, then the +installation script will download it for you automatically. So usually, +this dependency should require no work on your part. + +However, if you have a version of Eigen already installed on your system, +you may want to use that. If the right directory is in your path for +include files (C_INCLUDE_PATH), it should find it. If not, you may specify +the right directory to use by setting the EIGEN_DIR environment variable. + +We require Eigen version >= 3.0. The version we download automatically is +3.3.4, so that version is known to work. We have also tested with versions +3.2.8 and 3.0.4, so probably any 3.x version will work. However, if you have +trouble with another version, try upgrading to 3.3.4 or later. + + +Installing Eigen yourself +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Eigen is available at the URL + +http://eigen.tuxfamily.org/index.php + +As of this writing, version 3.3.4 is the current latest release, for which +the following commands should work to download and install it:: + + wget http://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2 + tar xfj 3.3.4.tar.bz2 + sudo cp eigen-eigen-5a0156e40feb/Eigen /usr/local/include + +In the final cp line, the MD5 hash (5a0156e40feb) will presumably change for +other versions, so use whatever directory tar expands into if you are using +a different version than 3.3.4. + +If you do not have sudo privileges, you can copy to a different directory such +as $HOME/include instead and leave off the sudo from the cp command. In this +case, make sure this directory is in your C_INCLUDE_PATH environment variable. + +Finally, you can also skip the last command above and instead set EIGEN_DIR +as an environment variable to tell GalSim where the files are:: + + export EIGEN_DIR=/some/path/to/eigen + +This should be the directory in which the Eigen subdirectory is found. E.g.:: + + export EIGEN_DIR=$HOME/eigen-eigen-5a0156e40feb + +Probably, you should put this into your .bash_profile file so it always gets +set when you log in. + + +Using an existing installation of Eigen +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If Eigen is already installed on your system, there may be nothing to do. +If it is in a standard location like /usr/local/include or in some other +directory in your C_INCLUDE_PATH, then GalSim should find it without +any extra work on your part. + +If it is in a non-standard location, and you do not want to add this path +to your C_INCLUDE_PATH, then you can instead set the EIGEN_DIR environment +variable to tell GalSim where to look:: + + export EIGEN_DIR=/some/path/to/eigen + +For instance, if Eigen was installed into /usr/include/eigen3, then you +could use that with:: + + export EIGEN_DIR=/usr/include/eigen3 + +This command would normally be done in your .bash_profile file so it gets +executed every time you log in. + +If you have multiple versions of Eigen installed on your system, this variable +can be used to specify which version you want GalSim to use as this will be +the first location it will check during the installation process. + + +Installing Eigen with conda +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use conda, Eigen can be install with:: + + conda install eigen + +This will put it into the anaconda/include directory on your system (within +your active environment if appropriate). GalSim knows to look here, so there +is nothing additional you need to do. + + +Installing Eigen with apt-get +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +On Linux machines that use apt-get, Eigen can be installed with:: + + apt-get install libeigen3-dev + + +Installing Eigen with fink +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use fink on a Mac, Eigen can be installed with:: + + fink install eigen + +This will put it into the /sw/include directory on your system. GalSim knows +to look here, so there is nothing additional you need to do. + + +Installing Eigen with MacPorts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you use MacPorts, Eigen can be installed with:: + + port install eigen + +This will put it into the /opt/local/include directory on your system. GalSim +knows to look here, so there is nothing additional you need to do. + + +Installing the C++ Shared Library +--------------------------------- + +GalSim is first and foremost a Python package. However, it uses C++ functions behind +the scenes to implement many of the numerically intensive operations. +Some users have found it useful to link directly to the GalSim C++ library +with their own C++ code. This use case is supported, but at a lower level of +API guarantees than we make for the Python code. + +The official public API is really only the python-layer classes and functions. +We strive to hew closely to the `Semantic Versioning `_ +specifications about maintaining backwards compatibility within major release cycles, +so user code that uses only the public API should continue to work correctly until a +major version upgrade. + +This is *NOT* guaranteed for the C++ API. These are officially implementation +details in support of the Python API. However, in practice, the C++ function +signatures rarely change very much. So for the most part, you can expect your +code to continue to work for updated GalSim versions. Or if a function signature +changes slightly, it will usually be fairly simple to update to the new syntax. + +We provide the appropriate header files to use in the installed python library +location in the ``include`` directory. These files precisely specify the +available C++-layer API for a given release. + +The compiled library file for linking user C++ code is not installed by the default +pip or setup.py installation procedure. The relevant functions are included +in the Python module file, called ``_galsim.so``, but this file is often not usable +for linking your own C++ code. + +For this purpose, you will need to perform an extra step to build a shared library +that has the C++-layer functions. Run the following command:: + + python setup.py build_shared_clib + +The built shared library will be located in ``build/shared_clib/``. The library file +is named ``libgalsim.M.m.dylib`` on OSX or ``libgalsim.M.m.so`` on Linux, where M,m +are the major and minor version numbers for the release. You should copy this file +to some appropriate directory where your C++ code will be able to link to it. diff --git a/docs/_build/html/_sources/integ.rst.txt b/docs/_build/html/_sources/integ.rst.txt new file mode 100644 index 00000000000..a298aa299cf --- /dev/null +++ b/docs/_build/html/_sources/integ.rst.txt @@ -0,0 +1,38 @@ +Integration +=========== + +.. autofunction:: galsim.integ.int1d + +.. autofunction:: galsim.integ.hankel + +.. autoclass:: galsim.integ.IntegrationRule + :members: + +.. autoclass:: galsim.integ.MidptRule + :members: + +.. autoclass:: galsim.integ.TrapzRule + :members: + +.. autoclass:: galsim.integ.QuadRule + :members: + +.. autodata:: galsim.integ.midptRule + +.. autodata:: galsim.integ.trapzRule + +.. autodata:: galsim.integ.quadRule + +.. autoclass:: galsim.integ.ImageIntegrator + :members: + + .. automethod:: galsim.integ.ImageIntegrator.__call__ + +.. autoclass:: galsim.integ.SampleIntegrator + :members: + :show-inheritance: + +.. autoclass:: galsim.integ.ContinuousIntegrator + :members: + :show-inheritance: + diff --git a/docs/_build/html/_sources/interpolant.rst.txt b/docs/_build/html/_sources/interpolant.rst.txt new file mode 100644 index 00000000000..5a2ab7fb95a --- /dev/null +++ b/docs/_build/html/_sources/interpolant.rst.txt @@ -0,0 +1,38 @@ +Interpolants +============ + +These classes are principally used by `InterpolatedImage`. They define how the surface +brightness between pixel centers is defined from the values at the centers. + +.. autoclass:: galsim.Interpolant + :members: + +.. autoclass:: galsim.Delta + :members: + :show-inheritance: + +.. autoclass:: galsim.Nearest + :members: + :show-inheritance: + +.. autoclass:: galsim.Linear + :members: + :show-inheritance: + +.. autoclass:: galsim.Cubic + :members: + :show-inheritance: + +.. autoclass:: galsim.Quintic + :members: + :show-inheritance: + +.. autoclass:: galsim.SincInterpolant + :members: + :show-inheritance: + +.. autoclass:: galsim.Lanczos + :members: + :show-inheritance: + + diff --git a/docs/_build/html/_sources/misc.rst.txt b/docs/_build/html/_sources/misc.rst.txt new file mode 100644 index 00000000000..83d344ddecc --- /dev/null +++ b/docs/_build/html/_sources/misc.rst.txt @@ -0,0 +1,141 @@ +Miscellaneous Utilities +======================= + +We have a whole bunch of miscellaneous helper functions and classes in the ``galsim.utilities`` +module. Most of these are probably not particularly useful for anything other than internal +GalSim code. But we highlight a few things that might be more widely useful beyond GalSim usage. + +Decorators +---------- + +.. autoclass:: galsim.utilities.lazy_property + +.. autoclass:: galsim.utilities.doc_inherit + +.. autoclass:: galsim.utilities.timer + + +OpenMP Utilties +--------------- + +.. autofunction:: galsim.utilities.get_omp_threads + +.. autofunction:: galsim.utilities.set_omp_threads + +.. autoclass:: galsim.utilities.single_threaded + + +LRU Cache +--------- + +.. autoclass:: galsim.utilities.LRU_Cache + :members: + + +Context Manager for writing AtmosphericScreen pickles +----------------------------------------------------- + +.. autofunction:: galsim.utilities.pickle_shared + + +Other Possibly Useful Classes +----------------------------- + +.. autoclass:: galsim.utilities.WeakMethod + +.. autoclass:: galsim.utilities.OrderedWeakRef + +.. autoclass:: galsim.utilities.SimpleGenerator + + +Math Calculations +----------------- + +.. autofunction:: galsim.utilities.horner + +.. autofunction:: galsim.utilities._horner + +.. autofunction:: galsim.utilities.horner2d + +.. autofunction:: galsim.utilities._horner2d + +.. autofunction:: galsim.utilities.binomial + +.. autofunction:: galsim.utilities.nCr + +.. autofunction:: galsim.utilities.rotate_xy + +.. autofunction:: galsim.utilities.g1g2_to_e1e2 + + +Utilities Related to NumPy Functions +------------------------------------ + +.. autofunction:: galsim.utilities.printoptions + +.. autofunction:: galsim.utilities.roll2d + +.. autofunction:: galsim.utilities.kxky + +.. autofunction:: galsim.utilities.merge_sorted + + +Test Suite Helper Functions and Contexts +---------------------------------------- + +.. autofunction:: galsim.utilities.check_pickle + +.. autofunction:: galsim.utilities.check_all_diff + +.. autoclass:: galsim.utilities.CaptureLog + +.. autoclass:: galsim.utilities.Profile + + +Other Helper Functions +---------------------- + +.. autofunction:: galsim.utilities.isinteger + +.. autofunction:: galsim.utilities.listify + +.. autofunction:: galsim.utilities.dol_to_lod + +.. autofunction:: galsim.utilities.functionize + +.. autofunction:: galsim.utilities.ensure_dir + + +GalSim-specific Helper Functions +-------------------------------- + +.. autofunction:: galsim.utilities.interleaveImages + +.. autofunction:: galsim.utilities.deInterleaveImage + +.. autofunction:: galsim.utilities.thin_tabulated_values + +.. autofunction:: galsim.utilities.old_thin_tabulated_values + +.. autofunction:: galsim.utilities.parse_pos_args + +.. autofunction:: galsim.utilities.rand_arr + +.. autofunction:: galsim.utilities.convert_interpolant + +.. autofunction:: galsim.utilities.structure_function + +.. autofunction:: galsim.utilities.combine_wave_list + +.. autofunction:: galsim.utilities.math_eval + +.. autofunction:: galsim.utilities.unweighted_moments + +.. autofunction:: galsim.utilities.unweighted_shape + +.. autofunction:: galsim.utilities.rand_with_replacement + +.. autofunction:: galsim.utilities.check_share_file + +.. autofunction:: galsim.utilities.find_out_of_bounds_position + diff --git a/docs/_build/html/_sources/nfwhalo.rst.txt b/docs/_build/html/_sources/nfwhalo.rst.txt new file mode 100644 index 00000000000..bceb808e292 --- /dev/null +++ b/docs/_build/html/_sources/nfwhalo.rst.txt @@ -0,0 +1,16 @@ + +NFW Halo Shears +=============== + +This is the "lensing engine" for calculating shears around an NFW halo. + +.. autoclass:: galsim.NFWHalo + :members: + + .. automethod:: galsim.NFWHalo._getShear + .. automethod:: galsim.NFWHalo._getConvergence + .. automethod:: galsim.NFWHalo._getMagnification + .. automethod:: galsim.NFWHalo._getLensing + +.. autoclass:: galsim.Cosmology + :members: diff --git a/docs/_build/html/_sources/noise.rst.txt b/docs/_build/html/_sources/noise.rst.txt new file mode 100644 index 00000000000..5730817047f --- /dev/null +++ b/docs/_build/html/_sources/noise.rst.txt @@ -0,0 +1,43 @@ + +Noise Generators +================ + +GalSim has a number of different noise models one can use for adding noise to an `Image`: + +* `GaussianNoise` adds Gaussian noise with a specified :math:`\sigma` (or variance) to each pixel. +* `PoissonNoise` treats each pixel value as the expectation value for the number of incident + photons in the pixel, and implements a Poisson process drawing a realization of the observed + number of photons in each pixel. +* `CCDNoise` combines the two above noise types to implement Poisson photon noise plus Gaussian + read noise. +* `VariableGaussianNoise` is like `GaussianNoise`, but allows for a different variance in each pixel. +* `DeviateNoise` adds noise according to any of the various `Random Deviates` implemented in GalSim. + +These are all subclasses of the base class `BaseNoise`, which mostly just defines the common +API for all of these classes. + +.. autoclass:: galsim.BaseNoise + :members: + + .. automethod:: galsim.BaseNoise.__mul__ + .. automethod:: galsim.BaseNoise.__div__ + +.. autoclass:: galsim.GaussianNoise + :members: + :show-inheritance: + +.. autoclass:: galsim.PoissonNoise + :members: + :show-inheritance: + +.. autoclass:: galsim.CCDNoise + :members: + :show-inheritance: + +.. autoclass:: galsim.VariableGaussianNoise + :members: + :show-inheritance: + +.. autoclass:: galsim.DeviateNoise + :members: + :show-inheritance: diff --git a/docs/_build/html/_sources/older.rst.txt b/docs/_build/html/_sources/older.rst.txt new file mode 100644 index 00000000000..0596863ba44 --- /dev/null +++ b/docs/_build/html/_sources/older.rst.txt @@ -0,0 +1,995 @@ + +Below is a summary of the major changes with each new tagged version of GalSim. +Each version may also include various other minor changes and bug fixes not +listed here for brevity. See the CHANGELOG files associated with each +version for a more complete list. Issue numbers related to each change are +given in parentheses. + +v2.5 +---- + +*API Changes* + +- Deprecated the use of filter W149 in roman module. New name is W146. (#1017) +- Deprecated automatic allocation of `PhotonArray` arrays via "get" access. (#1191) +- Changed the ``.SED`` attribute name of `ChromaticObject` to lowercase ``.sed``. (#1245) +- Deprecated `PhotonArray.setCorrelated` and `PhotonArray.isCorrelated`. (#1259) +- Deprecated `PhotonArray.assignAt` in favor of `PhotonArray.copyFrom`. (#1259) + + +*Config Updates* + +- Fixed a bug in Scattered type, when world_pos is specified in the stamp field. (#1190) +- Added a new ``initial_image`` input type. (#1237) +- Added skip_failures option in stamp fields. (#1238) +- Let input items depend on other input items. (#1239) +- Allow profiling output to reach the logger when running with -v0. (#1245) +- Added Eval type for GSObjects. (#1250) + + +*New Features* + +- Updated Roman telescope data to Phase C (aka Cycle 9) specifications (#1017) +- Added `ShapeData.applyWCS` method to convert HSM shapes to sky coordinates. (#1221) +- Added `DoubleZernike` class and related functionality. (#1221) +- Added some test-related utility functions to galsim.utilities. (#1240) +- Added `utilities.merge_sorted` function. (#1243) +- Added `EmissionLine` class to represent emission line SEDs. (#1247, #1249) +- Updated data in `roman` module to Phase C (Cycle 9) information. (#1017, #1251) +- Added interpolant option to `SED` and `Bandpass` classes. (#1257) +- Added interpolant option to `galsim.trapz`. (#1257) +- Added clip_neg option to `DistDeviate` class. (#1257) +- Implemented algorithm for `ChromaticSum` to let it be used as a photon_op. (#1259) +- Added `PhotonArray.copyFrom` method. (#1259) + + +*Performance Improvements* + +- Added some support for GPU offloading. (#1212, #1217, #1218, #1222, #1224, #1230) +- Don't add a WavelengthSampler to photon_ops if it is already there. (#1229, #1236) +- Work around an OMP bug that disables multiprocessing on some systems. (#1241) +- Sped up the `combine_wave_list` function, using the new `merge_sorted` function. (#1243) +- No longer keep a separate ``wave_list`` array in `ChromaticObject`. (#1245) +- Delayed the calculation of the ``sed`` attributes of `ChromaticObject` until needed. (#1245) +- Reduce long-term memory usage of Silicon class. (#1246) +- Improved the behavior of SEDs when using spline interpolant. (#1187, #1257) +- No longer pickle the SED of chromatic objects when the SED is a derived value. (#1257) + + +*Bug Fixes* + +- Fixed a bug that could lead to overflow in extremely large images. (#1017) +- Fixed a slight error in the Moffat maxk calculation. (#1208, #1210) +- Fixed a bug that prevented Eval types from generating lists in config files in some contexts. + (#1220, #1223) +- Fixed the absorption depth calculation in the Silicon class to allow wavelengths that are + outside the given range of the absorption lookup table. (#1227) +- Changed the SED class to correctly broadcast over waves when the SED is constant. (#1228, #1235) +- Fixed some errors when drawing ChromaticTransformation objects with photon shooting. (#1229) +- Fixed the flux drawn by ChromaticConvolution with photon shooting when poisson_flux=True. (#1229) +- Fixed a slight inaccuracy in the FFT phase shifts for single-precision images. (#1231, #1234) +- Fixed a bug that prevented a convolution of two PhaseScreenPSF objects from being drawn with + photon shooting. (#1242) +- Fixed a bug in the SED class normalization when using astropy.units for flux_type. (#1254, #1256) +- Fixed a bug in `SiliconSensor` if the image is outside the range where tree rings are defined. + (#1258) + + +v2.4 +---- + +*API Changes* + +- Removed CppEllipse, AstronomicalConstants.h in C++ layer. (#1129) +- Removed AttributeDict. (#1129) +- Some changes to the C++ Image constructors to include a maxptr value. (#1149) +- Changed `SincInterpolant.ixrange` to be consistent with the value of xrange. (#1154) +- Changed ``galsim.scene`` namespace name to ``galsim.galaxy_sample``. (#1174) + + +*Config Updates* + +- Added ``Correlated`` noise type. (#731, #1174) +- Added ``galaxy_sample`` input type and ``SampleGalaxy`` GSObject type. (#795, #1174) +- Added ``COSMOSValue`` and ``SampleValue`` value types. (#954, #1174) +- Allowed template file names to be evaluated using the "$" shorthand notation. (#1138) +- Added `RegisterTemplate`. (#1143) +- Fixed some errors in `PhotonDCR` usage in the config layer. (#1148) +- Added option to specify the dtype for images built by config. (#1160) +- Fixed inconsistent behavior of image.world_pos in image type=Single. (#1160) +- Let a flux item for an object with an SED normalize the SED. (#1160) +- Fixed some edge cases where the created image could not have the requested wcs. (#1160) +- Added option to ``initialize`` input objects in an `InputLoader`. (#1162, #1163) +- Fixed error in returned variance for ``CCDNoise`` builder. (#1166, #1167) +- Changed the way the internal random number sequence works. (#1169) +- Made it possible to delete items in a config list. (#1183) +- Fixed error in how input fields check when they are current. (#1184) +- Fixed drawImage to work correctly for method=fft when using photon_ops. (#1193) +- Fixed the proxies used by config Input items to allow access to attributes. (#1195) +- Add --log_format option to galsim executable. (#1201) +- Allow input objects with has_nobj=True to return an approximate number of objects. (#1202) +- Add options to InputLoader to make inputs with AtmosphericScreens work properly. (#1206) +- Only use proxies for input objects if not yet in a multiprocessing context. (#1206) +- Made it possible to delete items in a config list. (#1183) +- Allowed input objects to return an approximate number of objects for the initial pass. (#1202) +- Added options to InputLoader to make inputs with AtmosphericScreens work properly. (#1206) +- Only use proxies for input objects if not yet in a multiprocessing context. (#1206) + + +*New Features* + +- Added `BaseCorrelatedNoise.from_file` class method. (#731, #1174) +- Added `GalaxySample` class. (#795, #1174) +- Added methods `Image.transpose`, `Image.flip_ud`, `Image.flip_lr`, `Image.rot_cw`, + `Image.rot_ccw`, and `Image.rot_180`. (#1139) +- Exposed our Si, Ci, sinc, and gammainc functions from C++. (#1146) +- Added pupil_u and pupil_v to `PhotonArray`. (#1147) +- Added `Image.depixelize` and ``depixelize=True`` option for `InterpolatedImage`. (#1154) +- Let `Bounds.expand` scale differently in different directions. (#1153, #1155) +- Added `BaseWCS.shearToWorld` and `BaseWCS.shearToImage`. (#1158, #1172) +- Added `PupilImageSampler` and `PupilAnnulusSampler` photon operators. (#1176) +- Added `TimeSampler` photon operator. (#1178) +- Added `BaseDeviate.as_numpy_generator`. (#1067, $1179) +- Added ``timeout`` option to control multiprocessing timeout limit and increased the default. (#1180) +- Added --log_format option to galsim executable. (#1201) + + +*Performance Improvements* + +- Made Silicon sensor use ~half as many points for the pixels. (#1118, #1137) +- Use single precision for Silicon pixel boundaries. (#1140) +- Moved some of the logic related to the Silicon sensor to the python layer. (#1141) +- Let `BaseDeviate.generate` use multiple threads in C++ layer. (#1177) +- Fixed a slow-down in multiprocessing especially when running very many (>10) processes. (#1213) + + +*Bug Fixes* + +- Fixed some cases where HSM would fail to converge. (#1132, #1149) +- Fixed error in `InterpolatedImage.withGSParams` not updating stepk and maxk. (#1154) +- Fixed error in `ChromaticSum` photon shooting when ``n_photons`` is given. (#1156, #1157) +- Fixed some rounding errors that could happen with integer-typed images. (#1160) +- Fixed an assert error that would trigger if hsm was run on images with negative stride. (#1185) +- Fix the flux scaling of drawReal for objects with non-diagonal jacobian. (#1197, #1198) +- Fixed the pip installation to include the galsim/share directory, which was missing. +- Fixed error in default nobj calculation for extra_object output when not doing the + normal BuildFile processing. +- Fixed error in how input fields check when they are current. (#1184) +- Fixed an assert error that would trigger if hsm was run on images with negative stride. (#1185) +- Fixed drawImage to work correctly for method=fft when using photon_ops. (#1193) +- Fixed the proxies used by config Input items to allow access to attributes. (#1195) +- Fixed the flux scaling of drawReal for objects with non-diagonal jacobian. (#1197, #1198) +- Fixed a potential segmentation fault when using photon_ops with FFT drawing. (#1216) +- Fixed the Silicon class to handle invalid wavelengths gracefully. (#1227) +- Fixed the config template processing to handle recursive templates. (#1233) +- Fixed the modules field in config files to allow sub-modules without the parent module. (#1233) + +v2.3 +---- + +*Dependency Changes* + +- Removed future as a dependency. (#1082) +- Download eigen automatically if not found on your system. (#1086) + + +*API Changes* + +- Deprecated the ``rng`` parameter of `WavelengthSampler` and `FRatioAngles`. (#540) +- Deprecated ``withOrigin`` method for non-local WCS types. (#1073) +- Updated numerical details of the `Kolmogorov` class. (#1084) +- Changed ``galsim.wfirst`` module to ``galsim.roman``. (#1088) +- Changed default ``ii_pad_factor`` for `PhaseScreenPSF`, `OpticalPSF` to 1.5. (#1089) +- Deprecated the ``high_accuracy`` and ``approximate_struts`` parameters for the + `galsim.roman.getPSF` function. (#1089) +- Changed ``surface_ops`` parameter to `GSObject.drawImage` to ``photon_ops``. (#1093) +- Added logger option to some config functions and methods. (#1095) +- Deprecated ``galsim.integ.trapz`` and ``galsim.integ.midpt``. (#1098) +- Changed the convention for the ``f`` array passed to the `LookupTable2D` + constructor to be the transpose of what it was. (#1103) +- Changed the behavior of `PhaseScreenPSF`, `OpticalPSF`, and + `ChromaticOpticalPSF` by adding the kwarg ``fft_sign``. (#1104) +- Changed `_InterpolatedImage` to not recenter the image to (0,0) as `InterpolatedImage` does. (#1151) + + +*Config Updates* + +- Added ability to draw chromatic objects with config files. (#510) +- Added demo12.yaml and demo13.yaml to the demo suite. (#510, #1121) +- Fixed a issues with using a ``Current`` item before it was parsed. (#1083) +- Added value-type-specific type names (e.g. ``Random_float``, etc.) (#1083) +- Fixed a subtle issue in ``Eval`` string processing. (#1083) +- Added ``photon_ops`` and ``sensor`` as options in the stamp processing. (#1093) +- Removed the ``_nobjects_only`` mechanism from input objects. (#1095) +- Allowed ``Eval`` fields to use any modules that are listed in the top-level + ``modules`` field. (#1121) +- Added Roman config types: ``RomanSCA``, ``RomanBandpass``, and ``RomanPSF``. (#1121) + + +*New Features* + +- Added ``atRedshift`` method for `ChromaticObject`. (#510) +- Added `galsim.utilities.pickle_shared` context. (#1057) +- Added ``force_stepk`` option to `VonKarman`. (#1059) +- Added `Refraction` and `FocusDepth` photon ops. (#1065, #1069) +- Updated LSST sensor files to match new lab measurements and use improved + Poisson code calculations. (#1077, #1081) +- Added `GSObject.makePhot` method. (#1078) +- Added individual kwargs syntax to `GSObject.withGSParams`. (#1089) +- Added ``pupil_bin`` option to the `galsim.roman.getPSF` function. (#1089) +- Added `FittedSIPWCS`. (#1092) +- Extended `GSFitsWCS` to support -SIP distortions for non-TAN WCSs. (#1092) +- Added ``wcs`` option to `galsim.roman.getPSF`. (#1094) +- Added `Position.shear` method. (#1090) +- Added `LookupTable.integrate`, `LookupTable.integrate_product`, and `galsim.trapz`. (#1098) +- Added `galsim.integ.hankel` function. (#1099) +- Added `galsim.bessel.jv_root` function. (#1099) +- Added support for TPV WCS files with order > 3. (#1101) +- Added `UserScreen` for arbitrary user-supplied phase screens (#1102) +- Added `galsim.zernike.describe_zernike`. (#1104) +- Added option to emit WCS warnings when reading a file via `galsim.fits.read`. (#1120) +- Added ``area`` and ``exptime`` parameters to `COSMOSCatalog` constructor. (#1121) + + +*Performance Improvements* + +- Implemented ``Transformation._drawReal`` and ``Transformation._drawKImage`` in python. (#934) +- Sped up the draw routines for `InterpolatedImage`. (#935) +- Improved the quality and speed of Roman PSFs. (#1089) +- Sped up `GSFitsWCS` calculations for SIP and PV distorted WCSs. (#1092) +- Various speed improvements in config processing. (#1095, #1098) +- Sped up `SED.calculateFlux` and some other SED and Bandpass calculations. (#1098) +- Sped up the Hankel transforms in several classes. (#1099) +- Improved the accuracy of ``stepk`` for `Kolmogorov` profiles. (#1110) +- Sped up Zernike arithmetic. (#1124) +- Removed some overhead in some "leading underscore" methods. (#1126) + + +*Bug Fixes* + +- Fixed `horner` and `horner2d` when inputs are complex. (#1054) +- Fixed `VonKarman` integration to be more reliable. (#1058) +- Fixed minor bug in repr of `OpticalPSF` class. (#1061) +- Fixed bug in `RandomKnots` when multiplied by an SED. (#1064) +- Fixed bug in `galsim.fits.writeMulti` not writing headers. (#1091) +- Fixed some problems with the shared library build. (#1128) +- Fixed a rare problem with SED.sampleWavelength sometimes generating invalid values. (#1131) +- Fixed a bug where InterpolatedImage.drawReal could possibly cause seg faults +- Fixed an error in our handling of the Roman Cycle 7 aberrations file. (#1142) +- Fixed an error when drawing an InterpolatedImage completely off the target image. (#1164) + + +v2.2 +---- + +*Deprecated Features* + +- Deprecated ``galsim.correlatednoise._BaseCorrelatedNoise``. (#160) +- Deprecated ``RandomWalk`` in favor of `RandomKnots`. (#977) +- Deprecated the ``tol`` parameter of the various Interpolant classes. (#1038) + +*API Changes* + +- Removed functionality to store/reload WFIRST PSFs, and to get multiple WFIRST PSFs (#919) +- Changed the function signature of StampBuilder.addNoise. (#1048) + +*Changes to Shared Files* + +- Added option to set the `galsim.meta_data.share_dir` via GALSIM_SHARE_DIR. (#1014) +- Changed hosting of COSMOS catalog to `Zenodo `_ (#1033) + +*Config Updates* + +- Added some more customization hooks in the StampBuilder class. (#1048) +- Added ``quick_skip``, ``obj_rng=False``, ``rng_index_key`` options. (#1048) + +*Documentation Updates* + +- Switched docs to `Sphinx `_. (#160) + +*New Features* + +- Added `FitsHeader.extend` method. Also, read_header option to `galsim.fits.read`. (#877) +- Updated lots of WFIRST module to use Cycle 7 specifications. (#919) +- Extended WFIRST aberrations to 22 Zernike coefficients and vary them across FOV. (#919) +- Improved efficiency of drawing `RandomKnots` objects when transformed. (#977) +- Added WFIRST fermi persistence model. (#992) +- Added ``r0_500`` argument to VonKarman. (#1005) +- Improved ability of `AtmosphericScreen` to use shared memory in multiprocessing context. (#1006) +- Use OpenMP when appropriate in `SiliconSensor.accumulate` (#1008) +- Added array versions of `BaseWCS.toWorld` and `BaseWCS.toImage`. (#1026) +- Exposed some methods of `Interpolant` classes that had only been in the C++ layer. (#1038) +- Added Zernike polynomial +, -, and * operators. (#1047) +- Added Zernike polynomial properties .laplacian and .hessian. (#1047) +- Added ``center`` option to the `GSObject.drawImage` method. (#1053) +- Added a new context, `galsim.utilities.pickle_shared`, which can be used to include shared + data in the pickle file. (#1057) +- Added ability to shear a position. (Backported from 2.3 series.) (#1090) + +*Bug Fixes* + +- Fixed a couple places where negative fluxes were not working correctly. (#472) +- Fixed FITS I/O to write out comments of header items properly. (#877) +- Fixed error in the serialization of `RandomKnots` instances. (#977) +- Fixed error in `PhaseScreenPSF` when aberrations has len=1. (#1006, #1029) +- Fixed error in `BaseWCS.makeSkyImage` when crossing ra=0 line for some WCS classes. (#1030) +- Fixed slight error in the realized flux of some profiles when using photon shooting. (#1036) +- Fixed error in `Sersic` class when n is very, very close to 0.5. (#1041) +- Fixed a compiler error for clang on linux systems. +- Fixed integration in VonKarman for some problematic r0 values. (#1058) +- Fixed a bug in RandomKnots when multiplied by an SED. (#1064) +- Fixed a bug in photon shooting which could cause seg faults. (#1079) + +v2.1 +---- + +*Deprecated Features* + +- Deprecated PhaseScreenPSF attributes img and finalized. (#990) +- Deprecated GSParams items allowed_flux_variation, small_fraction_of_flux, + and range_division_for_extreama. (#993) + +*New Features* + +- Added RandomWalk profile option. (#821) +- Added spline as LookupTable2D interpolant. (#982) +- Added ability to use an Interpolant in LookupTable and LookupTable2D. (#982) +- Added option for faster grid interpolation of LookupTable2D. (#982) +- Added offset and flux_ratio options to WCS.toWorld and toImage. (#993) + +*Bug Fixes* + +- Corrected the diffusion functional form in SiliconSensor. (#981) +- Fixed a bug in the PhaseScreenPSF withGSParams function. (#990) +- Fixed a seg fault bug when PoissonDeviate is given mean=0. (#996) +- Fixed the galsim executable to work correctly when installed by SCons. +- Fixed Convolve and Sum sometimes making unnecessary copies. +- Fixed error when using non-int integer types as seed of BaseDeviate (#1009) +- Fixed error in use of non-integer grid_spacing in PowerSpectrum (#1020) +- Fixed FitsHeader to not unnecessarily read data of fits file. (#1024) +- Switched to yaml.safe_load to avoid PyYAML v5.0 warnings (#1025) +- Fixed cases where numpy objected to subtracting floats from ints. (#1025) + + +v2.0 +---- + +*Installation Changes* + +- Now installable via pip or setup.py install. (#809) + +*Dependency Changes* + +- Officially no longer support Python 2.6 or 3.4. (#755) +- No longer support pre-astropy versions of pyfits or astropy obj.flux, etc. (#904) +- Deprecated ChromaticObject.obj. (#904) +- Changed the objlist attribute of ChromaticSum and ChromaticConvolution to + obj_list. (#904) +- Deprecated OpticalScreen.coef_array. (#904) +- Changed a number of GSObject methods to properties. (#904) + + - obj.stepK() -> obj.stepk + - obj.maxK() -> obj.maxk + - obj.nyquistScale() -> obj.nyquist_scale + - obj.centroid() -> obj.centroid + - obj.getPositiveFlux() -> obj.positive_flux + - obj.getNegativeFlux() -> obj.negative_flux + - obj.maxSB() -> obj.max_sb + - obj.isAxisymmetric() -> obj.is_axisymmetric + - obj.isAnalyticX() -> obj.is_analytic_x + - obj.isAnalyticK() -> obj.is_analytic_k + - obj.hasHardEdges() -> obj.has_hard_edges + +- Renamed ChromaticObject.centroid(bandpass) to calculateCentroid. (#904) +- Changed a few Image methods to properties. (#904) + + - image.center() -> image.center + - image.trueCenter() -> image.true_center + - image.origin() -> image.origin + +*New Features* + +- Added DeltaFunction. (#533) +- Added ChromaticRealGalaxy. (#640) +- Added CovarianceSpectrum. (#640) +- Added HST bandpasses covering AEGIS and CANDELS surveys (#640) +- Added drawKImage method for ChromaticObject and CorrelatedNoise (#640) +- Updated WFIRST WCS some other basic numbers to Cycle 7 design. (#675) +- Added support for unsigned int Images. (#715) +- Added a new Sensor class hierarchy, including SiliconSensor. (#722) +- Added save_photons option to drawImage. (#722) +- Added image.bin and image.subsample methods. (#722) +- Added annular Zernike option for optical aberration coefficients. (#771) +- Added ability to use numpy, np, or math in all places where we evaluate + user input. (#776) +- Added keywords exptime and area to drawImage(). (#789) +- Added ability to use astropy.units for units of SEDs. (#789). +- Added InclinedExponential and InclinedSersic. (#782, #811) +- Added ability to select from a RealGalaxyCatalog or COSMOSCatalog using + the 'weight' entries to account for selection effects. (#787) +- Added complex Image dtypes (aka ImageCD and ImageCF). (#799, #873) +- Added maxSB() method to GSObjects. (#799) +- Added im[x,y] = value and value = im[x,y] syntax. (#799) +- Added ability to do FFTs directly on images. (#799) +- Added galsim.RandomWalk. (#819) +- Added generate function to BaseDeviate and sed.sampleWavelength. (#822) +- Added function assignPhotonAngles (#823) +- Added geometric optics approximation for photon-shooting PhaseScreenPSFs. + (#824) +- Added gradient method to LookupTable2D. (#824) +- Added surface_ops option to drawImage function. (#827) +- Added ii_pad_factor kwarg to PhaseScreenPSF and OpticalPSF. (#835) +- Added galsim.fft module. (#840) +- Added a hook to the WCS classes to allow them to vary with color. (#865) +- Added optional variance parameter to PowerSpectrum.buildGrid. (#865) +- Added CelestialCoord.get_xyz() and CeletialCoord.from_xyz(). (#865) +- Added an optional center argument for Angle.wrap(). (#865) +- Added recenter option to drawKImage. (#873) +- Added option to use circular weight function in HSM moments. (#917) + +*New config features* + +- Changed galsim.config.CalculateNoiseVar to CalculateNoiseVariance. (#820) +- Setting config['rng'] is no longer required when manually running commands + like galsim.config.BuildGSObject. (#820) +- Allow PoissonNoise and CCDNoise without any sky level. (#820) +- Let 'None' in the config file mean None. (#820) +- Remove default value for 'max_extra_noise' for photon shooting. (#820) +- Added --except_abort option to galsim executable. (#820) +- Added optional probability parameter 'p' for Random bool values. (#820) +- Added ability to specify world_pos in celestial coordinates. (#865) +- Added the ability to have multiple rngs. (#865) +- Added ngrid, center, variance, index options to power_spectrum input field. + (#865) +- Added skip option in stamp field. (#865) +- Added ':field' syntax for templates. (#865) + + +v1.4 +---- + +*API Changes* + +- Changed the galsim.Bandpass and galsim.SED classes to require formerly + optional keywords wave_type and flux_type. (#745) + +*Dependency Changes* + +- Added future module as a dependency. (#534) +- Changed PyYAML to a non-optional dependency. (#768) + +*Bug Fixes* + +- Improved ability of galsim.fits.read to handle invalid FITS headers. (#602) +- Fixed bug in des module, building meds file with wcs from input images. (#654) +- Fixed a bug in the way Images are instantiated for certain combinations of + ChromaticObjects and image-setup keyword arguments (#683) +- Added ability to manipulate the width of the moment-measuring weight function + for the KSB shear estimation method of the galsim.hsm package. (#686) +- Fixed an error in the CCDNoise.getVariance() function. (#713) +- Fixed an assert failure in InterpolatedImage if image is all zeros. (#720) +- Updated ups table file so that setup command is setup galsim. (#724) +- Improved algorithm for thinning SEDs and Bandpasses. (#739) +- Fixed a bug in how DistDeviate handles nearly flat pdfs. (#741) +- Fixed a bug in chromatic parametric COSMOS galaxy models. (#745) +- Fixed a bug in the Sum and Convolution constructors when list has only a + single element. (#763) +- Fixed a bug related to boost-v1.60 python shared_ptr registration. (#764) +- Changed an assert in the HSM module to an exception. (#784) + +*Deprecated Features* + +- Deprecated the gal.type=Ring option in the config files. (#698) + +*New Features* + +- Added OutputCatalog class. (#301, #691) +- Added methods calculateHLR, calculateMomentRadius, and calculateFWHM. (#308) +- Added LookupTable2D. (#465) +- Added support for Python 3. (#534) +- Added AtmosphericScreen, OpticalScreen, and PhaseScreenList. (#549) +- Added PhaseScreenPSF. (#549) +- Added Atmosphere function. (#549) +- Rewrote OpticalPSF using new PhaseScreen framework. (#549) +- Extended OpticalPSF to handle arbitrary Zernike order. (#549) +- Added a simple, linear model for persistence. (#554) +- Added BoundsI.numpyShape(). (#654) +- Enabled FITS files with unsigned integer to read as ImageI or ImageS. (#654) +- Made COSMOSCatalog write an index parameter. (#654, #694) +- Added ability to specify lambda and r0 separately for Kolmogorov. (#657) +- Enabled initializing an InterpolatedImage from a user-specified HDU. (#660) +- Changed galsim.fits.writeMulti to allow hdus in "image" list. (#691) +- Added wcs argument to Image.resize(). (#691) +- Added BaseDeviate.discard(n) and BaseDeviate.raw(). (#691) +- Added sersic_prec option to COSMOSCatalog.makeGalaxy(). (#691) +- Enabled image quality cuts in the COSMOSCatalog class. (#693) +- Added convergence_threshold in HSMParams. (#709) +- Improved the readability of Image and BaseDeviate reprs. (#723) +- Sped up Bandpass, SED, and LookupTable classes. (#735) +- Added the FourierSqrt operator. (#748) +- Made Bandpass.thin() and truncate() preserve the zeropoint. (#711) +- Added version information to the compiled C++ library. (#750) + +*Updates to galsim executable* + +- Dropped default verbosity from 2 to 1. (#691) +- Added galsim -n njobs -j jobnum to split run into multiple jobs. (#691) +- Added galsim -p to perform profiling on the run. (#691) + +*New config features* + +- Added ability to write truth catalogs using output.truth field. (#301, #691) +- Improved the extensibility of the config parsing. (#691, #774) +- Added the 'template' option. (#691) +- Made '$' and '@' shorthand for Eval and Current. (#691) +- Allowed gsobjects to be referenced from Current types. (#691) +- Added x,f specification for a RandomDistribution. (#691) +- Added a new 'stamp' top level field. (#691) +- Added new stamp type=Ring to effect ring tests. (#698) + + +v1.3 +---- + +*Installation Changes* + +- Require functionality in TMV 0.72. (#616) + +*API Changes* + +- Changed the name of the bounds.addBorder() method to withBorder. (#218) +- Removed (from the python layer) Interpolant2d and InterpolantXY. (#218) +- Removed the MultipleImage way of constructing an SBInterpolatedImage. (#218, #642) +- Made the default tolerance for all Interpolants equal to 1.e-4.. (#218) +- Deprecated the __rdiv__ operator from Bandpass and SED. (#218) +- Made all returned matrices consistently use numpy.array, rather than + numpy.matrix. (#218) +- Made the classes PositionI, PositionD, GSParams, and HSMParams immutable. + (#218, #643) +- Deprecated CorrelatedNoise.calculateCovarianceMatrix. (#630) +- Officially deprecated the methods and functions that had been described as + having been removed or changed to a different name. (#643) +- Added function to interleave a set of dithered images into a single + higher-resolution image. (#666) + +*New Features* + +- Made all GalSim objects picklable unless they use fundamentally unpicklable + things such as lambda expressions, improved str and repr representations, + made __eq__, __ne__, and __hash__ work better. (#218) +- Added ability to set the zeropoint of a bandpass to a numeric value on + construction. (#218) +- Added ability to set the redshift of an SED on construction. (#218) +- Updated CorrelatedNoise to work with images that have a non-trivial WCS. + (#501) +- Added new methods of the image class to simulate detector effects (#555, #558). +- Enabled constructing a FitsHeader object from a dict, and allow + FitsHeader to be default constructed with no keys. (#590) +- Added a module, galsim.wfirst, that includes information about the planned + WFIRST mission, along with helper routines for constructing appropriate PSFs, + bandpasses, WCS, etc. (#590) +- Added native support for the TAN-SIP WCS type using GSFitsWCS. (#590) +- Added a helper program, galsim_download_cosmos, that downloads the COSMOS + RealGalaxy catalog. (#590) +- Added new class with methods for making realistic galaxy samples using COSMOS: + the COSMOSCatalog class and its method makeObj(). (#590 / #635). +- Added information about PSF to the data returned by EstimateShear(). (#612) +- Added Spergel(2010) profile GSObject (#616). +- Added an option to the ChromaticObject class that allows for faster image + rendering via interpolation between stored images. (#618) +- Added new ChromaticAiry and ChromaticOpticalPSF classes for representing + chromatic optical PSFs. (#618) +- Enable initializing a DES_PSFEx object using a pyfits HDU directly instead + of a filename. (#626) +- Added TopHat class implementing a circular tophat profile. (#639) +- Added ability of Noise objects to take a new random number generator (a + BaseDeviate instance) when being copied. (#643) +- Added InterpolatedKImage GSObject for constructing a surface brightness + profile out of samples of its Fourier transform. (#642) +- Enabled constructing a FitsHeader object from a list of (key, value) pairs, + which preserves the order of the items in the header. (#672) + +*Bug Fixes and Improvements* + +- Fixed a bug in the normalization of SEDs that use wave_type='A'. (#218) +- Switched the sign of the angle returned by CelestialCoord.angleBetween. + (#590) +- Fixed a bug in UncorrelatedNoise where the variance was set incorrectly. + (#630) +- Changed the implementation of drawing Box and Pixel profiles in real space + (i.e. without being convolved by anything) to actually draw the surface + brightness at the center of each pixel. (#639) +- Fixed a bug where InterpolatedImage and Box profiles were not correctly + rendered when transformed by something that includes a flip. (#645) +- Fixed a bug in rendering profiles that involve two separate shifts. (#645) +- Fixed a bug if drawImage was given odd nx, ny parameters, the drawn profile + was not correctly centered in the image. (#645) +- Added intermediate results cache to ChromaticObject.drawImage and + ChromaticConvolution.drawImage to speed up the rendering of groups + of similar (same SED, same Bandpass, same PSF) chromatic profiles. (#670) + +*Updates to config options* + +- Added COSMOSGalaxy type, with corresponding cosmos_catalog input type. (#590) +- Added Spergel type. (#616) +- Added lam, diam, scale_units options to Airy and OpticalPSF types. (#618) +- Added TopHat type. (#639) + + +v1.2 +---- + +*New Features* + +- Changed name of noise whitening routine from noise.applyWhiteningTo(image) + to image.whitenNoise(noise). (#529) +- Added image.symmetrizeNoise. (#529) +- Added magnitudes as a method to set the flux of SED objects. (#547) +- Added SED.calculateDCRMomentShifts, SED.calculateChromaticSeeingRatio. (#547) +- Added image.applyNonlinearity and image.addReciprocityFaiure. (#552) +- Renamed alias_threshold to folding_threshold. (#562) +- Extended to the rotate, shear, and transform methods of ChromaticObject + the ability to take functions of wavelength for the arguments. (#581) +- Added cdmodel module to describe charge deflection in CCD pixels. (#524) +- Added pupil_plane_im option to OpticalPSF. (#601) +- Added nx, ny, and bounds keywords to drawImage() and drawKImage() + methods. (#603) + +*Bug Fixes and Improvements* + +- Improved efficiency of noise generation by correlated noise models. (#563) +- Modified BoundsI and PositionI initialization to ensure that integer elements + in NumPy arrays with dtype==int are handled without error. (#486) +- Changed the default seed used for Deviate objects when no seed is given to + use /dev/urandom if it is available. (#537) +- Changed SED and Bandpass methods to preserve type when returning a new object + when possible. (#547) +- Made file_name argument to CorrelatedNoise.getCOSMOSNoise() be able + to have a default value in the repo. (#548) +- Fixed the dtype= kwarg of Image constructor on some platforms. (#571) +- Added workaround for bug in pyfits 3.0 in galsim.fits.read. (#572) +- Fixed a bug in the Image constructor when passed a NumPy array with the + opposite byteorder as the system native one. (#594) +- Fixed bug that prevented calling LookupTables on non-square 2d arrays. (#599) +- Updated the code to account for a planned change in NumPy 1.9. (#604) +- Fixed a bug where the dtype of an Image could change when resizing. (#604) +- Defined a hidden __version__ attribute according to PEP 8 standards. (#610) + +*Updates to config options* + +- Moved noise whitening option from being an attribute of the RealGalaxy class, + to being a part of the description of the noise. (#529) +- Added RandomPoisson, RandomBinomial, RandomWeibull, RandomGamma, and + RandomChi2 random number generators. (#537) + + +v1.1 +---- + +*Non-backward-compatible API changes* + +- Changed Pixel to take a single scale parameter. (#364) +- Added new Box class. (#364) +- Changed Angle.wrap() to return the wrapped angle. (#364) +- Changed Bounds methods addBorder, shift, and expand to return new + Bounds objects. (#364) +- Merged the GSParams parameters shoot_relerr and shoot_abserr into the + parameters integration_relerr and integration_abserr. (#535) + +*Other changes to the API* + +- Changed the name of the dx parameter in various places to scale. (#364) +- Combined the old Image, ImageView and ConstImageView arrays of class + names into a single python layer Image class. (#364) +- Changed the methods createSheared, createRotated, etc. to more succinct + names shear, rotate, etc. (#511) +- Changed the setFlux and scaleFlux methods to return new objects. (#511) +- Changed the Shapelet.fitImage method to FitShapelet (#511) +- Changed the nyquistDx method to nyquistScale. (#511) +- Moved as many classes as possible toward an immutable design. (#511) +- Combined the draw and drawShoot methods into a single drawImage method + with more options about how the profile should be rendered. (#535) +- Changed the name of drawK to drawKImage. (#535) + +*New Features* + +- Added new set of WCS classes. (#364) +- Added CelestialCoord class to represent (ra,dec) coordinates. (#364) +- Added Bandpass, SED, and ChromaticObject classes. (#467) +- Added aberrations parameter of OpticalPSF. (#409) +- Added max_size parameter to OpticalPSF. (#478) +- Added text_file parameter to FitsHeader and FitsWCS. (#508) +- Modified addNoiseSNR() method to return the added variance. (#526) +- Added dtype option to drawImage and drawKImage. (#526) + +*Bug fixes and improvements* + +- Sped up the gzip and bzip2 I/O. (#344) +- Fixed some bugs in the treatment of correlated noise. (#526, #528) + +*Updates to config options* + +- Added more options for image.wcs field. (#364) +- Changed the name of sky_pos to world_pos. (#364) +- Removed pix top layer in config structure. Add draw_method=no_pixel to + do what pix : None used to do. (#364) +- Added draw_method=real_space to try to use real-space convolution. (#364) +- Added ability to index Sequence types by any running index. (#364, #536) +- Added Sum type for value types for which it makes sense. (#457) +- Allowed modification of config parameters from the command line. (#479) +- Added image.retry_failures. (#482) +- Added output.retry_io item to retry failed write commands. (#482) +- Changed the default sequence indexing for most things to be 'obj_num_in_file' + rather than 'obj_num'. (#487) +- Added draw_method=sb. (#535) +- Changed the output.psf.real_space option to output.psf.draw_method + and allow all of the options that exist for image.draw_method. (#535) +- Added an index item for Ring objects. (#536) + + +v1.0 +---- + +*Notable bug fixes and improvements* + +- Fixed bug in the rendering of shifted images. (#424) +- Improved the fidelity of the Lanczos conserve_dc=True option. (#442) +- Switched default interpolant for RealGalaxy to Quintic, since it was + found to be more accurate in general. (#442) +- Fixed a bug in InterpolatedImage calculateStepK function. (#454) +- Fixed a bug in Image class resize function. (#461) +- Sped up OpticalPSF and RealGalaxy significantly. (#466, #474) +- Fixed a bug in the fourier rendering of truncated Sersic profiles. (#470) +- Fixed some bugs in the config machinery when files have varying numbers + of objects. (#487) +- Support astropy.io.fits in lieu of stand-alone pyfits module. (#488) +- Fixed a bug in config where 'safe' objects were not being correctly + invalidated when a new input item should have invalidated them. +- Fixed a bug in the drawing of a Pixel all by itself. (#497) + +*New features* + +- Added galsim executable (instead of galsim_yaml, galsim_json). (#460) +- Updated the allowed range for Sersic n to 0.3 -- 6.2. (#325) +- Made RealGalaxy objects keep track of their (correlated) noise. (#430) +- Changed noise padding options for RealGalaxy and InterpolatedImage. (#430) +- Added VariableGaussianNoise class. (#430) +- Added offset parameter to both draw and drawShoot. (#439) +- Changed the name of InputCatalog to just Catalog. (#449) +- Added Dict class. (#449) +- Added MEDS file output to des module. (#376) +- Removed des module from default imports of GalSim. Now need to import + galsim.des explicitly or load with galsim -m des ... (#460) + +*Updates to config options* + +- Added RealGalaxyOriginal galaxy type. (#389) +- Added whiten option for RealGalaxy objects. (#430) +- Added Current type. (#430) +- Added offset option in image field. (#449) +- Added the ability to have multiple input catalogs, dicts, etc. (#449) +- Added signal_to_noise option for PSFs when there is no galaxy. (#459) +- Added output.skip and output.noclobber options. (#474) + + +v0.5 +---- + +*New features* + +- Added Shapelet class. (#350) +- Added ability to truncate Sersic profiles. (#388) +- Added trefoil and struts to OpticalPSF. (#302, #390) +- Updates to lensing engine: + + - Added document describing how lensing engine code works. (#248) + - Added ability to draw (gamma,kappa) from same power spectrum. (#304) + - Added kmin_factor and kmax_factor parameters to buildGrid. (#377) + - Added PowerSpectrumEstimator class in pse module. (#382) + +- Added GSParams (#343, #426) and HSMParams (#365) classes. +- Added des module and example scripts. (#350) +- Added applyWhiteningTo method to CorrelatedNoise class. (#352) +- Changed the default centering convention for even-sized images to be in the + actual center, rather than 1/2 pixel off-center. (#380) +- Enabled InputCatalog to read FITS catalogs. (#350) +- Added FitsHeader class and config option. (#350) +- Added the ability to read/write to a specific HDU. (#350) +- Add new function galsim.fits.writeFile. (#417) +- Added LINKFLAGS SCons option. (#380) + +*Updates to config* + +- Added index_convention option. (#380) +- Changed the name of the center item for the Scattered image type to + image_pos, and added a new sky_pos item. (#380) + +*Bug fixes* + +- Fix some errors related to writing to an HDUList. (#417) +- Fixed ringing when Sersic objectss were drawn with FFTs. (#426) +- Fixed bugs in obj.drawK() function. (#407) +- Fixed bugs with InterpolatedImage objects. (#389, #432) +- Fixed bug in draw routine for shifted objects. (#380) +- Fixed bug in the generation of correlated noise fields. (#352) + + +v0.4 +---- + +- Added ability to pad images for InterpolatedImage or RealGalaxy with either + correlated or uncorrelated noise. (#238) +- Added python-level LookupTable class which wraps the existing C++ Table + class. (#305) +- Lensing engine updates: (#305) + + - Added the option of drawing shears from a tabulated P(k) + - Added ability to handle conversions between different angular units. + - Fixed an important bug in the normalization of the generated shears. + +- Added a DistDeviate class. (#306) +- Added galsim.correlatednoise.get_COSMOS_CorrFunc(...). (#345) +- Added im.addNoiseSNR(). (#349) +- Made a new Noise hierarchy for CCDNoise (no longer a BaseDeviate), + GaussianNoise, PoissonNoise, DeviateNoise. (#349) + + +v0.3 +---- + +- Fixed several bugs in the Sersic class that had been causing ringing. + (#319, #330) +- Added support for reading and writing compressed fits files. (#299) +- Added InterpolatedImage class to wrap existing C++ level SBInterpolatedImage. + (#333) +- Added a new class structure for representing 2D correlation functions, used + to describe correlated noise in images. (#297). +- Add FormattedStr option for string values in config files. (#315) +- Added obj.drawK() to the python layer. (#319) +- Fixed several sources of memory leaks. (#327) +- Updated the moments and PSF correction code to use the Image class and TMV; + to handle weight and bad pixel maps for the input Images; and to run ~2-3 + times faster. (#331, #332) +- Fixed bug in config RandomCircle when using inner_radius option. + + +v0.2 +---- + +- Significant revamping and commenting of demos, including both python and + config versions (#243, #285, #289). +- Added python-level int1d function to wrap C++-level integrator, which + allowed us to remove our dependency on scipy. (#288) +- Significant expansion in config functionality, using YAML/JSON format + config files (#291, #295). +- Fixed some bugs in Image handling (including bugs related to duplicate + numpy.int32 types), and made Image handling generally more robust (#293, #294). +- Fixed bug in wrapping of angles (now not automatic -- use wrap() explicitly). + + +v0.1 +---- + +Initial version of GalSim with most of the basic functionality. diff --git a/docs/_build/html/_sources/overview.rst.txt b/docs/_build/html/_sources/overview.rst.txt new file mode 100644 index 00000000000..2cebb63c49e --- /dev/null +++ b/docs/_build/html/_sources/overview.rst.txt @@ -0,0 +1,6 @@ + +Overview +######## + +.. include:: ../README.rst + diff --git a/docs/_build/html/_sources/phase_psf.rst.txt b/docs/_build/html/_sources/phase_psf.rst.txt new file mode 100644 index 00000000000..116ae2f9a64 --- /dev/null +++ b/docs/_build/html/_sources/phase_psf.rst.txt @@ -0,0 +1,108 @@ + +Phase-screen PSFs +================= + +We have available a more complicated kind of PSF model that tries to correctly model the +wavefront as it passed through various "screens" such as the atmosphere or optics. +It has a number of ancillary helper functions and classes associated with it to +define things like the aperture and the effect of the various screens. + +For PSFs drawn using real-space or Fourier methods, these utilities essentially evaluate the +Fourier optics diffraction equation: + +.. math:: + PSF(x,y) = \int \left| {\cal F}\left(A(u, v) e^{i \phi(u, v, x, y, t)} \right) \right|^2 dt + +where :math:`x`, :math:`y` are focal plane coordinates and :math:`u`, :math:`v` are +pupil plane coordinates, :math:`A` is the aperture, :math:`\phi` is the phase of the +incident wavefront, and :math:`\cal F` is the Fourier transform operator. + +For ``method='phot'``, two possible strategies are available. + +1. The first strategy is to draw the PSF using Fourier methods into an `InterpolatedImage`, + and then shoot photons from that profile. This strategy has good accuracy, but can be + computationally expensive, particularly for atmospheric PSFs that need to be built up in + small increments to simulate a finite exposure time. +2. The second strategy, which can be significantly faster, especially for atmospheric PSFs, + is to use the geometric optics approximation. This approximation has good accuracy for + atmospheric PSFs, so we make it the default for `PhaseScreenPSF`. The accuracy is somewhat + less good for purely optical PSFs though, so the default behavior for OpticalPSF is to use + the first strategy. The ``geometric_shooting`` keyword can be used in both cases to + override the default. + +The main classes of note are: + +`Aperture` + Class representing the illuminated region of pupil. + +`AtmosphericScreen` + Class implementing phase(u, v, x, y, t) for von Karman type turbulence, with possibly evolving + "non-frozen-flow" phases. + +`OpticalScreen` + Class implementing optical aberrations using Zernike polynomial expansions in the wavefront. + +`UserScreen` + Class implementing a user-defined phase screen. + +`PhaseScreenList` + Python sequence type to hold multiple phase screens, for instance to simulate turbulence at + different altitudes, or self-consistently model atmospheric and optical phase aberrations. + A key method is `PhaseScreenList.makePSF`, which will take the list of phase screens, add + them together linearly (Fraunhofer approximation), and evaluate the above diffraction equation + to yield a `PhaseScreenPSF` object. + +`PhaseScreenPSF` + A `GSObject` holding the evaluated PSF from a set of phase screens. + +`OpticalPSF` + A `GSObject` for optical PSFs with potentially complicated pupils and Zernike aberrations. + + .. note:: + + `OpticalPSF` is technically a kind of `PhaseScreenPSF`, but if you only want the + optical model, you generally don't need to bother with building any of the screens + manually. The `OpticalPSF` class constructor will handle this for you. + +`SecondKick` + A `GSObject` describing the high-k turbulence portion of an atmospheric PSF convolved by + an `Airy` PSF. When using photon shooting with a `PhaseScreenPSF`, small scale (high-k) + features are not well approximated by the geometric optics approximation, since this is + where Fourier effects such as diffraction become important. The `SecondKick` class can + compensate by simulating the appropriate behavior at high k values. + +`Atmosphere` + Convenience function to quickly assemble multiple `AtmosphericScreen` instances into a + `PhaseScreenList`. + + +.. autoclass:: galsim.Aperture + :members: + +.. autoclass:: galsim.AtmosphericScreen + :members: + +.. autoclass:: galsim.OpticalScreen + :members: + +.. autoclass:: galsim.UserScreen + :members: + +.. autoclass:: galsim.PhaseScreenList + :members: + +.. autoclass:: galsim.PhaseScreenPSF + :members: + :show-inheritance: + +.. autoclass:: galsim.SecondKick + :members: + :show-inheritance: + +.. autofunction:: galsim.Atmosphere + +.. autofunction:: galsim.phase_screens.initWorkerArgs + +.. autofunction:: galsim.phase_screens.initWorker + +.. autofunction:: galsim.phase_screens.reset_shared_screens diff --git a/docs/_build/html/_sources/photon.rst.txt b/docs/_build/html/_sources/photon.rst.txt new file mode 100644 index 00000000000..8d88c33a41d --- /dev/null +++ b/docs/_build/html/_sources/photon.rst.txt @@ -0,0 +1,114 @@ +Photon Shooting +=============== + +Photon shooting was used successfully to generate the simulated images for the GREAT08 and GREAT10 +weak lensing challenges. The objects were convolutions of elliptical Sersic-profile galaxies with +Moffat-profile PSFs. GalSim extends this technique to enable photon shooting for nearly all of its +possible objects, except for deconvolutions. + +When we "shoot" a `GSObject` or `ChromaticObject`, +:math:`N_\gamma` photons are created with fluxes :math:`f_i` and +positions :math:`x_i`. The total photon flux within any region has an expectation value of the +integrated surface brightness of the object in that region, and the total photon flux in any +two regions are uncorrelated. The actual realized flux in each region is distributed according +to Poisson statistics of the number of photons that actually fall in the region. + +We allow for non-uniform :math:`f_i` values primarily so that we can represent negative values of +surface brightness. This is necessary to realize interpolation with kernels that have negative +regions (as will any interpolant that approximates band-limited behavior), and to correctly render +interpolated images that have negative pixel values, such as might arise from using empirical, +noisy galaxy images. + +The basic way to activate photon shooting is to use ``method='phot'`` when calling the +`GSObject.drawImage` or `ChromaticObject.drawImage` method. +This will switch over to photon shooting, and the resulting +image will have photon shot noise included from the finite number of photons being shot. + +.. note:: + + This method necessarily accounts for integration over the pixel by summing the photons that + are incident in each. This means that if your surface brightness profile already + includes the pixel convolution, then you will get the wrong answer. Such profiles should + normally use ``method='no_pixel'``. This kind of profile is often the result of PSF estimation + codes, so some care is required if you intend to use photon shooting with PSFs that come from + measurements of real data. + +There are a number of other parameters that are relevant only when photon shooting that let you +customize the behavior to some extent: + + n_photons + The total number of photons to shoot is normally calculated from the object's + flux. This flux is taken to be given in photons/cm^2/s, so for most simple + profiles, this times ``area * exptime`` (both of which default to 1) will equal + the number of photons shot. (See the discussion in Rowe et al, 2015, for why + this might be modified for `InterpolatedImage` and related profiles.) However, + you can manually set a different number of photons with ``n_photons``. + + rng + Since photon shooting is a stochastic process, it needs a random number generator. + This should be a `BaseDeviate` instance. If none is provided, one will be + created automatically. + + max_extra_noise + This allows you to gain some speed by shooting fewer photons with :math:`f_i > 1` + at the expense of increasing the noise in each pixel above the natural Poisson + value. This parameter specifies how much extra noise you are willing to tolerate. + It is only relevant if you are not setting ``n_photons``, so the number of photons + is being automatically calculated. The ``max_extra_noise`` parameter specifies + how much extra noise per pixel is allowed because of this approximation. A + typical value might be ``max_extra_noise = sky_level / 100`` where ``sky_level`` + is the flux per pixel due to the sky. + + poisson_flux + Normally the total flux of the shot photons will itself be a Poisson random + value with `GSObject.flux` as the expectation value. However, you can disable + this effect by setting ``poisson_flux=False`` to have it shoot exactly the + flux of the `GSObject`. + + sensor + The default behavior is for the photons to simply accumulate in the pixel where + they land. However, more sophisticated behavior is possible by providing a + `Sensor` object, which can implement e.g. the brighter-fatter effect, charge + diffusion, and other effects present in real sensors. See `Sensor Models` + for more information about the current options. + + photon_ops + Prior to accumulating on the sensor, one might want to apply one or more + `Photon Operators` to the photons. These operators can be used to apply + a variety of effects to the photons: changing their fluxes or positions, + assigning wavelengths or incidence angles, etc. The ``photon_ops`` argument + should be a list of any such operators you want to apply. + + maxN + For very bright objects, one might want to limit the number of photons that are + shot before being accumulated. Normally all the photons are generated first + and stored in a `PhotonArray`. Then the `Photon Operators` (if any) are + applied. And finally the photons are accumulated onto the image pixels. + If you set ``maxN``, then this process will be done in batches of at most this + many photons at a time. + + save_photons + This provides the ability to return the `PhotonArray` that was accumulated + in case you want to do anything else with it. + + +If you prefer even more fine-grained control over photon shooting, you can use the following +methods: + + `GSObject.drawPhot` + This is the actual driver function that `GSObject.drawImage` calls after + performing some basic sanity checks and image setup. If you are trying to + optimize your code for low flux objects, you might find it useful to do the + image setup yourself and then call this directly. + + `GSObject.shoot` + This is the method that actually shoots the photons for a `GSObject`. It + does not apply any photon operators or accumulate onto the `Image`. + + +.. toctree:: + :maxdepth: 2 + + photon_array + sensor + photon_ops diff --git a/docs/_build/html/_sources/photon_array.rst.txt b/docs/_build/html/_sources/photon_array.rst.txt new file mode 100644 index 00000000000..ac35631eb3b --- /dev/null +++ b/docs/_build/html/_sources/photon_array.rst.txt @@ -0,0 +1,7 @@ +Photon Arrays +============= + +.. autoclass:: galsim.PhotonArray + :members: + + diff --git a/docs/_build/html/_sources/photon_ops.rst.txt b/docs/_build/html/_sources/photon_ops.rst.txt new file mode 100644 index 00000000000..11e3b08f87c --- /dev/null +++ b/docs/_build/html/_sources/photon_ops.rst.txt @@ -0,0 +1,29 @@ +Photon Operators +================ + +.. autoclass:: galsim.PhotonOp + :members: + +.. autoclass:: galsim.WavelengthSampler + :members: + +.. autoclass:: galsim.FRatioAngles + :members: + +.. autoclass:: galsim.PhotonDCR + :members: + +.. autoclass:: galsim.FocusDepth + :members: + +.. autoclass:: galsim.Refraction + :members: + +.. autoclass:: galsim.PupilImageSampler + :members: + +.. autoclass:: galsim.PupilAnnulusSampler + :members: + +.. autoclass:: galsim.TimeSampler + :members: diff --git a/docs/_build/html/_sources/pos.rst.txt b/docs/_build/html/_sources/pos.rst.txt new file mode 100644 index 00000000000..5596fd45603 --- /dev/null +++ b/docs/_build/html/_sources/pos.rst.txt @@ -0,0 +1,13 @@ +Positions +========= + +.. autoclass:: galsim.Position + :members: + +.. autoclass:: galsim.PositionI + :members: + :show-inheritance: + +.. autoclass:: galsim.PositionD + :members: + :show-inheritance: diff --git a/docs/_build/html/_sources/powerspectrum.rst.txt b/docs/_build/html/_sources/powerspectrum.rst.txt new file mode 100644 index 00000000000..ca708f83459 --- /dev/null +++ b/docs/_build/html/_sources/powerspectrum.rst.txt @@ -0,0 +1,21 @@ + +Power Spectrum Shears +===================== + +This is the "lensing engine" for calculating shears according to a Gaussian process with +specified E- and/or B-mode power spectra. + +.. autoclass:: galsim.PowerSpectrum + :members: + + .. automethod:: galsim.PowerSpectrum._getShear + .. automethod:: galsim.PowerSpectrum._getConvergence + .. automethod:: galsim.PowerSpectrum._getMagnification + .. automethod:: galsim.PowerSpectrum._getLensing + +.. autoclass:: galsim.lensing_ps.PowerSpectrumRealizer + :members: + + .. automethod:: galsim.lensing_ps.PowerSpectrumRealizer.__call__ + +.. autofunction:: galsim.lensing_ps.theoryToObserved diff --git a/docs/_build/html/_sources/pse.rst.txt b/docs/_build/html/_sources/pse.rst.txt new file mode 100644 index 00000000000..d6538d8274e --- /dev/null +++ b/docs/_build/html/_sources/pse.rst.txt @@ -0,0 +1,12 @@ +Power Spectrum Estimation +========================= + +The ``galsim.pse`` module was developed largely by Joe Zuntz and tweaked by assorted GalSim +developers. This development and testing of this module took place in a separate (private) +repository before the code was moved into the GalSim repository, but there are some demonstrations +of the performance of this code in devel/modules/lensing_engine.pdf. + +.. autoclass:: galsim.pse.PowerSpectrumEstimator + :members: + + galsim.pse.PowerSpectrumEstimator.__init__ diff --git a/docs/_build/html/_sources/psf.rst.txt b/docs/_build/html/_sources/psf.rst.txt new file mode 100644 index 00000000000..e2072395609 --- /dev/null +++ b/docs/_build/html/_sources/psf.rst.txt @@ -0,0 +1,44 @@ + +Point-spread functions +====================== + +There are a number of profiles that are designed to be used for point-spread functions (PSFs). +There is nothing in GalSim restricting these classes to be used only for PSFs or stars, +but that was the use case they were designed for. + +Airy Profile +------------ + +.. autoclass:: galsim.Airy + :members: + :show-inheritance: + +Moffat Profile +-------------- + +.. autoclass:: galsim.Moffat + :members: + :show-inheritance: + +Kolmogorov Profile +------------------ + +.. autoclass:: galsim.Kolmogorov + :members: + :show-inheritance: + +Von Karman Profile +------------------ + +.. autoclass:: galsim.VonKarman + :members: + :show-inheritance: + +Optical PSF +----------- + +.. autoclass:: galsim.OpticalPSF + :members: + :show-inheritance: + + diff --git a/docs/_build/html/_sources/random.rst.txt b/docs/_build/html/_sources/random.rst.txt new file mode 100644 index 00000000000..da27c8230ee --- /dev/null +++ b/docs/_build/html/_sources/random.rst.txt @@ -0,0 +1,22 @@ + +Noise and Random Values +####################### + +An important part of generating realistic images is to add appropriate levels of noise. +When simulating objects, you may also want the actual objects being drawn to be random in some +way. GalSim has a number of classes to help inject these kinds of randomness into your +simulations. + +* `Random Deviates` describes how to generate pseudo-random numbers according to a variety of + different probability distribution functions. +* `Noise Generators` describes how to add noise to images according a a few different noise models. +* `Correlated Noise` describes both how to add correlated noise to images and also ways to remove + (or reduce) existing correlations from an image. + +.. toctree:: + :maxdepth: 2 + + deviate + noise + corr_noise + diff --git a/docs/_build/html/_sources/real_gal.rst.txt b/docs/_build/html/_sources/real_gal.rst.txt new file mode 100644 index 00000000000..b1a308490b8 --- /dev/null +++ b/docs/_build/html/_sources/real_gal.rst.txt @@ -0,0 +1,126 @@ +"Real" Galaxies +=============== + +Individual Real Galaxies +------------------------ + +The `RealGalaxy` class uses images of galaxies from real astrophysical data (e.g. the Hubble Space +Telescope), along with a PSF model of the optical properties of the telescope that took these +images, to simulate new galaxy images with a different (must be larger) telescope PSF. A +description of the simulation method can be found in Section 5 of Mandelbaum et al. (2012; MNRAS, +540, 1518), although note that the details of the implementation in Section 7 of that work are not +relevant to the more recent software used here. + +The `RealGalaxyCatalog` class stores all required information about a real galaxy simulation +training sample and accompanying PSF model. +This modelling requires external data for the galaxy images and PSF models, which is read into a +`RealGalaxyCatalog` object from FITS files. An example catalog of 100 real galaxies is in the +repository itself at + + GalSim/examples/data/real_galaxy_catalog_23.5_example.fits + +For access to larger catalogs of objects, see `Downloading the COSMOS Catalog` below. + +.. autoclass:: galsim.RealGalaxy + :members: + :show-inheritance: + +.. autoclass:: galsim.RealGalaxyCatalog + :members: + +Realistic Scene +--------------- + +The `COSMOSCatalog` class is also based on the above `RealGalaxyCatalog`, and has functionality +for defining a "sky scene", i.e., a galaxy sample with reasonable properties that can then be +placed throughout a large image. It can simulate either `RealGalaxy` objects using the HST images +or parametric models based on those images. + +.. note:: + Currently, this only includes routines for making a COSMOS-based galaxy sample, but it could be + expanded to include star samples as well. + + +.. autoclass:: galsim.COSMOSCatalog + :members: + +.. autoclass:: galsim.GalaxySample + :members: + +Downloading the COSMOS Catalog +------------------------------ + +A set of ~56 000 real galaxy images with I<23.5, or another set of ~87 000 with I<25.2, with +original PSFs, can be downloaded from Zenodo: + +https://zenodo.org/record/3242143 + +The tar ball for the I<23.5 sample (``COSMOS_23.5_training_sample.tar.gz``) is roughly 4 GB and +contains catalogs and images with a README. The tar ball for the I<25.2 sample +(``COSMOS_25.2_training_sample.tar.gz``) is of similar size and format. + +GalSim also comes with a script ``galsim_download_cosmos`` that downloads the I<23.5 sample. +It works with both samples, with the I<25.2 sample being the default but with keyword arguments +to choose between the two:: + + usage: galsim_download_cosmos [-h] [-v {0,1,2,3}] [-f] [-q] [-u] [--save] + [-d DIR] [-s {23.5,25.2}] [--nolink] + + This program will download the COSMOS RealGalaxy catalog and images and place + them in the GalSim share directory so they can be used as the default files + for the RealGalaxyCatalog class. See https://github.com/GalSim- + developers/GalSim/wiki/RealGalaxy%20Data for more details about the files + being downloaded. + + optional arguments: + -h, --help show this help message and exit + -v {0,1,2,3}, --verbosity {0,1,2,3} + Integer verbosity level: min=0, max=3 [default=2] + -f, --force Force overwriting the current file if one exists + -q, --quiet Don't ask about re-downloading an existing file. + (implied by verbosity=0) + -u, --unpack Re-unpack the tar file if not downloading + --save Save the tarball after unpacking. + -d DIR, --dir DIR Install into an alternate directory and link from the + share/galsim directory + -s {23.5,25.2}, --sample {23.5,25.2} + Flux limit for sample to download; either 23.5 or 25.2 + --nolink Don't link to the alternate directory from + share/galsim + + Note: The unpacked files total almost 6 GB in size! + +.. note:: + + The ``galsim_download_cosmos`` program will put the downloaded files into a subdirectory + of the ``galsim.meta_data.share_dir`` directory. (cf. `Shared Data`) + This is normally convenient for access, since classes such as `RealGalaxyCatalog` and + `COSMOSCatalog` will look in this directory automatically for you. However, if you + reinstall GalSim, everything in this directory will be removed and overwritten. + Therefore, we normally recommend using the ``-d DIR`` option to place the downloaded + files into another location. E.g.:: + + galsim_download_cosmos -d ~/share + + It will still be required to rerun this after reinstalling GalSim, but it will notice that + you already have the files downloaded and merely update the symbolic link. + +Instructions for how to download a copy of the GREAT3 data are found at + +https://github.com/barnabytprowe/great3-public#how-to-get-the-data + +HSC Postage Stamp Data +---------------------- + +The HST postage stamp data from + +http://adsabs.harvard.edu/abs/2017arXiv171000885M + +which includes studies of the impact of blending on shear estimation in HSC, was released as part +of the HSC survey's second incremental data release. The sample is larger than the above, goes to +the depth of COSMOS, and does not have nearby objects masked. + +The links to download it and the instructions on its use are at + +https://hsc-release.mtk.nao.ac.jp/doc/index.php/weak-lensing-simulation-catalog-pdr1/ + diff --git a/docs/_build/html/_sources/roman.rst.txt b/docs/_build/html/_sources/roman.rst.txt new file mode 100644 index 00000000000..2846339ad21 --- /dev/null +++ b/docs/_build/html/_sources/roman.rst.txt @@ -0,0 +1,244 @@ + +The Roman Space Telescope Module +################################ + +The galsim.roman module contains information and functionality that can be used to simulate +images for the Roman Space Telescope. Some of the functionality is specific to Roman per se, but +some of the routines are more generically simulating aspects of the HgCdTe detectors, which will +be used on Roman. These routines might therefore be useful for simulating observations from +other telescopes that will use these detectors. + +The demo script demo13.py illustrates the use of most of this functionality. + +.. note:: + To use this module, you must separately ``import galsim.roman``. These functions are + not automatically imported when you ``import galsim``. + + +Module-level Attributes +======================= + +There are a number of attributes of the ``galsim.roman`` module, which define some numerical +parameters related to the Roman geometry. Some of these parameters relate to the entire +wide-field imager. Others, especially the return values of the functions to get the +PSF and WCS, are specific to each SCA (Sensor Chip Assembly, the equivalent of a chip for an optical +CCD) and therefore are indexed based on the SCA. All SCA-related arrays are 1-indexed, i.e., the +entry with index 0 is None and the entries from 1 to n_sca are the relevant ones. This is +consistent with diagrams and so on provided by the Roman project, which are 1-indexed. + +The NIR detectors that will be used for Roman have a different photon detection process from CCDs. +In particular, the photon detection process begins with charge generation. However, instead of +being read out along columns (as for CCDs), they are read directly from each pixel. Moreover, the +actual quantity that is measured is technically not charge, but rather voltage. The charge is +inferred based on the capacitance. To use a common language with that for CCDs, we will often refer +to quantities measured in units of e-/pixel, but for some detector non-idealities, it is important +to keep in mind that it is voltage that is sensed. + +gain + The gain for all SCAs (sensor chip assemblies) is expected to be the roughly the same, + and we currently have no information about how different they will be, so this is a + single value rather than a list of values. Once the actual detectors exist and have been + characterized, it might be updated to be a dict with entries for each SCA. + +pixel_scale + The pixel scale in units of arcsec/pixel. This value is approximate and does not + include effects like distortion, which are included in the WCS. + +diameter + The telescope diameter in meters. + +obscuration + The linear obscuration of the telescope, expressed as a fraction of the diameter. + +collecting_area + The actual collecting area after accounting for obscuration, struts, etc. in + units of cm^2. + +exptime + The typical exposure time in units of seconds. The number that is stored is for a + single dither. Each location within the survey will be observed with a total of 5-7 + dithers across 2 epochs. + +n_dithers + The number of dithers per filter (typically 5-7, so this is currently 6 as a + reasonable effective average). + +dark_current + The dark current in units of e-/pix/s. + +nonlinearity_beta + The coefficient of the (counts)^2 term in the detector nonlinearity + function. This will not ordinarily be accessed directly by users; instead, + it will be accessed by the convenience function in this module that defines + the nonlinearity function as counts_out = counts_in + beta*counts_in^2. + Alternatively users can use the `galsim.roman.applyNonlinearity` routine, + which already knows about the expected form of the nonlinearity in the + detectors. + +reciprocity_alpha + The normalization factor that determines the effect of reciprocity failure + of the detectors for a given exposure time. Alternatively, users can use + the `galsim.roman.addReciprocityFailure` routine, which knows about this + normalization factor already, and allows users to choose an exposure time or + use the default Roman exposure time. + +read_noise + A total of 8.5e-. This comes from 20 e- per correlated double sampling (CDS) and a + 5 e- floor, so the CDS read noise dominates. The source of CDS read noise is the + noise introduced when subtracting a single pair of reads; this can be reduced by + averaging over multiple reads. Also, this read_noise value might be reduced + based on improved behavior of newer detectors which have lower CDS noise. + +thermal_backgrounds + The thermal backgrounds (in units of e-/pix/s) are based on a temperature + of 282 K, but this plan might change in future. The thermal backgrounds + depend on the band, so this is not a single number; instead, it's a + dictionary that is accessed by the name of the optical band, e.g., + ``galsim.roman.thermal_backgrounds['F184']`` (where the names of the + bandpasses can be obtained using the `getBandpasses` routine described + below). + +pupil_plane_file + There is actually a separate file for each SCA giving the pupil plane mask + for the Roman telescope as seen from the center of each SCA. When building + the PSF with galsim.roman.getPSF, it will use the correct one for the given + SCA. However, for backwards compatibility, if anyone needs a generic image + of the pupil plane, this file is for SCA 2, near the center of the WFC field. + +pupil_plane_scale + The pixel scale in meters per pixel for the image in pupil_plane_file. + +stray_light_fraction + The fraction of the sky background that is allowed to contribute as stray + light. Currently this is required to be <10% of the background due to + zodiacal light, so its value is set to 0.1 (assuming a worst-case). This + could be used to get a total background including stray light. + +ipc_kernel + The 3x3 kernel to be used in simulations of interpixel capacitance (IPC), using + `galsim.roman.applyIPC`. + +persistence_coefficients + The retention fraction of the previous eight exposures in a simple, + linear model for persistence. + +persistence_fermi_params + The parameters in the fermi persistence model. + +n_sca + The number of SCAs in the focal plane. + +n_pix_tot + Each SCA has n_pix_tot x n_pix_tot pixels. + +n_pix + The number of pixels that are actively used. The 4 outer rows and columns will be + attached internally to capacitors rather than to detector pixels, and used to monitor + bias voltage drifts. Thus, images seen by users will be n_pix x n_pix. + +jitter_rms + The worst-case RMS jitter per axis for Roman in the current design (reality + will likely be much better than this). Units: arcsec. + +charge_diffusion + The per-axis sigma to use for a Gaussian representing charge diffusion for + Roman. Units: pixels. + +For example, to get the gain value, use galsim.roman.gain. Most numbers related to the nature of +the detectors are subject to change as further lab tests are done. + +Roman Functions +=============== + +This module also contains the following routines: + +`galsim.roman.getBandpasses` + A utility to get a dictionary containing galsim.Bandpass objects for each of + the Roman imaging bandpasses, which by default have AB zeropoints given using + the GalSim zeropoint convention (see `getBandpasses` docstring for more details). + +`galsim.roman.getSkyLevel` + A utility to find the expected sky level due to zodiacal light at a given + position, in a given band. + +`galsim.roman.applyNonlinearity` + A routine to apply detector nonlinearity of the type expected for Roman. + +`galsim.roman.addReciprocityFailure` + A routine to include the effects of reciprocity failure in images at + the level expected for Roman. + +`galsim.roman.applyIPC` + A routine to incorporate the effects of interpixel capacitance in Roman images. + +`galsim.roman.applyPersistence` + A routine to incorporate the effects of persistence - the residual images + from earlier exposures after resetting. + +`galsim.roman.allDetectorEffects` + A routine to add all sources of noise and all (implemented) detector + effects to an image containing astronomical objects plus background. In + principle, users can simply use this routine instead of separately using + the various routines like `galsim.roman.applyNonlinearity`. + +`galsim.roman.getPSF` + A routine to get a chromatic representation of the PSF in a single SCA. + +`galsim.roman.getWCS` + A routine to get the WCS for each SCA in the focal plane, for a given target RA, dec, + and orientation angle. + +`galsim.roman.findSCA` + A routine that can take the WCS from `getWCS` and some sky position, and indicate in + which SCA that position can be found, optionally including half of the gaps between + SCAs (to identify positions that are in the focal plane array but in the gap between SCAs). + +`galsim.roman.allowedPos` + A routine to check whether Roman is allowed to look at a given position on a + given date, given the constraints on orientation with respect to the sun. + +`galsim.roman.bestPA` + A routine to calculate the best observatory orientation angle for Roman when looking + at a given position on a given date. + +Another routine that may be necessary is `galsim.utilities.interleaveImages`. +The Roman PSFs at native Roman pixel scale are undersampled. A Nyquist-sampled PSF image can be +obtained by a two-step process: + + 1. Call the `galsim.roman.getPSF` routine and convolve the PSF with the Roman pixel response + to get the effective PSF. + 2. Draw the effective PSF onto an Image using drawImage routine, with a pixel scale lesser + than the native pixel scale (using the 'method=no_pixel' option). + +However, if pixel-level effects such as nonlinearity and interpixel capacitance must be applied to +the PSF images, then they must drawn at the native pixel scale. A Nyquist-sampled PSF image can be +obtained in such cases by generating multiple images with offsets (a dither sequence) and then +combining them using `galsim.utilities.interleaveImages`. + + +.. autofunction:: galsim.roman.getBandpasses + +.. autofunction:: galsim.roman.getSkyLevel + +.. autofunction:: galsim.roman.getPSF + +.. autofunction:: galsim.roman.getWCS + +.. autofunction:: galsim.roman.findSCA + +.. autofunction:: galsim.roman.allowedPos + +.. autofunction:: galsim.roman.bestPA + +.. autofunction:: galsim.roman.convertCenter + +.. autofunction:: galsim.roman.applyNonlinearity + +.. autofunction:: galsim.roman.addReciprocityFailure + +.. autofunction:: galsim.roman.applyIPC + +.. autofunction:: galsim.roman.applyPersistence + +.. autofunction:: galsim.roman.allDetectorEffects + diff --git a/docs/_build/html/_sources/sb.rst.txt b/docs/_build/html/_sources/sb.rst.txt new file mode 100644 index 00000000000..19c5b7a0a36 --- /dev/null +++ b/docs/_build/html/_sources/sb.rst.txt @@ -0,0 +1,32 @@ + +Surface Brightness Profiles +########################### + +There are many possible ways to define a surface brightness profile of an astronomical +object (stars, galaxies, etc.) in GalSim. We have classes for common PSF models (e.g. +`Moffat` and `Airy`), analytic galaxy profiles (e.g. `Exponential` and `Sersic`), interpolation +of an arbitrary input image (`InterpolatedImage`), some other more complicated models, +and ways to combine models as sums or convolutions. Models can also be arbitrarily +stretched, rotated, and dilated in various ways. + +The classes for these various surface brightness profiles are all subclasses of the +`GSObject` base class. See that section for details about most of the public API +methods that are defined for all (or almost all) of these classes. + +Next, we list the subclasses of `GSObject` organized by their intended use. These are +the classes you would actually use when building the profile for an astronomical object. + + +.. toctree:: + :maxdepth: 2 + + gsobject + simple + psf + gal + arbitrary + phase_psf + real_gal + composite + transform + gsparams diff --git a/docs/_build/html/_sources/sed.rst.txt b/docs/_build/html/_sources/sed.rst.txt new file mode 100644 index 00000000000..0df775a8519 --- /dev/null +++ b/docs/_build/html/_sources/sed.rst.txt @@ -0,0 +1,14 @@ +Spectral Energy Distributions +============================= + +.. autoclass:: galsim.SED + :members: + + .. automethod:: galsim.SED.__call__ + .. automethod:: galsim.SED.__mul__ + .. automethod:: galsim.SED._mul_sed + .. automethod:: galsim.SED._mul_bandpass + .. automethod:: galsim.SED._mul_scalar + +.. autoclass:: galsim.EmissionLine + :members: diff --git a/docs/_build/html/_sources/sensor.rst.txt b/docs/_build/html/_sources/sensor.rst.txt new file mode 100644 index 00000000000..b91125a7a69 --- /dev/null +++ b/docs/_build/html/_sources/sensor.rst.txt @@ -0,0 +1,20 @@ +Sensor Models +============= + +The `Sensor` classes implement the process of turning a set of photons incident at the surface +of the detector in the focal plane into an image with counts of electrons in each pixel. + +The `Sensor` class itself implements the simplest possible sensor model, which just converts each +photon into an electron in whatever pixel is below the location where the photon hits. +However, it also serves as a base class for other classes that implement more sophisticated +treatments of the photon to electron conversion and the drift from the conversion layer to the +bottom of the detector. + + +.. autoclass:: galsim.Sensor + :members: + +.. autoclass:: galsim.SiliconSensor + :members: + :show-inheritance: + diff --git a/docs/_build/html/_sources/shared.rst.txt b/docs/_build/html/_sources/shared.rst.txt new file mode 100644 index 00000000000..9abfcaf2670 --- /dev/null +++ b/docs/_build/html/_sources/shared.rst.txt @@ -0,0 +1,434 @@ +Shared Data +########### + +GalSim includes some ancillary data along with the installed code. They are installed in +the sub-directory ``share`` wherever your GalSim module is installed. + +The location of these files are given by the variable + +.. py:data:: galsim.meta_data.share_dir + + The installed location of your ``share`` directory. + +.. note:: + + Normally, the installation process will write a file called ``meta_data.py`` which sets + the above variable automatically to the installation directory on your machine. + + However, if you install into a temporary location and then move the entire galsim directory to + a different location, this will not have the correct location. (It will instead be set to the + temporary location.) If you do this, you should define an environment variable, + GALSIM_SHARE_DIR, to the correct location of the ``share`` directory. Alternatively, you can + set the value of `galsim.meta_data.share_dir` by hand in any Python programs that might need + it. + +Usually, you do not need to use the `galsim.meta_data.share_dir` variable directly. Routines +that open files that might be in the ``share`` directory will automatically prepend this directory +name to the given file name when trying to open the file. + +The following files are distributed in the ``share`` directory. In each case, we provide the +command you would typically use to load the file with the appropriate GalSim class or function. + +Shared SED files +================ + +vega.txt + Use ``galsim.SED('vega.txt', wave_type='nm', flux_type='flam')`` + + Specrum of the star Vega (aka Alpha Lyra), derived from HST CALSPEC data. + File taken from http://www.stsci.edu/hst/observatory/crds/calspec.html + Filename: alpha_lyr_mod_001.fits + Clipped on the red side at 2200 nm + Units converted to nm and erg/s/cm^2/nm. + +CWW_E_ext.sed + Use ``galsim.SED('CWW_E_ext.sed', wave_type='A', flux_type='flam')`` + + E SED of Coleman, Wu, and Weedman (1980) + Extended below 1400 A and beyond 10000 A by + Bolzonella, Miralles, and Pello (2000) using evolutionary models + of Bruzual and Charlot (1993) + + Obtained from ZPHOT code at + + http://webast.ast.obs-mip.fr/hyperz/zphot_src_1.1.tar.gz + + Truncated to wavelengths less than 22050 Angstroms, and thinned by + galsim.utilities.thin_tabulated_values to a relative error of 1.e-5 + with fast_search=False. See devel/modules/getSEDs.py for details. + +CWW_E_ext_more.sed + Use ``galsim.SED('CWW_E_ext_more.sed', wave_type='A', flux_type='flam')`` + + Same as CWW_E_ext.sed, but thinned to a relative error of 1.e-3 + +CWW_Im_ext.sed + Use ``galsim.SED('CWW_Im_ext.sed', wave_type='A', flux_type='flam')`` + + Im SED of Coleman, Wu, and Weedman (1980) + Extended below 1400 A and beyond 10000 A by + Bolzonella, Miralles, and Pello (2000) using evolutionary models + of Bruzual and Charlot (1993) + + Obtained from ZPHOT code at + 'http://webast.ast.obs-mip.fr/hyperz/zphot_src_1.1.tar.gz' + + Truncated to wavelengths less than 22050 Angstroms, and thinned by + galsim.utilities.thin_tabulated_values to a relative error of 1.e-5 + with fast_search=False. See devel/modules/getSEDs.py for details. + +CWW_Im_ext_more.sed + Use ``galsim.SED('CWW_Im_ext_more.sed', wave_type='A', flux_type='flam')`` + + Same as CWW_Im_ext.sed, but thinned to a relative error of 1.e-3 + +CWW_Sbc_ext.sed + Use ``galsim.SED('CWW_Sbc_ext.sed', wave_type='A', flux_type='flam')`` + + Sbc SED of Coleman, Wu, and Weedman (1980) + Extended below 1400 A and beyond 10000 A by + Bolzonella, Miralles, and Pello (2000) using evolutionary models + of Bruzual and Charlot (1993) + + Obtained from ZPHOT code at + 'http://webast.ast.obs-mip.fr/hyperz/zphot_src_1.1.tar.gz' + + Truncated to wavelengths less than 22050 Angstroms, and thinned by + galsim.utilities.thin_tabulated_values to a relative error of 1.e-5 + with fast_search=False. See devel/modules/getSEDs.py for details. + +CWW_Sbc_ext_more.sed + Use ``galsim.SED('CWW_Sbc_ext_more.sed', wave_type='A', flux_type='flam')`` + + Same as CWW_Sbc_ext.sed, but thinned to a relative error of 1.e-3 + +For more details about how the above files were generated, see the script: + + GalSim/devel/getSEDs.py + +Shared Bandpass files +===================== + +ACS_wfc_F435W.dat + Use ``galsim.Bandpass('ACS_wfc_F435W.dat', wave_type='nm')`` + + ACS wfc_F435W total throughput + File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F435W.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +ACS_wfc_F606W.dat + Use ``galsim.Bandpass('ACS_wfc_F606W.dat', wave_type='nm')`` + + ACS wfc_F606W total throughput + File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F606W.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +ACS_wfc_F775W.dat + Use ``galsim.Bandpass('ACS_wfc_F775W.dat', wave_type='nm')`` + + ACS wfc_F775W total throughput + File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F775W.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +ACS_wfc_F814W.dat + Use ``galsim.Bandpass('ACS_wfc_F814W.dat', wave_type='nm')`` + + ACS wfc_F814W total throughput + File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F814W.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +ACS_wfc_F850LP.dat + Use ``galsim.Bandpass('ACS_wfc_F850LP.dat', wave_type='nm')`` + + ACS wfc_F850LP total throughput + File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F850LP.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +LSST_u.dat + Use ``galsim.Bandpass('LSST_u.dat', wave_type='nm')`` + + LSST u-band total throughput at airmass 1.2 + File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_u.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +LSST_g.dat + Use ``galsim.Bandpass('LSST_g.dat', wave_type='nm')`` + + LSST g-band total throughput at airmass 1.2 + File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_g.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +LSST_r.dat + Use ``galsim.Bandpass('LSST_r.dat', wave_type='nm')`` + + LSST r-band total throughput at airmass 1.2 + File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_r.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +LSST_i.dat + Use ``galsim.Bandpass('LSST_i.dat', wave_type='nm')`` + + LSST i-band total throughput at airmass 1.2 + File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_i.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +LSST_z.dat + Use ``galsim.Bandpass('LSST_z.dat', wave_type='nm')`` + + LSST z-band total throughput at airmass 1.2 + File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_z.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +LSST_y.dat + Use ``galsim.Bandpass('LSST_y.dat', wave_type='nm')`` + + LSST Y-band total throughput at airmass 1.2 + File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_y.dat + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +WFC3_uvis_F275W.dat + Use ``galsim.Bandpass('WFC_uvis_F275W.dat', wave_type='nm')`` + + WFC3 UVIS f275w total throughput + Average of UVIS1 and UVIS2 throughputs, from files + http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f275w.UVIS1.tab + http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f275w.UVIS2.tab + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +WFC3_uvis_F336W.dat + Use ``galsim.Bandpass('WFC_uvis_F336W.dat', wave_type='nm')`` + + WFC3 UVIS f336w total throughput + Average of UVIS1 and UVIS2 throughputs, from files + http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f336w.UVIS1.tab + http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f336w.UVIS2.tab + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +WFC3_ir_F105W.dat + Use ``galsim.Bandpass('WFC_ir_F105W.dat', wave_type='nm')`` + + WFC3 IR f105w total throughput + File taken from http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f105w.IR.tab + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +WFC3_ir_F125W.dat + Use ``galsim.Bandpass('WFC_ir_F125W.dat', wave_type='nm')`` + + WFC3 IR f125w total throughput + File taken from http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f125w.IR.tab + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +WFC3_ir_F160W.dat + Use ``galsim.Bandpass('WFC_ir_F160W.dat', wave_type='nm')`` + + WFC3 IR f160w total throughput + File taken from http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f160w.IR.tab + + Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 + with fast_search=False. + +For more details about how the above files were generated, see the scripts: + + * GalSim/devel/getLSSTBandpass.py + * GalSim/devel/getACSBandpass.py + * GalSim/devel/getWFC3Bandpass.py + + +Shared Sensor models +===================== + +lsst_itl_8 + Use ``galsim.SiliconSensor('lsst_itl_8')`` + + The ITL sensor being used for LSST, using 8 points along each side of the + pixel boundaries. + +lsst_itl_32 + Use ``galsim.SiliconSensor('lsst_itl_32')`` + + The ITL sensor being used for LSST, using 32 points along each side of the + pixel boundaries. (This is more accurate than the lsst_itl_8, but slower.) + +lsst_etv_32 + Use ``galsim.SiliconSensor('lsst_etv_32')`` + + The ETV sensor being used for LSST, using 32 points along each side of the + pixel boundaries. (This file is still somewhat preliminary and may be + updated in the future.) + + +Shared HST noise model +====================== + +acs_I_unrot_sci_20_cf.fits + Use ``galsim.getCOSMOSNoise()`` + + +Shared Roman ST files +===================== + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_01.txt + Use ``galsim.roman.getPSF(1, bandpass)`` + + Roman PSF information for SCA 1 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_02.txt + Use ``galsim.roman.getPSF(2, bandpass)`` + + Roman PSF information for SCA 2 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_03.txt + Use ``galsim.roman.getPSF(3, bandpass)`` + + Roman PSF information for SCA 3 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_04.txt + Use ``galsim.roman.getPSF(4, bandpass)`` + + Roman PSF information for SCA 4 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_05.txt + Use ``galsim.roman.getPSF(5, bandpass)`` + + Roman PSF information for SCA 5 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_06.txt + Use ``galsim.roman.getPSF(6, bandpass)`` + + Roman PSF information for SCA 6 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_07.txt + Use ``galsim.roman.getPSF(7, bandpass)`` + + Roman PSF information for SCA 7 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_08.txt + Use ``galsim.roman.getPSF(8, bandpass)`` + + Roman PSF information for SCA 8 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_09.txt + Use ``galsim.roman.getPSF(9, bandpass)`` + + Roman PSF information for SCA 9 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_10.txt + Use ``galsim.roman.getPSF(10, bandpass)`` + + Roman PSF information for SCA 10 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_11.txt + Use ``galsim.roman.getPSF(11, bandpass)`` + + Roman PSF information for SCA 11 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_12.txt + Use ``galsim.roman.getPSF(12, bandpass)`` + + Roman PSF information for SCA 12 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_13.txt + Use ``galsim.roman.getPSF(13, bandpass)`` + + Roman PSF information for SCA 13 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_14.txt + Use ``galsim.roman.getPSF(14, bandpass)`` + + Roman PSF information for SCA 14 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_15.txt + Use ``galsim.roman.getPSF(15, bandpass)`` + + Roman PSF information for SCA 15 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_16.txt + Use ``galsim.roman.getPSF(16, bandpass)`` + + Roman PSF information for SCA 16 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_17.txt + Use ``galsim.roman.getPSF(17, bandpass)`` + + Roman PSF information for SCA 17 + +Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_18.txt + Use ``galsim.roman.getPSF(18, bandpass)`` + + Roman PSF information for SCA 18 + +Roman_SRR_WFC_Pupil_Mask_Shortwave_2048_reformatted.fits.gz + Use ``galsim.roman.getPSF(sca, bandpass)`` + + Roman Pupil Mask for the shorter wavelength bandpasses. + Relevant for bands Z087, Y106, J129, and H158 + +Roman_SRR_WFC_Pupil_Mask_Longwave_2048_reformatted.fits.gz + Use ``galsim.roman.getPSF(sca, bandpass)`` + + Roman Pupil Mask for the longer wavelength bandpasses. + Relevant for bands F184 and W149 + +afta_throughput.txt + Use ``galsim.roman.getBandpasses()`` + + Roman throughputs for all the Roman bands in a single file. + +sip_7_6_8.txt + Use ``galsim.roman.getWCS(world_pos)`` + + Roman ST WCS information for all SCAs. + +Shared COSMOS files +=================== + +These files are not shipped with GalSim, but can be installed into the ``share`` directory +by the executable ``galsim_download_cosmos``. See `Downloading the COSMOS Catalog` for details. + +COSMOS_25.2_training_sample + | Use ``galsim.RealGalaxyCatalog(sample=25.2)`` + | Or ``galsim.COSMOSCatalog(sample=25.2)`` + + Download with ``galsim_download_cosmos -s 25.2`` + + A directory containing files for creating a `RealGalaxyCatalog` or a `COSMOSCatalog` using the + F814W < 25.2 sample. + +COSMOS_23.5_training_sample + | Use ``galsim.RealGalaxyCatalog(sample=23.5)`` + | Or ``galsim.COSMOSCatalog(sample=23.5)`` + + Download with ``galsim_download_cosmos -s 23.5`` + + A directory containing files for creating a `RealGalaxyCatalog` or a `COSMOSCatalog` using the + F814W < 23.5 sample. diff --git a/docs/_build/html/_sources/shear.rst.txt b/docs/_build/html/_sources/shear.rst.txt new file mode 100644 index 00000000000..8d13c775962 --- /dev/null +++ b/docs/_build/html/_sources/shear.rst.txt @@ -0,0 +1,9 @@ + +The Shear class +=============== + +.. autoclass:: galsim.Shear + :members: + +.. autofunction:: galsim._Shear + diff --git a/docs/_build/html/_sources/simple.rst.txt b/docs/_build/html/_sources/simple.rst.txt new file mode 100644 index 00000000000..637ad69e5cf --- /dev/null +++ b/docs/_build/html/_sources/simple.rst.txt @@ -0,0 +1,40 @@ +Simple Profiles +=============== + +We have a few profiles that are just simple shapes. + +Gaussian Profile +---------------- + +.. autoclass:: galsim.Gaussian + :members: + :show-inheritance: + +Pixel Profile +------------- + +.. autoclass:: galsim.Pixel + :members: + :show-inheritance: + +Box Profile +----------- + +.. autoclass:: galsim.Box + :members: + :show-inheritance: + +TopHat Profile +-------------- + +.. autoclass:: galsim.TopHat + :members: + :show-inheritance: + +Delta Function +-------------- + +.. autoclass:: galsim.DeltaFunction + :members: + :show-inheritance: + diff --git a/docs/_build/html/_sources/spectral.rst.txt b/docs/_build/html/_sources/spectral.rst.txt new file mode 100644 index 00000000000..173222218af --- /dev/null +++ b/docs/_build/html/_sources/spectral.rst.txt @@ -0,0 +1,6 @@ + +Spectral Correlated Noise +========================= + +.. autoclass:: galsim.CovarianceSpectrum + :members: diff --git a/docs/_build/html/_sources/table.rst.txt b/docs/_build/html/_sources/table.rst.txt new file mode 100644 index 00000000000..6877ba3ec39 --- /dev/null +++ b/docs/_build/html/_sources/table.rst.txt @@ -0,0 +1,17 @@ +Lookup Tables +============= + +.. autoclass:: galsim.LookupTable + :members: + + .. automethod:: galsim.LookupTable.__call__ + +.. autoclass:: galsim.LookupTable2D + :members: + + .. automethod:: galsim.LookupTable2D.__call__ + +.. autofunction:: galsim.table._LookupTable2D + +.. autofunction:: galsim.trapz + diff --git a/docs/_build/html/_sources/transform.rst.txt b/docs/_build/html/_sources/transform.rst.txt new file mode 100644 index 00000000000..e818d8d38df --- /dev/null +++ b/docs/_build/html/_sources/transform.rst.txt @@ -0,0 +1,39 @@ +Transformed Profiles +==================== + +Any profile can be modified in a number of ways, including stretching, dilating, +rotating, and translating, and even more complicated things like forming the +inverse in Fourier space to effect a deconvolution. + +Affine Transformations +---------------------- + +.. autoclass:: galsim.Transformation + :members: + :show-inheritance: + +.. autofunction:: galsim.Transform + +.. autofunction:: galsim._Transform + + +De-convolution of a GSObject +---------------------------- + +.. autoclass:: galsim.Deconvolution + :members: + :show-inheritance: + +.. autofunction:: galsim.Deconvolve + + +Fourier-space Square Root +------------------------- + +.. autoclass:: galsim.FourierSqrtProfile + :members: + :show-inheritance: + +.. autofunction:: galsim.FourierSqrt + + diff --git a/docs/_build/html/_sources/tutorials.rst.txt b/docs/_build/html/_sources/tutorials.rst.txt new file mode 100644 index 00000000000..18d357f48c5 --- /dev/null +++ b/docs/_build/html/_sources/tutorials.rst.txt @@ -0,0 +1,632 @@ +Tutorials +========= + +The ``GalSim/examples`` directory contains demo files serve as tutorials on how to use the +GalSim code. + +There are versions of each demo in both Python (``demo*.py``) and YAML (``demo*.yaml``). +The demos start fairly simple and progress to more sophisticated simulations, adding +a modest number of new features each time with copious documentation about the new features +being introduced. + +The YAML files are run using the ``galsim`` executable, which parses the YAML file into a Python +dict and runs this through `The Config Module` in GalSim. For complicated simulations, +we generally recommend using config files such as these, since they tend to be more quickly +readable than the Python scripts, which makes it easy to see how to modify them to effect +some desired change in the simulation. For more information about running the ``galsim`` +executable, see `The galsim Executable`. + +Both versions of each demo produce identical output files. Internally, this serves as a useful +test of the config parsing code. But it also serves as a kind of implicit documentation +about how some of the config features are handled by GalSim. + +Demo 1 +------ + +:gh-link:`demo1.py ` +:gh-link:`demo1.yaml ` + +This first demo is about as simple as it gets. We draw an image of a single galaxy +convolved with a PSF and write it to disk. We use a circular Gaussian profile for both the +PSF and the galaxy, and add a constant level of Gaussian noise to the image. + +**New features in the Python file**: + +- obj = galsim.Gaussian(flux, sigma) +- obj = galsim.Convolve([list of objects]) +- image = obj.drawImage(scale) +- image.added_flux (Only present after a drawImage command.) +- noise = galsim.GaussianNoise(sigma) +- image.addNoise(noise) +- image.write(file_name) +- image.FindAdaptiveMom() + +**New features in the YAML file**: + +- top level fields gal, psf, image, output +- obj type : Gaussian (flux, sigma) +- image : pixel_scale +- image : noise +- noise type : Gaussian (sigma) +- output : dir, file_name + +Demo 2 +------ + +:gh-link:`demo2.py ` +:gh-link:`demo2.yaml ` + +This demo is a bit more sophisticated, but still pretty basic. We still only make +a single image, but now the galaxy has an exponential radial profile and is sheared. +The PSF is a circular Moffat profile. The noise is drawn from a Poisson distribution +using the flux from both the object and a background sky level to determine the +variance in each pixel. + +**New features in the Python file**: + +- obj = galsim.Exponential(flux, scale_radius) +- obj = galsim.Moffat(beta, flux, half_light_radius) +- obj = obj.shear(g1, g2) -- with explanation of other ways to specify shear +- rng = galsim.BaseDeviate(seed) +- noise = galsim.PoissonNoise(rng, sky_level) +- galsim.hsm.EstimateShear(image, image_epsf) + +**New features in the YAML file**: + +- obj type : Exponential (flux, scale_radius) +- obj type : Moffat (flux, beta, half_light_radius) +- obj : shear +- shear type : G1G2 (g1, g2) +- noise type : Poisson (sky_level) +- image : random_seed + + +Demo 3 +------ + +:gh-link:`demo3.py ` +:gh-link:`demo3.yaml ` + +This demo gets reasonably close to including all the principal features of an image +from a ground-based telescope. The galaxy is represented as the sum of a bulge and a disk, +where each component is represented by a sheared Sersic profile (with different Sersic +indices). The PSF has both atmospheric and optical components. The atmospheric +component is a Kolmogorov turbulent spectrum. The optical component includes defocus, +coma and astigmatism, as well as obscuration from a secondary mirror. The noise model +includes both a gain and read noise. And finally, we include the effect of a slight +telescope distortion. + +**New features in the Python file**: + +- obj = galsim.Sersic(n, flux, half_light_radius) +- obj = galsim.Sersic(n, flux, scale_radius) +- obj = galsim.Kolmogorov(fwhm) +- obj = galsim.OpticalPSF(lam_over_diam, defocus, coma1, coma2, astig1, astig2, obscuration) +- obj = obj.shear(e, beta) -- including how to specify an angle in GalSim +- shear = galsim.Shear(q, beta) +- obj = obj.shear(shear) +- obj3 = x1 * obj1 + x2 * obj2 +- obj = obj.withFlux(flux) +- image = galsim.ImageF(image_size, image_size) +- image = obj.drawImage(image, wcs) +- image = obj.drawImage(method='sb') +- world_profile = wcs.toWorld(profile) +- shear3 = shear1 + shear2 +- noise = galsim.CCDNoise(rng, sky_level, gain, read_noise) + +**New features in the YAML file**: + +- obj type : Sum (items) +- obj type : Convolve (items) +- obj type : Sersic (flux, n, half_light_radius) +- obj type : Sersic (flux, n, scale_radius) +- obj type : Kolmogorov (fwhm) +- obj type : OpticalPSF (lam_over_diam, defocus, coma1, coma2, astig1, astig2, obscuration) +- obj : ellip +- shear type : QBeta (q, beta) -- including how to specify an angle +- shear type : EBeta (e, beta) +- noise type : CCD (sky_level, gain, read_noise) +- image : size +- image : wcs +- wcs type : Shear +- output : psf + +Demo 4 +------ + +:gh-link:`demo4.py ` +:gh-link:`demo4.yaml ` + +This demo is our first one to create multiple images. Typically, you would want each object +to have at least some of its attributes vary when you are drawing multiple images (although +not necessarily -- you might just want different noise realization of the same profile). +The easiest way to do this is to read in the properties from a catalog, which is what we +do in this case. The PSF is a truncated Moffat profile, and the galaxy is bulge plus disk. +Both components get many of their parameters from an input catalog. We also shift the +profile by a fraction of a pixel in each direction so the effect of pixelization varies +among the images. Each galaxy has the same applied shear. The noise is simple Poisson noise. +We write the images out into a multi-extension fits file. + +**New features in the Python file**: + +- cat = galsim.Catalog(file_name, dir) +- obj = galsim.Moffat(beta, fwhm, trunc) +- obj = galsim.DeVaucouleurs(flux, half_light_radius) +- obj = galsim.RandomKnots(npoints, half_light_radius, flux) +- obj = galsim.Add([list of objects]) +- obj = obj.shift(dx,dy) +- galsim.fits.writeMulti([list of images], file_name) + +**New features in the YAML file**: + +- obj type : Moffat (..., trunc) +- obj type : DeVaucouleurs (flux, half_light_radius) +- obj type : RandomKnots (npoints, half_light_radius, flux) +- value type : Catalog (col) +- obj : shift +- shift type : XY (x, y) +- shear type : E1E2 (e1, e2) +- image : xsize, ysize +- top level field input +- input : catalog (file_name, dir) +- output type : MultiFits (file_name, dir) +- Using both ellip and shear for the same object +- Using variables in a YAML file + +Demo 5 +------ + +:gh-link:`demo5.py ` +:gh-link:`demo5.yaml ` + +This demo is intended to mimic a Great08 (Bridle, et al, 2010) LowNoise image. +We produce a single image made up of tiles of postage stamps for each individual object. +(We only do 10 x 10 postage stamps rather than 100 x 100 as they did in the interest of time.) +Each postage stamp is 40 x 40 pixels. One image is all stars. A second image is all galaxies. +The stars are truncated Moffat profiles. The galaxies are Exponential profiles. +(Great08 mixed pure bulge and pure disk for its LowNoise run. We just use disks to +make things simpler. However see demo3 for an example of using bulge+disk galaxies.) +The galaxies are oriented randomly, but in 90 degree-rotated pairs to cancel the effect of +shape noise. The applied shear is the same for each galaxy. + +**New features in the Python file**: + +- ud = galsim.UniformDeviate(seed) +- gd = galsim.GaussianDeviate(ud, sigma) +- ccdnoise = galsim.CCDNoise(ud) +- image \*= scalar +- bounds = galsim.BoundsI(xmin, xmax, ymin, ymax) +- pos = bounds.center +- pos.x, pos.y +- sub_image = image[bounds] +- Build a single large image, and access sub-images within it. +- Set the galaxy size based on the PSF size and a resolution factor. +- Set the object flux according to a target S/N value. +- Use 90 degree-rotated pairs for the intrinsic galaxy shapes. +- Shift by a random (dx, dy) drawn from a unit circle top hat. + +**New features in the YAML file**: + +- gal : resolution +- gal : signal_to_noise +- stamp type : Ring (first, num) +- value type : RandomGaussian (sigma, min, max) +- angle type : Random +- shift type : RandomCircle (radius) +- image type : Tiled (nx_tiles, ny_tiles, stamp_xsize, stamp_ysize, border) +- output type : Fits (file_name, dir) +- output.psf : shift + +Demo 6 +------ + +:gh-link:`demo6.py ` +:gh-link:`demo6.yaml ` + +This demo uses real galaxy images from COSMOS observations. The catalog of real galaxy +images distributed with GalSim only includes 100 galaxies, but you can download a much +larger set of images as described in `Downloading the COSMOS Catalog`. + +The galaxy images are already convolved with the effective PSF for the original +observations, so GalSim considers the galaxy profile to be the observed image deconvolved +by that PSF (also distributed with the galaxy data). +In this case, we then randomly rotate the galaxies, apply a given gravitational shear as +well as gravitational magnification, and then finally convolve by a double Gaussian PSF. +The final image can of course have any pixel scale, not just that of the original images. +The output for this demo is to a FITS "data cube". With DS9, this can be viewed with a +slider to quickly move through the different images. + +**New features in the Python file**: + +- real_cat = galsim.RealGalaxyCatalog(file_name, dir) +- obj = galsim.Gaussian(fwhm, flux) +- obj = galsim.RealGalaxy(real_cat, index, flux) +- obj = obj.rotate(theta) +- obj = obj.magnify(mu) +- image += background +- noise = galsim.PoissonNoise() # with no sky_level given +- obj.drawImage(..., offset) +- galsim.fits.writeCube([list of images], file_name) + +**New features in the YAML file**: + +- input : real_catalog (file_name, dir, image_dir) +- obj type : RealGalaxy (index) +- obj : rotate +- obj : magnify +- image : sky_level +- image : offset +- value type : Sequence (first, last, step) +- output type : DataCube (file_name, dir, nimages) +- Using YAML multiple document feature to do more than one thing + + +Demo 7 +------ + +:gh-link:`demo7.py ` +:gh-link:`demo7.yaml ` + +This demo introduces drawing profiles with photon shooting rather than doing the +convolution with an FFT. It makes images using 5 different kinds of PSF and 5 different +kinds of galaxy. Some of the parameters (flux, size and shape) are random variables, so +each of the 25 pairings is drawn 4 times with different realizations of the random numbers. +The profiles are drawn twice: once with the FFT method, and once with photon shooting. +The two images are drawn side by side into the same larger image so it is easy to +visually compare the results. The 100 total profiles are written to a FITS data cube, +which makes it easy to scroll through the images comparing the two drawing methods. + +**New features in the Python file**: + +- obj = galsim.Airy(lam_over_diam) +- obj = galsim.Sersic(n, half_light_radius, trunc) +- psf = galsim.OpticalPSF(..., aberrations=aberrations, ...) +- obj = obj.dilate(scale) +- str(obj) +- image.scale = pixel_scale +- obj.drawImage(image, method='fft') +- obj.drawImage(image, method='phot', max_extra_noise, rng) +- dev = galsim.PoissonDeviate(rng, mean) +- noise = galsim.DeviateNoise(dev) +- writeCube(..., compress='gzip') +- gsparams = galsim.GSParams(...) + +**New features in the YAML file**: + +- obj type : List (items) +- obj type : Airy (lam_over_diam) +- obj type : Sersic (..., trunc) +- obj : dilate +- value type : Sequence (..., repeat, index_key) +- value type : Random (min, max) +- image type : Tiled (..., stamp_size, xborder, yborder) +- stamp : draw_method (fft or phot) +- stamp : gsparams +- output : file_name with .gz, .bz2 or .fz extension automatically uses compression. + +Demo 8 +------ + +:gh-link:`demo8.py ` +:gh-link:`demo8.yaml ` + +In this demo, we show how to run the GalSim config processing using a python dict rather +than using a config file. The previous demos have shown what Python code corresponds to +the given YAML files. Now we turn the tables +and show how to use some of the machinery in the GalSim configuration processing +from within Python itself. + +This could be useful if you want to use the config machinery to build the images, but then +rather than write the images to disk, you want to keep them in memory and do further +processing with them. (e.g. Run your shape measurement code on the images from within python.) + +**New features in the Python file**: + +- galsim.config.Process(config, logger) +- galsim.config.ProcessInput(config, logger) +- galsim.config.BuildFile(config, file_num, logger) +- image = galsim.config.BuildImage(config, image_num, logger) +- galsim.fits.read(file_name) + +**New features in the YAML file**: + +- stamp : retry_failures +- shear type : Eta1Eta2 (eta1, eta2) +- image : nproc + +Demo 9 +------ + +:gh-link:`demo9.py ` +:gh-link:`demo9.yaml ` + +This script simulates cluster lensing or galaxy-galaxy lensing. The gravitational shear +applied to each galaxy is calculated for an NFW halo mass profile. We simulate observations +of galaxies around 20 different clusters -- 5 each of 4 different masses. Each cluster +has its own file, organized into 4 directories (one for each mass). For each cluster, we +draw 20 lensed galaxies located at random positions in the image. The PSF is appropriate for a +space-like simulation. (Some of the numbers used are the values for HST.) And we apply +a cubic telescope distortion for the WCS. Finally, we also output a truth catalog for each +output image that could be used for testing the accuracy of shape or flux measurements. + +**New features in the Python file**: + +- psf = OpticalPSF(lam, diam, ..., trefoil1, trefoil2, nstruts, strut_thick, strut_angle) +- im = galsim.ImageS(xsize, ysize, wcs) +- pos = galsim.PositionD(x, y) +- nfw = galsim.NFWHalo(mass, conc, z, omega_m, omega_lam) +- g1,g2 = nfw.getShear(pos, z) +- mag = nfw.getMagnification(pos, z) +- distdev = galsim.DistDeviate(rng, function, x_min, x_max) +- pos = bounds.true_center +- wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc, origin) +- wcs.toWorld(profile, image_pos) +- wcs.makeSkyImage(image, sky_level) +- image_pos = wcs.toImage(pos) +- image.invertSelf() +- truth_cat = galsim.OutputCatalog(names, types) +- bounds.isDefined() +- Make multiple output files. +- Place galaxies at random positions on a larger image. +- Write a bad pixel mask and a weight image as the second and third HDUs in each file. +- Use multiple processes to construct each file in parallel. + +**New features in the YAML file**: + +- obj type : OpticalPSF (lam, diam, ..., trefoil1, trefoil2, nstruts, strut_thick, strut_angle) +- obj type : InclinedExponential (scale_radius, scale_h_over_r, inclination) +- angle type : Radians +- shear type : NFWHaloShear (redshift) +- float type : NFWHaloMagnification (redshift) +- float type : RandomDistribution(function, x_min, x_max) +- input : nfw_halo (mass, conc, redshift) +- shear type : Sum (items) +- image type : Scattered (size, nobjects) +- wcs type : UVFunction (ufunc, vfunc, xfunc, yfunc, origin) +- str type : NumberedFile (root, num, ext, digits) +- str type : FormattedStr (format, items) +- pos type : RandomCircle (..., inner_radius) +- value type : Sequence (..., nitems) +- output : nproc +- output : weight +- output : badpix +- output : truth +- output : skip +- output : noclobber + +Demo 10 +------- + +:gh-link:`demo10.py ` +:gh-link:`demo10.yaml ` + +This script uses both a variable PSF and variable shear, taken from a power spectrum, along +the lines of a Great10 (Kitching, et al, 2012) image. The galaxies are placed on a grid +(10 x 10 in this case, rather than 100 x 100 in the interest of time.) Each postage stamp +is 48 x 48 pixels. Instead of putting the PSF images on a separate image, we package them +as the second HDU in the file. For the galaxies, we use a random selection from 5 specific +RealGalaxy objects, selected to be 5 particularly irregular ones. (These are taken from +the same catalog of 100 objects that demo6 used.) The galaxies are oriented in a ring +test (Nakajima & Bernstein 2007) of 20 each. And we again output a truth catalog with the +correct applied shear for each object (among other information). + +**New features in the Python file**: + +- im.wcs = galsim.OffsetWCS(scale, origin) +- rng = galsim.BaseDeviate(seed) +- obj = galsim.RealGalaxy(real_galaxy_catalog, id) +- obj = galsim.Convolve([list], real_space) +- ps = galsim.PowerSpectrum(e_power_function, b_power_function) +- g1,g2 = ps.buildGrid(grid_spacing, ngrid, rng) +- g1,g2 = ps.getShear(pos) +- galsim.random.permute(rng, list1, list2, ...) +- Choosing PSF parameters as a function of (x,y) +- Selecting RealGalaxy by ID rather than index. +- Putting the PSF image in a second HDU in the same file as the main image. +- Using PowerSpectrum for the applied shear. +- Doing a full ring test (i.e. not just 90 degree rotated pairs) + +**New features in the YAML file**: + +- obj type : Ring (..., full_rotation) +- obj type : RealGalaxy (..., id) +- type : Eval using world_pos variable, user-defined variables and math functions +- type : Current +- shear_value : PowerSpectrumShear +- pos_value : RTheta (r, theta) +- image type : Tiled (..., order) +- input : power_spectrum (e_power_function, b_power_function) +- output.psf : hdu, signal_to_noise, draw_method, offset +- output.truth : hdu +- Evaluated values in output.truth.columns + +Demo 11 +------- + +:gh-link:`demo11.py ` +:gh-link:`demo11.yaml ` + +This script uses a constant PSF from real data (an image read in from a bzipped FITS file, not a +parametric model) and variable shear and magnification according to some cosmological model for +which we have a tabulated shear power spectrum at specific k values only. The 288 galaxies in the 0.1 x +0.1 degree field (representing a number density of 8/arcmin^2) are randomly located and +permitted to overlap. For the galaxies, we use a mix of real and parametric galaxies modeled off +the COSMOS observations with the Hubble Space Telescope. The real galaxies are similar to those +used in demo10. The parametric galaxies are based on parametric fits to the same observed galaxies. +The flux and size distribution are thus realistic for an I < 23.5 magnitude limited sample. + +**New features in the Python file**: + +- coord = galsim.CelestialCoord(ra, dec) +- wcs = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin) +- wcs = galsim.TanWCS(affine, world_origin, units) +- psf = galsim.InterpolatedImage(psf_filename, scale, flux) +- tab = galsim.LookupTable(file) +- cosmos_cat = galsim.COSMOSCatalog(file_name, dir) +- gal = cosmos_cat.makeGalaxy(gal_type, rng, noise_pad_size) +- ps = galsim.PowerSpectrum(..., units) +- gal = gal.lens(g1, g2, mu) +- image.whitenNoise(correlated_noise) +- image.symmetrizeNoise(correlated_noise) +- vn = galsim.VariableGaussianNoise(rng, var_image) +- image.addNoise(cn) +- image.setOrigin(x,y) +- angle.dms(), angle.hms() +- Power spectrum shears and magnifications for non-gridded positions. +- Reading a compressed FITS image (using BZip2 compression). +- Writing a compressed FITS image (using Rice compression). +- Writing WCS information to a FITS header that ds9 reads as RA, Dec + +**New features in the YAML file**: + +- obj type : InterpolatedImage(image, scale) +- obj type : COSMOSGalaxy +- obj : scale_flus +- image : draw_method (no_pixel) +- input : power_spectrum (e_power_file, delta2, units) +- input : cosmos_catalog (file_name, dir, use_real) +- image : index_convention +- image.noise : whiten +- image.noise : symmetrize +- wcs type : Tan(dudx, dudy, dvdx, dvdy, units, origin, ra, dec) +- top level field eval_variables +- Power spectrum shears and magnifications for non-gridded positions. +- Reading a compressed FITS image (using BZip2 compression). +- Writing a compressed FITS image (using Rice compression). +- Using $ as a shorthand for Eval type. + +Demo 12 +------- + +:gh-link:`demo12.py ` +:gh-link:`demo12.yaml ` + +This demo introduces wavelength-dependent profiles. Three kinds of chromatic profiles are +demonstrated: + +1. A chromatic object representing a DeVaucouleurs galaxy with an early-type SED at redshift 0.8. + This galaxy is drawn using the six LSST filters, which demonstrate that the galaxy is a + g-band dropout. +2. A two-component bulge+disk galaxy, in which the bulge and disk have different SEDs. +3. A wavelength-dependent atmospheric PSF, which includes the effect of differential chromatic + refraction and the wavelength dependence of Kolmogorov-turbulence-induced seeing. This PSF + is convolved with a simple Exponential galaxy. + +**New features in the Python file**: + +- SED = galsim.SED(wave, flambda, wave_type, flux_type) +- SED2 = SED.atRedshift(redshift) +- bandpass = galsim.Bandpass(filename, wave_type) +- bandpass2 = bandpass.truncate(relative_throughput) +- bandpass3 = bandpass2.thin(rel_err) +- gal = GSObject * SED +- obj = galsim.Add([list of ChromaticObjects]) +- ChromaticObject.drawImage(bandpass) +- PSF = galsim.ChromaticAtmosphere(GSObject, base_wavelength, zenith_angle) + +**New features in the YAML file**: + +- sed : file_name, wave_type, flux_type, norm_flux_density, norm_wavelength, + norm_flux, norm_bandpass +- bandpass : filename, wave_type, thin +- gal : redshift +- psf_type : ChromaticAtmosphere (base_profile, base_wavelength, latitude, HA) + + +Demo 13 +------- + +:gh-link:`demo13.py ` +:gh-link:`demo13.yaml ` + +This script is intended to produce a relatively realistic scene of galaxies and stars as will +be observed by the Roman Space Telescope, including the Roman PSF, WCS, and various NIR detector +effects. + +It introduces several non-idealities arising from NIR detectors, in particular those that will +be observed and accounted for in the Roman Space Telescope. Three such non-ideal effects are +demonstrated, in the order in which they are introduced in the detectors: + +1. Reciprocity failure: Flux-dependent sensitivity of the detector. +2. Non-linearity: Charge-dependent gain in converting from units of electrons to ADU. Non-linearity + in some form is also relevant for CCDs in addition to NIR detectors. +3. Interpixel capacitance: Influence of charge in a pixel on the voltage reading of neighboring + ones. + +It also uses chromatic photon shooting, which is generally a more efficient way to simulate +scenes with many faint galaxies. The default FFT method for drawing chromatically is fairly +slow, since it needs to integrate the image over the bandpass. With photon shooting, the +photons are assigned wavelengths according to the SED of the galaxy, and then each photon has +the appropriate application of the chromatic PSF according to the wavelength. + +**New features in the Python file**: + +- image.quantize() +- obj = galsim.DeltaFunction(flux) +- galsim.roman.addReciprocityFailure(image) +- galsim.roman.applyNonlinearity(image) +- galsim.roman.applyIPC(image) +- galsim.roman.getBandpasses() +- galsim.roman.getPSF() +- galsim.roman.getWCS() +- galsim.roman.allowedPos() +- galsim.roman.getSkyLevel() + +**New features in the YAML file**: + +- Top-level field modules +- obj type: RomanPSF +- image type: RomanSCA +- draw_method=phot in conjunction with chromatic objects +- Multiple random seeds (particular so one can repeat the same values for multiple images) +- rng_num specification in various fields +- Multiple inputs of the same type. Use num to specify which item in the list to use each time. + + +Advanced Simulations +-------------------- + +Great3 Simulations +^^^^^^^^^^^^^^^^^^ + +In the directory ``GalSim/examples/great3``, +there are YAML config files that perform essentially the same +simulations that were done for Great3. The config apparatus had not matured sufficiently by the +time the Great3 sims were run, so these are not what the Great3 team used. However, the files +in this directory produce essentially equivalent simulations as those used in Great3. + +So far there are only config files for the cgc and rgc branches of Great3, but we plan to add +the files for the other branches (Issue #699). + +**Significant features in these files**: + +- template option to load another config file and then modify a few aspects of it. (e.g. + :gh-link:`rgc.yaml `) +- template option to load only a particular field from another config file. (e.g. + :gh-link:`cgc_psf.yaml `) +- stamp.reject +- custom value type (e.g. Great3Reject in :gh-link:`cgc.yaml `) +- custom extra output type (e.g. noise_free in :gh-link:`cgc.yaml `) +- top-level module field +- use of '$' and '@' shorthand in Eval items. + +DES Simulations +^^^^^^^^^^^^^^^ + +In the directory ``examples/des``, +there are YAML config files that showcase some of the classes +defined in the ``galsim.des`` module. These are mostly gratuitous demos designed to showcase +various features, although :gh-link:`meds.yaml ` +is very close to a real simulation we actually used in DES for testing shear measurements. + +**Significant features in these files**: + +- top-level module field +- special object types from galsim.des module (e.g. DES_Shapelet and DES_PSFEx in + :gh-link:`draw_psf.yaml `) +- special output type from galsim.des module (e.g. MEDS in :gh-link:`meds.yaml `) +- custom value type (e.g. HSM_Shape_Measure in meds.yaml, LogNormal in :gh-link:`blend.yaml `) +- custom WCS type (e.g. DES_Local in :gh-link:`meds.yaml `) +- custom input type (e.g. des_wcs in :gh-link:`meds.yaml `) +- custom stamp types (e.g. Blend in blend.yaml and BlendSet in :gh-link:`blendset.yaml `) +- custom extra output type (e.g. deblend in :gh-link:`blend.yaml `) diff --git a/docs/_build/html/_sources/units.rst.txt b/docs/_build/html/_sources/units.rst.txt new file mode 100644 index 00000000000..235625e191b --- /dev/null +++ b/docs/_build/html/_sources/units.rst.txt @@ -0,0 +1,117 @@ + +Units +===== + +Size Units +---------- + +GalSim models surface brightnesses of objects in sky coordinates. The physical size of a +galaxy in light years or kpc is not really relevant to its appearance in the sky. Rather, +the size is an angle, being the angle subtended by the galaxy as seen from Earth. + +The most common choice of unit for these objects is arcseconds. This happens to be the +right order of magnitude for most objects of astronomical interest. The smallest objects +we typically observe are somewhat less than an arcsecond in size. The largest objects +(e.g. M31) are more than a degree, but by far the majority of the objects of interest are +only a few arcseconds across. + +So this is usually the units one would use for the various `GSObject` size parameters +(e.g. ``fwhm``, ``half_light_radius`` and so forth) when you are building them. However, +this choice is actually up to the user. You may choose to define all your sizes in +degrees or radians if you want. You just need to be consistent with the unit you use +for the sizes of all your objects and with the units of the pixel scale when you are +building your `Image` (via the ``scale`` parameter of the `Image` constructor) or when +drawing (the ``scale`` argument to `GSObject.drawImage`) + +.. note:: + If you are using a more complicated WCS than a simple `PixelScale` (i.e. using ``wcs`` when + building the `Image` rather than ``scale``), then you need to be even more careful about this. + Some of the FITS-based WCS classes assume you are using arcseconds for the distance unit, + and it is not always easy to coerce them into using a different unit. In these cases, + you are probably well-advised to just stick with arcseconds for your sizes. + +Some classes specify their angular sizes somewhat indirectly. For the `Airy` class, for instance, +you can specify ``lam`` as the wavelength of the light (:math:`\lambda`, say +at the middle of the bandpass) in nm, and ``diam``, the telescope diameter (:math:`D`) in meters. +The ratio of these :math:`\lambda / D` (after putting them into the same units) gives the +fundamental scale radius for an `Airy` profile. But this angle is in radians, which is normally +not particularly convenient to use for the image pixel scale. The `Airy` constructor thus takes +another parameter, ``scale_unit``, which defaults to arcsec to specify the unit you want to +convert this angle to. + +Flux Units +---------- + +The units for the ``flux`` of a `GSObject` are nominally photons/cm^2/s, and the units for an +image are ADUs (analog-to-digital units). There are thus four conversion factors to apply to +go from one to the other. + +1. The exposure time (s) +2. The effective collecting area of the telescope (cm^2) +3. The quantum efficiency (QE) of the collector (e-/photon) +4. The gain of the read-out amplifier (e-/ADU) + +In GalSim, we generally just lump the QE in with the gain, so our gain is taken to have units of +photons/ADU, and it really represents gain / QE. + +Note, however, that the default exposure time, telescope collecting area, and gain are 1 s, 1 cm^2, +and 1 ADU/photon respectively, so users who wish to ignore the intricacies of managing exposure +times, collecting areas, and gains can simply think of the flux of a `GSObject` in either ADUs or +photons. + +However, if you prefer to think of your flux as having physical units, then you can declare +the appropriate telescope collecting area (``area``), the exposure time (``exptime``), and the +total effective gain (``gain``) as arguments to `GSObject.drawImage`. + +SED Units +--------- + +These details matter more when working with `ChromaticObject` instances, where the flux +normalization is handled with an `SED` object. The units of an input `SED` can be any of +several possible options: + +1. erg/nm/cm^2/s or erg/A/cm^2/s (use ``flux_type='flambda'``) +2. erg/Hz/cm^2/s (use ``flux_typ='fnu'``) +3. photons/nm/cm^2/s or photons/A/cm^2/s (use ``flux_typ='fphotons'``) +4. Any units that qualify as an ``astropy.units.spectral_density`` using the AstroPy ``units`` + module +5. dimensionless (use ``flux_typ='1'``) + +.. note:: + The last one is a bit different from the others. It is generally only appropriate for the + "SED" of a PSF, not that of a galaxy or star. The PSF may have a different effect as a + function of wavelength, in which case that can be treated similarly to how we treat an SED. + In any object that is a convolution of several components, only one of them should have a + spectral SED. The rest should be dimensionless (possibly flat). The net SED of the + composite object will then also be spectral. + +Internally, all spectral units are converted to photons/nm/cm^2/s. Then when drawing a +`ChromaticObject`, spectrum is integrated over the `Bandpass` to obtain the normal units of +photons/cm^2/s. If you trust your SED, you can then just draw with the appropriate ``area``, +``exptime`` and ``gain`` when you call `ChromaticObject.drawImage`. + +However, it is often more convenient to target a particular flux or magnitude of your object +as observed through a particular `Bandpass` (probably in ADU) and then ignore all of these +parameters when you are drawing. This is possible using the methods `SED.withFlux` or +`SED.withMagnitude`. + +Angles +------ + +For nearly all angular values, we require the argument to be an `Angle` instance. +We use the ``LSSTDESC.Coord`` package for this (and its `CelestialCoord` class): + +https://github.com/LSSTDESC/Coord + +An earlier version of this code was originally implemented in GalSim, so we +still import the relevant classes into the ``galsim`` namespace, so for example +``gasim.Angle`` is a valid alias for ``coord.Angle``. You may therefor use either namespace +for your use of these classes. + +.. autoclass:: galsim.Angle + :members: + +.. autofunction:: galsim._Angle + +.. autoclass:: galsim.AngleUnit + :members: diff --git a/docs/_build/html/_sources/utilities.rst.txt b/docs/_build/html/_sources/utilities.rst.txt new file mode 100644 index 00000000000..536cd448dd2 --- /dev/null +++ b/docs/_build/html/_sources/utilities.rst.txt @@ -0,0 +1,23 @@ + +Helper Functions and Classes +############################ + +We have a number of helper functions and classes that are used by various parts of the +code base. These may be useful to know about, since they may be useful for other projects +that use galsim and have similar needs. + +.. toctree:: + :maxdepth: 2 + + table + catalog + interpolant + fits + integ + bessel + fft + zernike + dcr + cd + misc + diff --git a/docs/_build/html/_sources/wcs.rst.txt b/docs/_build/html/_sources/wcs.rst.txt new file mode 100644 index 00000000000..b9bd4b9b6dd --- /dev/null +++ b/docs/_build/html/_sources/wcs.rst.txt @@ -0,0 +1,129 @@ + +World Coordinate Systems +######################## + +The World Coordinate System (or WCS) is the traditional term for the mapping from pixel coordinates +to the coordinate system on the sky. +(I know, the world's down here, and the sky's up there, so you'd think it would +be reversed, but that's the way it goes. Astronomy is full of terms that don't quite make sense +when you look at them too closely.) + +There are two kinds of world coordinates that we use here: + +- Celestial coordinates are defined in terms of right ascension (RA) and declination (Dec). + They are a spherical coordinate system on the sky, akin to longitude and latitude on Earth. + cf. http://en.wikipedia.org/wiki/Celestial_coordinate_system + +- Euclidean coordinates are defined relative to a tangent plane projection of the sky. + If you imagine the sky coordinates on an actual sphere with a particular radius, then the + tangent plane is tangent to that sphere. We use the labels (u,v) for the coordinates in + this system, where +v points north and +u points west. (Yes, west, not east. As you look + up into the sky, if north is up, then west is to the right.) + +The classes in this file provide a mapping from image coordinates (in pixels) to one of these +two kinds of world coordinates. We use the labels ``(x,y)`` for the image coordinates. + +WCS Base Classes +================ + +.. autoclass:: galsim.BaseWCS + :members: + +.. autoclass:: galsim.wcs.CelestialWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.wcs.EuclideanWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.wcs.UniformWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.wcs.LocalWCS + :members: + :show-inheritance: + +Euclidean WCS's +=============== + +.. autoclass:: galsim.PixelScale + :members: + :show-inheritance: + +.. autoclass:: galsim.OffsetWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.ShearWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.OffsetShearWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.JacobianWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.AffineTransform + :members: + :show-inheritance: + +.. autoclass:: galsim.UVFunction + :members: + :show-inheritance: + + +Celestial WCS's +=============== + +.. autoclass:: galsim.RaDecFunction + :members: + :show-inheritance: + +.. autoclass:: galsim.AstropyWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.PyAstWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.WcsToolsWCS + :members: + :show-inheritance: + +.. autoclass:: galsim.GSFitsWCS + :members: + :show-inheritance: + +.. autofunction:: galsim.FitsWCS + +.. autofunction:: galsim.TanWCS + +.. autofunction:: galsim.FittedSIPWCS + +Celestial Coordinates +===================== + +Our `CelestialCoord` class is currently hosted as part of the ``LSSTDESC.Coord`` package: + +https://github.com/LSSTDESC/Coord + +An earlier version of this code was originally implemented in GalSim, so we +still import the relevant classes into the ``galsim`` namespace. You may therefore +use either ``galsim.CelestialCoord`` or ``coord.CelestialCoord`` as they are equivalent. + +.. autoclass:: galsim.CelestialCoord + :members: + + +WCS Utilities +============= + +.. autofunction:: galsim.wcs.readFromFitsHeader + +.. autofunction:: galsim.wcs.compatible diff --git a/docs/_build/html/_sources/wl.rst.txt b/docs/_build/html/_sources/wl.rst.txt new file mode 100644 index 00000000000..e98bf610e44 --- /dev/null +++ b/docs/_build/html/_sources/wl.rst.txt @@ -0,0 +1,23 @@ + +Weak Lensing +############ + +GalSim was originally built for the Great03 weak lensing challenge. As such, it includes +various classes and routines for accurately handling and constructing weak lensing shear and +magnification. + +* The `Shear` class is our basic object for handling and manipulating shear values. +* `PowerSpectrum` can be used to generate shear and convergence fields according to an input + power spectrum function. +* `NFWHalo` can generate tangential shear profiles around an NFW halo mass profile. +* `galsim.pse.PowerSpectrumEstimator` can be used to estimate a shear power spectrum from + gridded shear values. + + +.. toctree:: + :maxdepth: 2 + + shear + powerspectrum + nfwhalo + pse diff --git a/docs/_build/html/_sources/zernike.rst.txt b/docs/_build/html/_sources/zernike.rst.txt new file mode 100644 index 00000000000..80eab7368e1 --- /dev/null +++ b/docs/_build/html/_sources/zernike.rst.txt @@ -0,0 +1,21 @@ +Zernike Functions +================= + +.. autoclass:: galsim.zernike.Zernike + :members: + + .. automethod:: galsim.zernike.Zernike.__add__ + .. automethod:: galsim.zernike.Zernike.__sub__ + .. automethod:: galsim.zernike.Zernike.__neg__ + .. automethod:: galsim.zernike.Zernike.__mul__ + .. automethod:: galsim.zernike.Zernike.__rmul__ + .. automethod:: galsim.zernike.Zernike.__call__ + +.. autoclass:: galsim.zernike.DoubleZernike + +.. autofunction:: galsim.zernike.noll_to_zern +.. autofunction:: galsim.zernike.zernikeRotMatrix +.. autofunction:: galsim.zernike.zernikeBasis +.. autofunction:: galsim.zernike.zernikeGradBases +.. autofunction:: galsim.zernike.doubleZernikeBasis +.. autofunction:: galsim.zernike.describe_zernike diff --git a/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js b/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 00000000000..81415803ec2 --- /dev/null +++ b/docs/_build/html/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/docs/_build/html/_static/basic.css b/docs/_build/html/_static/basic.css new file mode 100644 index 00000000000..7577acb1ad1 --- /dev/null +++ b/docs/_build/html/_static/basic.css @@ -0,0 +1,903 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/docs/_build/html/_static/css/badge_only.css b/docs/_build/html/_static/css/badge_only.css new file mode 100644 index 00000000000..c718cee4418 --- /dev/null +++ b/docs/_build/html/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 00000000000..6cb60000181 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 00000000000..7059e23142a Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 00000000000..f815f63f99d Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 00000000000..f2c76e5bda1 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot b/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 00000000000..e9f60ca953f Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg b/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 00000000000..855c845e538 --- /dev/null +++ b/docs/_build/html/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf b/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 00000000000..35acda2fa11 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 00000000000..400014a4b06 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 00000000000..4d13fc60404 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 00000000000..88ad05b9ff4 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 00000000000..c4e3d804b57 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff b/docs/_build/html/_static/css/fonts/lato-bold.woff new file mode 100644 index 00000000000..c6dff51f063 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-bold.woff2 b/docs/_build/html/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 00000000000..bb195043cfc Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-bold.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 00000000000..76114bc0336 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 00000000000..3404f37e2e3 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff b/docs/_build/html/_static/css/fonts/lato-normal.woff new file mode 100644 index 00000000000..ae1307ff5f4 Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal.woff differ diff --git a/docs/_build/html/_static/css/fonts/lato-normal.woff2 b/docs/_build/html/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 00000000000..3bf9843328a Binary files /dev/null and b/docs/_build/html/_static/css/fonts/lato-normal.woff2 differ diff --git a/docs/_build/html/_static/css/theme.css b/docs/_build/html/_static/css/theme.css new file mode 100644 index 00000000000..19a446a0e70 --- /dev/null +++ b/docs/_build/html/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel,.rst-content .menuselection{font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content .guilabel,.rst-content .menuselection{border:1px solid #7fbbe3;background:#e7f2fa}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/docs/_build/html/_static/doctools.js b/docs/_build/html/_static/doctools.js new file mode 100644 index 00000000000..d06a71d7518 --- /dev/null +++ b/docs/_build/html/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/docs/_build/html/_static/documentation_options.js b/docs/_build/html/_static/documentation_options.js new file mode 100644 index 00000000000..f35a7ed390b --- /dev/null +++ b/docs/_build/html/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '2.6.0', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/docs/_build/html/_static/file.png b/docs/_build/html/_static/file.png new file mode 100644 index 00000000000..a858a410e4f Binary files /dev/null and b/docs/_build/html/_static/file.png differ diff --git a/docs/_build/html/_static/jquery.js b/docs/_build/html/_static/jquery.js new file mode 100644 index 00000000000..c4c6022f298 --- /dev/null +++ b/docs/_build/html/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_build/html/_static/js/html5shiv.min.js b/docs/_build/html/_static/js/html5shiv.min.js new file mode 100644 index 00000000000..cd1c674f5e3 --- /dev/null +++ b/docs/_build/html/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/docs/_build/html/_static/js/theme.js b/docs/_build/html/_static/js/theme.js new file mode 100644 index 00000000000..1fddb6ee4a6 --- /dev/null +++ b/docs/_build/html/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/docs/_build/html/_static/minus.png b/docs/_build/html/_static/minus.png new file mode 100644 index 00000000000..d96755fdaf8 Binary files /dev/null and b/docs/_build/html/_static/minus.png differ diff --git a/docs/_build/html/_static/plus.png b/docs/_build/html/_static/plus.png new file mode 100644 index 00000000000..7107cec93a9 Binary files /dev/null and b/docs/_build/html/_static/plus.png differ diff --git a/docs/_build/html/_static/pygments.css b/docs/_build/html/_static/pygments.css new file mode 100644 index 00000000000..0d49244edab --- /dev/null +++ b/docs/_build/html/_static/pygments.css @@ -0,0 +1,75 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/docs/_build/html/_static/searchtools.js b/docs/_build/html/_static/searchtools.js new file mode 100644 index 00000000000..97d56a74d82 --- /dev/null +++ b/docs/_build/html/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/docs/_build/html/_static/sphinx_highlight.js b/docs/_build/html/_static/sphinx_highlight.js new file mode 100644 index 00000000000..aae669d7ea6 --- /dev/null +++ b/docs/_build/html/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/docs/_build/html/arbitrary.html b/docs/_build/html/arbitrary.html new file mode 100644 index 00000000000..0e6da28265c --- /dev/null +++ b/docs/_build/html/arbitrary.html @@ -0,0 +1,765 @@ + + + + + + + Arbitrary Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Arbitrary Profiles

+

If none of the above classes seem appropriate, it is possible to define any arbitrary +profile by an image and then interpolating between the pixel positions to define the +surface brightness at any arbitrary location. Similarly, one can define the image in +Fourier space, or use shapelets, which are a different complete basis set, instead of +using pixels.

+
+

Interpolated Images

+
+
+class galsim.InterpolatedImage(image, x_interpolant=None, k_interpolant=None, normalization='flux', scale=None, wcs=None, flux=None, pad_factor=4.0, noise_pad_size=0, noise_pad=0.0, rng=None, pad_image=None, calculate_stepk=True, calculate_maxk=True, use_cache=True, use_true_center=True, depixelize=False, offset=None, gsparams=None, _force_stepk=0.0, _force_maxk=0.0, hdu=None)[source]
+

Bases: GSObject

+

A class describing non-parametric profiles specified using an Image, which can be +interpolated for the purpose of carrying out transformations.

+

The InterpolatedImage class is useful if you have a non-parametric description of an object as +an Image, that you wish to manipulate / transform using GSObject methods such as +GSObject.shear, GSObject.magnify, GSObject.shift, etc. Note that when convolving an +InterpolatedImage, the use of real-space convolution is not recommended, since it is typically +a great deal slower than Fourier-space convolution for this kind of object.

+

There are three options for determining the flux of the profile.

+
    +
  1. You can simply specify a flux value explicitly.

  2. +
  3. If you set normalization = 'flux', the flux will be taken as the sum of the pixel values +in the input image. This corresponds to an image that was drawn with +drawImage(method='no_pixel'). This is the default if flux is not given.

  4. +
  5. If you set normalization = 'sb', the pixel values are treated as samples of the surface +brightness profile at each location. This corresponds to an image drawn with +drawImage(method='sb'). The flux is then the sum of the pixels in the input image +multiplied by the pixel area.

  6. +
+

You can also use images that were drawn with one of the pixel-integrating methods (‘auto’, +‘phot’, ‘fft’, or ‘real_space’), or real images where nature has integrated over the pixel for +you. However, the resulting profile will by default include a convolution by a Pixel (this +is equivalent to integration over the pixel). This is often acceptable, and the resulting +InterpolatedImage object can be convolved by other profiles as usual. One just needs to +remember to draw the final convolved profile using method='no_pixel' to avoid including +the pixel convolution a second time. In particular, one should not use it in conjunction +with photon shooting, for the same reason.

+

However, if you want to try to remove the effect of the pixel and have the InterpolatedImage +model the pre-pixelized profile, then you can set depixelize=True. This will call +Image.depixelize on the image automatically to try to remove the effect of the pixelization. +We recommend using a Lanczos interpolant with this option for best results. (Higher order +tends to work better here.) This step can be rather slow and memory-demanding, so use this +with caution. But if used, the resulting profile represents the true underlying profile, +without the pixel convolution. It can therefore be rotated, sheared, etc. And when rendering, +one should use the methods that do involve integration over the pixel: auto, phot, etc.

+
+

Warning

+

Input images that are undersampled and/or noisy may not necessarily work well with the +depixelize=True option. Users should treat this option with some care and validate +that the results are sufficiently accurate for your particular use case.

+
+

If the input Image has a scale or wcs associated with it, then there is no need to +specify one as a parameter here. But if one is provided, that will override any scale or +wcs that is native to the Image.

+

The user may optionally specify an interpolant, x_interpolant, for real-space manipulations +(e.g., shearing, resampling). If none is specified, then by default, a Quintic interpolant is +used. The user may also choose to specify two quantities that can affect the Fourier space +convolution: the k-space interpolant (k_interpolant) and the amount of padding to include +around the original images (pad_factor). The default values for x_interpolant, +k_interpolant, and pad_factor were chosen based on the tests of branch #389 to reach +good accuracy without being excessively slow. Users should be particularly wary about changing +k_interpolant and pad_factor from the defaults without careful testing. The user is +given complete freedom to choose interpolants and pad factors, and no warnings are raised when +the code is modified to choose some combination that is known to give significant error. More +details can be found in http://arxiv.org/abs/1401.2636, especially table 1, and in comment +https://github.com/GalSim-developers/GalSim/issues/389#issuecomment-26166621 and the following +comments.

+

The user can choose to pad the image with a noise profile if desired. To do so, specify +the target size for the noise padding in noise_pad_size, and specify the kind of noise +to use in noise_pad. The noise_pad option may be a Gaussian random noise of some +variance, or a Gaussian but correlated noise field that is specified either as a +BaseCorrelatedNoise instance, an Image (from which a correlated noise model is derived), or +a string (interpreted as a filename of an image to use for deriving a CorrelatedNoise). +The user can also pass in a random number generator to be used for noise generation. Finally, +the user can pass in a pad_image for deterministic image padding.

+

By default, the InterpolatedImage recalculates the Fourier-space step and number of points to +use for further manipulations, rather than using the most conservative possibility. For typical +objects representing galaxies and PSFs this can easily make the difference between several +seconds (conservative) and 0.04s (recalculated). However, the user can turn off this option, +and may especially wish to do so when using images that do not contain a high S/N object - e.g., +images of noise fields.

+

Example:

+
>>> interpolated_image = galsim.InterpolatedImage(
+        image, x_interpolant=None, k_interpolant=None, normalization='flux', scale=None,
+        wcs=None, flux=None, pad_factor=4., noise_pad_size=0, noise_pad=0., use_cache=True,
+        pad_image=None, rng=None, calculate_stepk=True, calculate_maxk=True,
+        use_true_center=True, offset=None, hdu=None)
+
+
+

Initializes interpolated_image as an InterpolatedImage instance.

+

For comparison of the case of padding with noise or zero when the image itself includes noise, +compare im1 and im2 from the following code snippet (which can be executed from the +examples/ directory):

+
>>> image = galsim.fits.read('data/147246.0_150.416558_1.998697_masknoise.fits')
+>>> int_im1 = galsim.InterpolatedImage(image)
+>>> int_im2 = galsim.InterpolatedImage(image, noise_pad='data/blankimg.fits')
+>>> im1 = galsim.ImageF(1000,1000)
+>>> im2 = galsim.ImageF(1000,1000)
+>>> int_im1.drawImage(im1, method='no_pixel')
+>>> int_im2.drawImage(im2, method='no_pixel')
+
+
+

Examination of these two images clearly shows how padding with a correlated noise field that is +similar to the one in the real data leads to a more reasonable appearance for the result when +re-drawn at a different size.

+
+
Parameters:
+
    +
  • image – The Image from which to construct the object. +This may be either an Image instance or a string indicating a fits +file from which to read the image. In the latter case, the hdu +kwarg can be used to specify a particular HDU in that file.

  • +
  • x_interpolant – Either an Interpolant instance or a string indicating which real-space +interpolant should be used. Options are ‘nearest’, ‘sinc’, ‘linear’, +‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the integer order +to use. [default: galsim.Quintic()]

  • +
  • k_interpolant – Either an Interpolant instance or a string indicating which k-space +interpolant should be used. Options are ‘nearest’, ‘sinc’, ‘linear’, +‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the integer order +to use. We strongly recommend leaving this parameter at its default +value; see text above for details. [default: galsim.Quintic()]

  • +
  • normalization

    Two options for specifying the normalization of the input Image:

    +
      +
    • ”flux” or “f” means that the sum of the pixels is normalized +to be equal to the total flux.

    • +
    • ”surface brightness” or “sb” means that the pixels sample +the surface brightness distribution at each location.

    • +
    +

    This is overridden if you specify an explicit flux value. +[default: “flux”]

    +

  • +
  • scale – If provided, use this as the pixel scale for the Image; this will +override the pixel scale stored by the provided Image, in any. +If scale is None, then take the provided image’s pixel scale. +[default: None]

  • +
  • wcs – If provided, use this as the wcs for the image. At most one of +scale or wcs may be provided. [default: None]

  • +
  • flux – Optionally specify a total flux for the object, which overrides the +implied flux normalization from the Image itself. [default: None]

  • +
  • pad_factor – Factor by which to pad the Image with zeros. We strongly recommend +leaving this parameter at its default value; see text above for +details. [default: 4]

  • +
  • noise_pad_size – If provided, the image will be padded out to this size (in arcsec) with +the noise specified by noise_pad. This is important if you are +planning to whiten the resulting image. You want to make sure that the +noise-padded image is larger than the postage stamp onto which you are +drawing this object. [default: None]

  • +
  • noise_pad

    Noise properties to use when padding the original image with +noise. This can be specified in several ways:

    +
      +
    1. as a float, which is interpreted as being a variance to use when +padding with uncorrelated Gaussian noise;

    2. +
    3. as a galsim.BaseCorrelatedNoise, which contains information about +the desired noise power spectrum - any random number generator passed +to the rng keyword will take precedence over that carried in +an input BaseCorrelatedNoise instance;

    4. +
    5. as an Image of a noise field, which is used to calculate +the desired noise power spectrum; or

    6. +
    7. as a string which is interpreted as a filename containing an +example noise field with the proper noise power spectrum (as an +Image in the first HDU).

    8. +
    +

    It is important to keep in mind that the calculation of the correlation +function that is internally stored within a BaseCorrelatedNoise +object is a non-negligible amount of overhead, so the recommended means +of specifying a correlated noise field for padding are (b) or (d). In +the case of (d), if the same file is used repeatedly, then the +use_cache keyword (see below) can be used to prevent the need for +repeated CorrelatedNoise initializations. [default: 0, i.e., pad +with zeros]

    +

  • +
  • use_cache – Specify whether to cache noise_pad read in from a file to save +having to build a CorrelatedNoise object repeatedly from the same image. +[default: True]

  • +
  • rng – If padding by noise, the user can optionally supply the random noise +generator to use for drawing random numbers as rng (may be any kind +of BaseDeviate object). Such a user-input random number generator +takes precedence over any stored within a user-input +BaseCorrelatedNoise instance (see the noise_pad parameter). +If rng=None, one will be automatically created, using the time as a +seed. [default: None]

  • +
  • pad_image

    Image to be used for deterministically padding the original image. +This can be specified in two ways:

    +
      +
    • as an Image; or

    • +
    • as a string which is interpreted as a filename containing an +image to use (in the first HDU).

    • +
    +

    The pad_image scale or wcs is ignored. It uses the same scale or +wcs for both the image and the pad_image. +The user should be careful to ensure that the image used for padding +has roughly zero mean. The purpose of this keyword is to allow for a +more flexible representation of some noise field around an object; if +the user wishes to represent the sky level around an object, they +should do that after they have drawn the final image instead. +[default: None]

    +

  • +
  • calculate_stepk – Specify whether to perform an internal determination of the extent of +the object being represented by the InterpolatedImage; often this is +useful in choosing an optimal value for the stepsize in the Fourier +space lookup table. +If you know a priori an appropriate maximum value for stepk, then +you may also supply that here instead of a bool value, in which case +the stepk value is still calculated, but will not go above the +provided value. +[default: True]

  • +
  • calculate_maxk – Specify whether to perform an internal determination of the highest +spatial frequency needed to accurately render the object being +represented by the InterpolatedImage; often this is useful in choosing +an optimal value for the extent of the Fourier space lookup table. +If you know a priori an appropriate maximum value for maxk, then +you may also supply that here instead of a bool value, in which case +the maxk value is still calculated, but will not go above the +provided value. +[default: True]

  • +
  • use_true_center – Similar to the same parameter in the GSObject.drawImage function, +this sets whether to use the true center of the provided image as the +center of the profile (if use_true_center=True) or the nominal +center given by image.center (if use_true_center=False) +[default: True]

  • +
  • depixelize – Whether to try to remove the effect of the pixelization. If this is +True, then drawing this profile with method=’auto’ should render an +image equivalent to the input image. If this is False (the default), +then you would need to draw with method=’no_pixel’ to get an equivalent +image. See discussion above. [default: False]

  • +
  • offset – The location in the input image to use as the center of the profile. +This should be specified relative to the center of the input image +(either the true center if use_true_center=True, or the nominal +center if use_true_center=False). [default: None]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • hdu – When reading in an Image from a file, this parameter can be used to +select a particular HDU in the file. [default: None]

  • +
  • _force_stepk – Override the normal stepk calculation (using gsparams.folding_threshold) +and force stepk to the given value. [default: 0]

  • +
  • _force_maxk – Override the normal maxk calculation (using gsparams.maxk_threshold) +and force maxk to the given value. This option in particular can help +reduce FFT artifacts in a manner that is currently unobtainable by +lowering maxk_threshold. [default: 0]

  • +
+
+
+
+
+property image
+

The underlying Image being interpolated.

+
+ +
+
+property k_interpolant
+

The Fourier-space Interpolant for this profile.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+

You may either provide a GSParams instance:

+
>>> gsparams = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+>>> obj = obj.withGSParams(gsparams)
+
+
+

or one or more named parameters as keyword arguments:

+
>>> obj = obj.withGSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+
+
+
+

Note

+

The latter style will leave all non-named parameters at their current +values. It only updates the named parameters to the given values.

+
+
+ +
+
+property x_interpolant
+

The real-space Interpolant for this profile.

+
+ +
+ +
+
+galsim._InterpolatedImage(image, x_interpolant=galsim.Quintic(gsparams=galsim.GSParams(128, 8192, 0.005, 5.0, 0.001, 1e-05, 1e-05, 1, 0.0001, 1e-06, 1e-06, 1e-08, 1e-05)), k_interpolant=galsim.Quintic(gsparams=galsim.GSParams(128, 8192, 0.005, 5.0, 0.001, 1e-05, 1e-05, 1, 0.0001, 1e-06, 1e-06, 1e-08, 1e-05)), use_true_center=True, offset=None, gsparams=None, force_stepk=0.0, force_maxk=0.0)[source]
+

Approximately equivalent to InterpolatedImage, but with fewer options and no sanity checks.

+

Some notable reductions in functionality relative to InterpolatedImage:

+
    +
  1. There are no padding options. The image must be provided with all padding already applied.

  2. +
  3. The stepk and maxk values will not be calculated. If you want to use values for these other +than the default, you may provide them as force_stepk and force_maxk. Otherwise +stepk ~= 2pi / image_size and maxk ~= x_interpolant.krange() / pixel_scale.

  4. +
  5. The flux is just the flux of the image. It cannot be rescaled to a different flux value.

  6. +
  7. The input image must have a defined wcs.

  8. +
  9. The image is not recentered to have its center at (0,0). The returned profile will be +centered wherever the (0,0) location is in the image, possibly with an offset governed +by offset and use_true_center. If you want to mimic the behavior of the regular +InterpolatedImage initializer, you can call image.setCenter(0,0) before calling this +function.

  10. +
+
+
Parameters:
+
    +
  • image – The Image from which to construct the object.

  • +
  • x_interpolant – An Interpolant instance for real-space interpolation +[default: Quintic]

  • +
  • k_interpolant – An Interpolant instance for k-space interpolation [default: Quintic]

  • +
  • use_true_center – Whether to adjust the offset by the difference between the integer +center and the true center. For odd-sized images, this does nothing, +but for even-sized dimensions, it adjusts the offset by -0.5. +[default: True]

  • +
  • offset – The location in the input image to use as the center of the profile +relative to position (0,0). [default: None]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • force_stepk – A stepk value to use rather than the default value. [default: 0.]

  • +
  • force_maxk – A maxk value to use rather than the default value. [default: 0.]

  • +
+
+
Returns:
+

an InterpolatedImage instance

+
+
+
+ +
+
+

Interpolated Fourier-space Images

+
+
+class galsim.InterpolatedKImage(kimage=None, k_interpolant=None, stepk=None, gsparams=None, real_kimage=None, imag_kimage=None, real_hdu=None, imag_hdu=None)[source]
+

Bases: GSObject

+

A class describing non-parametric profiles specified by samples of their complex Fourier +transform.

+

The InterpolatedKImage class is useful if you have a non-parametric description of the Fourier +transform of the profile (provided as either a complex Image or two images giving the real +and imaginary parts) that you wish to manipulate / transform using GSObject methods such as +GSObject.shear, GSObject.magnify, GSObject.shift, etc. Note that neither real-space +convolution nor photon-shooting of InterpolatedKImages is currently implemented. Please submit +an issue at http://github.com/GalSim-developers/GalSim/issues if you require either of these +use cases.

+

The images required for creating an InterpolatedKImage are precisely those returned by the +GSObject.drawKImage method. The a and b objects in the following command will +produce essentially equivalent images when drawn with the GSObject.drawImage method:

+
>>> a = returns_a_GSObject()
+>>> b = galsim.InterpolatedKImage(a.drawKImage())
+
+
+

The input kimage must have dtype=numpy.complex64 or dtype=numpy.complex128, which are also +known as ImageCF and ImageCD objects respectively. +The only wcs permitted is a simple PixelScale (or OffsetWCS), in which case kimage.scale +is used for the stepk value unless overridden by the stepk initialization argument.

+

Furthermore, the complex-valued Fourier profile given by kimage must be Hermitian, since it +represents a real-valued real-space profile. (To see an example of valid input to +InterpolatedKImage, you can look at the output of GSObject.drawKImage).

+

The user may optionally specify an interpolant, k_interpolant, for Fourier-space +manipulations (e.g., shearing, resampling). If none is specified, then by default, a Quintic +interpolant is used. The Quintic interpolant has been found to be a good compromise between +speed and accuracy for real-and Fourier-space interpolation of objects specified by samples of +their real-space profiles (e.g., in InterpolatedImage), though no extensive testing has been +performed for objects specified by samples of their Fourier-space profiles (e.g., this +class).

+

Example:

+
>>> interpolated_kimage = galsim.InterpolatedKImage(kimage, k_interpolant=None, stepk=0.,
+                                                    gsparams=None)
+
+
+

Initializes interpolated_kimage as an InterpolatedKImage instance.

+
+
Parameters:
+
    +
  • kimage – The complex Image corresponding to the Fourier-space samples.

  • +
  • k_interpolant – Either an Interpolant instance or a string indicating which k-space +interpolant should be used. Options are ‘nearest’, ‘sinc’, ‘linear’, +‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the integer order +to use. [default: galsim.Quintic()]

  • +
  • stepk – By default, the stepk value (the sampling frequency in Fourier-space) +of the profile is set by the scale attribute of the supplied images. +This keyword allows the user to specify a coarser sampling in Fourier- +space, which may increase efficiency at the expense of decreasing the +separation between neighboring copies of the DFT-rendered real-space +profile. (See the GSParams docstring for the parameter +folding_threshold for more information). [default: kimage.scale]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • real_kimage – Optionally, rather than provide kimage, you may provide the real +and imaginary parts separately. These separate real-valued images +may be strings, in which case they refer to FITS files from which +to read the images. [default: None]

  • +
  • imag_kimage – The imaginary image corresponding to real_kimage. [default: None]

  • +
  • real_hdu – When reading in real_kimage from a file, this parameter can be used to +select a particular HDU in the file. [default: None]

  • +
  • imag_hdu – When reading in imag_kimage from a file, this parameter can be used to +select a particular HDU in the file. [default: None]

  • +
+
+
+
+
+property k_interpolant
+

The Fourier-space Interpolant for this profile.

+
+ +
+
+property kimage
+

The underlying Image being interpolated.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+

You may either provide a GSParams instance:

+
>>> gsparams = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+>>> obj = obj.withGSParams(gsparams)
+
+
+

or one or more named parameters as keyword arguments:

+
>>> obj = obj.withGSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+
+
+
+

Note

+

The latter style will leave all non-named parameters at their current +values. It only updates the named parameters to the given values.

+
+
+ +
+ +
+
+galsim._InterpolatedKImage(kimage, k_interpolant, gsparams)[source]
+

Approximately equivalent to InterpolatedKImage, but with fewer options and no sanity +checks.

+
+
Parameters:
+
    +
  • kimage – The complex Image corresponding to the Fourier-space samples.

  • +
  • k_interpolant – An Interpolant instance indicating which k-space interpolant should be +used.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+ +
+
+

Shapelet Decomposition

+
+
+class galsim.Shapelet(sigma, order, bvec=None, gsparams=None)[source]
+

Bases: GSObject

+

A class describing polar shapelet surface brightness profiles.

+

This class describes an arbitrary profile in terms of a shapelet decomposition. A shapelet +decomposition is an eigenfunction decomposition of a 2-d function using the eigenfunctions +of the 2-d quantum harmonic oscillator. The functions are Laguerre polynomials multiplied +by a Gaussian. See Bernstein & Jarvis, 2002 or Massey & Refregier, 2005 for more detailed +information about this kind of decomposition. For this class, we follow the notation of +Bernstein & Jarvis.

+

The decomposition is described by an overall scale length, sigma, and a vector of +coefficients, b. The b vector is indexed by two values, which can be either (p,q) or +(N,m). In terms of the quantum solution of the 2-d harmonic oscillator, p and q are the number +of quanta with positive and negative angular momentum (respectively). Then, N=p+q, m=p-q.

+

The 2D image is given by (in polar coordinates):

+
+\[I(r,\theta) = \frac{1}{\sigma^2} \sum_{pq} b_{pq} \psi_{pq}(r/\sigma, \theta)\]
+

where \(\psi_{pq}\) are the shapelet eigenfunctions, given by:

+
+\[\psi_pq(r,\theta) = \frac{(-)^q}{\sqrt{\pi}} \sqrt{\frac{q!}{p!}} + r^m \exp(i m \theta) \exp(-r^2/2) L_q^{(m)}(r^2)\]
+

and \(L_q^{(m)}(x)\) are generalized Laguerre polynomials.

+

The coeffients \(b_{pq}\) are in general complex. However, we require that the resulting +\(I(r,\theta)\) be purely real, which implies that \(b_{pq} = b_{qp}^*\) +(where \({}^*\) means complex conjugate). +This further implies that \(b_{pp}\) (i.e. \(b_{pq}\) with \(p==q\)) is real.

+
    +
  1. Make a blank Shapelet instance with all \(b_{pq} = 0.\):

    +
    >>> shapelet = galsim.Shapelet(sigma, order)
    +
    +
    +
  2. +
  3. Make a Shapelet instance using a given vector for the \(b_{pq}\) values.:

    +
    >>> order = 2
    +>>> bvec = [ 1, 0, 0, 0.2, 0.3, -0.1 ]
    +>>> shapelet = galsim.Shapelet(sigma, order, bvec)
    +
    +
    +
  4. +
+

We use the following order for the coefficients, where the subscripts are in terms of p,q.

+

[ b00 Re(b10) Im(b10) Re(b20) Im(b20) b11 Re(b30) Im(b30) Re(b21) Im(b21) … ]

+

i.e. we progressively increase N, and for each value of N, we start with m=N and go down to +m=0 or 1 as appropriate. And since m=0 is intrinsically real, it only requires one spot +in the list.

+
+
Parameters:
+
    +
  • sigma – The scale size in the standard units (usually arcsec).

  • +
  • order – The order of the shapelet decomposition. This is the maximum +N=p+q included in the decomposition.

  • +
  • bvec – The initial vector of coefficients. [default: None, which means to use +all zeros]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property bvec
+

The vector of shapelet coefficients

+
+ +
+
+classmethod fit(sigma, order, image, center=None, normalization='flux', gsparams=None)[source]
+

Fit for a shapelet decomposition of a given image.

+

The optional normalization parameter mirrors the parameter of the InterpolatedImage +class. The following sequence should produce drawn images that are approximate matches to +the original image:

+
>>> image = [...]
+>>> shapelet = galsim.FitShapelet(sigma, order, image, normalization='sb')
+>>> im2 = shapelet.drawImage(image=im2, scale=image.scale, method='sb')
+>>> shapelet = galsim.FitShapelet(sigma, order, image, normalization='flux')
+>>> im3 = shapelet.drawImage(image=im3, scale=image.scale, method='no_pixel')
+
+
+

Then im2 and im3 should be as close as possible to image for the given sigma +and order. Increasing the order can improve the fit, as can having sigma match the +natural scale size of the image. However, it should be noted that some images are not well +fit by a shapelet for any (reasonable) order.

+
+
Parameters:
+
    +
  • sigma – The scale size in the standard units (usually arcsec).

  • +
  • order – The order of the shapelet decomposition. This is the maximum +N=p+q included in the decomposition.

  • +
  • image – The Image for which to fit the shapelet decomposition

  • +
  • center – The position in pixels to use for the center of the decomposition. +[default: image.true_center]

  • +
  • normalization – The normalization to assume for the image. +[default: “flux”]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
Returns:
+

the fitted Shapelet profile

+
+
+
+ +
+
+getNM(N, m)[source]
+

Return the coefficient according to N,m rather than p,q where N=p+q and m=p-q.

+
+
Parameters:
+
    +
  • N – The value of N=p+q to get.

  • +
  • m – The value of m=p-q to get.

  • +
+
+
Returns:
+

a tuple (Re(b_pq), Im(b_pq))

+
+
+
+ +
+
+getPQ(p, q)[source]
+

Return the (p,q) coefficient.

+
+
Parameters:
+
    +
  • p – The p index to get.

  • +
  • q – The q index to get.

  • +
+
+
Returns:
+

a tuple (Re(b_pq), Im(b_pq))

+
+
+
+ +
+
+property order
+

The shapelet order.

+
+ +
+
+property sigma
+

The scale size, sigma.

+
+ +
+
+classmethod size(order)[source]
+

The size of the shapelet vector.

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/bandpass.html b/docs/_build/html/bandpass.html new file mode 100644 index 00000000000..6ce031154fb --- /dev/null +++ b/docs/_build/html/bandpass.html @@ -0,0 +1,391 @@ + + + + + + + Bandpass Filters — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Bandpass Filters

+
+
+class galsim.Bandpass(throughput, wave_type, blue_limit=None, red_limit=None, zeropoint=None, interpolant='linear', _wave_list=None, _tp=None)[source]
+

Simple bandpass object, which models the transmission fraction of incident light as a +function of wavelength, for either an entire optical path (e.g., atmosphere, reflecting and +refracting optics, filters, CCD quantum efficiency), or some intermediate piece thereof. +Bandpasses representing individual components may be combined through the * operator to form +a new Bandpass object representing the composite optical path.

+

Bandpasses are callable, returning dimensionless throughput as a function of wavelength in nm.

+

Bandpasses are immutable; all transformative methods return new Bandpasses, and leave their +originating Bandpasses unaltered.

+

Bandpasses require blue_limit and red_limit attributes, which may either be explicitly +set at initialization, or are inferred from the initializing LookupTable or 2-column file.

+

Outside of the wavelength interval between blue_limit and red_limit, the throughput is +returned as zero, regardless of the throughput input parameter.

+

Bandpasses may be multiplied by other Bandpasses, functions, scalars, or SED instances. +The product of a Bandpass with an SED is a new SED.

+

The Bandpass effective wavelength is stored in the python property effective_wavelength. We +use throughput-weighted average wavelength (which is independent of any SED) as our +definition for effective wavelength.

+

For Bandpasses defined using a LookupTable, a numpy.array of wavelengths, wave_list, +defining the table is maintained. Bandpasses defined as products of two other Bandpasses will +define their wave_list as the union of multiplicand wave_list values, although limited +to the range between the new product blue_limit and red_limit. (This implementation +detail may affect the choice of integrator used to draw a ChromaticObject.)

+

The input parameter, throughput, may be one of several possible forms:

+
    +
  1. a regular python function (or an object that acts like a function)

  2. +
  3. a LookupTable

  4. +
  5. a file from which a LookupTable can be read in

  6. +
  7. a string which can be evaluated to a function of wave via +eval('lambda wave : '+throughput), e.g.:

    +
    throughput = '0.8 + 0.2 * (wave-800)'
    +
    +
    +
  8. +
+

The argument of throughput will be the wavelength in units specified by wave_type. (See +below.) The output should be the dimensionless throughput at that wavelength. (Note we use +wave rather than lambda, since lambda is a python reserved word.)

+

The argument wave_type specifies the units to assume for wavelength and must be one of +‘nm’, ‘nanometer’, ‘nanometers’, ‘A’, ‘Ang’, ‘Angstrom’, or ‘Angstroms’, or an astropy +distance unit. (For the string values, case is unimportant.) If given as floats, blue_limit +and red_limit are taken to be in these units as well. (If given as an astropy Quantity, then +the units are taken directly and converted to wave_type.)

+

Note that the wave_type parameter does not propagate into other methods of Bandpass. +For instance, Bandpass.__call__ assumes its input argument is in nanometers.

+

Finally, a Bandpass may have zeropoint attribute, which is a float used to convert flux +(in photons/s/cm^2) to magnitudes:

+
mag = -2.5*log10(flux) + zeropoint
+
+
+

You can either set the zeropoint at initialization, or via the withZeropoint method. Note +that the zeropoint attribute does not propagate if you get a new Bandpass by multiplying or +dividing an old Bandpass.

+
+
Parameters:
+
    +
  • throughput – Function defining the throughput at each wavelength. See above for +valid options for this parameter.

  • +
  • wave_type – The units to use for the wavelength argument of the throughput +function. See above for details.

  • +
  • blue_limit – Hard cut off of bandpass on the blue side. [default: None, but required +if throughput is not a LookupTable or file. See above.]

  • +
  • red_limit – Hard cut off of bandpass on the red side. [default: None, but required +if throughput is not a LookupTable or file. See above.]

  • +
  • zeropoint – Set the zero-point for this Bandpass. Here, this can only be a float +value. See the method withZeropoint for other options for how to +set this using a particular spectrum (AB, Vega, etc.) [default: None]

  • +
  • interpolant – If reading from a file, what interpolant to use. [default: ‘linear’]

  • +
+
+
+
+
+__call__(wave)[source]
+

Return dimensionless throughput of bandpass at given wavelength in nanometers.

+

Note that outside of the wavelength range defined by the blue_limit and red_limit +attributes, the throughput is assumed to be zero.

+
+
Parameters:
+

wave – Wavelength in nanometers. (Either a scalar or a numpy array)

+
+
Returns:
+

the dimensionless throughput.

+
+
+
+ +
+
+calculateEffectiveWavelength(precise=False)[source]
+

Calculate, store, and return the effective wavelength for this bandpass.

+

We define the effective wavelength as the throughput-weighted average wavelength, which is +SED-independent. Units are nanometers.

+
+
Parameters:
+

precise – Optionally use a more precise integration method when the bandpass uses +a LookupTable rather than the normal trapezoid rule. [default: False]

+
+
+
+ +
+
+property effective_wavelength
+

The effective wavelength of the Bandpass.

+

An alias for self.calculateEffectiveWavelength().

+
+ +
+
+thin(rel_err=0.0001, trim_zeros=True, preserve_range=True, fast_search=True, preserve_zp=True)[source]
+

Thin out the internal wavelengths of a Bandpass that uses a LookupTable.

+

If the bandpass was initialized with a LookupTable or from a file (which internally +creates a LookupTable), this function removes tabulated values while keeping the integral +over the set of tabulated values still accurate to the given relative error.

+

That is, the integral of the bandpass function is preserved to a relative precision +of rel_err, while eliminating as many internal wavelength values as possible. This +process will usually help speed up integrations using this bandpass. You should weigh +the speed improvements against your fidelity requirements for your particular use +case.

+

By default, this routine will preserve the zeropoint of the original bandpass by assigning +it to the new thinned bandpass. The justification for this choice is that when using an AB +zeropoint, a typical optical bandpass, and the default thinning rel_err value, the +zeropoint for the new and thinned bandpasses changes by 10^-6. However, if you are thinning +a lot, and/or want to do extremely precise tests, you can set preserve_zp=False and then +recalculate the zeropoint after thinning.

+
+
Parameters:
+
    +
  • rel_err – The relative error allowed in the integral over the throughput +function. [default: 1.e-4]

  • +
  • trim_zeros – Remove redundant leading and trailing points where f=0? (The last +leading point with f=0 and the first trailing point with f=0 will +be retained). Note that if both trim_leading_zeros and +preserve_range are True, then the only the range of x after +zero trimming is preserved. [default: True]

  • +
  • preserve_range – Should the original range (blue_limit and red_limit) of the +Bandpass be preserved? (True) Or should the ends be trimmed to +include only the region where the integral is significant? (False) +[default: True]

  • +
  • fast_search – If set to True, then the underlying algorithm will use a +relatively fast O(N) algorithm to select points to include in the +thinned approximation. If set to False, then a slower O(N^2) +algorithm will be used. We have found that the slower algorithm +tends to yield a thinned representation that retains fewer samples +while still meeting the relative error requirement, and may also +be somewhat more robust when computing an SED flux through +a Bandpass when a significant fraction of the integrated flux +passes through low throughput bandpass light leaks. +[default: True]

  • +
  • preserve_zp – If True, the new thinned Bandpass will be assigned the same +zeropoint as the original. If False, the new thinned Bandpass +will have a zeropoint of None. [default: True]

  • +
+
+
Returns:
+

the thinned Bandpass.

+
+
+
+ +
+
+truncate(blue_limit=None, red_limit=None, relative_throughput=None, preserve_zp='auto')[source]
+

Return a bandpass with its wavelength range truncated.

+

This function truncate the range of the bandpass either explicitly (with blue_limit or +red_limit or both) or automatically, just trimming off leading and trailing wavelength +ranges where the relative throughput is less than some amount (relative_throughput).

+

This second option using relative_throughput is only available for bandpasses initialized +with a LookupTable or from a file, not when using a regular python function or a string +evaluation.

+

This function does not remove any intermediate wavelength ranges, but see thin() for +a method that can thin out the intermediate values.

+

When truncating a bandpass that already has an assigned zeropoint, there are several +possibilities for what should happen to the new (returned) bandpass by default. If red +and/or blue limits are given, then the new bandpass will have no assigned zeropoint because +it is difficult to predict what should happen if the bandpass is being arbitrarily +truncated. If relative_throughput is given, often corresponding to low-level truncation +that results in little change in observed quantities, then the new bandpass is assigned the +same zeropoint as the original. This default behavior is called ‘auto’. The user can also +give boolean True or False values.

+
+
Parameters:
+
    +
  • blue_limit – Truncate blue side of bandpass at this wavelength in nm. +[default: None]

  • +
  • red_limit – Truncate red side of bandpass at this wavelength in nm. +[default: None]

  • +
  • relative_throughput – Truncate leading or trailing wavelengths that are below +this relative throughput level. (See above for details.) +Either blue_limit and/or red_limit should be supplied, +or relative_throughput should be supplied – but +relative_throughput should not be combined with one of the +limits. +[default: None]

  • +
  • preserve_zp – If True, the new truncated Bandpass will be assigned the same +zeropoint as the original. If False, the new truncated +Bandpass will have a zeropoint of None. If ‘auto’, the new +truncated Bandpass will have the same zeropoint as the +original when truncating using relative_throughput, but +will have a zeropoint of None when truncating using +‘blue_limit’ and/or ‘red_limit’. [default: ‘auto’]

  • +
+
+
Returns:
+

the truncated Bandpass.

+
+
+
+ +
+
+withZeropoint(zeropoint)[source]
+

Assign a zeropoint to this Bandpass.

+

A bandpass zeropoint is a float used to convert flux (in photons/s/cm^2) to magnitudes:

+
mag = -2.5*log10(flux) + zeropoint
+
+
+

Note that the zeropoint attribute does not propagate if you get a new Bandpass by +multiplying or dividing an old Bandpass.

+

The zeropoint argument can take a variety of possible forms:

+
    +
  1. a number, which will be the zeropoint

  2. +
  3. a galsim.SED. In this case, the zeropoint is set such that the magnitude of the +supplied SED through the Bandpass is 0.0

  4. +
  5. the string ‘AB’. In this case, use an AB zeropoint.

  6. +
  7. the string ‘Vega’. Use a Vega zeropoint.

  8. +
  9. the string ‘ST’. Use a HST STmag zeropoint.

  10. +
+
+
Parameters:
+

zeropoint – See above for valid input options

+
+
Returns:
+

new Bandpass with zeropoint set.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/bessel.html b/docs/_build/html/bessel.html new file mode 100644 index 00000000000..823acd159e7 --- /dev/null +++ b/docs/_build/html/bessel.html @@ -0,0 +1,245 @@ + + + + + + + Bessel Functions — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Bessel Functions

+

These are probably not super useful for most users. They should all be equivalent to +the scipy bessel functions. However, in C++ we wanted to avoid dependencies that would +have given us these Bessel functions, so we implemented our own. The Python interface +is mostly to enable unit tests that these C++ function are correct.

+
+
+galsim.bessel.j0(arg0: float) float
+
+ +
+
+galsim.bessel.j1(arg0: float) float
+
+ +
+
+galsim.bessel.jv(arg0: float, arg1: float) float
+
+ +
+
+galsim.bessel.jn()
+

jv(arg0: float, arg1: float) -> float

+
+ +
+
+galsim.bessel.kv(arg0: float, arg1: float) float
+
+ +
+
+galsim.bessel.kn()
+

kv(arg0: float, arg1: float) -> float

+
+ +
+
+galsim.bessel.yv(arg0: float, arg1: float) float
+
+ +
+
+galsim.bessel.yn()
+

yv(arg0: float, arg1: float) -> float

+
+ +
+
+galsim.bessel.iv(arg0: float, arg1: float) float
+
+ +
+
+galsim.bessel.j0_root(arg0: int) float
+
+ +
+
+galsim.bessel.jv_root(arg0: float, arg1: int) float
+
+ +

The next few are not really related to Bessel functions, but they are also exposed +from the C++ layer.

+
+
+galsim.bessel.si(arg0: float) float
+
+ +
+
+galsim.bessel.ci(arg0: float) float
+
+ +
+
+galsim.bessel.sinc(arg0: float) float
+
+ +
+
+galsim.bessel.gammainc(arg0: float, arg1: float) float
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/bounds.html b/docs/_build/html/bounds.html new file mode 100644 index 00000000000..19ca84a065f --- /dev/null +++ b/docs/_build/html/bounds.html @@ -0,0 +1,369 @@ + + + + + + + Bounding boxes — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Bounding boxes

+
+
+class galsim.Bounds[source]
+

A class for representing image bounds as 2D rectangles.

+

Bounds is a base class for two slightly different kinds of bounds: +BoundsD describes bounds with floating point values in x and y. +BoundsI described bounds with integer values in x and y.

+

The bounds are stored as four numbers in each instance, (xmin, xmax, ymin, ymax), with an +additional boolean switch to say whether or not the Bounds rectangle has been defined. The +rectangle is undefined if the min value > the max value in either direction.

+

Initialization:

+

A BoundsI or BoundsD instance can be initialized in a variety of ways. The most direct is +via four scalars:

+
>>> bounds = galsim.BoundsD(xmin, xmax, ymin, ymax)
+>>> bounds = galsim.BoundsI(imin, imax, jmin, jmax)
+
+
+

In the BoundsI example above, imin, imax, jmin and jmax must all be integers +to avoid a TypeError exception.

+

Another way to initialize a Bounds instance is using two Position instances, the first +for (xmin,ymin) and the second for (xmax,ymax):

+
>>> bounds = galsim.BoundsD(galsim.PositionD(xmin, ymin), galsim.PositionD(xmax, ymax))
+>>> bounds = galsim.BoundsI(galsim.PositionI(imin, jmin), galsim.PositionI(imax, jmax))
+
+
+

In both the examples above, the I/D type of PositionI/PositionD must match that of +BoundsI/BoundsD.

+

Finally, there are a two ways to lazily initialize a bounds instance with xmin = xmax, +ymin = ymax, which will have an undefined rectangle and the instance method isDefined() +will return False. The first sets xmin = xmax = ymin = ymax = 0:

+
>>> bounds = galsim.BoundsD()
+>>> bounds = galsim.BoundsI()
+
+
+

The second method sets both upper and lower rectangle bounds to be equal to some position:

+
>>> bounds = galsim.BoundsD(galsim.PositionD(xmin, ymin))
+>>> bounds = galsim.BoundsI(galsim.PositionI(imin, jmin))
+
+
+

Once again, the I/D type of PositionI/PositionD must match that of BoundsI/BoundsD.

+

For the latter two initializations, you would typically then add to the bounds with:

+
>>> bounds += pos1
+>>> bounds += pos2
+>>> [etc.]
+
+
+

Then the bounds will end up as the bounding box of all the positions that were added to it.

+

You can also find the intersection of two bounds with the & operator:

+
>>> overlap = bounds1 & bounds2
+
+
+

This is useful for adding one image to another when part of the first image might fall off +the edge of the other image:

+
>>> overlap = stamp.bounds & image.bounds
+>>> image[overlap] += stamp[overlap]
+
+
+
+
+area()[source]
+

Return the area of the enclosed region.

+

The area is a bit different for integer-type BoundsI and float-type BoundsD instances. +For floating point types, it is simply (xmax-xmin)*(ymax-ymin). However, for integer +types, we add 1 to each size to correctly count the number of pixels being described by the +bounding box.

+
+ +
+
+property center
+

The central position of the Bounds.

+

For a BoundsI, this will return an integer PositionI, which will be above and/or to +the right of the true center if the x or y ranges have an even number of pixels.

+

For a BoundsD, this is equivalent to true_center.

+
+ +
+
+expand(factor_x, factor_y=None)[source]
+

Grow the Bounds by the supplied factor about the center.

+

If two arguments are given, then these are separate x and y factors to +expand by.

+
+ +
+
+getXMax()[source]
+

Get the value of xmax.

+
+ +
+
+getXMin()[source]
+

Get the value of xmin.

+
+ +
+
+getYMax()[source]
+

Get the value of ymax.

+
+ +
+
+getYMin()[source]
+

Get the value of ymin.

+
+ +
+
+includes(*args)[source]
+

Test whether a supplied (x,y) pair, Position, or Bounds lie within a defined +Bounds rectangle of this instance.

+

Examples:

+
>>> bounds = galsim.BoundsD(0., 100., 0., 100.)
+>>> bounds.includes(50., 50.)
+True
+>>> bounds.includes(galsim.PositionD(50., 50.))
+True
+>>> bounds.includes(galsim.BoundsD(-50., -50., 150., 150.))
+False
+
+
+

The type of the PositionI/PositionD and BoundsI/BoundsD instances (i.e. integer or +float type) should match that of the bounds instance.

+
+ +
+
+isDefined()[source]
+

Test whether Bounds rectangle is defined.

+
+ +
+
+property origin
+

The lower left position of the Bounds.

+
+ +
+
+shift(delta)[source]
+

Shift the Bounds instance by a supplied Position.

+

Examples:

+

The shift method takes either a PositionI or PositionD instance, which must match +the type of the Bounds instance:

+
>>> bounds = BoundsI(1,32,1,32)
+>>> bounds = bounds.shift(galsim.PositionI(3, 2))
+>>> bounds = BoundsD(0, 37.4, 0, 49.9)
+>>> bounds = bounds.shift(galsim.PositionD(3.9, 2.1))
+
+
+
+ +
+
+property true_center
+

The central position of the Bounds as a PositionD.

+

This is always (xmax + xmin)/2., (ymax + ymin)/2., even for integer BoundsI, where +this may not necessarily be an integer PositionI.

+
+ +
+
+withBorder(dx, dy=None)[source]
+

Return a new Bounds object that expands the current bounds by the specified width.

+

If two arguments are given, then these are separate dx and dy borders.

+
+ +
+ +
+
+class galsim.BoundsI(*args, **kwargs)[source]
+

Bases: Bounds

+

A Bounds that takes only integer values.

+

Typically used to define the bounding box of an image.

+

See the Bounds doc string for more details.

+
+
+numpyShape()[source]
+

A simple utility function to get the numpy shape that corresponds to this Bounds object.

+
+ +
+ +
+
+class galsim.BoundsD(*args, **kwargs)[source]
+

Bases: Bounds

+

A Bounds that takes floating point values.

+

See the Bounds doc string for more details.

+
+ +
+
+galsim._BoundsI(xmin, xmax, ymin, ymax)[source]
+

Equivalent to BoundsI constructor, but skips some sanity checks and argument parsing. +This requires that the four values be int types.

+
+ +
+
+galsim._BoundsD(xmin, xmax, ymin, ymax)[source]
+

Equivalent to BoundsD constructor, but skips some sanity checks and argument parsing. +This requires that the four values be float types.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/catalog.html b/docs/_build/html/catalog.html new file mode 100644 index 00000000000..bb07e5b5d84 --- /dev/null +++ b/docs/_build/html/catalog.html @@ -0,0 +1,434 @@ + + + + + + + Catalogs and Input Dictionaries — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Catalogs and Input Dictionaries

+
+
+class galsim.Catalog(file_name, dir=None, file_type=None, comments='#', hdu=1)[source]
+

A class storing the data from an input catalog.

+

Each row corresponds to a different object to be built, and each column stores some item of +information about that object (e.g. flux or half_light_radius).

+
+
Parameters:
+
    +
  • file_name – Filename of the input catalog. (Required)

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it.

  • +
  • file_type – Either ‘ASCII’ or ‘FITS’. If None, infer from file_name ending. +[default: None]

  • +
  • comments – The character used to indicate the start of a comment in an +ASCII catalog. [default: ‘#’]

  • +
  • hdu – Which hdu to use for FITS files. [default: 1]

  • +
+
+
+

After construction, the following attributes are available:

+
+
Attributes:
+
    +
  • nobjects – The number of objects in the catalog.

  • +
  • ncols – The number of columns in the catalog.

  • +
  • isfits – Whether the catalog is a fits catalog.

  • +
  • names – For a fits catalog, the valid column names.

  • +
+
+
+
+
+get(index, col)[source]
+

Return the data for the given index and col in its native type.

+

For ASCII catalogs, col is the column number. +For FITS catalogs, col is a string giving the name of the column in the FITS table.

+

Also, for ASCII catalogs, the “native type” is always str. For FITS catalogs, it is +whatever type is specified for each field in the binary table.

+
+ +
+
+getFloat(index, col)[source]
+

Return the data for the given index and col as a float if possible

+
+ +
+
+getInt(index, col)[source]
+

Return the data for the given index and col as an int if possible

+
+ +
+
+readAscii()[source]
+

Read in an input catalog from an ASCII file.

+
+ +
+
+readFits()[source]
+

Read in an input catalog from a FITS file.

+
+ +
+ +
+
+class galsim.OutputCatalog(names, types=None, _rows=(), _sort_keys=())[source]
+

A class for building up a catalog for output, typically storing truth information +about a simulation.

+

Each row corresponds to a different object, and each column stores some item of +information about that object (e.g. flux or half_light_radius).

+

Note: no type checking is done when the data are added in addRow(). It is up to +the user to make sure that the values added for each row are compatible with the +types given here in the types parameter.

+
+
Parameters:
+
    +
  • names – A list of names for the output columns.

  • +
  • types – A list of types for the output columns. [default: None, which assumes all +columns are float]

  • +
+
+
+

After construction, the following attributes are available:

+
+
Attributes:
+
    +
  • nobjects – The number of objects so far in the catalog.

  • +
  • ncols – The number of columns in the catalog.

  • +
  • names – The names of the columns.

  • +
  • types – The types of the columns.

  • +
  • rows – The rows of data that have been accumulated so far.

  • +
+
+
+
+
+addRow(row, sort_key=None)[source]
+

Add a row of data to the catalog.

+

Warning: no type checking is done at this point. If the values in the row do not +match the column types, you may get an error when writing, or you may lose precision, +depending on the nature of the mismatch.

+
+
Parameters:
+
    +
  • row – A list with one item per column in the same order as the names list.

  • +
  • sort_key – If the rows may be added out of order, you can provide a sort_key, +which will be used at the end to re-sort the rows.

  • +
+
+
+
+ +
+
+getNCols()[source]
+

Equivalent to sef.ncols.

+
+ +
+
+getNObjects()[source]
+

Equivalent to sef.nobjects.

+
+ +
+
+getNames()[source]
+

Equivalent to sef.names.

+
+ +
+
+getTypes()[source]
+

Equivalent to sef.types.

+
+ +
+
+makeData()[source]
+

Returns a numpy array of the data as it should be written to an output file.

+
+ +
+
+property ncols
+

The number of columns in the OutputCatalog.

+
+ +
+
+property nobjects
+

The number of objects in the OutputCatalog.

+
+ +
+
+setTypes(types)[source]
+

Equivalent to sef.types = types.

+
+ +
+
+write(file_name, dir=None, file_type=None, prec=8)[source]
+

Write the catalog to a file.

+
+
Parameters:
+
    +
  • file_name – The name of the file to write to.

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • file_type – Which kind of file to write to. [default: determine from the file_name +extension]

  • +
  • prec – Output precision for ASCII. [default: 8]

  • +
+
+
+
+ +
+
+writeAscii(file_name, prec=8)[source]
+

Write catalog to an ASCII file.

+
+
Parameters:
+
    +
  • file_name – The name of the file to write to.

  • +
  • prec – Output precision for floats. [default: 8]

  • +
+
+
+
+ +
+
+writeFits(file_name)[source]
+

Write catalog to a FITS file.

+
+
Parameters:
+

file_name – The name of the file to write to.

+
+
+
+ +
+
+writeFitsHdu()[source]
+

Write catalog to a FITS hdu.

+
+
Returns:
+

an HDU with the FITS binary table of the catalog.

+
+
+
+ +
+ +
+
+class galsim.Dict(file_name, dir=None, file_type=None, key_split='.')[source]
+

A class that reads a python dict from a file.

+

After construction, it behaves like a regular python dict, with one exception. +In order to facilitate getting values in a hierarchy of fields, we allow the ‘.’ +character to chain keys together for the get() method. So,:

+
>>> d.get('noise.properties.variance')
+
+
+

is expanded into:

+
>>> d['noise']['properties']['variance']
+
+
+

Furthermore, if a “key” is really an integer, then it is used as such, which accesses +the corresponding element in a list. e.g.:

+
>>> d.get('noise_models.2.variance')
+
+
+

is equivalent to:

+
>>> d['noise_models'][2]['variance']
+
+
+

This makes it much easier to access arbitrary elements within parameter files.

+

Caveat: The above prescription means that an element whose key really has a ‘.’ in it +won’t be accessed correctly. This is probably a rare occurrence, but the workaround is +to set key_split to a different character or string and use that to chain the keys.

+
+
Parameters:
+
    +
  • file_name – Filename storing the dict.

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • file_type – Options are ‘Pickle’, ‘YAML’, or ‘JSON’ or None. If None, infer from +file_name extension (‘.p*’, ‘.y*’, ‘.j*’ respectively). +[default: None]

  • +
  • key_split – The character (or string) to use to split chained keys. (cf. the +description of this feature above.) [default: ‘.’]

  • +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cd.html b/docs/_build/html/cd.html new file mode 100644 index 00000000000..8d6e152e5c8 --- /dev/null +++ b/docs/_build/html/cd.html @@ -0,0 +1,280 @@ + + + + + + + Charge Deflection Model — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Charge Deflection Model

+

We include in GalSim a basic implementation of the Antilogus charge deflection model. +Probably at this point, there are better implementations of this model, so this might not +be very useful for most users. However, if you just want a quick and dirty way to simulate +the so-called “Brighter-Fatter Effect”, you can use this.

+
+

Note

+

A better implementation of brighter-fatter is now available as part of the SiliconSensor +model. It does not use the Antilogus model at all, but rather tries to simulate the +underlying physics of the effect.

+
+
+
+class galsim.cdmodel.BaseCDModel(a_l, a_r, a_b, a_t)[source]
+

Base class for the most generic, i.e. no with symmetries or distance scaling relationships +assumed, pixel boundary charge deflection model (as per Antilogus et al 2014).

+
+
+__init__(a_l, a_r, a_b, a_t)[source]
+

Initialize a generic CDModel (charge deflection model).

+

Usually this class will not be instantiated directly, but there is nothing to prevent you +from doing so. Each of the input a_l, a_r, a_b & a_t matrices must have the same shape and +be odd-dimensioned.

+

The model implemented here is described in Antilogus et al. (2014). The effective border +of a pixel shifts to an extent proportional to the flux in a pixel at separation (dx,dy) +and a coefficient a(dx,dy). Contributions of all neighbouring pixels are superposed. Border +shifts are calculated for each (l=left, r=right (=positive x), b=bottom, t=top (=pos. y)) +border and the resulting change in flux in a pixel is the shift times the mean of its flux +and the flux in the pixel on the opposite side of the border (caveat: in Antilogus et al. +2014 the sum is used instead of the mean, making the a(dx,dy) a factor of 2 smaller).

+

The parameters of the model are the a_l/r/b/t matrices, whose entry at (dy,dx) gives the +respective shift coefficient. Note that for a realistic model, the matrices have a number +of symmetries, as described in Antilogus et al. (2014). Use derived classes like PowerLawCD +to have a model that automatically fulfills the symmetry conditions.

+

Note that there is a gain factor included in the coefficients. When the a_* are measured +from flat fields according to eqn. 4.10 in Antilogus et. al (2014) and applied to images +that have the same gain as the flats, the correction is as intended. If the gain in the +images is different, this can be accounted for with the gain_ratio parameter when calling +applyForward or applyBackward.

+
+
Parameters:
+
    +
  • a_l – NumPy array containing matrix of deflection coefficients of left pixel border

  • +
  • a_r – NumPy array containing matrix of deflection coefficients of right pixel border

  • +
  • a_b – NumPy array containing matrix of deflection coefficients of bottom pixel border

  • +
  • a_t – NumPy array containing matrix of deflection coefficients of top pixel border

  • +
+
+
+
+ +
+
+applyBackward(image, gain_ratio=1.0)[source]
+

Apply the charge deflection model in the backward direction (accurate to linear order).

+

Returns an image with the backward charge deflection transformation applied. The input +image is not modified, but its WCS is included in the returned image.

+
+
Parameters:
+

gain_ratio – Ratio of gain_image/gain_flat when shift coefficients were derived from +flat fields; default value is 1., which assumes the common case that your +flat and science images have the same gain value

+
+
+
+ +
+
+applyForward(image, gain_ratio=1.0)[source]
+

Apply the charge deflection model in the forward direction.

+

Returns an image with the forward charge deflection transformation applied. The input image +is not modified, but its WCS is included in the returned image.

+
+
Parameters:
+

gain_ratio – Ratio of gain_image/gain_flat when shift coefficients were derived from +flat fields; default value is 1., which assumes the common case that your +flat and science images have the same gain value

+
+
+
+ +
+ +
+
+class galsim.cdmodel.PowerLawCD(n, r0, t0, rx, tx, r, t, alpha)[source]
+

Bases: BaseCDModel

+

Class for parametrizing charge deflection coefficient strengths as a power law in distance +from affected pixel border.

+
+
+__init__(n, r0, t0, rx, tx, r, t, alpha)[source]
+

Initialize a power-law charge deflection model.

+

The deflections from charges in the six pixels directly neighbouring a pixel border are +modelled independently by the parameters r0, t0 (directly adjacent to borders +between two pixels in the same row=y / column=x) and rx, tx (pixels on the corner +of pixel borders).

+

Deflections due to charges further away are modelled as a power-law:

+
a = A * numpy.sin(theta) * (r_distance)**(-alpha)
+
+
+

where A is a power-law amplitude (r for a_l / a_b and t for a_b / a_t), theta is +the angle between the pixel border line and the line from border center to the other pixel +center.

+

Sign conventions are such that positive r0, t0, rx, tx, r, t +correspond to physical deflection of equal charges (this is also how the theta above is +defined).

+
+
Parameters:
+
    +
  • n – Maximum separation [pix] out to which charges contribute to deflection

  • +
  • r0 – a_l(0,-1)=a_r(0,+1) deflection coefficient along x direction

  • +
  • t0 – a_b(-1,0)=a_t(+1,0) deflection coefficient along y direction

  • +
  • rx – a_l(-1,-1)=a_r(+1,+1) diagonal contribution to deflection along x direction

  • +
  • tx – a_b(-1,-1)=a_t(+1,+1) diagonal contribution to deflection along y direction

  • +
  • r – Power-law amplitude for contribution to deflection along x from further away

  • +
  • t – Power-law amplitude for contribution to deflection along y from further away

  • +
  • alpha – Power-law exponent for deflection from further away

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/chromatic.html b/docs/_build/html/chromatic.html new file mode 100644 index 00000000000..0e3f1d6ff30 --- /dev/null +++ b/docs/_build/html/chromatic.html @@ -0,0 +1,212 @@ + + + + + + + Wavelength-dependent Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Wavelength-dependent Profiles

+

Real astronomical objects emit photons at a range of wavelengths according to a potentially +complicated spectral energy distribution (SED). These photons then may be affected differently +by the atmosphere and optics as part of the point-spread function (PSF). Then they typically +pass through a bandpass filter with a variable transmission as a function of wavelength. +Finally, there may be other wavelength-dependent effects when converting from photons to +electrons in the sensor.

+

GalSim supplies a number of tools to simulate these chromatic effects. +An SED is used to define the SED of the objects. There are a variety of options as to the units +of the input SED function; e.g. photons/cm^2/nm/sec, ergs/cm^2/Hz/s, etc. There are also ways +to adjust the normalization of the SED to give a particular observed magnitude when observed +through a particular Bandpass. And there is a dimensionless option, which may be appropriate +for defining chromatic PSFs.

+

The Bandpass class represents a spectral throughput function, which could be an +entire imaging system throughput response function (reflection off of mirrors, transmission through +filters, lenses and the atmosphere, and quantum efficiency of detectors), or individual pieces +thereof. Both a Bandpass and the SED are necessary to compute the relative contribution of +each wavelength of a ChromaticObject to a drawn image.

+

Then there are a number of kinds of ChromaticObject to define the wavelength dependence of an +object’s surface brightness profile. The simplest one is when the spatial and spectral +dependencies are separable; i.e. every part of the profile has the same SED. In this case, +one forms the ChromaticObject simply by multiplying a GSObject by an SED:

+
>>> obj = galsim.Sersic(n=2.3, half_light_radius=3.5)
+>>> sed = galsim.SED('CWW_Sbc_ext.sed', wave_type'Ang', flux_type='flambda')
+>>> chromatic_object = obj * sed
+
+
+

Other more complicated kinds of chromatic profiles are subclasses of ChromaticObject and +have their own initialization arguments. See the listings below.

+

To draw any kind of ChromaticObject, you call its drawImage() +method, which works largely the same as GSObject:drawImage(), but requires a +Bandpass argument to define what bandpass is being used for the observation:

+
>>> gband = galsim.Bandpass(lambda w:1.0, wave_type='nm', blue_limit=410, red_limit=550)
+>>> image = chromatic_obj.drawImage(gband)
+
+
+

The transformation methods of ChromaticObject, like dilate() and +shift(), can also accept as an argument a function of wavelength (in +nanometers) that returns a wavelength-dependent dilation, shift, etc. These can be used to +implement chromatic PSFs. For example, a diffraction limited PSF might look like:

+
>>> psf500 = galsim.Airy(lam_over_diam=2.0)
+>>> chromatic_psf = ChromaticObject(psf500).dilate(lambda w:(w/500)**1.0)
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/chromaticobject.html b/docs/_build/html/chromaticobject.html new file mode 100644 index 00000000000..bc1ec25a0cc --- /dev/null +++ b/docs/_build/html/chromaticobject.html @@ -0,0 +1,2079 @@ + + + + + + + Chromatic Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Chromatic Profiles

+

The ChromaticObject class and its various subclasses Define wavelength-dependent surface +brightness profiles.

+

Implementation is done by constructing GSObject instances as functions of wavelength. +The ChromaticObject.drawImage method then integrates over wavelength while also multiplying by a +throughput function (a galsim.Bandpass instance).

+

These classes can be used to simulate galaxies with color gradients, observe a given galaxy through +different filters, or implement wavelength-dependent point spread functions.

+

So far photon-shooting a ChromaticObject is not implemented, but there is ongoing work to +include this option in GalSim, as the current FFT drawing method is very slow. So these are not +yet particularly useful for large image simulations, especially ones including many faint sources.

+
+
+class galsim.ChromaticObject(obj)[source]
+

Base class for defining wavelength-dependent objects.

+

This class primarily serves as the base class for chromatic subclasses. See the docstrings for +subclasses for more details.

+

A ChromaticObject can be instantiated directly from an existing GSObject. In this case, the +newly created ChromaticObject will act in nearly the same way as the original GSObject works, +except that it has access to the ChromaticObject transformation methods described below (e.g., +expand(), dilate(), shift(), withFlux(), …) These can all take functions as arguments to +describe wavelength-dependent transformations. E.g.,:

+
>>> gsobj = galsim.Gaussian(fwhm=1)
+>>> chrom_obj = galsim.ChromaticObject(gsobj).dilate(lambda wave: (wave/500.)**(-0.2))
+
+
+

In this and similar cases, the argument to the transformation method should be a python callable +that accepts wavelength in nanometers and returns whatever type the transformation method +normally accepts (so an int or float above).

+

One caveat to creating a ChromaticObject directly from a GSObject like this is that even +though the source GSObject instance has flux units in photons/s/cm^2, the newly formed +ChromaticObject will be interpreted as dimensionless, i.e., it will have a dimensionless SED +(and have its .dimensionless attribute set to True). See below for more discussion about the +dimensions of ChromaticObjects.

+

Another way to instantiate a ChromaticObject from a GSObject is to multiply by an SED. +This can be useful to consistently generate the same galaxy observed through different filters, +or, with ChromaticSum, to construct multi-component galaxies, each component with a different +SED. For example, a bulge+disk galaxy could be constructed:

+
>>> bulge_sed = user_function_to_get_bulge_spectrum()
+>>> disk_sed = user_function_to_get_disk_spectrum()
+>>> bulge_mono = galsim.DeVaucouleurs(half_light_radius=1.0)
+>>> disk_mono = galsim.Exponential(half_light_radius=2.0)
+>>> bulge = bulge_mono * bulge_sed
+>>> disk = disk_mono * disk_sed
+>>> gal = bulge + disk
+
+
+

The sed instances above describe the flux density in photons/nm/cm^2/s of an object, possibly +normalized with either the SED.withFlux or SED.withMagnitude methods (see their docstrings +for details about these and other normalization options). Note that for dimensional +consistency, in this case, the flux attribute of the multiplied GSObject is interpreted +as being dimensionless instead of in its normal units of [photons/s/cm^2]. The photons/s/cm^2 +units are (optionally) carried by the SED instead, or even left out entirely if the SED is +dimensionless itself (see discussion on ChromaticObject dimensions below). The GSObject +flux attribute does still contribute to the ChromaticObject normalization, though.

+

For example, the following are equivalent:

+
>>> chrom_obj = (sed * 3.0) * gsobj
+>>> chrom_obj2 = sed * (gsobj * 3.0)
+
+
+

Subclasses that instantiate a ChromaticObject directly, such as ChromaticAtmosphere, also +exist. Even in this case, however, the underlying implementation always eventually wraps one +or more GSObject instances.

+

Dimensions:

+

ChromaticObjects can generally be sorted into two distinct types: those that represent galaxies +or stars and have dimensions of [photons/wavelength-interval/area/time/solid-angle], and those +that represent other types of wavelength dependence besides flux, like chromatic PSFs (these +have dimensions of [1/solid-angle]). The former category of ChromaticObjects will have their +.spectral attribute set to True, while the latter category of ChromaticObjects will have +their .dimensionless attribute set to True. These two classes of ChromaticObjects have +different restrictions associated with them. For example, only spectral ChromaticObjects can +be drawn using drawImage, only ChromaticObjects of the same type can be added together, and +at most one spectral ChromaticObject can be part of a ChromaticConvolution.

+

Multiplying a dimensionless ChromaticObject a spectral SED produces a spectral ChromaticObject +(though note that the new object’s SED may not be equal to the SED being multiplied by since +the original ChromaticObject may not have had unit normalization.)

+

Methods:

+

evaluateAtWavelength returns the monochromatic surface brightness profile (as a GSObject) +at a given wavelength (in nanometers).

+

interpolate can be used for non-separable ChromaticObjects to expedite the image rendering +process. See the docstring of that method for more details and discussion of when this is a +useful tool (and the interplay between interpolation, object transformations, and convolutions).

+

Also, ChromaticObject has most of the same methods as GSObject with the following exceptions:

+

The GSObject access methods (e.g. GSObject.xValue, GSObject.maxk, etc.) are not available. +Instead, you would need to evaluate the profile at a particular wavelength and access what you +want from that.

+

The withFlux, withFluxDensity, and withMagnitude methods will return a new chromatic +object with the appropriate spatially integrated flux, flux density, or magnitude.

+

The drawImage method draws the object as observed through a particular bandpass, so the +arguments are somewhat different. See the docstring of drawImage for more details.

+
+
+__mul__(flux_ratio)[source]
+

Scale the flux of the object by the given flux ratio, which may be an SED, a float, or +a univariate callable function (of wavelength in nanometers) that returns a float.

+

The normalization of a ChromaticObject is tracked through its .sed attribute, which +may have dimensions of either [photons/wavelength-interval/area/time/solid-angle] or +[1/solid-angle].

+

If flux_ratio is a spectral SED (i.e., flux_ratio.spectral==True), then self.sed +must be dimensionless for dimensional consistency. The returned object will have a +spectral sed attribute. On the other hand, if flux_ratio is a dimensionless SED, +float, or univariate callable function, then the returned object will have .spectral +and .dimensionless matching self.spectral and self.dimensionless.

+
+
Parameters:
+

flux_ratio – The factor by which to scale the normalization of the object. +flux_ratio may be a float, univariate callable function, in which +case the argument should be wavelength in nanometers and return value +the flux ratio for that wavelength, or an SED.

+
+
Returns:
+

a new object with scaled flux.

+
+
+
+ +
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Apply the chromatic profile as a convolution to an existing photon array.

+

This method allows instances of this class to duck type as a PhotonOp, so one can apply it +in a photon_ops list.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use to effect the convolution. +[default: None]

  • +
+
+
+
+ +
+
+atRedshift(redshift)[source]
+

Create a version of the current object with a different redshift.

+

This will both adjust the SED to have the given redshift and set a redshift attribute +with the given value.

+
+
Returns:
+

the object with the new redshift

+
+
+
+ +
+
+calculateCentroid(bandpass)[source]
+

Determine the centroid of the wavelength-integrated surface brightness profile.

+
+
Parameters:
+

bandpass – The bandpass through which the observation is made.

+
+
Returns:
+

the centroid of the integrated surface brightness profile, as a PositionD.

+
+
+
+ +
+
+calculateFlux(bandpass)[source]
+

Return the flux (photons/cm^2/s) of the ChromaticObject through a Bandpass bandpass.

+
+
Parameters:
+

bandpass – A Bandpass object representing a filter, or None to compute the bolometric +flux. For the bolometric flux the integration limits will be set to +(0, infinity) unless overridden by non-None SED attributes blue_limit +or red_limit. Note that an SED defined using a LookupTable +automatically has blue_limit and red_limit set.

+
+
Returns:
+

the flux through the bandpass.

+
+
+
+ +
+
+calculateMagnitude(bandpass)[source]
+

Return the ChromaticObject magnitude through a Bandpass bandpass.

+

Note that this requires bandpass to have been assigned a zeropoint using +Bandpass.withZeropoint.

+
+
Parameters:
+

bandpass – A Bandpass object representing a filter, or None to compute the +bolometric magnitude. For the bolometric magnitude the integration +limits will be set to (0, infinity) unless overridden by non-None SED +attributes blue_limit or red_limit. Note that an SED defined +using a LookupTable automatically has blue_limit and red_limit +set.

+
+
Returns:
+

the bandpass magnitude.

+
+
+
+ +
+
+dilate(scale)[source]
+

Dilate the linear size of the profile by the given (possibly wavelength-dependent) +scale, while preserving flux.

+

e.g. half_light_radius <– half_light_radius * scale

+

See expand() and magnify() for versions that preserve surface brightness, and thus +change the flux.

+
+
Parameters:
+

scale – The linear rescaling factor to apply. In addition, scale may be a +callable function, in which case the argument should be wavelength in +nanometers and the return value the scale for that wavelength.

+
+
Returns:
+

the dilated object.

+
+
+
+ +
+
+property dimensionless
+

Boolean indicating if the ChromaticObject is dimensionless.

+
+ +
+
+drawImage(bandpass, image=None, integrator='quadratic', **kwargs)[source]
+

Base implementation for drawing an image of a ChromaticObject.

+

Some subclasses may choose to override this for specific efficiency gains. For instance, +most GalSim use cases will probably finish with a convolution, in which case +ChromaticConvolution.drawImage will be used.

+

The task of drawImage() in a chromatic context is to integrate a chromatic surface +brightness profile multiplied by the throughput of bandpass, over the wavelength +interval indicated by bandpass.

+

Several integrators are available in galsim.integ to do this integration when using the +first method (non-interpolated integration). By default, galsim.integ.SampleIntegrator +will be used if either bandpass.wave_list or self.wave_list have len() > 0.

+

If lengths of both are zero, which may happen if both the bandpass throughput and the SED +associated with self are analytic python functions for example, then +galsim.integ.ContinuousIntegrator will be used instead. This latter case by default will +evaluate the integrand at 250 equally-spaced wavelengths between bandpass.blue_limit +and bandpass.red_limit.

+

By default, the above two integrators will use the rule galsim.integ.quadRule +for integration. The midpoint rule for integration can be specified instead by passing an +integrator that has been initialized with the rule set to galsim.integ.midptRule. +When creating a ContinuousIntegrator, the number of samples N is also an argument. +For example:

+
>>> integrator = galsim.integ.ContinuousIntegrator(rule=galsim.integ.midptRule, N=100)
+>>> image = chromatic_obj.drawImage(bandpass, integrator=integrator)
+
+
+

Finally, this method uses a cache to avoid recomputing the integral over the product of +the bandpass and object SED when possible (i.e., for separable profiles). Because the +cache size is finite, users may find that it is more efficient when drawing many images +to group images using the same SEDs and bandpasses together in order to hit the cache more +often. The default cache size is 10, but may be resized using the +ChromaticObject.resize_multiplier_cache method.

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object representing the filter against which to +integrate.

  • +
  • image – Optionally, the Image to draw onto. (See GSObject.drawImage +for details.) [default: None]

  • +
  • integrator – When doing the exact evaluation of the profile, this argument should +be one of the image integrators from galsim.integ, or a string +‘trapezoidal’, ‘midpoint’, or ‘quadratic’, in which case the routine +will use a SampleIntegrator or ContinuousIntegrator depending on +whether or not the object has a wave_list. [default: ‘quadratic’, +which will try to select an appropriate integrator using the +quadratic integration rule automatically.]

  • +
  • **kwargs – For all other kwarg options, see GSObject.drawImage

  • +
+
+
Returns:
+

the drawn Image.

+
+
+
+ +
+
+drawKImage(bandpass, image=None, integrator='quadratic', **kwargs)[source]
+

Base implementation for drawing the Fourier transform of a ChromaticObject.

+

The task of drawKImage() in a chromatic context is exactly analogous to the task of +drawImage in a chromatic context: to integrate the sed * bandpass weighted Fourier +profiles over wavelength.

+

See drawImage for details on integration options.

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object representing the filter against which to integrate.

  • +
  • image – If provided, this will be the complex Image onto which to draw the +k-space image. If image is None, then an automatically-sized image +will be created. If image is given, but its bounds are undefined, +then it will be resized appropriately based on the profile’s size. +[default: None]

  • +
  • integrator – When doing the exact evaluation of the profile, this argument should be +one of the image integrators from galsim.integ, or a string +‘trapezoidal’, ‘midpoint’, or ‘quadratic’, in which case the routine will +use a SampleIntegrator or ContinuousIntegrator depending on whether or +not the object has a wave_list. [default: ‘quadratic’, which will try +to select an appropriate integrator using the quadratic integration rule +automatically.]

  • +
  • **kwargs – For all other kwarg options, see GSObject.drawKImage.

  • +
+
+
Returns:
+

a complex Image instance (created if necessary)

+
+
+
+ +
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+expand(scale)[source]
+

Expand the linear size of the profile by the given (possibly wavelength-dependent) +scale factor scale, while preserving surface brightness.

+

This doesn’t correspond to either of the normal operations one would typically want to +do to a galaxy. The functions dilate() and magnify() are the more typical usage. But this +function is conceptually simple. It rescales the linear dimension of the profile, while +preserving surface brightness. As a result, the flux will necessarily change as well.

+

See dilate() for a version that applies a linear scale factor while preserving flux.

+

See magnify() for a version that applies a scale factor to the area while preserving surface +brightness.

+
+
Parameters:
+

scale – The factor by which to scale the linear dimension of the object. In +addition, scale may be a callable function, in which case the argument +should be wavelength in nanometers and the return value the scale for that +wavelength.

+
+
Returns:
+

the expanded object

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+interpolate(waves, **kwargs)[source]
+

Build interpolation images to (possibly) speed up subsequent drawImage calls.

+

This method is used as a pre-processing step that can expedite image rendering using objects +that have to be built up as sums of GSObject instances with different parameters at each +wavelength, by interpolating between images at each wavelength instead of making a more +costly instantiation of the relevant GSObject at each value of wavelength at which the +bandpass is defined.

+

This routine does a costly initialization process to build up a grid of Image instances to +be used for the interpolation later on. However, the object can get reused with different +bandpasses, so there should not be any need to make many versions of this object, and there +is a significant savings each time it is drawn into an image.

+

As a general rule of thumb, chromatic objects that are separable do not benefit from this +particular optimization, whereas those that involve making GSObject instances with +wavelength-dependent keywords or transformations do benefit from it.

+

Note that the interpolation scheme is simple linear interpolation in wavelength, and no +extrapolation beyond the originally-provided range of wavelengths is permitted. However, +the overall flux at each wavelength will use the exact SED at that wavelength to give +more accurate final flux values. You can disable this feature by setting +use_exact_sed = False.

+

The speedup involved in using interpolation depends in part on the bandpass used for +rendering (since that determines how many full profile evaluations are involved in rendering +the image). However, for ChromaticAtmosphere with simple profiles like Kolmogorov, the +speedup in some simple example cases is roughly a factor of three, whereas for more +expensive to render profiles like the ChromaticOpticalPSF, the speedup is more typically a +factor of 10-50.

+

Achromatic transformations can be applied either before or after setting up interpolation, +with the best option depending on the application. For example, when rendering many times +with the same achromatic transformation applied, it is typically advantageous to apply the +transformation before setting up the interpolation. But there is no value in this when +applying different achromatic transformation to each object. Chromatic transformations +should be applied before setting up interpolation; attempts to render images of +ChromaticObject instances with interpolation followed by a chromatic transformation will +result in the interpolation being unset and the full calculation being done.

+

Because of the clever way that the ChromaticConvolution routine works, convolutions of +separable chromatic objects with non-separable ones that use interpolation will still +benefit from these optimizations. For example, a non-separable chromatic PSF that uses +interpolation, when convolved with a sum of two separable galaxy components each with their +own SED, will be able to take advantage of this optimization. In contrast, when +convolving two non-separable profiles that already have interpolation set up, there is no +way to take advantage of that interpolation optimization, so it will be ignored and the +full calculation will be done. However, interpolation can be set up for the convolution of +two non-separable profiles, after the convolution step. This could be beneficial for +example when convolving a chromatic optical PSF and chromatic atmosphere, before convolving +with multiple galaxy profiles.

+

For use cases requiring a high level of precision, we recommend a comparison between the +interpolated and the more accurate calculation for at least one case, to ensure that the +required precision has been reached.

+

The input parameter waves determines the input grid on which images are precomputed. It +is difficult to give completely general guidance as to how many wavelengths to choose or how +they should be spaced; some experimentation compared with the exact calculation is warranted +for each particular application. The best choice of settings might depend on how strongly +the parameters of the object depend on wavelength.

+
+
Parameters:
+
    +
  • waves – The list, tuple, or NumPy array of wavelengths to be used when +building up the grid of images for interpolation. The wavelengths +should be given in nanometers, and they should span the full range +of wavelengths covered by any bandpass to be used for drawing an +Image (i.e., this class will not extrapolate beyond the given +range of wavelengths). They can be spaced any way the user likes, +not necessarily linearly, though interpolation will be linear in +wavelength between the specified wavelengths.

  • +
  • oversample_fac – Factor by which to oversample the stored profiles compared to the +default, which is to sample them at the Nyquist frequency for +whichever wavelength has the highest Nyquist frequency. +oversample_fac>1 results in higher accuracy but costlier +pre-computations (more memory and time). [default: 1]

  • +
  • use_exact_sed – If true, then rescale the interpolated image for a given wavelength +by the ratio of the exact SED at that wavelength to the linearly +interpolated SED at that wavelength. Thus, the flux of the +interpolated object should be correct, at the possible expense of +other features. [default: True]

  • +
+
+
Returns:
+

the version of the Chromatic object that uses interpolation +(This will be an InterpolatedChromaticObject instance.)

+
+
+
+ +
+
+lens(g1, g2, mu)[source]
+

Apply a lensing shear and magnification to this object.

+

This ChromaticObject method applies a lensing (reduced) shear and magnification. +The shear must be specified using the g1, g2 definition of shear (see Shear for details). +This is the same definition as the outputs of the PowerSpectrum and NFWHalo classes, +which compute shears according to some lensing power spectrum or lensing by an NFW dark +matter halo. The magnification determines the rescaling factor for the object area and +flux, preserving surface brightness.

+

While gravitational lensing is achromatic, we do allow the parameters g1, g2, and +mu to be callable functions to be parallel to all the other transformations of +chromatic objects. In this case, the functions should take the wavelength in nanometers as +the argument, and the return values are the corresponding value at that wavelength.

+
+
Parameters:
+
    +
  • g1 – First component of lensing (reduced) shear to apply to the object.

  • +
  • g2 – Second component of lensing (reduced) shear to apply to the object.

  • +
  • mu – Lensing magnification to apply to the object. This is the factor by which +the solid angle subtended by the object is magnified, preserving surface +brightness.

  • +
+
+
Returns:
+

the lensed object.

+
+
+
+ +
+
+magnify(mu)[source]
+

Apply a lensing magnification, scaling the area and flux by mu at fixed surface +brightness.

+

This process applies a lensing magnification mu, which scales the linear dimensions of the +image by the factor sqrt(mu), i.e., half_light_radius <– half_light_radius * sqrt(mu) +while increasing the flux by a factor of mu. Thus, magnify() preserves surface +brightness.

+

See dilate() for a version that applies a linear scale factor while preserving flux.

+
+
Parameters:
+

mu – The lensing magnification to apply. In addition, mu may be a callable +function, in which case the argument should be wavelength in nanometers +and the return value the magnification for that wavelength.

+
+
Returns:
+

the magnified object.

+
+
+
+ +
+
+property redshift
+

The redshift of the object.

+
+ +
+
+static resize_multiplier_cache(maxsize)[source]
+

Resize the cache (default size=10) containing the integral over the product of an SED +and a Bandpass, which is used by ChromaticObject.drawImage.

+
+
Parameters:
+

maxsize – The new number of products to cache.

+
+
+
+ +
+
+rotate(theta)[source]
+

Rotate this object by an Angle theta.

+
+
Parameters:
+

theta – Rotation angle (Angle object, +ve anticlockwise). In addition, theta +may be a callable function, in which case the argument should be wavelength +in nanometers and the return value the rotation angle for that wavelength, +returned as a galsim.Angle instance.

+
+
Returns:
+

the rotated object.

+
+
+
+ +
+
+shear(*args, **kwargs)[source]
+

Apply an area-preserving shear to this object, where arguments are either a Shear, +or arguments that will be used to initialize one.

+

For more details about the allowed keyword arguments, see the Shear docstring.

+

The shear() method precisely preserves the area. To include a lensing distortion with +the appropriate change in area, either use shear() with magnify(), or use lens(), which +combines both operations.

+

Note that, while gravitational shear is monochromatic, the shear method may be used for +many other use cases including some which may be wavelength-dependent, such as +intrinsic galaxy shape, telescope dilation, atmospheric PSF shape, etc. Thus, the +shear argument is allowed to be a function of wavelength like other transformations.

+
+
Parameters:
+

shear – The shear to be applied. Or, as described above, you may instead supply +parameters to construct a Shear directly. eg. obj.shear(g1=g1,g2=g2). +In addition, the shear parameter may be a callable function, in which +case the argument should be wavelength in nanometers and the return value +the shear for that wavelength, returned as a galsim.Shear instance.

+
+
Returns:
+

the sheared object.

+
+
+
+ +
+
+shift(*args, **kwargs)[source]
+

Apply a (possibly wavelength-dependent) (dx, dy) shift to this chromatic object.

+

For a wavelength-independent shift, you may supply dx,dy as either two arguments, as a +tuple, or as a PositionD or PositionI object.

+

For a wavelength-dependent shift, you may supply two functions of wavelength in nanometers +which will be interpreted as dx(wave) and dy(wave), or a single function of +wavelength in nanometers that returns either a 2-tuple, PositionD, or PositionI.

+
+
Parameters:
+
    +
  • dx – Horizontal shift to apply (float or function).

  • +
  • dy – Vertical shift to apply (float or function).

  • +
+
+
Returns:
+

the shifted object.

+
+
+
+ +
+
+property spectral
+

Boolean indicating if the ChromaticObject has units compatible with a spectral density.

+
+ +
+
+transform(dudx, dudy, dvdx, dvdy)[source]
+

Apply a transformation to this object defined by an arbitrary Jacobian matrix.

+

This works the same as GSObject.transform, so see that method’s docstring for more +details.

+

As with the other more specific chromatic trasnformations, dudx, dudy, dvdx, and dvdy +may be callable functions, in which case the argument should be wavelength in nanometers +and the return value the appropriate value for that wavelength.

+
+
Parameters:
+
    +
  • dudx – du/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dudy – du/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdx – dv/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdy – dv/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
+
+
Returns:
+

the transformed object.

+
+
+
+ +
+
+withFlux(target_flux, bandpass)[source]
+

Return a new ChromaticObject with flux through the Bandpass bandpass set to +target_flux.

+
+
Parameters:
+
    +
  • target_flux – The desired flux normalization of the ChromaticObject.

  • +
  • bandpass – A Bandpass object defining a filter bandpass.

  • +
+
+
Returns:
+

the new normalized ChromaticObject.

+
+
+
+ +
+
+withFluxDensity(target_flux_density, wavelength)[source]
+

Return a new ChromaticObject with flux density set to target_flux_density at +wavelength wavelength.

+
+
Parameters:
+
    +
  • target_flux_density – The target normalization in photons/nm/cm^2/s.

  • +
  • wavelength – The wavelength, in nm, at which the flux density will be set.

  • +
+
+
Returns:
+

the new normalized SED.

+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+
+withMagnitude(target_magnitude, bandpass)[source]
+

Return a new ChromaticObject with magnitude through bandpass set to +target_magnitude. Note that this requires bandpass to have been assigned a +zeropoint using Bandpass.withZeropoint.

+
+
Parameters:
+
    +
  • target_magnitude – The desired magnitude of the ChromaticObject.

  • +
  • bandpass – A Bandpass object defining a filter bandpass.

  • +
+
+
Returns:
+

the new normalized ChromaticObject.

+
+
+
+ +
+
+withScaledFlux(flux_ratio)[source]
+

Multiply the flux of the object by flux_ratio

+
+
Parameters:
+

flux_ratio – The factor by which to scale the normalization of the object. +flux_ratio may be a float, univariate callable function, in which +case the argument should be wavelength in nanometers and return value +the flux ratio for that wavelength, or an SED.

+
+
Returns:
+

a new object with scaled flux.

+
+
+
+ +
+ +
+
+class galsim.ChromaticAtmosphere(base_obj, base_wavelength, scale_unit=None, **kwargs)[source]
+

Bases: ChromaticObject

+

A ChromaticObject implementing two atmospheric chromatic effects: differential +chromatic refraction (DCR) and wavelength-dependent seeing.

+

Due to DCR, blue photons land closer to the zenith than red photons. Kolmogorov turbulence +also predicts that blue photons get spread out more by the atmosphere than red photons, +specifically FWHM is proportional to wavelength^(-0.2). Both of these effects can be +implemented by wavelength-dependent shifts and dilations.

+

Since DCR depends on the zenith angle and the parallactic angle (which is the position angle of +the zenith measured from North through East) of the object being drawn, these must be specified +via keywords. There are four ways to specify these values:

+
+
    +
  1. explicitly provide zenith_angle as a keyword of type Angle, and +parallactic_angle will be assumed to be 0 by default.

  2. +
  3. explicitly provide both zenith_angle and parallactic_angle as keywords of type +Angle.

  4. +
  5. provide the coordinates of the object obj_coord and the coordinates of the zenith +zenith_coord as keywords of type CelestialCoord.

  6. +
  7. provide the coordinates of the object obj_coord as a CelestialCoord, the hour angle +of the object HA as an Angle, and the latitude of the observer latitude as an +Angle.

  8. +
+
+

DCR also depends on temperature, pressure and water vapor pressure of the atmosphere. The +default values for these are expected to be appropriate for LSST at Cerro Pachon, Chile, but +they are broadly reasonable for most observatories.

+

Note that a ChromaticAtmosphere by itself is NOT the correct thing to use to draw an image of a +star. Stars (and galaxies too, of course) have an SED that is not flat. To draw a real star, +you should either multiply the ChromaticAtmosphere object by an SED, or convolve it with a +point source multiplied by an SED:

+
>>> psf = galsim.ChromaticAtmosphere(...)
+>>> star = galsim.DeltaFunction() * psf_sed
+>>> final_star = galsim.Convolve( [psf, star] )
+>>> final_star.drawImage(bandpass = bp, ...)
+
+
+
+
Parameters:
+
    +
  • base_obj – Fiducial PSF, equal to the monochromatic PSF at base_wavelength

  • +
  • base_wavelength – Wavelength represented by the fiducial PSF, in nanometers.

  • +
  • scale_unit – Units used by base_obj for its linear dimensions. +[default: galsim.arcsec]

  • +
  • alpha – Power law index for wavelength-dependent seeing. [default: -0.2, +the prediction for Kolmogorov turbulence]

  • +
  • zenith_angleAngle from object to zenith [default: 0]

  • +
  • parallactic_angle – Parallactic angle, i.e. the position angle of the zenith, measured +from North through East. [default: 0]

  • +
  • obj_coord – Celestial coordinates of the object being drawn as a +CelestialCoord. [default: None]

  • +
  • zenith_coord – Celestial coordinates of the zenith as a CelestialCoord. +[default: None]

  • +
  • HA – Hour angle of the object as an Angle. [default: None]

  • +
  • latitude – Latitude of the observer as an Angle. [default: None]

  • +
  • pressure – Air pressure in kiloPascals. [default: 69.328 kPa]

  • +
  • temperature – Temperature in Kelvins. [default: 293.15 K]

  • +
  • H2O_pressure – Water vapor pressure in kiloPascals. [default: 1.067 kPa]

  • +
+
+
+
+
+build_obj()[source]
+

Build a ChromaticTransformation object for this ChromaticAtmosphere.

+

We don’t do this right away to help make ChromaticAtmosphere objects be picklable. +Building this is quite fast, so we do it on the fly in evaluateAtWavelength and +ChromaticObject.drawImage.

+
+ +
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticOpticalPSF(lam, diam=None, lam_over_diam=None, aberrations=None, scale_unit=None, gsparams=None, **kwargs)[source]
+

Bases: ChromaticObject

+

A subclass of ChromaticObject meant to represent chromatic optical PSFs.

+

Chromaticity plays two roles in optical PSFs. First, it determines the diffraction limit, via +the wavelength/diameter factor. Second, aberrations such as defocus, coma, etc. are typically +defined in physical distances, but their impact on the PSF depends on their size in units of +wavelength. Other aspects of the optical PSF do not require explicit specification of their +chromaticity, e.g., once the obscuration and struts are specified in units of the aperture +diameter, their chromatic dependence gets taken care of automatically. Note that the +ChromaticOpticalPSF implicitly defines diffraction limits in units of scale_units, which by +default are arcsec, but can in principle be set to any of our GalSim angle units.

+

When using interpolation to speed up image rendering (see the ChromaticObject.interpolate +method for details), the ideal number of wavelengths to use across a given bandpass depends on +the application and accuracy requirements. In general it will be necessary to do a test in +comparison with a more exact calculation to ensure convergence. However, a typical calculation +might use ~10-15 samples across a typical optical bandpass, with oversample_fac in the range +1.5-2; for moderate accuracy, ~5 samples across the bandpass and oversample_fac=1 may +suffice. All of these statements assume that aberrations are not very large (typically <~0.25 +waves, which is commonly satisfied by space telescopes); if they are larger than that, then more +stringent settings are required.

+

Note that a ChromaticOpticalPSF by itself is NOT the correct thing to use to draw an image of a +star. Stars (and galaxies too, of course) have an SED that is not flat. To draw a real star, +you should either multiply the ChromaticOpticalPSF object by an SED, or convolve it with a +point source multiplied by an SED:

+
>>> psf = galsim.ChromaticOpticalPSF(...)
+>>> star = galsim.DeltaFunction() * psf_sed
+>>> final_star = galsim.Convolve( [psf, star] )
+>>> final_star.drawImage(bandpass = bp, ...)
+
+
+
+

Note

+

When geometric_shooting is False (the default), the photon shooting implementation is +only approximately correct with respect to the wavelength dependence. It is also +not particularly fast, since it generates three optical screens to span the wavelength +range and shoots from these (with a subsequent adjustment to improve the accuracy +of this approximation). We expect that most users who want to use photon shooting in +conjunction with this class will prefer to make an InterpolatedChromaticObject +(by calling psf.interpolate(...)), especially if it is a good approximation to +use the same optical PSF for a whole exposure or CCD image, so the setup time for +the interpolation is able to be amortized for many objects.

+
+
+
Parameters:
+
    +
  • lam – Fiducial wavelength for which diffraction limit and aberrations are +initially defined, in nanometers.

  • +
  • diam – Telescope diameter in meters. Either diam or lam_over_diam must be +specified.

  • +
  • lam_over_diam – Ratio of (fiducial wavelength) / telescope diameter in units of +scale_unit. Either diam or lam_over_diam must be specified.

  • +
  • aberrations – An array of aberrations, in units of fiducial wavelength lam. The +size and format of this array is described in the OpticalPSF docstring.

  • +
  • scale_unit – Units used to define the diffraction limit and draw images. +[default: galsim.arcsec]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • geometric_shooting – If True, then when drawing using photon shooting, use geometric +optics approximation where the photon angles are derived from the +phase screen gradient. If False, then first draw using Fourier +optics and then shoot from the derived InterpolatedImage. [default: False]

  • +
  • **kwargs – Any other keyword arguments to be passed to OpticalPSF, for example, +related to struts, obscuration, oversampling, etc. See OpticalPSF +docstring for a complete list of options.

  • +
+
+
+
+
+evaluateAtWavelength(wave)[source]
+

Method to directly instantiate a monochromatic instance of this object.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticAiry(lam, diam=None, lam_over_diam=None, scale_unit=None, gsparams=None, **kwargs)[source]
+

Bases: ChromaticObject

+

A subclass of ChromaticObject meant to represent chromatic Airy profiles.

+

For more information about the basics of Airy profiles, please see galsim.Airy.

+

This class is a chromatic representation of Airy profiles, including the wavelength-dependent +diffraction limit. One can also get this functionality using the ChromaticOpticalPSF class, +but that class includes additional complications beyond a simple Airy profile, and thus has a +more complicated internal representation. For users who only want a (possibly obscured) Airy +profile, the ChromaticAiry class is likely to be a less computationally expensive and more +accurate option.

+
+
Parameters:
+
    +
  • lam – Fiducial wavelength for which diffraction limit is initially defined, in +nanometers.

  • +
  • diam – Telescope diameter in meters. Either diam or lam_over_diam must be +specified.

  • +
  • lam_over_diam – Ratio of (fiducial wavelength) / telescope diameter in units of +scale_unit. Either diam or lam_over_diam must be specified.

  • +
  • scale_unit – Units used to define the diffraction limit and draw images. +[default: galsim.arcsec]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • **kwargs – Any other keyword arguments to be passed to Airy: either flux, or +gsparams. See galsim.Airy docstring for a complete description of these +options.

  • +
+
+
+
+
+evaluateAtWavelength(wave)[source]
+

Method to directly instantiate a monochromatic instance of this object.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticRealGalaxy(real_galaxy_catalogs, index=None, id=None, random=False, rng=None, gsparams=None, logger=None, **kwargs)[source]
+

Bases: ChromaticSum

+

A class describing real galaxies over multiple wavelengths, using some multi-band training +dataset. The underlying implementation models multi-band images of individual galaxies +as chromatic PSF convolutions (and integrations over wavelength) with a sum of profiles +separable into spatial and spectral components. The spectral components are specified by the +user, and the spatial components are determined one Fourier mode at a time by the class. This +decomposition can be thought of as a constrained chromatic deconvolution of the multi-band +images by the associated PSFs, similar in spirit to RealGalaxy.

+

Because ChromaticRealGalaxy involves an InterpolatedKImage, method = 'phot' is unavailable +for the ChromaticObject.drawImage function.

+

Fundamentally, the required inputs for this class are:

+
    +
  1. a series of high resolution input Image instances of a single galaxy in different bands,

  2. +
  3. a list of Bandpass corresponding to those images,

  4. +
  5. the PSFs of those images as either GSObject or ChromaticObject instances, and

  6. +
  7. the noise properties of the input images as BaseCorrelatedNoise instances.

  8. +
+

If you want to specify these inputs directly, that is possible via the makeFromImages factory +method of this class:

+
>>> crg = galsim.ChromaticRealGalaxy.makeFromImages(imgs, bands, PSFs, xis, ...)
+
+
+

Alternatively, you may create a ChromaticRealGalaxy via a list of RealGalaxyCatalog that +correspond to a set of galaxies observed in different bands:

+
>>> crg = galsim.ChromaticRealGalaxy(real_galaxy_catalogs, index=0, ...)
+
+
+

The above will use the 1st object in the catalogs, which should be the same galaxy, just +observed in different bands. Note that there are multiple keywords for choosing a galaxy from +a catalog; exactly one must be set. In the future we may add more such options, e.g., to +choose at random but accounting for the non-constant weight factors (probabilities for +objects to make it into the training sample).

+

The flux normalization of the returned object will by default match the original data, scaled to +correspond to a 1 second HST exposure (though see the area_norm parameter). If you want +a flux appropriate for a longer exposure or telescope with different collecting area, you can +use the ChromaticObject.withScaledFlux method on the returned object, or use the exptime +and area keywords to ChromaticObject.drawImage.

+

Note that while you can also use ChromaticObject.withFlux, ChromaticObject.withMagnitude, +and ChromaticObject.withFluxDensity to set the absolute normalization, these methods +technically adjust the flux of the entire postage stamp image (including noise!) and not +necessarily the flux of the galaxy itself. (These two fluxes will be strongly correlated for +high signal-to-noise ratio galaxies, but may be considerably different at low signal-to-noise +ratio.)

+

Note that ChromaticRealGalaxy objects use arcsec for the units of their linear dimension. If +you are using a different unit for other things (the PSF, WCS, etc.), then you should dilate the +resulting object with gal.dilate(galsim.arcsec / scale_unit).

+

Noise from the original images is propagated by this class, though certain restrictions apply +to when and how that noise is made available. The propagated noise depends on which Bandpass +the ChromaticRealGalaxy is being imaged through, so the noise is only available after the +ChromaticObject.drawImage method has been called. Also, since ChromaticRealGalaxy will +only produce reasonable images when convolved with a (suitably wide) PSF, the noise attribute is +attached to the ChromaticConvolution (or ChromaticTransformation of the +ChromaticConvolution) which holds as one of its convolutants the ChromaticRealGalaxy.:

+
>>> crg = galsim.ChromaticRealGalaxy(...)
+>>> psf = ...
+>>> obj = galsim.Convolve(crg, psf)
+>>> bandpass = galsim.Bandpass(...)
+>>> assert not hasattr(obj, 'noise')
+>>> image = obj.drawImage(bandpass)
+>>> assert hasattr(obj, 'noise')
+>>> noise1 = obj.noise
+
+
+

Note that the noise attribute is only associated with the most recently used bandpass. If you +draw another image of the same object using a different bandpass, the noise object will be +replaced.:

+
>>> bandpass2 = galsim.Bandpass(...)
+>>> image2 = obj.drawImage(bandpass2)
+>>> assert noise1 != obj.noise
+
+
+
+
Parameters:
+
    +
  • real_galaxy_catalogs – A list of RealGalaxyCatalog objects from which to create +ChromaticRealGalaxy objects. Each catalog should represent the +same set of galaxies, and in the same order, just imaged through +different filters.

  • +
  • index – Index of the desired galaxy in the catalog. [One of index, +id, or random is required.]

  • +
  • id – Object ID for the desired galaxy in the catalog. [One of index, +id, or random is required.]

  • +
  • random – If True, then just select a completely random galaxy from the +catalog. [One of index, id, or random is required.]

  • +
  • rng – A random number generator to use for selecting a random galaxy (may +be any kind of BaseDeviate or None) and to use in generating any +noise field when padding.

  • +
  • SEDs – An optional list of SED instances to use when representing real +galaxies as sums of separable profiles. By default, it will use +len(real_galaxy_catalogs) SEDs that are polynomials in +wavelength. Note that if given, len(SEDs) must equal +len(real_galaxy_catalogs). [default: None]

  • +
  • k_interpolant – Either an Interpolant instance or a string indicating which +k-space interpolant should be used. Options are ‘nearest’, ‘sinc’, +‘linear’, ‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the +integer order to use. We strongly recommend leaving this parameter +at its default value; see text above for details. +[default: galsim.Quintic()]

  • +
  • maxk – Optional maxk argument. If you know you will be convolving the +resulting ChromaticRealGalaxy with a “fat” PSF in a subsequent +step, then it can be more efficient to limit the range of Fourier +modes used when solving for the sum of separable profiles below. +[default: None]

  • +
  • pad_factor – Factor by which to internally oversample the Fourier-space images +that represent the ChromaticRealGalaxy (equivalent to zero-padding +the real-space profiles). We strongly recommend leaving this +parameter at its default value; see text in Realgalaxy docstring +for details. [default: 4]

  • +
  • noise_pad_size – If provided, the image will be padded out to this size (in arcsec) +with the noise specified in the real galaxy catalog. This is +important if you are planning to whiten the resulting image. You +should make sure that the padded image is larger than the postage +stamp onto which you are drawing this object. +[default: None]

  • +
  • area_norm – Area in cm^2 by which to normalize the flux of the returned object. +When area_norm=1 (the default), using exptime=1 and area=1 +arguments in ChromaticObject.drawImage (also the default) will +simulate an image with the appropriate number of counts for a 1 +second exposure with the original telescope/camera (e.g., with HST +when using the COSMOS catalog). +If you would rather explicitly specify the collecting area of the +telescope when using ChromaticObject.drawImage with a +ChromaticRealGalaxy, then you should set area_norm equal to the +collecting area of the source catalog telescope when creating the +ChromaticRealGalaxy (e.g., area_norm=45238.93416 for HST). +[default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • logger – A logger object for output of progress statements if the user wants +them. [default: None]

  • +
+
+
+
+
+classmethod makeFromImages(images, bands, PSFs, xis, **kwargs)[source]
+

Create a ChromaticRealGalaxy directly from images, bandpasses, PSFs, and noise +descriptions. See the ChromaticRealGalaxy docstring for more information.

+
+
Parameters:
+
    +
  • images – An iterable of high resolution Image instances of a galaxy +through different bandpasses.

  • +
  • bands – An iterable of Bandpass objects corresponding to the input +images.

  • +
  • PSFs – Either an iterable of GSObject or ChromaticObject indicating +the PSFs of the different input images, or potentially a single +GSObject or ChromaticObject that will be used as the PSF for +all images.

  • +
  • xis – An iterable of BaseCorrelatedNoise objects characterizing the +noise in the input images.

  • +
  • SEDs – An optional list of SED instances to use when representing real +galaxies as sums of separable profiles. By default, it will use +len(images) SEDs that are polynomials in wavelength. Note that +if given, len(SEDs) must equal len(images). [default: None]

  • +
  • k_interpolant – Either an Interpolant instance or a string indicating which +k-space interpolant should be used. Options are ‘nearest’, ‘sinc’, +‘linear’, ‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the +integer order to use. We strongly recommend leaving this parameter +at its default value; see text above for details. [default: +galsim.Quintic()]

  • +
  • maxk – Optional maxk argument. If you know you will be convolving the +resulting ChromaticRealGalaxy with a “fat” PSF in a subsequent +step, then it can be more efficient to limit the range of Fourier +modes used when solving for the sum of separable profiles below. +[default: None]

  • +
  • pad_factor – Factor by which to internally oversample the Fourier-space images +that represent the ChromaticRealGalaxy (equivalent to zero-padding +the real-space profiles). We strongly recommend leaving this +parameter at its default value; see text in Realgalaxy docstring +for details. [default: 4]

  • +
  • noise_pad_size – If provided, the image will be padded out to this size (in arcsec) +with the noise specified in the real galaxy catalog. This is +important if you are planning to whiten the resulting image. You +should make sure that the padded image is larger than the postage +stamp onto which you are drawing this object. +[default: None]

  • +
  • area_norm – Area in cm^2 by which to normalize the flux of the returned object. +When area_norm=1 (the default), using exptime=1 and area=1 +arguments in ChromaticObject.drawImage (also the default) will +simulate an image with the appropriate number of counts for a 1 +second exposure with the original telescope/camera (e.g., with HST +when using the COSMOS catalog). +If you would rather explicitly specify the collecting area of the +telescope when using ChromaticObject.drawImage with a +ChromaticRealGalaxy, then you should set area_norm equal to the +collecting area of the source catalog telescope when creating the +ChromaticRealGalaxy (e.g., area_norm=45238.93416 for HST). +[default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • logger – A logger object for output of progress statements if the user wants +them. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.InterpolatedChromaticObject(original, waves, oversample_fac=1.0, use_exact_sed=True, use_exact_SED=None)[source]
+

Bases: ChromaticObject

+

A ChromaticObject that uses interpolation of predrawn images to speed up subsequent +rendering.

+

This class wraps another ChromaticObject, which is stored in the attribute deinterpolated. +Any ChromaticObject can be used, although the interpolation procedure is most effective +for non-separable objects, which can sometimes be very slow to render.

+

Normally, you would not create an InterpolatedChromaticObject directly. It is the +return type from ChromaticObject.interpolate. See the description of that function +for more details.

+
+
Parameters:
+
    +
  • original – The ChromaticObject to be interpolated.

  • +
  • waves – The list, tuple, or NumPy array of wavelengths to be used when +building up the grid of images for interpolation. The wavelengths +should be given in nanometers, and they should span the full range +of wavelengths covered by any bandpass to be used for drawing an Image +(i.e., this class will not extrapolate beyond the given range of +wavelengths). They can be spaced any way the user likes, not +necessarily linearly, though interpolation will be linear in +wavelength between the specified wavelengths.

  • +
  • oversample_fac – Factor by which to oversample the stored profiles compared to the +default, which is to sample them at the Nyquist frequency for +whichever wavelength has the highest Nyquist frequency. +oversample_fac>1 results in higher accuracy but costlier +pre-computations (more memory and time). [default: 1]

  • +
  • use_exact_sed – If true, then rescale the interpolated image for a given wavelength by +the ratio of the exact SED at that wavelength to the linearly +interpolated SED at that wavelength. Thus, the flux of the interpolated +object should be correct, at the possible expense of other features. +[default: True]

  • +
+
+
+
+
+drawImage(bandpass, image=None, integrator='quadratic', **kwargs)[source]
+

Draw an image as seen through a particular bandpass using the stored interpolated +images at the specified wavelengths.

+

This integration will take place using interpolation between stored images that were +setup when the object was constructed. (See interpolate() for more details.)

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object representing the filter against which to +integrate.

  • +
  • image – Optionally, the Image to draw onto. (See GSObject.drawImage +for details.) [default: None]

  • +
  • integrator – The integration algorithm to use, given as a string. Either +‘midpoint’, ‘trapezoidal’, or ‘quadratic’ is allowed. +[default: ‘quadratic’]

  • +
  • **kwargs – For all other kwarg options, see GSObject.drawImage.

  • +
+
+
Returns:
+

the drawn Image.

+
+
+
+ +
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this ChromaticObject at a particular wavelength using interpolation.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength, as a GSObject.

+
+
+
+ +
+
+classmethod from_images(images, waves, _force_stepk=None, _force_maxk=None, **kwargs)[source]
+

Alternative initiazliation to InterpolatedChromaticObject from input images at specific wavelenghts. Any parameters accepted by +the InterpolatedImage class can be passed in kwargs. Note that stepk and maxk are parameters that can depend on the image size, +and therefore on the wavelength. If not given, as a list for every image, or a single number for all images, it will be caluclated +using the input image pixel scale and dimensions. This means it will be identical for all images. This will cause small differences +from the normal use of this class using chromatic objects whose stepk and maxk are wavelength-dependent. To avoid sanity checks +from method initialization, such as wavelength sorting, pixel scale and image dimensions compatibility, simply call the function +InterpolatedChromaticObject._from_images directly.

+
+
Parameters:
+
    +
  • images – list of Galsim Image objects to use as interpolated images. All images must have the same pixel scale and image +dimensions.

  • +
  • waves – list of wavelength values in nanometers.

  • +
  • _force_stepk – list of step_k values to pass to InterpolatedImages for each image. Can also be single value +to pass for all images alike. If not given stepk is calculated from image pixel scale and dimensions. [Default: None]

  • +
  • _force_maxk – list of max_k values to pass to InterpolatedImages for each image. Can also be single value +to pass for all images alike. If not given maxk is calculated from image pixel scale. [Default: None]

  • +
+
+
Returns:
+

An InterpolatedChromaticObject intialized from input images.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticSum(*args, **kwargs)[source]
+

Bases: ChromaticObject

+

A sum of several ChromaticObject and/or GSObject instances.

+

Any GSObject in the sum is assumed to have a flat SED with spectral density of 1 +photon/s/cm**2/nm.

+

This is the type returned from galsim.Add if any of the objects are a ChromaticObject.

+

Typically, you do not need to construct a ChromaticSum object explicitly. Normally, you +would just use the + operator, which returns a ChromaticSum when used with chromatic objects:

+
>>> bulge = galsim.Sersic(n=3, half_light_radius=0.8) * bulge_sed
+>>> disk = galsim.Exponential(half_light_radius=1.4) * disk_sed
+>>> gal = bulge + disk
+
+
+

You can also use the Add factory function, which returns a ChromaticSum object if any of +the individual objects are chromatic:

+
>>> gal = galsim.Add([bulge,disk])
+
+
+
+
Parameters:
+
    +
  • args – Unnamed args should be a list of objects to add.

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+drawImage(bandpass, image=None, integrator='quadratic', **kwargs)[source]
+

Slightly optimized draw method for ChromaticSum instances.

+

Draws each summand individually and add resulting images together. This might waste time if +two or more summands are separable and have the same SED, and another summand with a +different SED is also added, in which case the summands should be added together first and +the resulting Sum object can then be chromaticized. In general, however, drawing +individual sums independently can help with speed by identifying chromatic profiles that +are separable into spectral and spatial factors.

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object representing the filter against which to +integrate.

  • +
  • image – Optionally, the Image to draw onto. (See GSObject.drawImage +for details.) [default: None]

  • +
  • integrator – When doing the exact evaluation of the profile, this argument should +be one of the image integrators from galsim.integ, or a string +‘trapezoidal’, ‘midpoint’, ‘quadratic’, in which case the routine will +use a SampleIntegrator or ContinuousIntegrator depending on whether +or not the object has a wave_list. [default: ‘quadratic’, +which will try to select an appropriate integrator using the +quadratic integration rule automatically.]

  • +
  • **kwargs – For all other kwarg options, see GSObject.drawImage.

  • +
+
+
Returns:
+

the drawn Image.

+
+
+
+ +
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength wave.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+property obj_list
+

The list of objects being added.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+
+withScaledFlux(flux_ratio)[source]
+

Multiply the flux of the object by flux_ratio

+
+
Parameters:
+

flux_ratio – The factor by which to scale the flux.

+
+
Returns:
+

the object with the new flux.

+
+
+
+ +
+ +
+
+class galsim.ChromaticConvolution(*args, **kwargs)[source]
+

Bases: ChromaticObject

+

A convolution of several ChromaticObject and/or GSObject instances.

+

Any GSObject in the convolution is assumed to have a flat SED with spectral density of 1 +photon/s/cm**2/nm.

+

This is the type returned from galsim.Convolve if any of the objects is a ChromaticObject.

+

The normal way to use this class is to use the Convolve factory function:

+
>>> gal = galsim.Sersic(n, half_light_radius) * galsim.SED(sed_file, 'nm', 'flambda')
+>>> psf = galsim.ChromaticAtmosphere(...)
+>>> final = galsim.Convolve([gal, psf])
+
+
+

The objects to be convolved may be provided either as multiple unnamed arguments (e.g. +Convolve(psf, gal, pix)) or as a list (e.g. Convolve([psf, gal, pix])). Any number of +objects may be provided using either syntax. (Well, the list has to include at least 1 item.)

+
+
Parameters:
+
    +
  • args – Unnamed args should be a list of objects to convolve.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the objects have hard +edges.]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+drawImage(bandpass, image=None, integrator='quadratic', iimult=None, **kwargs)[source]
+

Optimized draw method for the ChromaticConvolution class.

+

Works by finding sums of profiles which include separable portions, which can then be +integrated before doing any convolutions, which are pushed to the end.

+

This method uses a cache to avoid recomputing ‘effective’ profiles, which are the +wavelength-integrated products of inseparable profiles, the spectral components of +separable profiles, and the bandpass. Because the cache size is finite, users may find +that it is more efficient when drawing many images to group images using the same +SEDs, bandpasses, and inseparable profiles (generally PSFs) together in order to hit the +cache more often. The default cache size is 10, but may be resized using the +ChromaticConvolution.resize_effective_prof_cache method.

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object representing the filter against which to +integrate.

  • +
  • image – Optionally, the Image to draw onto. (See GSObject.drawImage +for details.) [default: None]

  • +
  • integrator – When doing the exact evaluation of the profile, this argument should +be one of the image integrators from galsim.integ, or a string +‘trapezoidal’, ‘midpoint’, or ‘quadratic’, in which case the routine +will use a SampleIntegrator or ContinuousIntegrator depending on +whether or not the object has a wave_list. [default: ‘quadratic’, +which will try to select an appropriate integrator using the +quadratic integration rule automatically.]

  • +
  • iimult – Oversample any intermediate InterpolatedImage created to hold +effective profiles by this amount. [default: None]

  • +
  • **kwargs – For all other kwarg options, see GSObject.drawImage.

  • +
+
+
Returns:
+

the drawn Image.

+
+
+
+ +
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength wave.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+property obj_list
+

The list of objects being convolved.

+
+ +
+
+static resize_effective_prof_cache(maxsize)[source]
+

Resize the cache containing effective profiles.

+

These are wavelength-integrated products of separable profile SEDs, inseparable profiles, +and Bandpasses) used by ChromaticConvolution.drawImage.

+
+
Parameters:
+

maxsize – The new number of effective profiles to cache.

+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticDeconvolution(obj, gsparams=None, propagate_gsparams=True)[source]
+

Bases: ChromaticObject

+

A class for deconvolving a ChromaticObject.

+

The ChromaticDeconvolution class represents a wavelength-dependent deconvolution kernel.

+

You may also specify a gsparams argument. See the docstring for GSParams for more +information about this option. Note: if gsparams is unspecified (or None), then the +ChromaticDeconvolution instance inherits the same GSParams as the object being deconvolved.

+

This is the type returned from galsim.Deconvolve if the argument is a ChromaticObject. +This is the normal way to construct this class.

+
+
Parameters:
+
    +
  • obj – The object to deconvolve.

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength wave.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticAutoConvolution(obj, real_space=None, gsparams=None, propagate_gsparams=True)[source]
+

Bases: ChromaticObject

+

A special class for convolving a ChromaticObject with itself.

+

It is equivalent in functionality to galsim.Convolve([obj,obj]), but takes advantage of +the fact that the two profiles are the same for some efficiency gains.

+

This is the type returned from galsim.AutoConvolve if the argument is a ChromaticObject. +This is the normal way to construct this class.

+
+
Parameters:
+
    +
  • obj – The object to be convolved with itself.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the objects have hard +edges.]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength wave.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticAutoCorrelation(obj, real_space=None, gsparams=None, propagate_gsparams=True)[source]
+

Bases: ChromaticObject

+

A special class for correlating a ChromaticObject with itself.

+

It is equivalent in functionality to:

+
galsim.Convolve([obj,obj.rotate(180.*galsim.degrees)])
+
+
+

but takes advantage of the fact that the two profiles are the same for some efficiency gains.

+

This is the type returned from galsim.AutoCorrelate if the argument is a ChromaticObject. +This is the normal way to construct this class.

+
+
Parameters:
+
    +
  • obj – The object to be convolved with itself.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the objects have hard +edges.]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength wave.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticTransformation(obj, jac=None, offset=(0.0, 0.0), flux_ratio=1.0, redshift=None, gsparams=None, propagate_gsparams=True, _redshift=None)[source]
+

Bases: ChromaticObject

+

A class for modeling a wavelength-dependent affine transformation of a ChromaticObject +instance.

+

Typically, you do not need to construct a ChromaticTransformation object explicitly. +This is the type returned by the various transformation methods of ChromaticObject such as +ChromaticObject.shear, ChromaticObject.rotate, ChromaticObject.shift, etc.

+

All the various transformations can be described as a combination of a jacobian matrix +(i.e. ChromaticObject.transform) and a translation (ChromaticObject.shift), which are +described by (dudx,dudy,dvdx,dvdy) and (dx,dy) respectively.

+
+
Parameters:
+
    +
  • obj – The object to be transformed.

  • +
  • jac – A list or tuple (dudx, dudy, dvdx, dvdy), or a numpy.array object +[[dudx, dudy], [dvdx, dvdy]] describing the Jacobian to apply. May +also be a function of wavelength returning a numpy array. +Use None to indicate that the Jacobian is the 2x2 unit matrix. +[default: None]

  • +
  • offset – A galsim.PositionD or list or tuple or numpy array giving the offset +(dx,dy) by which to shift the profile. May also be a function of +wavelength returning a numpy array. [default: None]

  • +
  • flux_ratio – A factor by which to multiply the flux of the object. [default: 1]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+drawImage(bandpass, image=None, integrator='quadratic', **kwargs)[source]
+

See ChromaticObject.drawImage for a full description.

+

This version usually just calls that one, but if the transformed object (self.original) is +an InterpolatedChromaticObject, and the transformation is achromatic, then it will still +be able to use the interpolation.

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object representing the filter against which to +integrate.

  • +
  • image – Optionally, the Image to draw onto. (See GSObject.drawImage +for details.) [default: None]

  • +
  • integrator – When doing the exact evaluation of the profile, this argument should +be one of the image integrators from galsim.integ, or a string +‘trapezoidal’, ‘midpoint’, ‘quadratic’, in which case the routine will +use a SampleIntegrator or ContinuousIntegrator depending on whether +or not the object has a wave_list. [default: ‘quadratic’, +which will try to select an appropriate integrator using the +quadratic integration rule automatically.] +If the object being transformed is an InterpolatedChromaticObject, +then integrator can only be a string, either ‘midpoint’, +‘trapezoidal’, or ‘quadratic’.

  • +
  • **kwargs – For all other kwarg options, see GSObject.drawImage.

  • +
+
+
Returns:
+

the drawn Image.

+
+
+
+ +
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+property original
+

The original object that was transformed.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+
+class galsim.ChromaticFourierSqrtProfile(obj, gsparams=None, propagate_gsparams=True)[source]
+

Bases: ChromaticObject

+

A class for computing the Fourier-space square root of a ChromaticObject.

+

The ChromaticFourierSqrtProfile class represents a wavelength-dependent Fourier-space square +root of a profile.

+

You may also specify a gsparams argument. See the docstring for GSParams for more +information about this option. Note: if gsparams is unspecified (or None), then the +ChromaticFourierSqrtProfile inherits the same GSParams as the object being operated on.

+

The normal way to use this class is to use the FourierSqrt factory function:

+
>>> fourier_sqrt = galsim.FourierSqrt(chromatic_obj)
+
+
+

If chromatic_obj is indeed a ChromaticObject, then that function will create a +ChromaticFourierSqrtProfile object.

+
+
Parameters:
+
    +
  • obj – The object to compute the Fourier-space square root of.

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams for +details. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+evaluateAtWavelength(wave)[source]
+

Evaluate this chromatic object at a particular wavelength wave.

+
+
Parameters:
+

wave – Wavelength in nanometers.

+
+
Returns:
+

the monochromatic object at the given wavelength.

+
+
+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+

Note: if this object wraps other objects (e.g. Convolution, Sum, Transformation, etc.) +those component objects will also have their gsparams updated to the new value.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/composite.html b/docs/_build/html/composite.html new file mode 100644 index 00000000000..ade88b1b1e4 --- /dev/null +++ b/docs/_build/html/composite.html @@ -0,0 +1,526 @@ + + + + + + + Composite Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Composite Profiles

+

The above profiles can be combined in various ways including sums and convolutions.

+
+

Sums of GSObjects

+
+
+class galsim.Sum(*args, **kwargs)[source]
+

Bases: GSObject

+

A class for adding 2 or more GSObject instances.

+

The Sum class is used to represent the sum of multiple GSObject instances. For example, it +might be used to represent a multiple-component galaxy as the sum of an Exponential and a +DeVaucouleurs, or to represent a PSF as the sum of multiple Gaussian objects.

+

Typically, you do not need to construct a Sum object explicitly. Normally, you would just +use the + operator, which returns a Sum:

+
>>> bulge = galsim.Sersic(n=3, half_light_radius=0.8)
+>>> disk = galsim.Exponential(half_light_radius=1.4)
+>>> gal = bulge + disk
+>>> psf = galsim.Gaussian(sigma=0.3, flux=0.3) + galsim.Gaussian(sigma=0.8, flux=0.7)
+
+
+

You can also use the Add() factory function, which returns a Sum object if none of the +individual objects are chromatic:

+
>>> gal = galsim.Add([bulge,disk])
+
+
+
+
Parameters:
+
    +
  • args – Unnamed args should be a list of objects to add.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+

Note: if gsparams is unspecified (or None), then the Sum instance will use the most +restrictive combination of parameters from each of the component objects. Normally, this means +the smallest numerical value (e.g. folding_threshold, xvalue_accuracy, etc.), but for a few +parameters, the largest numerical value is used. See GSParams.combine for details.

+

Furthermore, the gsparams used for the Sum (either given explicitly or derived from the +components) will normally be applied to each of the components. It doesn’t usually make much +sense to apply stricter-than-normal accuracy or threshold values to one component but not +another in a Sum, so this ensures that they all have consistent rendering behavior. +However, if you want to keep the existing gsparams of the component objects (e.g. because +one object is much fainter and can thus use looser accuracy tolerances), then you may +set propagate_gsparams=False.

+
+
+property obj_list
+

The list of objects being added.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of each object being added.

+
+
+ +
+ +
+
+galsim.Add(*args, **kwargs)[source]
+

A function for adding 2 or more GSObject or ChromaticObject instances.

+

This function will inspect its input arguments to decide if a Sum object or a +ChromaticSum object is required to represent the sum of surface brightness profiles.

+

Typically, you do not need to call Add() explicitly. Normally, you would just use the + +operator, which returns a Sum:

+
>>> bulge = galsim.Sersic(n=3, half_light_radius=0.8)
+>>> disk = galsim.Exponential(half_light_radius=1.4)
+>>> gal = bulge + disk
+>>> psf = galsim.Gaussian(sigma=0.3, flux=0.3) + galsim.Gaussian(sigma=0.8, flux=0.7)
+
+
+

If one of the items is chromatic, it will return a ChromaticSum:

+
>>> disk = galsim.Exponential(half_light_radius=1.4) * galsim.SED(sed_file)
+>>> gal = bulge + disk
+
+
+
+
Parameters:
+
    +
  • args – Unnamed args should be a list of objects to add.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

a Sum or ChromaticSum instance as appropriate.

+
+
+
+ +
+
+

Convolutions of GSObjects

+
+
+class galsim.Convolution(*args, **kwargs)[source]
+

Bases: GSObject

+

A class for convolving 2 or more GSObject instances.

+

The convolution will normally be done using discrete Fourier transforms of each of the component +profiles, multiplying them together, and then transforming back to real space.

+

There is also an option to do the convolution as integrals in real space. To do this, use the +optional keyword argument `real_space = True. Currently, the real-space integration is only +enabled for convolving 2 profiles. (Aside from the trivial implementaion for 1 profile.) If +you try to use it for more than 2 profiles, an exception will be raised.

+

The real-space convolution is normally slower than the DFT convolution. The exception is if +both component profiles have hard edges, e.g. a truncated Moffat or Sersic with a Pixel. +In that case, the highest frequency maxk for each component is quite large since the +ringing dies off fairly slowly. So it can be quicker to use real-space convolution instead. +Also, real-space convolution tends to be more accurate in this case as well.

+

If you do not specify either real_space = True or False explicitly, then we check if +there are 2 profiles, both of which have hard edges. In this case, we automatically use +real-space convolution. In all other cases, the default is not to use real-space convolution.

+

The normal way to use this class is to use the Convolve() factory function:

+
>>> gal = galsim.Sersic(n, half_light_radius)
+>>> psf = galsim.Gaussian(sigma)
+>>> final = galsim.Convolve([gal, psf])
+
+
+

The objects to be convolved may be provided either as multiple unnamed arguments (e.g. +Convolve(psf, gal)) or as a list (e.g. Convolve([psf, gal])). Any number of objects may +be provided using either syntax. (Well, the list has to include at least 1 item.)

+
+
Parameters:
+
    +
  • args – Unnamed args should be a list of objects to convolve.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the objects have hard +edges.]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+

Note: if gsparams is unspecified (or None), then the Convolution instance will use the most +restrictive combination of parameters from each of the component objects. Normally, this means +the smallest numerical value (e.g. folding_threshold, xvalue_accuracy, etc.), but for a few +parameters, the largest numerical value is used. See GSParams.combine for details.

+

Furthermore, the gsparams used for the Convolution (either given explicitly or derived from the +components) will normally be applied to each of the components. It doesn’t usually make much +sense to apply stricter-than-normal accuracy or threshold values to one component but not +another in a Convolution, so this ensures that they all have consistent rendering behavior. +However, if you want to keep the existing gsparams of the component objects, then you may +set propagate_gsparams=False.

+
+
+property obj_list
+

The list of objects being convolved.

+
+ +
+
+property real_space
+

Whether this Convolution should be drawn using real-space convolution rather +than FFT convolution.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of each object being convolved.

+
+
+ +
+ +
+
+galsim.Convolve(*args, **kwargs)[source]
+

A function for convolving 2 or more GSObject or ChromaticObject instances.

+

This function will inspect its input arguments to decide if a Convolution object or a +ChromaticConvolution object is required to represent the convolution of surface +brightness profiles.

+
+
Parameters:
+
    +
  • args – Unnamed args should be a list of objects to convolve.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the objects have hard +edges.]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to each of the components. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

a Convolution or ChromaticConvolution instance as appropriate.

+
+
+
+ +
+
+class galsim.AutoConvolution(obj, real_space=None, gsparams=None, propagate_gsparams=True)[source]
+

Bases: Convolution

+

A special class for convolving a GSObject with itself.

+

It is equivalent in functionality to Convolve([obj,obj]), but takes advantage of +the fact that the two profiles are the same for some efficiency gains.

+

The normal way to use this class is to use the AutoConvolve() factory function:

+
>>> psf_sq = galsim.AutoConvolve(psf)
+
+
+
+
Parameters:
+
    +
  • obj – The object to be convolved with itself.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the object has hard +edges.]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the auto-convolved object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+property orig_obj
+

The original object that is being auto-convolved.

+
+ +
+
+property real_space
+

Whether this Convolution should be drawn using real-space convolution rather +than FFT convolution.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of the wrapped component object.

+
+
+ +
+ +
+
+galsim.AutoConvolve(obj, real_space=None, gsparams=None, propagate_gsparams=True)[source]
+

A function for autoconvolving either a GSObject or ChromaticObject.

+

This function will inspect its input argument to decide if a AutoConvolution object or a +ChromaticAutoConvolution object is required to represent the convolution of a surface +brightness profile with itself.

+
+
Parameters:
+
    +
  • obj – The object to be convolved with itself.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the object has hard +edges.]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the auto-convolved object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

a AutoConvolution or ChromaticAutoConvolution instance as appropriate.

+
+
+
+ +
+
+class galsim.AutoCorrelation(obj, real_space=None, gsparams=None, propagate_gsparams=True)[source]
+

Bases: Convolution

+

A special class for correlating a GSObject with itself.

+

It is equivalent in functionality to:

+
galsim.Convolve([obj,obj.createRotated(180.*galsim.degrees)])
+
+
+

but takes advantage of the fact that the two profiles are the same for some efficiency gains.

+

This class is primarily targeted for use by the BaseCorrelatedNoise models when convolving +with a GSObject.

+

The normal way to use this class is to use the AutoCorrelate factory function:

+
>>> psf_sq = galsim.AutoCorrelate(psf)
+
+
+
+
Parameters:
+
    +
  • obj – The object to be convolved with itself.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the object has hard +edges.]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the auto-convorrelated object. +This is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+property orig_obj
+

The original object that is being auto-correlated.

+
+ +
+
+property real_space
+

Whether this Convolution should be drawn using real-space convolution rather +than FFT convolution.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of the wrapped component object.

+
+
+ +
+ +
+
+galsim.AutoCorrelate(obj, real_space=None, gsparams=None, propagate_gsparams=True)[source]
+

A function for autocorrelating either a GSObject or ChromaticObject.

+

This function will inspect its input argument to decide if a AutoCorrelation object or a +ChromaticAutoCorrelation object is required to represent the correlation of a surface +brightness profile with itself.

+
+
Parameters:
+
    +
  • obj – The object to be convolved with itself.

  • +
  • real_space – Whether to use real space convolution. [default: None, which means +to automatically decide this according to whether the object has hard +edges.]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the auto-convorrelated object. +This is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

an AutoCorrelation or ChromaticAutoCorrelation instance as appropriate.

+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config.html b/docs/_build/html/config.html new file mode 100644 index 00000000000..aa05aac9540 --- /dev/null +++ b/docs/_build/html/config.html @@ -0,0 +1,229 @@ + + + + + + + The Config Module — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The Config Module

+

The basic configuration method is to use a dictionary which can be parsed in python. +Within that structure, each field can either be a value, another dictionary which is then +further parsed, or occasionally a list of items (which can be either values or dictionaries). +The hierarchy can go as deep as necessary.

+

Our example config files are all yaml files, which are read using the executable galsim. +This is a nice format for config files, but it is not required. Anything that can represent a +dictionary will do. For example, the executable galsim also reads in and processes json-style +config files if you prefer.

+

If you would like a kind of tutorial that goes through typical uses of the config files, there +are a series of demo config files in the GalSim/examples directory. +See Tutorials for more information.

+

For a concrete example of what a config file looks like, here is +demo1.yaml +(the first file in the aforementioned tutorial) stripped of most of the comments to make it easier +to see the essence of the structure:

+
gal :
+    type : Gaussian
+    sigma : 2  # arcsec
+    flux : 1.e5  # total counts in all pixels
+
+psf :
+    type : Gaussian
+    sigma : 1  # arcsec
+
+image :
+    pixel_scale : 0.2  # arcsec / pixel
+    noise :
+        type : Gaussian
+        sigma : 30  # standard deviation of the counts in each pixel
+
+output :
+    dir : output_yaml
+    file_name : demo1.fits
+
+
+

This file defines a dictionary, which in python would look like:

+
config = {
+    'gal' : {
+        'type' : 'Gaussian',
+        'sigma' : 2.,
+        'flux' : 1.e5
+    },
+    'psf' : {
+        'type' : 'Gaussian',
+        'sigma' : 1.
+    },
+    'image' : {
+        'pixel_scale' : 0.2,
+        'noise' : {
+            'type' : 'Gaussian',
+            'sigma' : 30.
+        }
+    },
+    'output' : {
+        'dir' : 'output_yaml',
+        'file_name' : 'demo1.fits'
+    }
+}
+
+
+

As you can see, there are several top level fields (gal, psf, image, and output) +that define various aspects of the simulation. There are others as well that we will describe +below, but most simulations will want to include at least these four.

+

Most fields have a type item that defines what the other items in the field mean. +(The image and output fields here have implicit types Single and Fits, +which are the default, so may be omitted.) +For instance, a Gaussian surface brightness profile is defined by the parameters +sigma and flux.

+

Most types have some optional items that take reasonable defaults if you omit them. +E.g. the flux is not relevant for a PSF, so it may be omitted in the psf field, in which +case the default of flux=1 is used.

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_galsim.html b/docs/_build/html/config_galsim.html new file mode 100644 index 00000000000..ceacfd71ee3 --- /dev/null +++ b/docs/_build/html/config_galsim.html @@ -0,0 +1,220 @@ + + + + + + + The galsim Executable — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The galsim Executable

+

The normal way to run a GalSim simulation using a config file is galsim config.yaml, where +config.yaml is the name of the config file to be parsed. For instance, to run demo1 (given +above), you would type:

+
galsim demo1.yaml
+
+
+
+

Changing or adding parameters

+

Sometimes it is convenient to be able to change some of the configuration parameters from the +command line, rather than edit the config file. For instance, you might want to make a number +of simulations, which are nearly identical but differ in one or two specific attribute.

+

To enable this, you can provide the changed (or new) parameters on the command line after the +name of the config file. E.g. +to make several simulations that are identical except for the flux of the galaxy and the output +file, one could do:

+
galsim demo1.yaml gal.flux=1.e4 output.file_name=demo1_1e4.fits
+galsim demo1.yaml gal.flux=2.e4 output.file_name=demo1_2e4.fits
+galsim demo1.yaml gal.flux=3.e4 output.file_name=demo1_3e4.fits
+galsim demo1.yaml gal.flux=4.e4 output.file_name=demo1_4e4.fits
+
+
+

Notice that the . is used to separate levels within the config hierarchy. +So gal.flux represents config['gal']['flux'].

+
+
+

Splitting up a config job

+

For large simulations, one will typically want to split the job up into multiple smaller jobs, +each of which can be run on a single node or core. The natural way to split this up is by +parceling some number of output files into each sub-job. We make this splitting very easy using +the command line options -n and -j. The total number of jobs you want should be given +with -n, and each separate job should be given a different -j. So to divide a run across +5 machines, you would run one of the following commands on each of the 5 different machines +(or more typically send these 5 commands as jobs in a queue system):

+
galsim config.yaml -n 5 -j 1
+galsim config.yaml -n 5 -j 2
+galsim config.yaml -n 5 -j 3
+galsim config.yaml -n 5 -j 4
+galsim config.yaml -n 5 -j 5
+
+
+
+
+

Other command line options

+

There are few other command line options that we describe here for completeness.

+
    +
  • -h or --help gives the help message. This is really the definitive information about the +galsim executable, so if that message disagrees with anything here, you should trust that +information over what is written here.

  • +
  • -v {0,1,2,3} or --verbosity {0,1,2,3} sets how verbose the logging output should be. +The default is -v 1, which provides some modest amount of output about each file being built. +-v 2 give more information about the progress within each output file, including one line of +information about each object that is drawn. +-v 3 (debug mode) gives a lot of output and should be reserved for diagnosing runtime problems. +-v 0 turns off all logging output except for error messages.

  • +
  • -l LOG_FILE or --log_file LOG_FILE gives a file name for writing the logging output. If +omitted, the default is to write to stdout.

  • +
  • -f {yaml,json} or --file_type {yaml,json} defines what type of configuration file to parse. +The default is to determine this from the file name extension, so it is not normally needed, +but if you have non-standard file names, you might need to set this. +* -m MODULE or --module MODULE gives a python module to import before parsing the config +file. This has been superseded by the modules top level field, which is normally more +convenient. However, this option is still allowed for backwards compatibility.

  • +
  • -p or --profile turns on profiling information that gets output at the end of the run +(or when multi-processing, at the end of execution of a process). This can be useful for +diagnosing where a simulation is spending most of its computation time.

  • +
  • -n NJOBS or --njobs NJOBS sets the total number of jobs that this run is a part of. Used in conjunction with -j (–job).

  • +
  • -j JOB or --job JOB sets the job number for this particular run. Must be in [1,njobs]. Used in conjunction with -n (–njobs).

  • +
  • -x or --except_abort aborts the whole job whenever any file raises an exception rather than continuing on. (new in version 1.5)

  • +
  • --version shows the version of GalSim.

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_image.html b/docs/_build/html/config_image.html new file mode 100644 index 00000000000..dcb03e9034a --- /dev/null +++ b/docs/_build/html/config_image.html @@ -0,0 +1,1075 @@ + + + + + + + Config Image Field — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Image Field

+

The image field defines some properties about how to draw the images.

+
+

Image Field Attributes

+

Some attributes that are allowed for all image types are:

+
    +
  • pixel_scale = float_value (default = 1.0) The pixel scale, typically taken to be arcsec/pixel. Most size parameters for the profiles are taken to be specified in arcsec. If you would rather specify everything in pixels, just leave off the pixel_scale (or set it to 1.0) and then 1 pixel = 1 arcsec, so everything should work the way you expect. Or if you want all your units to be degrees or radians or something else, then just set this pixel scale in the same units.

  • +
  • sky_level = float_value (default = 0.0; only one of sky_level and sky_level_pixel is allowed) The background level of the image in ADU/arcsec^2

  • +
  • sky_level_pixel = float_value (default = 0.0; only one of sky_level and sky_level_pixel is allowed) The background level of the image in ADU/pixel

  • +
  • index_convention = str_value (default = ‘FITS’) The convention for what to call the lower left pixel of the image. The standard FITS convention is to call this pixel (1,1). However, this can be counter-intuitive to people used to C or python indexing. So if index_convention is ‘C’ or ‘python’ or ‘0’, then the image origin will be considered (0,0) instead. (While unnecessary to specify explicitly since it is the default, the (1,1) convention may be called ‘FITS’, ‘Fortran’ or ‘1’.)

  • +
  • dtype = str_value (default = ‘np.float32’) The data type to use for the output image. Allowed values include all the Python and numpy types that are allowed for an Image.

  • +
  • random_seed = int_value or list (optional) The random seed to use for random numbers in the simulation.

    +
    +
      +
    • The typical use case is that this is a simple integer value. Then, internally we scramble this value in a deterministic way and use a sequence of seeds based on the scrambled value so that the output is deterministic even when using multiple processes to build each image.

    • +
    • If random_seed is a list, then the first one will be converted as described above, but the later ones will not. Rather, it will respect your specification and evaluate the seed for each object as you specifiy. This will create multiple random number generators, according to the multiple seed specifications. This is normally used to have one random number behave normally for noise and such, and another one (or more) repeat with some other cadence (e.g. repeat for each image in an exposure to make sure you generate the same PSFs for multiple CCDs in an exposure). See Demo 7 and Demo 13 for examples of this. Whenever you want to use an rng other than the first one, add rng_num to the field and set it to the number of the rng you want to use in this list.

      +
      +

      Note

      +

      We use 0-based indexing for rng_num, so the first item in the random_seed list is rng_num=0, the second one is rng_num=1, etc.

      +
      +
    • +
    • The default behavior, if random_seed is not given, is to get a seed from the system (/dev/urandom if possible, otherwise based on the time).

    • +
    +
    +
  • +
  • nproc = int_value (default = 1) Specify the number of processors to use when drawing images. If nproc <= 0, then this means to try to automatically figure out the number of cpus and use that.

  • +
  • timeout = float_value (default = 900) Specify the number of seconds to allow for each job when multiprocessing before the multiprocessing queue times out. The default is generally appropriate to prevent jobs from hanging forever from some kind of multiprocessing snafu, but if your jobs are expected to take more than 15 minutes per image, you might need to increase this.

  • +
  • wcs See WCS Field below.

  • +
  • bandpass See Bandpass Field below.

  • +
  • sensor See Sensor Field below.

  • +
+
+
+

Image Types

+

The default image type is ‘Single’, which means that the image contains just a single +postage stamp. Other types are possible (and common) that draw more than one postage stamp +on a full image in different ways. Each type define extra attributes that are either +allowed or required. +The image types defined by GalSim are:

+
    +
  • ‘Single’ The image contains a single object at the center (unless it has been shifted of course – see shift attribute above).

    +
    +
      +
    • size = int_value (optional) If you want square images for each object (common), you just need to set this one value and the images will be size x size. The default is for GalSim to automatically determine a good size for the image that will encompass most of the flux of the object.

    • +
    • xsize = int_value (default = size) If you want non-square images, you can specify xsize and ysize separately instead. It is an error for only one of them to be non-zero.

    • +
    • ysize = int_value (default = size)

    • +
    • world_pos = pos_value The position of the object in world coordinates. For the Single image type, this is unconnected to the object rendering on the image, which is always at the center. However, it may be provided as something that other calculations need to access. e.g. shear from a PowerSpectrum or NFWHalo.

    • +
    • image_pos = pos_value The nominal position on the image at which to center of the object. For the Single image type, the object is always placed as close as possible to the center of the image (unless an explicit offset is specified), but the bounds will be adjusted so that position is equal to image_pos.

    • +
    +
    +
  • +
  • ‘Tiled’ The image consists of a tiled array of postage stamps.

    +
    +
      +
    • nx_tiles = int_value (required)

    • +
    • ny_tiles = int_value (required)

    • +
    • stamp_size = int_value (either stamp_size or both stamp_xsize and stamp_ysize are required) The size of square stamps on which to draw the objects.

    • +
    • stamp_xsize = int_value (either stamp_size or both stamp_xsize and stamp_ysize are required) The xsize of the stamps on which to draw the objects.

    • +
    • stamp_ysize = int_value (either stamp_size or both stamp_xsize and stamp_ysize are required) The ysize of the stamps on which to draw the objects.

    • +
    • border = int_value (default = 0) number of pixels between tiles. Note: the border value may be negative, in which case the tiles will overlap each other.

    • +
    • xborder = int_value (default = border) number of pixels between tiles in the x direction

    • +
    • yborder = int_value (default = border) number of pixels between tiles in the y direction

    • +
    • order = str_value (default = ‘row’) Which order to fill the stamps. ‘row’ means to proceed row by row starting at the bottom row (each row is filled from left to right). ‘column’ means to fill the columns from left to right (each column is filled from bottom to top). ‘random’ means to place the tiles in a random order.

    • +
    +
    +
  • +
  • ‘Scattered’ The image consists of a large contiguous area on which postage stamps of each object are placed at arbitrary positions, possibly overlapping each other (in which case the fluxes are added together for the final pixel value).

    +
    +
      +
    • size = int_value (either size or both xsize and ysize are required)

    • +
    • xsize = int_value (either size or both xsize and ysize are required)

    • +
    • ysize = int_value (either size or both xsize and ysize are required)

    • +
    • nobjects = int_value (default if using an input catalog and the output type is ‘Fits’ is the number of entries in the input catalog; otherwise required)

    • +
    • stamp_size = int_value (optional) The stamp_size attribute works like the size attribute for ‘Single’.

    • +
    • stamp_xsize = int_value (default = stamp_size)

    • +
    • stamp_ysize = int_value (default = stamp_size)

    • +
    • world_pos = pos_value (only one of world_pos and image_pos is allowed) The position in world coordinates relative to the center of the image at which to center of the object.

    • +
    • image_pos = pos_value (only one of world_pos and image_pos is allowed; default if neither is given is to use type ‘XY’ with x = ‘Random’ from 1 .. xsize, y = ‘Random’ from 1 .. ysize) The position on the image at which to center of the object.

    • +
    +
    +
  • +
+
+
+

Custom Image Types

+

To define your own image type, you will need to write an importable Python module +(typically a file in the current directory where you are running galsim, but it could also +be something you have installed in your Python distro) with a class that will be used +to build the image.

+

The class should be a subclass of galsim.config.ImageBuilder, which is the class used for +the default ‘Single’ type. There are a number of class methods, and you only need to override +the ones for which you want different behavior than that of the ‘Single’ type.

+
+
+class galsim.config.ImageBuilder[source]
+

A base class for building full images.

+

The base class defines the call signatures of the methods that any derived class should follow. +It also includes the implementation of the default image type: Single.

+
+
+addNoise(image, config, base, image_num, obj_num, current_var, logger)[source]
+

Add the final noise to the image.

+

In the base class, this is a no op, since it directs the BuildStamp function to build +the noise at that level. But some image types need to do extra work at the end to +add the noise properly.

+
+
Parameters:
+
    +
  • image – The image onto which to add the noise.

  • +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • image_num – The current image number.

  • +
  • obj_num – The first object number in the image.

  • +
  • current_var – The current noise variance in each postage stamps.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
+
+ +
+
+buildBandpass(config, base, image_num, obj_num, logger)[source]
+

If thre is a ‘bandpass’ field in config[‘image’], load it.

+
+
Parameters:
+
    +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • image_num – The current image number.

  • +
  • obj_num – The first object number in the image.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

a gasim.Bandpass or None

+
+
+
+ +
+
+buildImage(config, base, image_num, obj_num, logger)[source]
+

Build an Image based on the parameters in the config dict.

+

For Single, this is just an image consisting of a single postage stamp.

+
+
Parameters:
+
    +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • image_num – The current image number.

  • +
  • obj_num – The first object number in the image.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the final image and the current noise variance in the image as a tuple

+
+
+
+ +
+
+buildSensor(config, base, image_num, obj_num, logger)[source]
+

Build the sensor if given in the config dict.

+
+
Parameters:
+
    +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • image_num – The current image number.

  • +
  • obj_num – The first object number in the image.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

a galsim.Sensor or None

+
+
+
+ +
+
+getNObj(config, base, image_num, logger=None, approx=False)[source]
+

Get the number of objects that will be built for this image.

+

For Single, this is just 1, but other image types would figure this out from the +configuration parameters.

+
+
Parameters:
+
    +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • image_num – The current image number.

  • +
  • logger – If given, a logger object to log progress.

  • +
  • approx – Whether an approximate/overestimate is ok [default: False]

  • +
+
+
Returns:
+

the number of objects

+
+
+
+ +
+
+makeTasks(config, base, jobs, logger)[source]
+

Turn a list of jobs into a list of tasks.

+

Each task is performed separately in multi-processing runs, so this provides a mechanism +to have multiple jobs depend on each other without being messed up by multi-processing. +E.g. you could have blends where each task consists of building several overlapping +galaxies (each of which would be a single job). Perhaps the first job would include +a calculation to determine where all the overlapping galaxies should go, and the later +jobs would use the results of this calculation and just place the later galaxies in the +appropriate place.

+

Normally, though, each task is just a single job, in which case, this function is very +simple.

+

For Single, this passes the job onto the MakeStampTasks function (which in turn is +normally quite simple). Most other types though probably want one job per task, for which +the appropriate code would be:

+
+

return [ [ (job, k) ] for k, job in enumerate(jobs) ]

+
+
+
Parameters:
+
    +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • jobs – A list of jobs to split up into tasks. Each job in the list is a +dict of parameters that includes ‘image_num’ and ‘obj_num’.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

a list of tasks

+
+
+
+ +
+
+setup(config, base, image_num, obj_num, ignore, logger)[source]
+

Do the initialization and setup for building the image.

+

This figures out the size that the image will be, but doesn’t actually build it yet.

+
+
Parameters:
+
    +
  • config – The configuration dict for the image field.

  • +
  • base – The base configuration dict.

  • +
  • image_num – The current image number.

  • +
  • obj_num – The first object number in the image.

  • +
  • ignore – A list of parameters that are allowed to be in config that we can +ignore here. i.e. it won’t be an error if these parameters are present.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

xsize, ysize

+
+
+
+ +
+ +

The base parameter is the original full configuration dict that is being used for running the +simulation. The config parameter is the local portion of the full dict that defines the image +being built, which would typically be base['image'].

+

Then, in the Python module, you need to register this function with some type name, which will +be the value of the type attribute that triggers the use of this Builder object:

+
galsim.config.RegisterImageType('CustomImage', CustomImageLoader())
+
+
+
+
+galsim.config.RegisterImageType(image_type, builder)[source]
+

Register an image type for use by the config apparatus.

+
+
Parameters:
+
    +
  • image_type – The name of the type in config[‘image’]

  • +
  • builder – A builder object to use for building the images. It should be an +instance of ImageBuilder or a subclass thereof.

  • +
+
+
+
+ +

Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple image types use the same class instantiated with different +initialization parameters. This is not used by the GalSim image types, but there may be use +cases where it would be useful for custom image types.

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_image.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_image
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_image, +which will read your file and execute the registration command to add the builder to the list +of valid image types.

+

Then you can use this as a valid image type:

+
image:
+    type: CustomImage
+    ...
+
+
+

We don’t currently have any examples of custom images, but it may be helpful to look at the GalSim +implementation of the included image types (click on the [source] links):

+
+
+class galsim.config.image_scattered.ScatteredImageBuilder[source]
+

Bases: ImageBuilder

+
+ +
+
+class galsim.config.image_tiled.TiledImageBuilder[source]
+

Bases: ImageBuilder

+
+ +
+
+

Noise

+

Typically, you will want to add noise to the image. The noise attribute should be a dict +with a type attribute to define what kind of noise should be added. The noise types +that are defined by GalSim are:

+
    +
  • ‘Gaussian’ is the simplest kind of noise. Just Gaussian noise across the whole image with a given sigma (or variance).

    +
    +
      +
    • sigma = float_value (either sigma or variance is required) The rms of the noise in ADU.

    • +
    • variance = float_value (either sigma or variance is required) The variance of the noise in ADU^2.

    • +
    +
    +
  • +
  • ‘Poisson’ adds Poisson noise for the flux value in each pixel, with an optional sky background level. This is the default noise if you don’t specify a different noise type.

    +
    +
      +
    • sky_level = float_value (default = 0.0) The sky level in ADU/arcsec^2 to use for the noise. If both this and image.sky_level are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be image.sky_level.

    • +
    • sky_level_pixel = float_value (default = 0.0) The sky level in ADU/pixel to use for the noise. If both this and image.sky_level_pixel are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be image.sky_level_pixel.

    • +
    +
    +
  • +
  • ‘CCD’ includes both Poisson noise for the flux value in each pixel (with an optional gain) and an optional Gaussian read noise.

    +
    +
      +
    • sky_level = float_value (default = 0.0) The sky level in ADU/arcsec^2 to use for the noise. If both this and image.sky_level are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be image.sky_level.

    • +
    • sky_level_pixel = float_value (default = 0.0) The sky level in ADU/pixel to use for the noise. If both this and image.sky_level_pixel are provided, then they will be added together for the purpose of the noise, but the background level in the final image will just be image.sky_level_pixel.

    • +
    • gain = float_value (default = 1.0) The CCD gain in e-/ADU.

    • +
    • read_noise = float_value (default = 0.0) The CCD read noise in e-.

    • +
    +
    +
  • +
  • ‘COSMOS’ provides spatially correlated noise of the sort found in the F814W HST COSMOS science images described by Leauthaud et al (2007). The point variance (given by the zero distance correlation function value) may be normalized by the user as required, as well as the dimensions of the correlation function.

    +
    +
      +
    • file_name = str_value (optional) The path and filename of the FITS file containing the correlation function data used to generate the COSMOS noise field. The default is to use the file packaged with GalSim as ‘share/acs_I_unrot_sci_20_cf.fits’, but this option lets you override this if desired.

    • +
    • cosmos_scale = float_value (default = 0.03) The ACS coadd images in COSMOS have a pixel scale of 0.03 arcsec, and so the pixel scale cosmos_scale adopted in the representation of of the correlation function takes a default value of 0.03. If you wish to use other units ensure that cosmos_scale takes the value corresponding to 0.03 arcsec in your chosen system.

    • +
    • variance = float_value (default = 0.) Scale the point variance of the noise field to the desired value, equivalent to scaling the correlation function to have this value at zero separation distance. Choosing the default scaling of 0. uses the variance in the original COSMOS noise fields.

    • +
    +
    +
  • +
  • ‘Correlated’ is a more general version of COSMOS, allowing any arbitrary correlated noise spectrum to be used. It requires the user to provide the appropriate file giving an image of the correlation function. See BaseCorrelatedNoise.from_file for details about how this file must look.

    +
    +
      +
    • file_name = str_value (required) The path and filename of the FITS file containing the correlation function data used to generate the Correlated noise field.

    • +
    • pixel_scale = float_value (required) The pixel scale of the original image data from which the correlated noise was constructed.

    • +
    • variance = float_value (default = 0.) Scale the point variance of the noise field to the desired value, equivalent to scaling the correlation function to have this value at zero separation distance. Choosing the default scaling of 0. uses the variance in the file without modification.

    • +
    +
    +
  • +
+

The noise field can also take the following attributes, which are relevant when using +object types that have some intrinsic noise already, such as ‘RealGalaxy’:

+
    +
  • whiten = bool_value (default = False) Whether or not a noise-whitening procedure should be done on the image after it is drawn to make the noise uncorrelated (white noise). This is only relevant when using the gal type ‘RealGalaxy’. Note: After the whitening process, there is white Gaussian noise in the image. We subtract this much noise from the variance of whatever is given in the image.noise field. However, unless this is type = 'Gaussian', the final noise field will not precisely match what you request. e.g. ‘Poisson’ noise would have a portion of the variance be Gaussian rather than Poisson. This probably does not matter in most cases, but if you are whitening, the most coherent noise profile is ‘Gaussian’, since that works seamlessly.

  • +
  • symmetrize = int_value (default = None) The order at which to impose N-fold symmetry on the noise in the image after it is drawn, after which there will be correlated Gaussian noise with the desired symmetry in the image (usually much less than must be added to achieve a fully white noise field). Similar caveats apply to this option as to the white option.

  • +
+

In addition to the above, you may also define your own custom noise type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of galsim.config.NoiseBuilder. This is really an abstract +base class. At least the first two of these methods need to be overridden:

+
+
+class galsim.config.NoiseBuilder[source]
+

A base class for building noise objects and applying the noise to images.

+

The base class doesn’t do anything, but it defines the call signatures of the methods +that derived classes should use for the different specific noise types.

+
+
+addNoise(config, base, im, rng, current_var, draw_method, logger)[source]
+

Read the noise parameters from the config dict and add the appropriate noise to the +given image.

+
+
Parameters:
+
    +
  • config – The configuration dict for the noise field.

  • +
  • base – The base configuration dict.

  • +
  • im – The image onto which to add the noise

  • +
  • rng – The random number generator to use for adding the noise.

  • +
  • current_var – The current noise variance present in the image already.

  • +
  • draw_method – The method that was used to draw the objects on the image.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the variance of the noise model (units are ADU if gain != 1)

+
+
+
+ +
+
+addNoiseVariance(config, base, im, include_obj_var, logger)[source]
+

Read the noise parameters from the config dict and add the appropriate noise variance +to the given image.

+

This is used for constructing the weight map iamge. It doesn’t add a random value to +each pixel. Rather, it adds the variance of the noise that was used in the main image to +each pixel in this image.

+

This method has a default implemenation that is appropriate for noise models that have +a constant noise variance. It just gets the variance from getNoiseVariance and adds +that constant value to every pixel.

+
+
Parameters:
+
    +
  • config – The configuration dict for the noise field.

  • +
  • base – The base configuration dict.

  • +
  • im – The image onto which to add the noise variance

  • +
  • include_obj_var – Whether the noise variance values should the photon noise from +object flux in addition to the sky flux. Only relevant for +noise models that are based on the image flux values such as +Poisson and CCDNoise.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
+
+ +
+
+getNoiseVariance(config, base, full=False)[source]
+

Read the noise parameters from the config dict and return the variance.

+
+
Parameters:
+
    +
  • config – The configuration dict for the noise field.

  • +
  • base – The base configuration dict.

  • +
  • full – If the noise is variable across the image, return the full image with the +noise variance at every pixel. Otherwise, just return the value at the +center.

  • +
+
+
Returns:
+

the variance of the noise model (units are ADU if gain != 1)

+
+
+
+ +
+ +

Then, as usual, you need to register this type using:

+
galsim.config.RegisterNoiseType('CustomNoise', CustomNoiseBuilder())
+
+
+
+
+galsim.config.RegisterNoiseType(noise_type, builder, input_type=None)[source]
+

Register a noise type for use by the config apparatus.

+
+
Parameters:
+
    +
  • noise_type – The name of the type in config[‘image’][‘noise’]

  • +
  • builder – A builder object to use for building the noise. It should be an +instance of a subclass of NoiseBuilder.

  • +
  • input_type – If the builder utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

and tell the config parser the name of the module to load at the start of processing.

+
modules:
+    - my_custom_noise
+
+
+

Then you can use this as a valid noise type:

+
image:
+    noise:
+        type: CustomNoise
+        ...
+
+
+

We don’t currently have any examples of custom noise types, but it may be helpful to look at the GalSim implementation of the various included noise types (click on the [source] links:

+
+
+class galsim.config.GaussianNoiseBuilder[source]
+

Bases: NoiseBuilder

+
+ +
+
+class galsim.config.PoissonNoiseBuilder[source]
+

Bases: NoiseBuilder

+
+ +
+
+class galsim.config.CCDNoiseBuilder[source]
+

Bases: NoiseBuilder

+
+ +
+
+class galsim.config.COSMOSNoiseBuilder[source]
+

Bases: CorrelatedNoiseBuilder

+
+ +
+
+

WCS Field

+

The pixel_scale attribute mentioned above is the usual way to define the connection between +pixel coordinates and sky coordinates. However, one can define a more complicated relationship, +which is known as a World Coordinate System (WCS) if desired. To do this, use the wcs +attribute instead of the pixel_scale attribute. This should be a dict with a type +attribute that defines what kind of WCS to use. The wcs types that are defined by GalSim are:

+
    +
  • ‘PixelScale’ implements a regular square pixel grid. If you do not specify any wcs item, this is what will be used, and the scale will be the image.pixel_scale value.

    +
    +
      +
    • scale = float_value (default = image.pixel_scale) The scale size of the pixels. The area is scale * scale.

    • +
    +
    +
  • +
  • ‘Shear’ implements a uniform shear of a regular square pixel grid. After the shear, the pixel area will still be scale * scale, but they will be parallelograms (rhombi actually) rather than squares.

    +
    +
      +
    • scale = float_value (required) The pixel scale of the grid before being sheared.

    • +
    • shear = shear_value (required) The shear to apply.

    • +
    +
    +
  • +
  • ‘Jacobian’ or ‘Affine’ implements an arbitrary affine transform. This is the most general WCS that has a uniform pixel shape. The world (u,v) coordinates are linearly related to the image (i.e. pixel) (x,y) coordinates.

    +
    +
      +
    • dudx = float_value (required) du/dx

    • +
    • dudy = float_value (required) du/dy

    • +
    • dvdx = float_value (required) dv/dx

    • +
    • dvdy = float_value (required) dv/dy

    • +
    +
    +
  • +
  • ‘UVFunction’ implements an arbitrary transformation from image coordinates (x,y) to world coordinates (u,v) via two functions u(x,y) and v(x,y). You can also provide the inverse functions x(u,v) and y(u,v). They are not required, but if they are not given, then positions of objects cannot be given in world coordinates via image.world_pos.

    +
    +
      +
    • ufunc = str_value (required) A string that can be turned into the function u(x,y) via the python command eval('lambda x,y : ' + ufunc).

    • +
    • vfunc = str_value (required) A string that can be turned into the function v(x,y) via the python command eval('lambda x,y : ' + vfunc).

    • +
    • xfunc = str_value (optional) A string that can be turned into the function x(u,v) of the inverse transformation via the python command eval('lambda u,v : ' + xfunc).

    • +
    • yfunc = str_value (optional) A string that can be turned into the function y(u,v) of the inverse transformation via the python command eval('lambda u,v : ' + yfunc).

    • +
    +
    +
  • +
  • ‘RaDecFunction’ implements an arbitrary transformation from image coordinates (x,y) to celestial coordinates (ra,dec) via two functions ra(x,y) and dec(x,y).

    +
    +
      +
    • ra_func = str_value (required) A string that can be turned into the function ra(x,y) via the python command eval('lambda x,y : ' + rafunc).

    • +
    • dec_func = str_value (required) A string that can be turned into the function dec(x,y) via the python command eval('lambda x,y : ' + decfunc).

    • +
    +
    +
  • +
  • ‘Fits’ reads a WCS from a FITS file. Most common WCS types are implemented, but if the file uses something a bit unusual, the success of the read may depend on what other python packages you have installed. See the documentation of FitsWCS for more details.

    +
    +
      +
    • file_name = str_value (required) The name of the FITS file.

    • +
    • dir = str_value (default = ‘.’)

    • +
    +
    +
  • +
  • ‘Tan’ implements a tangent-plane projection of the celestial sphere around a given right ascension and declination. There is an arbitrary Jacobian matrix relating the image coordinates to the coordinates in the tangent plane.

    +
    +
      +
    • dudx = float_value (required) du/dx

    • +
    • dudy = float_value (required) du/dy

    • +
    • dvdx = float_value (required) dv/dx

    • +
    • dvdy = float_value (required) dv/dy

    • +
    • ra = angle_value (required) the right ascension of the tangent point

    • +
    • dec = angle_value (required) the declination of the tangent point

    • +
    • unit = str_value (default = ‘arcsec’) the units to use for the intermediate (u,v) coordinates. Options are ‘arcsec’, ‘arcmin’, ‘deg’, ‘rad’, ‘hr’.

    • +
    +
    +
  • +
+

In addition, all wcs types can define an origin in either image coordinates, world coordinates, or +both:

+
    +
  • origin = pos_value (default = (0,0)) Optionally set the image coordinates to use as the origin position, if not (x,y) = (0,0). Special: You can also specify origin to be ‘center’, in which case the origin is taken to be the center of the image rather than the corner.

  • +
  • world_origin = pos_value (default = (0,0)) Optionally set the world coordinates to use as the origin position, if not (u,v) = (0,0). (Not available for the celestial WCS types: ‘RaDecFunction’, ‘Fits’, and ‘Tan’.)

  • +
+

In addition to the above, you may also define your own custom WCS type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of galsim.config.WCSBuilder.

+
+
+class galsim.config.WCSBuilder[source]
+

A base class for building WCS objects.

+

The base class defines the call signatures of the methods that any derived class should follow.

+
+
+buildWCS(config, base, logger)[source]
+

Build the WCS based on the specifications in the config dict.

+

Note: Sub-classes must override this function with a real implementation.

+
+
Parameters:
+
    +
  • config – The configuration dict for the wcs type.

  • +
  • base – The base configuration dict.

  • +
  • logger – If provided, a logger for logging debug statements.

  • +
+
+
Returns:
+

the constructed WCS object.

+
+
+
+ +
+ +

Then, as usual, you need to register this type using:

+
galsim.config.RegisterWCSType('CustomWCS', CustomWCSBuilder())
+
+
+
+
+galsim.config.RegisterWCSType(wcs_type, builder, input_type=None)[source]
+

Register a wcs type for use by the config apparatus.

+
+
Parameters:
+
    +
  • wcs_type – The name of the type in config[‘image’][‘wcs’]

  • +
  • builder – A builder object to use for building the WCS object. It should +be an instance of a subclass of WCSBuilder.

  • +
  • input_type – If the WCS builder utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

If the builder will use a particular input type, you should let GalSim know this by specifying +the input_type when registering. e.g. if it uses an input FitsHeader, you would write:

+
galsim.config.RegisterWCSType('CustomWCS', CustomWCSBuilder(), input_type='fits_header')
+
+
+

and tell the config parser the name of the module to load at the start of processing.

+
modules:
+    - my_custom_wcs
+
+
+

Then you can use this as a valid wcs type:

+
image:
+    wcs:
+        type: CustomWCS
+        ...
+
+
+

For examples of custom wcs types, see des_wcs.py , which implements DES_SlowLocal and DES_Local. +The latter is faster because it uses in input field, ‘des_wcs’, which saves on I/O time by only loading the files once. DES_Local is used by meds.yaml .

+

It may also be helpful to look at the GalSim implementation of the various included wcs types (click on +the [source] links):

+
+
+class galsim.config.SimpleWCSBuilder(init_func)[source]
+

Bases: WCSBuilder

+

A class for building simple WCS objects.

+

The initializer takes an init_func, which is the class or function to call to build the WCS. +For the kwargs, it calls getKwargs, which does the normal parsing of the req_params and +related class attributes.

+
+ +
+
+class galsim.config.OriginWCSBuilder(init_func, origin_init_func)[source]
+

Bases: SimpleWCSBuilder

+

A specialization for WCS classes that use a different type depending on whether there +is an origin or world_origin parameter in the config dict.

+
+ +
+
+class galsim.config.TanWCSBuilder[source]
+

Bases: WCSBuilder

+

The TanWCS type needs special handling to get the kwargs, since the TanWCS function +takes an AffineTransform as one of the arguments, so we need to build that from +dudx, dudy, etc. We also need to construct a CelestialCoord object for the world_origin, +which we make from ra, dec paramters.

+
+ +
+
+class galsim.config.ListWCSBuilder[source]
+

Bases: WCSBuilder

+

Select a wcs from a list

+
+ +
+
+

Bandpass Field

+

If you want to draw chromatic objects, then you also need to define the Bandpass to use +for the image in a image.bandpass field. Currently, there is only one defined +type to use for the Bandpass, but the code is written in a modular way to allow for +other types, including custom Bandpass types.

+
    +
  • ‘FileBandpass’ is the default type here, and you may omit the type name when using it.

    +
    +
      +
    • file_name = str_value (required) The file to read in.

    • +
    • wave_type = str_value or unit_value (required) The unit of the wavelengths in the file (‘nm’ or ‘Ang’ or variations on these – cf. Bandpass)

    • +
    • blue_limit = float_value or quantity_value (optional) Hard cut off on the blue side.

    • +
    • red_limit = float value or quantity_value (optional) Hard cut off on the red side.

    • +
    • zeropoint = float_value (optional) The zero-point to use.

    • +
    • thin = float_value (optional) If given, call Bandpass.thin on the Bandpass after reading in from the file, using this for the rel_err.

    • +
    +
    +
  • +
+

You may also define your own custom Bandpass type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of galsim.config.BandpassBuilder.

+
+
+class galsim.config.BandpassBuilder[source]
+

A base class for building Bandpass objects.

+

The base class defines the call signatures of the methods that any derived class should follow.

+
+
+buildBandpass(config, base, logger)[source]
+

Build the Bandpass based on the specifications in the config dict.

+

Note: Sub-classes must override this function with a real implementation.

+
+
Parameters:
+
    +
  • config – The configuration dict for the bandpass type.

  • +
  • base – The base configuration dict.

  • +
  • logger – If provided, a logger for logging debug statements.

  • +
+
+
Returns:
+

the constructed Bandpass object.

+
+
+
+ +
+ +

Then, as usual, you need to register this type using:

+
galsim.config.RegisterBandpassType('CustomBandpass', CustomBandpassBuilder())
+
+
+
+
+galsim.config.RegisterBandpassType(bandpass_type, builder, input_type=None)[source]
+

Register a bandpass type for use by the config apparatus.

+
+
Parameters:
+
    +
  • bandpass_type – The name of the type in the config dict.

  • +
  • builder – A builder object to use for building the Bandpass object. It should +be an instance of a subclass of BandpassBuilder.

  • +
  • input_type – If the Bandpass builder utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

and tell the config parser the name of the module to load at the start of processing.

+
modules:
+    - my_custom_bandpass
+
+
+

Then you can use this as a valid bandpass type:

+
image:
+    bandpass:
+        type: CustomBandpass
+        ...
+
+
+
+
+

Sensor Field

+

When drawing with method='phot', one can use a Sensor to model the conversion of photons +to electrons and then the accumulation of these electrons in the pixels.

+

The sensor types defined by GalSim are:

+
    +
  • ‘Simple’ is the default, which does nothing but accumulate the photons at their (x,y) +positions onto the corresponding pixels in the image. Typically, in this case, you would +omit the sensor field entirely.

  • +
  • ‘Silicon’ models a silicon-based CCD sensor that converts photons to electrons at a wavelength- +dependent depth (probabilistically) and drifts them down to the wells, properly taking +into account the repulsion of previously accumulated electrons (known as the brighter-fatter +effect). See SiliconSensor for details.

    +
    +
      +
    • name = str_value (default = ‘lsst_itl_50_8’) The naem of the sensor model to use. +See SiliconSensor for the other allowed values.

    • +
    • strength = float_value (default = 1.0) The strength of the brighter-fatter effect +relative to the nominal value in the model.

    • +
    • diffusion_factor = float_value (default = 1.0) The magnitude of the diffusion +relative to the nominal value in the model.

    • +
    • qdist = int_value (default = 3) The maximum number of pixels away to calculate the +distortions due to charge accumulation.

    • +
    • nrecalc = int_value (default = 10000) The number of electrons to accumulate before +recalculating the distortion of the pixel shapes.

    • +
    • treering_func = table_value (optional) A LookupTable giving the tree ring pattern +as a radial function f(r).

    • +
    • treering_center = pos_value (optional) The position of the center of the tree ring +pattern in image coordinates (which may be off the edge of the image).

    • +
    • transpose = bool_value (default = False) Whether to transpose the meaning of (x,y) +for the purpose of the brighter-fatter effect. This changes which direction the BFE +causes more elongation.

    • +
    +
    +
  • +
+

You may also define your own custom Sensor type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of galsim.config.SensorBuilder.

+
+
+class galsim.config.SensorBuilder[source]
+

A base class for building Sensor objects.

+

The base class defines the call signatures of the methods that any derived class should follow.

+
+
+buildSensor(config, base, logger)[source]
+

Build the Sensor based on the specifications in the config dict.

+

Note: Sub-classes must override this function with a real implementation.

+
+
Parameters:
+
    +
  • config – The configuration dict for the Sensor

  • +
  • base – The base configuration dict.

  • +
  • logger – If provided, a logger for logging debug statements.

  • +
+
+
Returns:
+

the constructed Sensor object.

+
+
+
+ +
+ +

Then, as usual, you need to register this type using:

+
galsim.config.RegisterSensorType('CustomSensor', CustomSensorBuilder())
+
+
+
+
+galsim.config.RegisterSensorType(sensor_type, builder, input_type=None)[source]
+

Register a sensor type for use by the config apparatus.

+
+
Parameters:
+
    +
  • sensor_type – The name of the config type to register

  • +
  • builder – A builder object to use for building the Sensor object. It should +be an instance of a subclass of SensorBuilder.

  • +
  • input_type – If the Sensor builder utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

and tell the config parser the name of the module to load at the start of processing.

+
modules:
+    - my_custom_sensor
+
+
+

Then you can use this as a valid sensor type:

+
image:
+    sensor:
+        type: CustomSensor
+        ...
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_input.html b/docs/_build/html/config_input.html new file mode 100644 index 00000000000..d41e40d7e0a --- /dev/null +++ b/docs/_build/html/config_input.html @@ -0,0 +1,606 @@ + + + + + + + Config Input Field — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Input Field

+

The input field indicates where to find any files that you want to use in building the images +or how to set up any objects that require initialization.

+
+

Input Types

+

The input fields defined by GalSim are:

+
    +
  • catalog defines an input catalog that has values for each object. Connected with ‘Catalog’ value type described in Config Values.

    +
    +
      +
    • file_name = str_value (required) The name of the file with the input catalog.

    • +
    • dir = str_value (default = ‘.’) The directory the file is in.

    • +
    • file_type = str_value (default = automatically determined from extension of file_name) Valid options are:

      +
      +
        +
      • ‘ascii’ Read from an ASCII file.

        +
        +
          +
        • comments = str_value (default = ‘#’) The character used to indicate the start of a comment in an ASCII catalog.

        • +
        +
        +
      • +
      • ‘fits’ Read from a FITS binary table.

        +
        +
          +
        • hdu = int_value (default = 1) Which hdu to use within the FITS file. Note: 0 means the primary HDU, the first extension is 1.

        • +
        +
        +
      • +
      +
      +
    • +
    +
    +
  • +
  • dict defines an input dictionary, such as a YAML or JSON file. Connected with ‘Dict’ value type described in Config Values.

    +
    +
      +
    • file_name = str_value (required) The name of the dictionary file.

    • +
    • dir = str_value (default = ‘.’) The directory the file is in.

    • +
    • file_type = str_value (default = automatically determined from extension of file_name) Valid options are:

      +
      +
        +
      • ‘yaml’ Read from a YAML file.

      • +
      • ‘json’ Read from a JSON file.

      • +
      • ‘pickle’ Read from a python pickle file.

      • +
      +
      +
    • +
    • key_split = str_value (default = ‘.’) For specifying keys below the first level of the dictionary, use this string to split the key value into multiple strings. e.g. If key_split = ‘.’ (the default) then key : galaxy_constants.redshift would be parsed as dict['galaxy_constants']['redshift']. Usually ‘.’ is an intuitive choice, but if some of your key names have a ‘.’ in them, then this would not work correctly, so you should pick something else.

    • +
    +
    +
  • +
  • fits_header lets you read from the header section of a FITS file. Connected with ‘FitsHeader’ value type described in Config Values.

    +
    +
      +
    • file_name = str_value (required) The name of the FITS file.

    • +
    • dir = str_value (default = ‘.’) The directory the file is in.

    • +
    • hdu = int_value (optional) Which HDU to read from the input file. Default is 0 (the primary HDU), unless the compression implies that the first extension should be used.

    • +
    • compression = str_value (optional) The kind of compression if any. The default is to base the compression on the file extension. e.g. ‘blah.fits.fz’ implies Rice compression. But it can also be specified explicitly. Supported values are ‘none’, ‘rice’, ‘gzip’, ‘bzip2’, ‘gzip_tile’, ‘hcompress’, ‘plio’.

    • +
    • text_file = bool_value (default = False) Whether the input file is actually a text file, rather than a binary FITS file. Normally the file is taken to be a FITS file, but if this is True, then it will read it as a text file containing the header information, such as the .head file output from SCamp.

    • +
    +
    +
  • +
  • real_catalog defines a catalog of real galaxy images. Connected with ‘RealGalaxy’ profile described in Config Objects.

    +
    +
      +
    • file_name = str_value (optional) The name of the file with the input catalog. If omitted, it will try to use the standard catalog in the $PREFIX/share/galsim directory. You can download the COSMOS catalog to that directory with the executable galsim_download_cosmos.

    • +
    • sample = str_value (optional) A string that can be used to specify the sample to use, either “23.5” or “25.2”. At most one of file_name and sample should be specified.

    • +
    • dir = str_value (optional) The directory the file is in (along with related image and noise files).

    • +
    • preload = bool_value (default = False) Whether to preload all the header information from the catalog fits file into memory at the start. If preload=True, the bulk of the I/O time happens at the start of the processing. If preload=False, there is approximately the same total I/O time (assuming you eventually use most of the image files referenced in the catalog), but it is spread over the various RealGalaxy objects that get built.

    • +
    +
    +
  • +
  • cosmos_catalog defines an input catalog that has values for each object. Connected with ‘COSMOSCatalog’ profile described in Config Objects.

    +
    +
      +
    • file_name = str_value (optional) The name of the file with the input catalog. If omitted, it will try to use the standard catalog in the $PREFIX/share/galsim directory. You can download the COSMOS catalog to that directory with the executable galsim_download_cosmos.

    • +
    • sample = str_value (optional) A string that can be used to specify the sample to use, either “23.5” or “25.2”. At most one of file_name and sample should be specified.

    • +
    • dir = str_value (optional) The directory the file is in (along with related image and noise files).

    • +
    • preload = bool_value (default = False) Whether to preload all the header information from the catalog fits file into memory at the start. If preload=True, the bulk of the I/O time happens at the start of the processing. If preload=False, there is approximately the same total I/O time (assuming you eventually use most of the image files referenced in the catalog), but it is spread over the various RealGalaxy objects that get built.

    • +
    • use_real = bool_value (default = True) Whether load the RealGalaxy catalog. If this is True, you can request either real or parametric galaxies from the catalog. But if you are only going to use parametric galaxies, then you may set this to False, and it will not bother to load the real galaxies.

    • +
    • exclusion_level = str_value (default=’marginal’) Level of additional cuts to make on the galaxies based on the quality of postage stamp definition and/or parametric fit quality [beyond the minimal cuts imposed when making the catalog - see Mandelbaum et al. (2012, MNRAS, 420, 1518) for details]. Valid options are:

      +
      +
        +
      • “none”: No cuts.

      • +
      • “bad_stamp”: Apply cuts to eliminate galaxies that have failures in postage stamp definition. These cuts may also eliminate a small subset of the good postage stamps as well.

      • +
      • “bad_fits”: Apply cuts to eliminate galaxies that have failures in the parametric fits. These cuts may also eliminate a small subset of the good parametric fits as well.

      • +
      • “marginal”: Apply the above cuts, plus ones that eliminate some more marginal cases.

      • +
      +
      +
    • +
    • min_hlr float_value (optional) Exclude galaxies whose fitted half-light radius is smaller than this value (in arcsec).

    • +
    • max_hlr float_value (optional) Exclude galaxies whose fitted half-light radius is larger than this value (in arcsec).

    • +
    • min_flux float_value (optional) Exclude galaxies whose fitted flux is smaller than this value (in arcsec).

    • +
    • max_flux float_value (optional) Exclude galaxies whose fitted flux is larger than this value (in arcsec).

    • +
    +
    +
  • +
  • galaxy_sample is a generalization of cosmos_catalog that provides similar functionality for arbitrary input sample catalogs. Connected with ‘SampleGalaxy’ profile described in Config Objects.

    +
    +
      +
    • file_name = str_value (required) The name of the file with the input catalog.

    • +
    • dir = str_value (optional) The directory the file is in (along with related image and noise files).

    • +
    • preload = bool_value (default = False) Whether to preload all the header information from the catalog fits file into memory at the start. If preload=True, the bulk of the I/O time happens at the start of the processing. If preload=False, there is approximately the same total I/O time (assuming you eventually use most of the image files referenced in the catalog), but it is spread over the various RealGalaxy objects that get built.

    • +
    • orig_exptime = float_value (default = 1.) The exposure time (in seconds) of the original observations.

    • +
    • orig_area = float_value (default = 1.) The effective collecting area (in cm^2) of the original observations.

    • +
    • use_real = bool_value (default = True) Whether load the RealGalaxy catalog. If this is True, you can request either real or parametric galaxies from the catalog. But if you are only going to use parametric galaxies, then you may set this to False, and it will not bother to load the real galaxies.

    • +
    • exclusion_level = str_value (default=’none’) Level of additional cuts to make on the galaxies based on the quality of postage stamp definition and/or parametric fit quality. Valid options are:

      +
      +
        +
      • “none”: No cuts.

      • +
      • “bad_stamp”: Apply cuts to eliminate galaxies that have failures in postage stamp definition. These cuts may also eliminate a small subset of the good postage stamps as well.

      • +
      • “bad_fits”: Apply cuts to eliminate galaxies that have failures in the parametric fits. These cuts may also eliminate a small subset of the good parametric fits as well.

      • +
      • “marginal”: Apply the above cuts, plus ones that eliminate some more marginal cases.

      • +
      +
      +
    • +
    • min_hlr = float_value (optional) Exclude galaxies whose fitted half-light radius is smaller than this value (in arcsec).

    • +
    • max_hlr = float_value (optional) Exclude galaxies whose fitted half-light radius is larger than this value (in arcsec).

    • +
    • min_flux = float_value (optional) Exclude galaxies whose fitted flux is smaller than this value (in arcsec).

    • +
    • max_flux = float_value (optional) Exclude galaxies whose fitted flux is larger than this value (in arcsec).

    • +
    • cut_ratio = float_value (optional) For the “bad_stamp” exclusions, cut out any stamps with average adjacent pixels larger than this fraction of the peak pixel count.

    • +
    • sn_limit = float_value (default = 10.) For the “bad_stamp” exclusions, cut out any stamps with estimated S/N for an elliptical Gaussian less than this limit.

    • +
    • min_mask_dist = float_value (default = 10.) For the “bad_stamp” exclusions, remove any stamps that have some masked pixels closer to the center than this minimum distance (in pixels).

    • +
    +
    +
  • +
  • nfw_halo defines an NFW halo. Connected with ‘NFWHaloShear’ and ‘NFWHaloMagnification’ value types described in Config Values.

    +
    +
      +
    • mass = float_value (required) The mass of the halo in units of (Msolar / h).

    • +
    • conc = float_value (required) The concentration parameter, defined as the virial radius / scale radius.

    • +
    • redshift = float_value (required) The redshift of the halo.

    • +
    • halo_pos = pos_value (default = 0,0) The position of the halo in world coordinates relative to the origin of the world coordinate system. (Typically you would want to to set wcs.origin to ‘center’ to get the halo in the center of the image.)

    • +
    • omega_m = float_value (default = 1 - omega_lam)

    • +
    • omega_lam = float_value (default = 1 - omega_m or 0.7 if neither is specified)

    • +
    +
    +
  • +
  • power_spectrum defines a lensing power spectrum. Connected with ‘PowerSpectrumShear’ and ‘PowerSpectrumMagnification’ value types described in Config Values.

    +
    +
      +
    • e_power_function = str_value (at least one of e_power_function and b_power_function is required) A string describing the function of k to use for the E-mode power function. e.g. 'k**2'. Alternatively, it may be a file name from which a tabulated power spectrum is read in.

    • +
    • b_power_function = str_value (at least one of e_power_function and b_power_function is required) A string describing the function of k to use for the B-mode power function. e.g. 'k**2'. Alternatively, it may be a file name from which a tabulated power spectrum is read in.

    • +
    • delta2 = bool_value (default = False) Whether the function is really Delta^2(k) = k^2 P(k)/2pi rather than P(k).

    • +
    • units = str_value (default = ‘arcsec’) The appropriate units for k^-1. The default is to use our canonical units, arcsec, for all position variables. However, power spectra are often more appropriately defined in terms of radians, so that can be specified here to let GalSim handle the units conversion. Other choices are arcmin or degrees.

    • +
    • grid_spacing = float_value (required for ‘Scattered’ image type, automatic for ‘Tiled’) The distance between grid points on which the power spectrum shears are instantiated.

    • +
    • interpolant = str_value (default = ‘Linear’) What to use for interpolating between pixel centers. Options are ‘Nearest’, ‘Linear’, ‘Cubic’, ‘Quintic’, ‘Sinc’, or ‘LanczosN’, where the ‘N’ after ‘Lanczos’ should be replaced with the integer order to use for the Lanczos filter.

    • +
    • ngrid = int_value (optional) The number of grid points to use. The default is to use image_size * scale / grid_spacing.

    • +
    • center = pos_value (optional) The center point of the grid. The default is to use the image center.

    • +
    • index = str_value (optional) If set, the power spectrum will only be computed when this index changed. E.g. if index is ‘file_num’, then it will only update with each new file, not with each image.

    • +
    • variance = float_value (optional) If set, rescale the overall variance of the generated shears to this value.

    • +
    +
    +
  • +
  • initial_image specifies an initial image (read from an FITS file) to draw onto, rather than create a new blank image.

    +
    +
      +
    • file_name = str_value (required) The name fo the file with the desired initial image.

    • +
    • dir = str_value (default = ‘.’) The directory the file is in.

    • +
    • read_header = bool_value (default = False) Whether to read the header as well.

    • +
    +
    +
  • +
+

Another feature of the input field is that it may optionally be a list. So you can have multiple input catalogs for instance; one for object parameters and one for overall image parameters perhaps. When using values with the type ‘InputCatalog’, you would specify which catalog to use with num. If you only have a single item for one of the inputs, you can omit the num parameter when you are using it.

+
+
+

Custom Input Types

+

To define your own input type, you will need to write an importable Python module +(typically a file in the current directory where you are running galsim, but it could also +be something you have installed in your Python distro) with a class that will be used +to load whatever information you want loaded:

+
class MyInputData(object):
+    """A class that knows how to load some information from a file or maybe build some
+    structure in advance that will be used repeatedly in the simulation.
+    """
+    def __init__(self, ...):
+        pass
+
+
+

Next you need to write a Loader class that is a subclass of galsim.config.InputLoader, +which the config code will use to build your input object with the right initialization kwargs.

+
+
+class galsim.config.InputLoader(init_func, has_nobj=False, file_scope=False, takes_logger=False, use_proxy=True, worker_init=None, worker_initargs=None)[source]
+

Define how to load a particular input type.

+

The base class is often sufficient for simple types, but you may derive from it and +override some of the functions to deal with special handling requirements.

+

The loader object defines a few attributes that will be used by the processing framework, +so any derived class should make sure to define them as well.

+
+
init_func

The class or function that will be used to build the input object.

+
+
has_nobj

Whether the object can be used to automatically determine the number of +objects to build for a given file or image. For example, a galsim.Catalog has +a specific number of rows in it. In many cases, you will just want to run +through the whole catalog for each output file. So the number of objects to +build will just be the number of objects in the input catalog. [default: False]

+

If this is True, the constructed input object must have a getNObjects() +method. The constructor may (if practical) only load enough to figure out +how many objects there are. Other attributes may use lazy properties to delay +finishing the read if that is efficient.

+

It may optionally also have a getApproxNObjects() method, which may +return an approximate estimate of the number of objects. This is used for the +initial calculation of what object numbers to use for each file/image, and it +is generally acceptable if this is an over-estimate, even a pretty egregious +over-estimate if necessary.

+
+
file_scope

Whether the input object might be relevant at file scope when the file and +image is initially being set up. [default: False]

+

If this is False, then the input object won’t be loaded until after the +initial file setup. For example, you might store the file names you want +to use for the output files in a YAML file, which you plan to read in as a +dict input object. Thus, dict is our canonical example of an input type for +which this parameter should be True.

+
+
takes_logger

Whether the input object has a logger attribute. If so, and a proxy is being +used, then the logger will be replaced with a logger proxy. [default: False]

+
+
use_proxy

Whether to use a proxy for commicating between processes. This is normally +necessary whenever multiprocessing is being used, but there are cases where it +is not necessary and just slows things down. [default: True]

+
+
worker_init

One way to avoid using a proxy is to follow the shared memory model decsribed +in the doc string of AtmosphericScreen. This mode uses a global dict, which +is initialized for each worker at the start of multi-processing. To use this +method, one needs to provide an initialization function that gets run when +each worker starts. [default: None]

+
+
worker_initargs

A function to provide the necessary arguments to worker_init. [default: None] +This is required whenever worker_init is not None.

+
+
+
+
+getKwargs(config, base, logger)[source]
+

Parse the config dict and return the kwargs needed to build the input object.

+

The default implementation looks for special class attributes called:

+
+
_req_params

A dict of required parameters and their types.

+
+
_opt_params

A dict of optional parameters and their types.

+
+
_single_params

A list of dicts of parameters such that one and only one of +parameter in each dict is required.

+
+
_takes_rng

A bool value saying whether an rng object is required.

+
+
+

See galsim.Catalog for an example of a class that sets these attributes.

+

In addition to the kwargs, we also return a bool value, safe, that indicates whether +the constructed object will be safe to keep around for multiple files (True) of if +it will need to be rebuilt for each output file (False).

+
+
Parameters:
+
    +
  • config – The config dict for this input item

  • +
  • base – The base config dict

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

kwargs, safe

+
+
+
+ +
+
+initialize(input_objs, num, base, logger)[source]
+

Do any global setup for input objects right after they are loaded.

+

In the base class, this function does not do anything. It can be used to do +things like load an input object and then assign it to an eval variable in the +base configuration dictionary.

+

When writing this method, remember that the entries in input objs are set to +None initially. This can cause problems for some operations on them and so +you may want to test for this condition with code like +all(iobj is not None for iobj in input_objs).

+
+
Parameters:
+
    +
  • input_objs – The (current) list of input objects.

  • +
  • num – The entry in the list that was loaded.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+setupImage(input_obj, config, base, logger)[source]
+

Do any necessary setup at the start of each image.

+

In the base class, this function does not do anything. But see PowerSpectrumLoader +for an example that does require some setup at the start of each image.

+
+
Parameters:
+
    +
  • input_obj – The input object to use

  • +
  • config – The configuration dict for the input type

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+useProxy(config, logger=None)[source]
+

Return whether to use a proxy for the input object.

+

The default behavior is to return self.use_proxy, which is set by the constructor +parameters. But if you want to decide whether to use a proxy based on something +in the config dict, you can override this and decide at run time.

+
+
Parameters:
+
    +
  • config – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+ +

The main thing you might want to override is the galsim.config.InputLoader.getKwargs function +to determine what kwargs you +want to pass to your initialization function or class based on the parameters in the config dict. +The base class, galsim.config.InputLoader uses special class attributes, which most GalSim classes +have defined (for precisely this purpose). If you want to follow that same model, and there is no +special setup to do at the start of each image, then you can just use the InputLoader itself +rather than defining your own subclass.

+

In either case, in your Python module, you need to register this function with some name, +which will be the name of the attribute in the input field that triggers the use of this +Loader object:

+
galsim.config.RegisterInputType('CustomInput', CustomInputLoader(MyInputData))
+
+
+

or:

+
galsim.config.RegisterInputType('CustomInput', InputLoader(MyInputData))
+
+
+
+
+galsim.config.RegisterInputType(input_type, loader)[source]
+

Register an input type for use by the config apparatus.

+
+
Parameters:
+
    +
  • input_type – The name of the type in config[‘input’]

  • +
  • loader – A loader object to use for loading in the input object. +It should be an instance of InputLoader or a subclass thereof.

  • +
+
+
+
+ +

In the above functions, the base parameter is the original full configuration dict that is being +used for running the simulation. The config parameter is the local portion of the full dict +that defines the object being built, which would in this case be base['input']['CustomInput'].

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_input.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_input
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_input, +which will read your file and execute the registration command to add the builder to the list +of valid input types.

+

Then you can use this as a valid value type:

+
input:
+    CustomInput:
+        ...
+
+
+

For examples of custom inputs, see des_wcs.py , +which is used by meds.yaml .

+

Also The DES Module uses custom inputs for the DES_PSFEx and DES_Shapelet object types, which are used by draw_psf.yaml .

+

It may also be helpful to look at the GalSim implementation of our various included input types +(click on the [source] links):

+
+
+class galsim.config.input_cosmos.SampleLoader(cls, input_field)[source]
+
+ +
+
+galsim.config.input_cosmos._BuildCOSMOSGalaxy(config, base, ignore, gsparams, logger)[source]
+

Build a COSMOS galaxy using the cosmos_catalog input item.

+
+ +
+
+class galsim.config.input_nfw.NFWLoader(init_func, has_nobj=False, file_scope=False, takes_logger=False, use_proxy=True, worker_init=None, worker_initargs=None)[source]
+
+ +
+
+galsim.config.input_nfw._GenerateFromNFWHaloShear(config, base, value_type)[source]
+

Return a shear calculated from an NFWHalo object.

+
+ +
+
+galsim.config.input_nfw._GenerateFromNFWHaloMagnification(config, base, value_type)[source]
+

Return a magnification calculated from an NFWHalo object.

+
+ +
+
+class galsim.config.input_powerspectrum.PowerSpectrumLoader(init_func, has_nobj=False, file_scope=False, takes_logger=False, use_proxy=True, worker_init=None, worker_initargs=None)[source]
+
+ +
+
+galsim.config.input_powerspectrum._GenerateFromPowerSpectrumShear(config, base, value_type)[source]
+

Return a shear calculated from a PowerSpectrum object.

+
+ +
+
+galsim.config.input_powerspectrum._GenerateFromPowerSpectrumMagnification(config, base, value_type)[source]
+

Return a magnification calculated from a PowerSpectrum object.

+
+ +
+
+galsim.config.input_real._BuildRealGalaxy(config, base, ignore, gsparams, logger, param_name='RealGalaxy')[source]
+

Build a RealGalaxy from the real_catalog input item.

+
+ +
+
+galsim.config.input_real._BuildRealGalaxyOriginal(config, base, ignore, gsparams, logger)[source]
+

Return the original image from a RealGalaxy using the real_catalog input item.

+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_objects.html b/docs/_build/html/config_objects.html new file mode 100644 index 00000000000..125c6726dd7 --- /dev/null +++ b/docs/_build/html/config_objects.html @@ -0,0 +1,762 @@ + + + + + + + Config Objects — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Objects

+

GalSim defines a number of object types, which correspond to the GSObject types in the python code. +Some are designed to be appropriate for describing PSFs and others for describing galaxies. +GalSim does not enforce this distinction in any way; you can use any object type in the psf +or gal fields. But normally, the psf field would use types from the PSF Types +below, and the gal field would use types from the Galaxy Types. +There are also some Generic Types that can be appropriate for either.

+

Each object type sets a number of other items that either must or may be present in the dict +for the object (i.e. in the top level psf or gal field or farther down in the dict where +an object is being defined, such is in a ‘List’ object type). These attributes are given as +bullet items for each type defined below.

+

There are also some Other Attributes that are allowed for any object (or +sometimes just galaxies), regardless of what type they are.

+

And finally, it is possible to define your own object type, +which we describe in Custom Object Types.

+
+

PSF Types

+
    +
  • ‘Moffat’ A Moffat profile: \(I(r) \sim (1 + (r/r_0)^2)^{-\beta}\), where \(r_0\) is the scale_radius.

    +
    +
      +
    • beta = float_value (required)

    • +
    • scale_radius = float_value (exactly one of scale_radius, fwhm or half_light_radius is required)

    • +
    • half_light_radius = float_value (exactly one of scale_radius, fwhm or half_light_radius is required)

    • +
    • fwhm = float_value (exactly one of scale_radius, fwhm or half_light_radius is required)

    • +
    • trunc = float_value (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation.

    • +
    +
    +
  • +
  • ‘Airy’ A simple Airy disk. (Typically one would convolve this by some model of the atmospheric component of the PSF. cf. ‘Convolution’ below.)

    +
    +
      +
    • lam_over_diam = float_value (either lam_over_diam or both lam and diam required) Lambda / telescope_diameter converted to units of arcsec (or whatever units you want your profile to use).

    • +
    • lam = float_value or quantity_value (either lam_over_diam or both lam and diam required). This should be the wavelength in nanometers.

    • +
    • diam = float_value or quantity_value (either lam_over_diam or both lam and diam required). This should be the telescope diameter in meters.

    • +
    • obscuration = float_value (default = 0) The linear size of an obstructing secondary mirror as a fraction of the full mirror size.

    • +
    • scale_unit = str_value (default = ‘arcsec’) Units to be used for internal calculations when calculating lam/diam.

    • +
    +
    +
  • +
  • ‘Kolmogorov’ A Kolmogorov turbulent spectrum: \(T(k) \sim \exp(-D(k)/2)\), where \(D(k) = 6.8839 (\lambda k/2\pi r0)^{5/3}\).

    +
    +
      +
    • lam_over_r0 = float_value (exactly one of lam_over_r0, fwhm or half_light_radius or both lam and r0 is required) Lambda / r0 converted to units of arcsec (or whatever units you want your profile to use).

    • +
    • lam = float_value or quantity_value (exactly one of lam_over_r0, fwhm or half_light_radius or both lam and r0 is required) The wavelength in nanometers.

    • +
    • r0 = float_value or quantity_value (exactly one of lam_over_r0, fwhm or half_light_radius or both lam and r0 is required) The Fried parameter in meters.

    • +
    • r0_500 = float_value or quantity_value (optional, in lieu of r0). The Fried parameter in meters at a wavelength of 500 nm. The correct r0 value will be calculated using the standard relation r0 = r0_500 * (lam/500)``1.2.

    • +
    • fwhm = float_value (exactly one of lam_over_r0, fwhm or half_light_radius or both lam and r0 is required)

    • +
    • half_light_radius = float_value (exactly one of lam_over_r0, fwhm or half_light_radius or both lam and r0 is required)

    • +
    • scale_unit = str_value (default = ‘arcsec’) Units to be used for internal calculations when calculating lam/r0.

    • +
    +
    +
  • +
  • ‘OpticalPSF’ A PSF from aberrated telescope optics.

    +
    +
      +
    • lam_over_diam = float_value (either lam_over_diam or both lam and diam required)

    • +
    • lam = float_value or quantity_value (either lam_over_diam or both lam and diam required). This should be the wavelength in nanometers.

    • +
    • diam = float_value or quantity_value (either lam_over_diam or both lam and diam required). This should be the telescope diameter in meters.

    • +
    • defocus = float_value (default = 0) The defocus value, using the Noll convention for the normalization. (Noll index 4)

    • +
    • astig1 = float_value (default = 0) The astigmatism in the y direction, using the Noll convention for the normalization. (Noll index 5)

    • +
    • astig2 = float_value (default = 0) The astigmatism in the x direction, using the Noll convention for the normalization. (Noll index 6)

    • +
    • coma1 = float_value (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 7)

    • +
    • coma2 = float_value (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 8)

    • +
    • trefoil1 = float_value (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 9)

    • +
    • trefoil2 = float_value (default = 0) The defocus value, using the Noll convention for the normalization. (Noll index 10)

    • +
    • spher = float_value (default = 0)The defocus value, using the Noll convention for the normalization. (Noll index 11)

    • +
    • aberrations = list (optional) This is an alternative way to specify the above aberrations. You can just give them as a list of values using the Noll convention for the ordering (starting at Noll index 1, since there is no 0). With this syntax, you may go to as high order as you want.

    • +
    • circular_pupil = bool_value (default = True) Whether the pupil should be circular (True, the default) or square (False).

    • +
    • obscuration = float_value (default = 0) The linear dimension of a central obscuration as a fraction of the pupil linear dimension.

    • +
    • interpolant = str_value (default = ‘quintic’) Which interpolant to use for the constructed InterpolatedImage object describing the PSF profile.

    • +
    • oversampling = float_value (default = 1.5) How much oversampling of the internal image is needed relative to the Nyquist scale of the corresponding Airy profile. The more aberrated the PSF, the higher this needs to be.

    • +
    • pad_factor = float_value (default = 1.5) How much padding to put around the edge of the internal image of the PSF.

    • +
    • suppress_warning = bool_value (default = False) Whether to suppress warnings about possible aliasing problems due to the choices of oversampling and pad_factor.

    • +
    • max_size = float_value (optional) If the PSF will only be used to draw images of some size, you can set the OpticalPSF class to not build the internal image of the PSF (much) larger than that. This can help speed up calculations if GalSim natively decides to build a very large image of the PSF, when the wings never actually affect the final image.

    • +
    • nstruts = int_value (default = 0) How many support struts to include.

    • +
    • strut_thick = float_value (default = 0.05) How thick the struts should be as a fraction of the pupil diameter.

    • +
    • strut_angle = angle_value (default = 0 degrees) The counter-clockwise angle between the vertical and one of the struts. The rest will be spaced equally from there.

    • +
    • pupil_plane_im = str_value (optional) Instead of using strut-related parameters to define the pupil plane geometry, you can use this parameter to specify a file-name containing an image of the pupil plane.

    • +
    • pupil_angle = angle_value (default = 0 degrees) When specifying a pupil_plane_im, use this parameter to rotate it by some angle defined counter-clockwise with respect to the vertical.

    • +
    • scale_unit = str_value (default = ‘arcsec’) Units to be used for internal calculations when calculating lam/diam.

    • +
    +
    +
  • +
  • +
    ‘ChromaticAtmosphere’ A chromatic PSF implementing both differential chromatic diffraction (DCR) and wavelength-dependent seeing. See ChromaticAtmosphere for valid combinations that can be used to set the zenith and parallactic angles needed for DCR.
      +
    • base_profile = object (required) The base profile to use for the profile shape at a given reference wavelength

    • +
    • base_wavelength = float_value (required) The wavelength at which the PSF has the base profile

    • +
    • alpha = float_value (default = -0.2) Power law index for wavelength-dependent seeing.

    • +
    • zenith_angle = Angle_value (optional) The zenith angle.

    • +
    • parallactic_angle = Angle_value (optional) The parallactic angle.

    • +
    • zenith_coord = CelestialCoord (optional) The (ra,dec) coordinate of the zenith.

    • +
    • HA = Angle_value (optional) Hour angle of the observation.

    • +
    • latitude = Angle_value (optional) Latitude of the observatory.

    • +
    • pressure = float_value or quantity_value (default = 69.328) Air pressure in kPa.

    • +
    • temperature = float_value or quantity_value (default = 293.15) Temperature in K.

    • +
    • H2O_pressure = float_value or quantity_value (default = 1.067) Water vapor pressure in kPa.

    • +
    +
    +
    +
  • +
+
+
+

Galaxy Types

+
    +
  • ‘Exponential’ A radial exponential profile: \(I(r) \sim \exp(-r/r_0)\), where \(r_0\) is the scale_radius.

    +
    +
      +
    • scale_radius = float_value (exactly one of scale_radius or half_light_radius is required)

    • +
    • half_light_radius = float_value (exactly one of scale_radius or half_light_radius is required)

    • +
    +
    +
  • +
  • ‘Sersic’ A Sersic profile: \(I(r) \sim \exp(-(r/r_0)^{1/n}) = \exp(-b (r/r_e)^{1/n})\), where \(r_0\) is the scale_radius and \(r_e\) is the half_light_radius.

    +
    +
      +
    • n = float_value (required)

    • +
    • half_light_radius = float_value (exactly one of half_light_radius or scale_radius is required)

    • +
    • scale_radius = float_value (exactly one of half_light_radius or scale_radius is required)

    • +
    • trunc = float_value (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation.

    • +
    • flux_untruncated = bool_value (default = False) Set the profile such that the specified flux corresponds to that of the untruncated profile. Valid only when trunc > 0; ignored otherwise.

    • +
    +
    +
  • +
  • ‘DeVaucouleurs’ A DeVaucouleurs profile: \(I(r) \sim \exp(-(r/r_0)^{1/4}) = \exp(-b (r/r_e)^{1/4})\) (aka n=4 Sersic).

    +
    +
      +
    • scale_radius = float_value (exactly one of half_light_radius or scale_radius is required)

    • +
    • half_light_radius = float_value (exactly one of half_light_radius or scale_radius is required)

    • +
    • trunc = float_value (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation.

    • +
    • flux_untruncated = bool_value (default = False) Set the profile such that the specified flux corresponds to that of the untruncated profile. Valid only when trunc > 0; ignored otherwise.

    • +
    +
    +
  • +
  • ‘Spergel’ A profile based on the Spergel (2010) paper with the form: \(I(r) \sim (r/r_0)^\nu * K_\nu(r/r_0)\) where \(r_0\) is the scale_radius and \(K_\nu\) is the modified Bessel function of the second kind.

    +
    +
      +
    • nu = float_value (required)

    • +
    • half_light_radius = float_value (exactly one of half_light_radius or scale_radius is required)

    • +
    • scale_radius = float_value (exactly one of half_light_radius or scale_radius is required)

    • +
    +
    +
  • +
  • ‘RealGalaxy’ A real galaxy image, typically taken from a deep HST image, deconvolved by the original PSF. Note that the deconvolution implies that this cannot be draw with photon shooting (image.draw_method = 'phot'). This requires that input.real_catalog be specified and uses the following fields:

    +
    +
      +
    • index = int_value (default = ‘Sequence’ from 0 to real_catalog.nobjects-1; only one of id or index may be specified) Which item in the catalog to use. Special: If index is either a ‘Sequence’ or ‘Random’ and last or max (respectively) is not specified, then it is automatically set to real_catalog.nobjects-1.

    • +
    • id = str_value (only one of id or index may be specified) The ID in the catalog of the object to use.

    • +
    • x_interpolant = str_value (default = ‘Quintic’) What to use for interpolating between pixel centers. Options are ‘Nearest’, ‘Linear’, ‘Cubic’, ‘Quintic’, ‘Sinc’, or ‘LanczosN’, where the ‘N’ after ‘Lanczos’ should be replaced with the integer order to use for the Lanczos filter.

    • +
    • k_interpolant = str_value (default = ‘Quintic’) What to use for interpolating between pixel centers in Fourier space, for convolution. Options are ‘Nearest’, ‘Linear’, ‘Cubic’, ‘Quintic’, ‘Sinc’, or ‘LanczosN’, where the ‘N’ after ‘Lanczos’ should be replaced with the integer order to use for the Lanczos filter. See docstring for this class for caveats about changing this parameter.

    • +
    • flux = float_value (default = catalog value) If set, this works as described below. However, ‘RealGalaxy’ has a different default. If flux is omitted, the flux of the actual galaxy in the catalog is used.

    • +
    • pad_factor = float_value (default = 4) Amount of zero-padding to use around the image when creating the InterpolatedImage. See docstring for this class for caveats about changing this parameter.

    • +
    • noise_pad_size = float (optional) If provided, then the original image is padded to a larger image of this size using the noise profile of the original image. This is important if you are using noise.whiten or noise.symmetrize. You want to make sure the image has the original noise all the way to the edge of the postage stamp. Otherwise, the edges will have the wrong noise profile.

    • +
    • num = int_value (default = 0) If input.real_catalog is a list, this indicates which number catalog to use.

    • +
    +
    +
  • +
  • ‘RealGalaxyOriginal’ This is the same as ‘RealGalaxy’ except that the profile is _not_ deconvolved by the original PSF. So this is the galaxy as observed in the original image. This requires that input.real_catalog be specified and uses the same fields as ‘RealGalaxy’. This may be more useful than the deconvolved version. For example, unlike ‘RealGalaxy’, it can be drawn with photon shooting (image.draw_method = 'phot').

  • +
  • ‘COSMOSGalaxy’ Either a real or parametric galaxy from the COSMOS catalog. This requires that input.cosmos_catalog be specified and uses the following fields:

    +
    +
      +
    • index = int_value (default = ‘Sequence’ from 0 to cosmos_catalog.nobjects-1) Which item in the catalog to use. Special: If index is either a ‘Sequence’ or ‘Random’ and last or max (respectively) is not specified, then it is automatically set to real_catalog.nobjects-1.

    • +
    • gal_type = str_vale (required, unless real_catalog.use_real is False, in which case ‘parametric’) Which type of galaxy to use. Options are ‘real’ or ‘parametric’.

    • +
    • noise_pad_size = float_val (default = 5) The size of a padding region in arcsec around the HST image when gal_type='real'. Only applies to galaxies whose original potage stamp size is smaller than this value; it effectively sets a minimum stamp size with the HST correlated noise for small galaxies.

    • +
    • deep = bool_value (default = False) Whether the flux and size should be rescaled to approximate a galaxy catalog with a limiting mag of 25 in F814W, rather than 23.5.

    • +
    • noise_pad_size = float (optional) If provided, then the original image is padded to a larger image of this size using the noise profile of the original image. This is important if you are using noise.whiten or noise.symmetrize. You want to make sure the image has the original noise all the way to the edge of the postage stamp. Otherwise, the edges will have the wrong noise profile.

    • +
    • sersic_prec = float_value (default = 0.05) The desired precision on the Sersic index n in parametric galaxies. GalSim is significantly faster if it gets a smallish number of Sersic values, so it can cache some of the calculations and use them again the next time it gets a galaxy with the same index. If sersic_prec is 0.0, then use the exact value of index n from the catalog. But if it is >0, then round the index to that precision.

    • +
    • chromatic = bool_value (default = False) Whether to build chromatic profiles. (Only valid if gal_type='parametric'.)

    • +
    • area = float_value (default = None, which will use the HST collecting area.) The effective collecting area in cm**2 of the telescope being simulated. Used for rescaling the flux values.

    • +
    • exptime = float_value (default = 1) The exposure time in seconds. Used for rescaling the flux values. (Note: The processed COSMOS ACS/HST science images have units of counts/second; i.e. they have an effective exposure time of 1 second in terms of their flux levels. The default value corresponds to a 1 second exposure on HST, which will match these processed images.)

    • +
    • num = int_value (default = 0) If input.cosmos_catalog is a list, this indicates which number catalog to use.

    • +
    +
    +
  • +
  • ‘SampleGalaxy’ Either a real or parametric galaxy from an input galaxy sample. This requires that input.galaxy_sample be specified and uses the following fields:

    +
    +
      +
    • index = int_value (default = ‘Sequence’ from 0 to cosmos_catalog.nobjects-1) Which item in the catalog to use. Special: If index is either a ‘Sequence’ or ‘Random’ and last or max (respectively) is not specified, then it is automatically set to real_catalog.nobjects-1.

    • +
    • gal_type = str_vale (required, unless real_catalog.use_real is False, in which case ‘parametric’) Which type of galaxy to use. Options are ‘real’ or ‘parametric’.

    • +
    • noise_pad_size = float_val (default = 5) The size of a padding region in arcsec around the HST image when gal_type='real'. Only applies to galaxies whose original potage stamp size is smaller than this value; it effectively sets a minimum stamp size with the HST correlated noise for small galaxies.

    • +
    • deep = bool_value (default = False) Whether the flux and size should be rescaled to approximate a galaxy catalog with a limiting mag of 25 in F814W, rather than 23.5.

    • +
    • noise_pad_size = float (optional) If provided, then the original image is padded to a larger image of this size using the noise profile of the original image. This is important if you are using noise.whiten or noise.symmetrize. You want to make sure the image has the original noise all the way to the edge of the postage stamp. Otherwise, the edges will have the wrong noise profile.

    • +
    • sersic_prec = float_value (default = 0.05) The desired precision on the Sersic index n in parametric galaxies. GalSim is significantly faster if it gets a smallish number of Sersic values, so it can cache some of the calculations and use them again the next time it gets a galaxy with the same index. If sersic_prec is 0.0, then use the exact value of index n from the catalog. But if it is >0, then round the index to that precision.

    • +
    • chromatic = bool_value (default = False) Whether to build chromatic profiles. (Only valid if gal_type='parametric'.)

    • +
    • area = float_value (default = None, which will use the HST collecting area.) The effective collecting area in cm**2 of the telescope being simulated. Used for rescaling the flux values.

    • +
    • exptime = float_value (default = 1) The exposure time in seconds. Used for rescaling the flux values. (Note: The processed COSMOS ACS/HST science images have units of counts/second; i.e. they have an effective exposure time of 1 second in terms of their flux levels. The default value corresponds to a 1 second exposure on HST, which will match these processed images.)

    • +
    • num = int_value (default = 0) If input.galaxy_sample is a list, this indicates which number catalog to use.

    • +
    +
    +
  • +
  • ‘InclinedExponential’ The 2D projection of a 3D exponential profile: \(I(R,z) \sim \mathrm{sech}^2 (z/h_s) * \exp(-R/R_s)\) at an arbitrary inclination angle, where \(h_s\) is the scale_height and \(R_s\) is the scale_radius The base profile is inclined along the y-axis, so if you want a different position angle, you should add a rotate field.

    +
    +
      +
    • inclination = angle_value (required) The inclination angle, defined such that 0 degrees is face-on and 90 degrees is edge-on.

    • +
    • half_light_radius = float_value (exactly one of half_light_radius or scale_radius is required) The half-light radius as an alternative to scale_radius.

    • +
    • scale_radius = float_value (exactly one of half_light_radius or scale_radius is required) The scale_radius, R_s.

    • +
    • scale_height = float_value (exactly one of scale_height or scale_h_over_r is required) The scale height, h_s.

    • +
    • scale_h_over_r = float_value (exactly one of scale_height or scale_h_over_r is required) The ratio h_s/R_s as an alternative to scale_height.

    • +
    +
    +
  • +
  • ‘InclinedSersic’ Like ‘InclinedExponential’, but using a Sersic profile in the plane of the disc.

    +
    +
      +
    • n = float_value (required)

    • +
    • half_light_radius = float_value (exactly one of half_light_radius or scale_radius is required) The half-light radius as an alternative to scale_radius.

    • +
    • scale_radius = float_value (exactly one of half_light_radius or scale_radius is required) The scale radius, R_s.

    • +
    • trunc = float_value (optional) The profile can be truncated to 0 at some radius if desired. The default is no truncation.

    • +
    • flux_untruncated = bool_value (default = False) Set the profile such that the specified flux corresponds to that of the untruncated profile. Valid only when trunc > 0; ignored otherwise.

    • +
    • scale_height = float_value (exactly one of scale_height or scale_h_over_r is required) The scale height, h_s.

    • +
    • scale_h_over_r = float_value (exactly one of scale_height or scale_h_over_r is required) The ratio h_s/R_s as an alternative to scale_height.

    • +
    +
    +
  • +
  • ‘DeltaFunction’ A delta function profile with a specified flux. This is typically not used for galaxies, but rather for stars in a scene that includes both. So when convolved by the PSF, the stars will have the profile from the PSF, but the correct flux.

  • +
  • ‘RandomKnots’ A profile made of a sum of a number of delta functions distributed according to either a Gaussian profile or a given specified profile. This is intended to represent knots of star formation, so it would typically be added to a smooth disk component and have the same size and shape.

    +
    +
      +
    • npoints = int_value (required) How many points to include.

    • +
    • half_light_radius = float_value (either half_light_radius or profile is required) The expectation of the half light radius, setting the overall scale of the Gaussian profile. Note: any given realized profile will not necessarily have exactly this half-light radius.

    • +
    • profile = GSObject (either half_light_radius or profile is required) The profile you want to use for the distribution of knots.

    • +
    +
    +
  • +
+
+
+

Generic Types

+
    +
  • ‘Gaussian’ A circular Gaussian profile: \(I(r) \sim \exp(-r^2 / (2 \sigma^2))\). This is not all that appropriate for either PSFs or galaxies, but as it is extremely simple, it is often useful for very basic testing, as many measured properties of the profile have analytic values.

    +
    +
      +
    • sigma = float_value (exactly one of sigma, fwhm or half_light_radius is required)

    • +
    • fwhm = float_value (exactly one of sigma, fwhm or half_light_radius is required)

    • +
    • half_light_radius = float_value (exactly one of sigma, fwhm or half_light_radius is required)

    • +
    +
    +
  • +
  • ‘InterpolatedImage’ A profile described simply by a provided image (given in a fits file).

    +
    +
      +
    • image = str_value (required) The file name from which to read the image.

    • +
    • x_interpolant = str_value (default = ‘Quintic’) What to use for interpolating between pixel centers. Options are ‘Nearest’, ‘Linear’, ‘Cubic’, ‘Quintic’, ‘Sinc’, or ‘LanczosN’, where the ‘N’ after ‘Lanczos’ should be replaced with the integer order to use for the Lanczos filter.

    • +
    • k_interpolant = str_value (default = ‘Quintic’) What to use for interpolating between pixel centers in Fourier space, for convolution. Options are ‘Nearest’, ‘Linear’, ‘Cubic’, ‘Quintic’, ‘Sinc’, or ‘LanczosN’, where the ‘N’ after ‘Lanczos’ should be replaced with the integer order to use for the Lanczos filter. See docstring for this class for caveats about changing this parameter.

    • +
    • normalization = str_value (default = ‘flux’) What normalization to assume for the input image. Options are (‘flux’ or ‘f’) or (‘surface brightness’ or ‘sb’).

    • +
    • scale = float_value (default = ‘GS_SCALE’ entry from the fits header, or 1 if not present) What pixel scale to use for the image pixels.

    • +
    • pad_factor = float_value (default = 4) Amount of zero-padding to use around the image when creating the SBInterpolatedImage. See docstring for this class for caveats about changing this parameter.

    • +
    • noise_pad_size = float_value (optional; required if noise_pad is provided) If non-zero, then the original image is padded to a larger image of this size using the noise specified in noise_pad.

    • +
    • noise_pad = str_value (optional; required if noise_pad_size is provided) Either a filename to use for padding the image with noise according to a noise correlation function, or a variance value to pad with Gaussian noise.

    • +
    • pad_image = str_value (optional) The name of an image file to use for directly padding the image (deterministically) rather than padding with noise.

    • +
    • calculate_stepk = bool_value (default = True) Recalculate optimal Fourier space separation for convolutions? Can lead to significant optimization compared to default values.

    • +
    • calculate_maxk = bool_value (default = True) Recalculate optimal Fourier space total k range for convolutions? Can lead to significant optimization compared to default values.

    • +
    • use_true_center = bool_value (default = True) Whether to use the true center of the provided image as the nominal center of the profile (True) or round up to the nearest integer value (False).

    • +
    • hdu = int_value (default = the primary HDU for uncompressed images, or the first extension for compressed images) Which HDU to use from the input FITS file.

    • +
    +
    +
  • +
  • ‘Box’ A rectangular boxcar profile: \(I(x,y) \sim H(w/2-|x|) H(h/2-|y|)\), +where \(H\) is the Heaviside function, \(w\) is width and \(h\) is height.

    +
    +
      +
    • width = float_value (required) The full width of the profile.

    • +
    • height = float_value (required) The full height of the profile.

    • +
    +
    +
  • +
  • ‘Pixel’ A square boxcar profile: \(I(x,y) \sim H(s/2-|x|) H(s/2-|y|)\), +where \(H\) is the Heaviside function and \(s\) is scale. +This is equivalent to a ‘Box’ type with width = height (called scale here). Note however, that the default rendering method already correctly accounts for the pixel response, so normally you will not need to use this as part of the PSF (or galaxy).

    +
    +
      +
    • scale = float_value The pixel scale, which is the width and height of the pixel.

    • +
    +
    +
  • +
  • ‘TopHat’ A circular tophat profile: \(I(r) \sim H(r-|r|)\), where \(H\) is the +Heaviside function and \(r\) is radius.

    +
    +
      +
    • radius = float_value The radius of the circular tophat profile.

    • +
    +
    +
  • +
  • ‘Sum’ or ‘Add’ Add several profiles together.

    +
    +
      +
    • items = list (required) A list of profiles to be added.

    • +
    +
    +
  • +
  • ‘Convolution’ or ‘Convolve’ Convolve several profiles together.

    +
    +
      +
    • items = list (required) A list of profiles to be convolved.

    • +
    +
    +
  • +
  • ‘List’ Select profile from a list.

    +
    +
      +
    • items = list (required) A list of profiles.

    • +
    • index = int_value (default = ‘Sequence’ from 0 to len(items)-1) Which item in the list to select each time.

    • +
    +
    +
  • +
  • ‘Eval’ Use Python’s eval function to evaluate a given string as a GSObject.

    +
    +
      +
    • str = str_value (required) The string to evaluate.

    • +
    +
    +
  • +
+
+
+

Other Attributes

+

There are a number of transformation attributes that are always allowed for any object type. +Some of these operations do not commute with each other, so the order is important. +The following transformations will be applied in the order given here, which corresponds +roughly to when they occur to the light packet traveling through the universe.

+

The first few, flux, dilate, ellip, and rotate, are typically used to define the +intrinsic profile of the object. +The next two, magnify and shear, are typically used to define how the profile is +modified by lensing. +The next one, shift, is used to shift the position of the galaxy relative to its nominal +position on the sky.

+
    +
  • flux = float_value (default = 1.0) Set the flux of the object in ADU. Note that the component items in a ‘Sum’ can also have fluxes specified, which can be used as fractional fluxes (e.g. 0.6 for the disk and 0.4 for the bulge). Then the outer level profile can set the real flux for the whole thing.

  • +
  • dilate or dilation = float_value (optional) Dilate the profile by a given scale, preserving the flux.

  • +
  • ellip = shear_value (optional) Shear the profile by a given shear to give the profile some non-round intrinsic shape.

  • +
  • rotate or rotation = angle_value (optional) Rotate the profile by a given angle.

  • +
  • scale_flux = float_value (optional) Factor by which to scale the flux of the galaxy profile.

  • +
  • magnify or magnification = float_value (optional) Magnify the profile by a given scale, preserving the surface brightness.

  • +
  • shear = shear_value (optional) Shear the profile by a given shear.

  • +
  • shift = pos_value (optional) Shift the centroid of the profile by a given amount relative to the center of the image on which it will be drawn.

  • +
  • skip = bool_value (default=False) Skip this object.

  • +
  • sed = SED (optional) If desired, you may set an SED to use for the object. See SED Field below for details.

  • +
+

There are also a few special attributes that are only allowed for the top-level gal field, +not for objects that are part of an aggregate object like ‘Sum’, ‘Convolution’ or ‘List’ +and not for psf.

+
    +
  • resolution = float_value (optional) If the base profile allows a half_light_radius parameter, and the psf is able to calculate a half_light_radius, then it is permissible to specify a resolution: resolution = r_gal / r_psf (where r_gal and r_psf are the half-light radii) in lieu of specifying the half_light_radius of the galaxy explicitly. This is especially useful if the PSF size is generated randomly.

  • +
  • signal_to_noise = float_value (optional) You may specify a signal-to-noise value rather than a flux. Our definition of the S/N derives from a weighted integral of the flux in the drawn image: +\(S = \sum W(x,y) I(x,y) / \sum W(x,y)\) where \(W(x,y)\) is taken to be a matched filter, so \(W(x,y) = I(x,y)\). (Note: This currently requires draw_method = 'fft'. It is a bit trickier to do this for photon shooting, and we have not enabled that yet.)

  • +
  • redshift = float_value (optional) The redshift of the galaxy. This is required when using ‘NFWHaloShear’ or ‘NFWHaloMagnification’. But note that this is only valid for achromatic objects. For chromatic objects, the redshift should be applied to the SED instead.

  • +
+
+
+

Custom Object Types

+

To define your own object type, you will need to write an importable Python module +(typically a file in the current directory where you are running galsim, but it could also +be something you have installed in your Python distro) with a function that will be used +to build a GalSim GSObject.

+

The build function should have the following functional form:

+
def BuildCustomObject(config, base, ignore, gsparams, logger):
+    """Build a custom GSObject of some sort
+
+    Parameters:
+        config:     The configuration dict of the object being built
+        base:       The base configuration dict.
+        ignore:     A list of parameters that might be in the config dict,
+                    but which may be ignored.  i.e. it is not an error for
+                    these items to be present.
+        gsparams:   An optional dict of items used to build a GSParams object
+                    (may be None).
+        logger:     An optional logger object to log progress (may be None).
+
+    Returns:
+        gsobject, safe
+
+    The returned gsobject is the built GSObject instance, and safe is a bool
+    value that indicates whether the object is safe to reuse for future stamps
+    (e.g. if all the parameters used to build this object are constant and will
+    not change for later stamps).
+    """
+    # If desired, log some output.
+    if logger:
+        logger.debug("Starting work on building CustomObject")
+
+    # The gsparams are passed around using a dict so they can be easily added to.
+    # At this point, we would typically convert them to a regular GSParams
+    # instance to use when building the GSObject.
+    if gsparams:
+        gsparams = galsim.GSParams( **gsparams )
+
+    # If you need a random number generator, this is the one to use.
+    rng = base['rng']
+
+    # Build the GSObject
+    # Probably something complicated that you want this function to do.
+    gsobject = [...]
+
+    safe = False  # typically, but set to True if this object is safe to reuse.
+    return gsobject, safe
+
+
+

The base parameter is the original full configuration dict that is being used for running the +simulation. The config parameter is the local portion of the full dict that defines the object +being built, e.g. config might be base['gal'] or it might be farther down as an item in +the items attribute of a ‘List’ or ‘Sum’ object.

+

Then, in the Python module, you need to register this function with some type name, which will +be the value of the type attribute that triggers running this function:

+
galsim.config.RegisterObjectType('CustomObject', BuildCustomObject)
+
+
+
+
+galsim.config.RegisterObjectType(type_name, build_func, input_type=None)[source]
+

Register an object type for use by the config apparatus.

+

A few notes about the signature of the build functions:

+
    +
  1. The config parameter is the dict for the current object to be generated. So it should +be the case that config[‘type’] == type_name.

  2. +
  3. The base parameter is the original config dict being processed.

  4. +
  5. The ignore parameter is a list of items that should be ignored in the config dict if they +are present and not valid for the object being built.

  6. +
  7. The gsparams parameter is a dict of kwargs that should be used to build a GSParams object +to use when building this object.

  8. +
  9. The logger parameter is a logging.Logger object to use for logging progress if desired.

  10. +
  11. The return value of build_func should be a tuple consisting of the object and a boolean, +safe, which indicates whether the generated object is safe to use again rather than +regenerate for subsequent postage stamps. e.g. if a PSF has all constant values, then it +can be used for all the galaxies in a simulation, which lets it keep any FFTs that it has +performed internally. OpticalPSF is a good example of where this can have a significant +speed up.

  12. +
+
+
Parameters:
+
    +
  • type_name – The name of the ‘type’ specification in the config dict.

  • +
  • build_func

    A function to build a GSObject from the config information. +The call signature is:

    +
    obj, safe = Build(config, base, ignore, gsparams, logger)
    +
    +
    +

  • +
  • input_type – If the type requires an input object, give the key name of the input +type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

If the builder will use a particular input type, you should let GalSim know this by specifying +the input_type when registering. E.g. if the builder expects to use an input dict file +to define some properties that will be used, you would register this fact using:

+
galsim.config.RegisterObjectType('CustomObject', BuildCustomObject,
+                                 input_type='dict')
+
+
+

The input object can be accessed in the build function as e.g.:

+
input_dict = galsim.config.GetInputObj('dict', config, base, 'CustomObject')
+ignore = ignore + ['num']
+
+
+

The last argument is just used to help give sensible error messages if there is some problem, +but it should typically be the name of the object type being built. When you are using an +input object, the ‘num’ attribute is reserved for indicating which of possibly several input +objects (dict in this case) to use. You should not also define a num attribute that has +some meaning for this object type. If you are using the ignore parameter to check for extra +invalid parameters, you would thus want to add ‘num’ to the list.

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_object.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_object
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_object, +which will read your file and execute the registration command to add the object to the list +of valid object types.

+

Then you can use this as a valid object type:

+
gal:
+    type: CustomObject
+    ...
+
+
+

For examples of custom objects, see des_psfex.py +and des_shapelet.py +in the galsim.des module, which define custom object types DES_PSFEx and DES_Shapelet. These objects are used by draw_psf.yaml +in the GalSim/examples/des directory. +It may also be helpful to look at the GalSim implementation of some of the included object builders (click on the [source] links):

+
+
+galsim.config.gsobject._BuildAdd(config, base, ignore, gsparams, logger)[source]
+

Build a Sum object.

+
+ +
+
+galsim.config.gsobject._BuildConvolve(config, base, ignore, gsparams, logger)[source]
+

Build a Convolution object.

+
+ +
+
+galsim.config.gsobject._BuildList(config, base, ignore, gsparams, logger)[source]
+

Build a GSObject selected from a List.

+
+ +
+
+galsim.config.gsobject._BuildOpticalPSF(config, base, ignore, gsparams, logger)[source]
+

Build an OpticalPSF.

+
+ +
+
+

SED Field

+

If you want your object to have a non-trivial wavelength dependence, you can include an +sed parameter to define its SED. Currently, there is only one defined +type to use for the SED, but the code is written in a modular way to allow for +other types, including custom SED types.

+
    +
  • ‘FileSED’ is the default type here, and you may omit the type name when using it.

    +
    +
      +
    • file_name = str_value (required) The file to read in.

    • +
    • wave_type = str_value or unit_value (required) The unit of the wavelengths in the file (‘nm’ or ‘Ang’ or variations on these – cf. SED)

    • +
    • flux_type = str_value (required) The type of spectral density or dimensionless normalization used in the file (‘flambda’, ‘fnu’, ‘fphotons’ or ‘1’ – cf. SED)

    • +
    • redshift = float_value (optional) If given, shift the spectrum to the given redshift. You can also specify the redshift as an object-level parameter if preferred.

    • +
    • norm_flux_density = float_value or quantity_value (optional) Set a normalization value of the flux density at a specific wavelength. If given, norm_wavelength is required.

    • +
    • norm_wavelength = float_value or quantity_value (optional) The wavelength to use for the normalization flux density.

    • +
    • norm_flux = float_value (optional) Set a normalization value of the flux over a specific bandpass. If given, norm_bandpass is required.

    • +
    • norm_bandpass = Bandpass (optional) The bandpass to use for the normalization flux.

    • +
    +
    +
  • +
+

You may also define your own custom SED type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of galsim.config.SEDBuilder.

+
+
+class galsim.config.SEDBuilder[source]
+

A base class for building SED objects.

+

The base class defines the call signatures of the methods that any derived class should follow.

+
+
+buildSED(config, base, logger)[source]
+

Build the SED based on the specifications in the config dict.

+

Note: Sub-classes must override this function with a real implementation.

+
+
Parameters:
+
    +
  • config – The configuration dict for the SED type.

  • +
  • base – The base configuration dict.

  • +
  • logger – If provided, a logger for logging debug statements.

  • +
+
+
Returns:
+

the constructed SED object.

+
+
+
+ +
+ +

Then, as usual, you need to register this type using:

+
galsim.config.RegisterSEDType('CustomSED', CustomSEDBuilder())
+
+
+
+
+galsim.config.RegisterSEDType(sed_type, builder, input_type=None)[source]
+

Register a SED type for use by the config apparatus.

+
+
Parameters:
+
    +
  • sed_type – The name of the type in the config dict.

  • +
  • builder – A builder object to use for building the SED object. It should +be an instance of a subclass of SEDBuilder.

  • +
  • input_type – If the SED builder utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

and tell the config parser the name of the module to load at the start of processing.

+
modules:
+    - my_custom_sed
+
+
+

Then you can use this as a valid sed type:

+
gal:
+    ...
+    sed:
+        type: CustomSED
+        ...
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_output.html b/docs/_build/html/config_output.html new file mode 100644 index 00000000000..363c0252ac5 --- /dev/null +++ b/docs/_build/html/config_output.html @@ -0,0 +1,870 @@ + + + + + + + Config Output Field — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Output Field

+

The output field indicates where to write the output files and what kind of output format +they should be.

+
+

Note

+

Multiprocessing

+

The config processing can use python multiprocessing to split the work among multiple +processes on a single node. This can be done either at the file level or the image level. +If you set output.nproc != 1, then it will parallelize the creation of files, building +and writing each file in a separate process. If you instead set image.nproc != 1, then +the files will be built one at a time, but the work for drawing the objects will be +parallelized across the processes.

+

There are tradeoffs between these two kinds of multiprocessing that the user should be +aware of. Python multiprocessing uses pickle to pass information between processes. +In the image-based multiprocessing, each process builds a postage stamp image for each +object and sends that stamp back to the main process to assemble into the final image. +If the objects are all very easy to draw, this communication can end up dominating the +run time as python will pickle the image data to send back to the main process.

+

File-based multiprocessing has much less communication between processes, since each image +is fully built and written all in a single process. However, this kind of multiprocessing +often requires more memory, since each process holds a full image to be written to disk +as it is building it. Users should consider this tradeoff carefully when deciding which +kind of multiprocessing (if either) is appropriate for their use case.

+

Finally, one last caveat about multiprocessing. Galsim turns off OpenMP threading when +in a multiprocessing context, so you don’t, for instance, have 64 processes, each spawning +64 OpenMP threads at once. This works for OpenMP, but not some other sources of threading +that may be initiated by numpy functions. If you get errors related to being unable to +create threads, you should install (via pip or conda) the threadpoolctl package. +If this package is installed, GalSim will use it to turn off threading for all of the +possible backends used by numpy.

+
+
+

Output Field Attributes

+

All output types use the following attributes to specify the location and number +of output files, or aspects of how to build and write the output files.

+
    +
  • file_name = str_value (default = ‘<config file root name>.fits’) You would typically want to specify this explicitly, but if you do not, then if the configuration file is called my_test.yaml, the output file would be my_test.fits.

  • +
  • dir = str_value (default = ‘.’) In which directory should the output file be put.

  • +
  • nfiles = int_value (default = 1) How many files to build. Note: if nfiles > 1, then file_name and/or dir should not be a simple string. Rather it should be some generated string that provides a different save location for each file. See the section below on setting str_value.

  • +
  • nproc = int_value (default = 1) Specify the number of processors to use when building files. If nproc <= 0, then this means to try to automatically figure out the number of cpus and use that. If you are doing many files, it is often more efficient to split up the processes at this level rather than when drawing the postage stamps (which is what image.nproc means).

  • +
  • timeout = float_value (default = 3600) Specify the number of seconds to allow for each job when multiprocessing before the multiprocessing queue times out. The default is generally appropriate to prevent jobs from hanging forever from some kind of multiprocessing snafu, but if your jobs are expected to take more than an hour per output file, you might need to increase this.

  • +
  • skip = bool_value (default = False) Specify files to skip. This would normally be an evaluated boolean rather than simply True or False of course. e.g. To only do the fifth file, you could use skip : { type : Eval, str : 'ffile_num != 4' }, which may be useful during debugging if you are trying to diagnose a problem in one particular file.

  • +
  • noclobber = bool_value (default = False) Specify whether to skip building files that already exist. This may be useful if you are running close to the memory limit on your machine with multiprocessing. e.g. You could use nproc > 1 for a first run using multiprocessing, and then run again with nproc = 1 and noclobber = True to clean up any files that failed from insufficient memory during the multiprocessing run.

  • +
  • retry_io = int_value (default = 0) How many times to retry the write command if there is any kind of failure. Some systems have trouble with multiple concurrent writes to disk, so if you are doing a big parallel job, this can be helpful. If this is > 0, then after an OSError exception on the write command, the code will wait an increasing number of seconds (starting with 1 for the first failure), and then try again up to this many times.

  • +
+
+
+

Output Types

+

The default output type is ‘Fits’, which means to write a FITS file with the constructed +image in the first HDU. But other types are possible, which are specified as usual with a +type field. Other types may define additional allowed and/or required fields. +The output types defined by GalSim are:

+
    +
  • ‘Fits’ A simple fits file. This is the default if type is not given.

  • +
  • ‘MultiFits’ A multi-extension fits file.

    +
    +
      +
    • nimages = int_value (default if using an input catalog and the image type is ‘Single’ is the number of entries in the input catalog; otherwise required) The number of hdu extensions on which to draw an image.

    • +
    +
    +
  • +
  • ‘DataCube’ A fits data cube.

    +
    +
      +
    • nimages = int_value (default if using an input catalog and the image type is ‘Single’ is the number of entries in the input catalog; otherwise required) The number of images in the data cube (i.e. the third dimension of the cube).

    • +
    +
    +
  • +
+
+
+

Custom Output Types

+

To define your own output type, you will need to write an importable Python module +(typically a file in the current directory where you are running galsim, but it could also +be something you have installed in your Python distro) with a class that will be used +to build the output file.

+

The class should be a subclass of galsim.config.OutputBuilder, which is the class used for +the default ‘Fits’ type. There are a number of class methods, and you only need to override +the ones for which you want different behavior than that of the ‘Fits’ type.

+
+
+class galsim.config.OutputBuilder[source]
+

A base class for building and writing the output objects.

+

The base class defines the call signatures of the methods that any derived class should follow. +It also includes the implementation of the default output type: Fits.

+
+
+addExtraOutputHDUs(config, data, logger)[source]
+

If appropriate, add any extra output items that go into HDUs to the data list.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • data – The data to write. Usually a list of images.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

data (possibly updated with additional items)

+
+
+
+ +
+
+buildImages(config, base, file_num, image_num, obj_num, ignore, logger)[source]
+

Build the images for output.

+

In the base class, this function just calls BuildImage to build the single image to +put in the output file. So the returned list only has one item.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • file_num – The current file_num.

  • +
  • image_num – The current image_num.

  • +
  • obj_num – The current obj_num.

  • +
  • ignore – A list of parameters that are allowed to be in config that we can +ignore here. i.e. it won’t be an error if they are present.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

a list of the images built

+
+
+
+ +
+
+canAddHdus()[source]
+

Returns whether it is permissible to add extra HDUs to the end of the data list.

+

In the base class, this returns True.

+
+ +
+
+getFilename(config, base, logger)[source]
+

Get the file_name for the current file being worked on.

+

Note that the base class defines a default extension = ‘.fits’. +This can be overridden by subclasses by changing the default_ext property.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output type.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the filename to build.

+
+
+
+ +
+
+getNFiles(config, base, logger=None)[source]
+

Returns the number of files to be built.

+

In the base class, this is just output.nfiles.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the number of files to build.

+
+
+
+ +
+
+getNImages(config, base, file_num, logger=None)[source]
+

Returns the number of images to be built for a given file_num.

+

In the base class, we only build a single image, so it returns 1.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • file_num – The current file number.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the number of images to build.

+
+
+
+ +
+
+getNObjPerImage(config, base, file_num, image_num, logger=None, approx=False)[source]
+

Get the number of objects that will be made for each image built as part of the file +file_num, which starts at image number image_num, based on the information in the config +dict.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • base – The base configuration dict.

  • +
  • file_num – The current file number.

  • +
  • image_num – The current image number (the first one for this file).

  • +
  • logger – If given, a logger object to log progress.

  • +
  • approx – Whether an approximate/overestimate is ok [default: False]

  • +
+
+
Returns:
+

a list of the number of objects in each image [ nobj0, nobj1, nobj2, … ]

+
+
+
+ +
+
+setup(config, base, file_num, logger)[source]
+

Do any necessary setup at the start of processing a file.

+

The base class just calls SetupConfigRNG, but this provides a hook for sub-classes to +do more things before any processing gets started on this file.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output type.

  • +
  • base – The base configuration dict.

  • +
  • file_num – The current file_num.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
+
+ +
+
+writeExtraOutputs(config, data, logger)[source]
+

If appropriate, write any extra output items that write their own files.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • data – The data to write. Usually a list of images.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
+
+ +
+
+writeFile(data, file_name, config, base, logger)[source]
+

Write the data to a file.

+
+
Parameters:
+
    +
  • data – The data to write. Usually a list of images returned by +buildImages, but possibly with extra HDUs tacked onto the end +from the extra output items.

  • +
  • file_name – The file_name to write to.

  • +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
+
+ +
+ +

The base parameter is the original full configuration dict that is being used for running the +simulation. The config parameter is the local portion of the full dict that defines the object +being built, which would typically be base['output'].

+

Then, in the Python module, you need to register this function with some type name, which will +be the value of the type attribute that triggers the use of this Builder object:

+
galsim.config.RegisterOutputType('CustomOutput', CustomOutputBuilder())
+
+
+
+
+galsim.config.RegisterOutputType(output_type, builder)[source]
+

Register an output type for use by the config apparatus.

+
+
Parameters:
+
    +
  • output_type – The name of the type in config[‘output’]

  • +
  • builder – A builder object to use for building and writing the output file. +It should be an instance of OutputBuilder or a subclass thereof.

  • +
+
+
+
+ +

Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple output types use the same class instantiated with different +initialization parameters. This is not used by the GalSim output types, but there may be use +cases where it would be useful for custom output types.

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_output.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_output
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_output, +which will read your file and execute the registration command to add the builder to the list +of valid output types.

+

Then you can use this as a valid output type:

+
output:
+    type: CustomOutput
+    ...
+
+
+

For an example of a custom output type, see MEDSBuilder in The DES Module, +which is used by meds.yaml .

+

It may also be helpful to look at the GalSim implementation of the included output types +(click on the [source] links):

+
+
+class galsim.config.output_datacube.DataCubeBuilder[source]
+

Bases: OutputBuilder

+

Builder class for constructing and writing DataCube output types.

+
+ +
+
+class galsim.config.output_multifits.MultiFitsBuilder[source]
+

Bases: OutputBuilder

+

Builder class for constructing and writing MultiFits output types.

+
+ +
+
+

Extra Outputs

+

In addition to the fields for defining the main output file(s), there may also be fields +specifying optional “extra” outputs. Either extra files to be written, or sometimes extra HDUs +to be added to the main FITS files. These extra output fields are dicts that may have a number +of parameters defining how they should be built or where they should be written.

+
    +
  • psf will output (typically) noiseless images of the PSF used for each galaxy.

    +
    +
      +
    • file_name = str_value (either file_name or hdu is required) Write the psf image to a different file (in the same directory as the main image).

    • +
    • hdu = int_value (either file_name or hdu is required) Write the psf image to another hdu in the main file. (This option is only possible if type == ‘Fits’) Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0.

    • +
    • dir = str_value (default = output.dir if that is provided, else ‘.’) (Only relevant if file_name is provided.)

    • +
    • draw_method = str_value (default = ‘auto’) The same options are available as for the image.draw_method item, but now applying to the rendering of the psf images.

    • +
    • shift = pos_value (optional) A shift to apply to the PSF object. Special: if this is ‘galaxy’ then apply the same shift as was applied to the galaxy.

    • +
    • offset = pos_value (optional) An offset to apply when drawing the PSF object. Special: if this is ‘galaxy’ then apply the same offset as was applied when drawing the galaxy.

    • +
    • signal_to_noise = float_value (optional) If provided, noise will be added at the same level as the main image, and the flux will be rescaled to result in the provided signal-to-noise. The default is to use flux=1 and not add any noise.

    • +
    +
    +
  • +
  • weight will output the weight image (an inverse variance map of the noise properties).

    +
    +
      +
    • file_name = str_value (either file_name or hdu is required) Write the weight image to a different file (in the same directory as the main image).

    • +
    • hdu = int_value (either file_name or hdu is required) Write the weight image to another hdu in the main file. (This option is only possible if type == ‘Fits’) Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0.

    • +
    • dir = str_value (default = output.dir if that is provided, else ‘.’) (Only relevant if file_name is provided.)

    • +
    • include_obj_var = bool_value (default = False) Normally, the object variance is not included as a component for the inverse variance map. If you would rather include it, set this to True.

    • +
    +
    +
  • +
  • badpix will output the bad-pixel mask image. This will be relevant when we eventually add the ability to add defects to the images. For now the bad-pixel mask will be all 0s.

    +
    +
      +
    • file_name = str_value (either file_name or hdu is required) Write the bad pixel mask image to a different file (in the same directory as the main image).

    • +
    • hdu = int_value (either file_name or hdu is required) Write the bad pixel mask image to another hdu in the main file. (This option is only possible if type == ‘Fits’) Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0.

    • +
    • dir = str_value (default = output.dir if that is provided, else ‘.’) (Only relevant if file_name is provided.)

    • +
    +
    +
  • +
  • truth will output a truth catalog. Note: assuming you are using the galsim executable to process the config file, the config dict is really read in as an OrderedDict, so the columns in the output catalog will be in the same order as in the YAML file. If you are doing this manually and just use a regular Python dict for config, then the output columns will be in some arbitrary order.

    +
    +
      +
    • file_name = str_value (either file_name or hdu is required) Write the bad pixel mask image to a different file (in the same directory as the main image).

    • +
    • hdu = int_value (either file_name or hdu is required) Write the bad pixel mask image to another hdu in the main file. (This option is only possible if type == ‘Fits’) Note: 0 means the primary HDU, the first extension is 1. The main image is always written in hdu 0.

    • +
    • dir = str_value (default = output.dir if that is provided, else ‘.’) (Only relevant if file_name is provided.)

    • +
    • columns = dict (required) A dict connecting the names of the output columns to the values that should be output. The values can be specified in a few different ways:

      +
      +
        +
      • A string indicating what current value in the config dict to use. e.g. ‘gal.shear.g1’ would grab the value of config[‘gal’][‘shear’][‘g1’] that was used for the current object.

      • +
      • A dict that should be evaluated in the usual way values are evaluated in the config processing. Caveat: Since we do not have a way to indicate what type the return value should be, this functionality is mostly limited to ‘Eval’ and ‘Current’ types, which is normally fine, since it would mostly be useful for just doing some extra processing to some current value.

      • +
      • An implicit Eval string starting with ‘$’, typically using ‘@’ values to get Current values. e.g. to output e1-style shapes for a Shear object that was built with (g1,g2), you could write ‘$(@gal.ellip).e1’ and ‘$(@gal.ellip).e2’.

      • +
      • A straight value. Not usually very useful, but allowed. e.g. You might want your truth catalogs to have a consistent format, but some simulations may not define a particular value. You could just output -999 (or anything) for that column in those cases.

      • +
      +
      +
    • +
    +
    +
  • +
+
+
+

Adding your own Extra Output Type

+

You can also add your own extra output type in a similar fashion as the other custom types that +you can define. (cf. e.g. [Custom Output Types](#custom-output-types)) As usual, you would +write a custom module that can be imported, which should contain a class for building and +writing the extra output, register it with GalSim, and add the module to the modules field.

+

The class should be a subclass of galsim.config.ExtraOutputBuilder. You may override any +of the following methods.

+
+
+class galsim.config.ExtraOutputBuilder[source]
+

A base class for building some kind of extra output object along with the main output.

+

The base class doesn’t do anything, but it defines the function signatures that a derived +class can override to perform specific processing at any of several steps in the processing.

+

The builder gets initialized with a list and and dict to use as work space. +The typical work flow is to save something in scratch[obj_num] for each object built, and then +process them all at the end of each image into data[k]. Then finalize may do something +additional at the end of the processing to prepare the data to be written.

+

It’s worth remembering that the objects could potentially be processed in a random order if +multiprocessing is being used. The above work flow will thus work regardless of the order +that the stamps and/or images are processed.

+

Also, because of how objects are duplicated across processes during multiprocessing, you +should not count on attributes you set in the builder object during the stamp or image +processing stages to be present in the later finalize or write stages. You should write +any information you want to persist into the scratch or data objects, which are set up +to handle the multiprocessing communication properly.

+
+
+ensureFinalized(config, base, main_data, logger)[source]
+

A helper function in the base class to make sure finalize only gets called once by the +different possible locations that might need it to have been called.

+
+
Parameters:
+
    +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • main_data – The main file data in case it is needed.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the final version of the object.

+
+
+
+ +
+
+finalize(config, base, main_data, logger)[source]
+

Perform any final processing at the end of all the image processing.

+

This function will be called after all images have been built.

+

It returns some sort of final version of the object. In the base class, it just returns +self.data, but depending on the meaning of the output object, something else might be +more appropriate.

+
+
Parameters:
+
    +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • main_data – The main file data in case it is needed.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

The final version of the object.

+
+
+
+ +
+
+initialize(data, scratch, config, base, logger)[source]
+

Do any initial setup for this builder at the start of a new output file.

+

The base class implementation saves two work space items into self.data and self.scratch +that can be used to safely communicate across multiple processes.

+
+
Parameters:
+
    +
  • data – An empty list of length nimages to use as work space.

  • +
  • scratch – An empty dict that can be used as work space.

  • +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+processImage(index, obj_nums, config, base, logger)[source]
+

Perform any necessary processing at the end of each image construction.

+

This function will be called after each full image is built.

+

Remember, these images may be processed out of order. But if using the default +constructor, the data list is already set to be the correct size, so it is safe to +access self.data[k], where k = base[‘image_num’] - base[‘start_image_num’] is the +appropriate index to use for this image.

+
+
Parameters:
+
    +
  • index – The index in self.data to use for this image. This isn’t the image_num +(which can be accessed at base[‘image_num’] if needed), but rather +an index that starts at 0 for the first image being worked on and +goes up to nimages-1.

  • +
  • obj_nums – The object numbers that were used for this image.

  • +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+processSkippedStamp(obj_num, config, base, logger)[source]
+

Perform any necessary processing for stamps that were skipped in the normal processing.

+

This function will be called for stamps that are not built because they were skipped +for some reason. Normally, you would not want to do anything for the extra outputs in +these cases, but in case some module needs to do something in these cases as well, this +method can be overridden.

+
+
Parameters:
+
    +
  • obj_num – The object number

  • +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+processStamp(obj_num, config, base, logger)[source]
+

Perform any necessary processing at the end of each stamp construction.

+

This function will be called after each stamp is built, but before the noise is added, +so the existing stamp image has the true surface brightness profile (unless photon shooting +was used, in which case there will necessarily be noise from that process).

+

Remember, these stamps may be processed out of order. Saving data to the scratch dict +is safe, even if multiprocessing is being used.

+
+
Parameters:
+
    +
  • obj_num – The object number

  • +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+setupImage(config, base, logger)[source]
+

Perform any necessary setup at the start of an image.

+

This function will be called at the start of each image to allow for any setup that +needs to happen at this point in the processing.

+
+
Parameters:
+
    +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+writeFile(file_name, config, base, logger)[source]
+

Write this output object to a file.

+

The base class implementation is appropriate for the cas that the result of finalize +is a list of images to be written to a FITS file.

+
+
Parameters:
+
    +
  • file_name – The file to write to.

  • +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+writeHdu(config, base, logger)[source]
+

Write the data to a FITS HDU with the data for this output object.

+

The base class implementation is appropriate for the cas that the result of finalize +is a list of images of length 1 to be written to a FITS file.

+
+
Parameters:
+
    +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

an HDU with the output data.

+
+
+
+ +
+ +

Then, in the Python module, you need to register this function with some type name, which will +be the value of the attribute in the output field that triggers the use of this Builder object:

+
galsim.config.RegisterExtraOutput('CustomExtraOutput', CustomExtraOutputBuilder())
+
+
+
+
+galsim.config.RegisterExtraOutput(key, builder)[source]
+

Register an extra output field for use by the config apparatus.

+

The builder parameter should be a subclass of galsim.config.ExtraOutputBuilder. +See that class for the functions that should be defined and their signatures. +Not all functions need to be overridden. If nothing needs to be done at a particular place +in the processing, you can leave the base class function, which doesn’t do anything.

+
+
Parameters:
+
    +
  • key – The name of the output field in config[‘output’]

  • +
  • builder – A builder object to use for building the extra output object. +It should be an instance of a subclass of ExtraOutputBuilder.

  • +
+
+
+
+ +

Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple output types use the same class instantiated with different +initialization parameters. This is not used by the GalSim output types, but there may be use +cases where it would be useful for custom output types.

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_output.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_output
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_output, +which will read your file and execute the registration command to add the builder to the list +of valid output types.

+

Then you can use this as a valid extra output directive:

+
output:
+    custom_extra_output:
+        ...
+
+
+

For examples of custom extra outputs, see

+ +

which use custom extra outputs deblend and deblend_meds defined in blend.py .

+

Also,

+ +

which uses custom extra output noise_free defined in noise_free.py .

+

It may also be helpful to look at the GalSim implementation of the included extra output types +(click on the [source] links):

+
+
+class galsim.config.extra_psf.ExtraPSFBuilder[source]
+

Bases: ExtraOutputBuilder

+

Build an image that draws the PSF at the same location as each object on the main image.

+

This makes the most sense when the main image consists of non-overlapping stamps, such as +a TiledImage, since you wouldn’t typically want the PSF images to overlap. But it just +follows whatever pattern of stamp locations the main image has.

+
+ +
+
+class galsim.config.extra_truth.TruthBuilder[source]
+

Bases: ExtraOutputBuilder

+

Build an output truth catalog with user-defined columns, typically taken from +current values of various quantities for each constructed object.

+
+ +
+
+class galsim.config.extra_weight.WeightBuilder[source]
+

Bases: ExtraOutputBuilder

+

This builds a weight map image to go along with each regular data image.

+

The weight is the inverse variance of the noise in the image.

+

There is a option called ‘include_obj_var’ that governs whether the weight should include the +Poisson variance of the signal. In real data, you don’t know the true signal, and estimating +the Poisson noise from the realized image can lead to biases. As such, different applications +may or may not want this included.

+
+ +
+
+class galsim.config.extra_badpix.BadPixBuilder[source]
+

Bases: ExtraOutputBuilder

+

This builds a bad pixel mask image to go along with each regular data image.

+

There’s not much here currently, since GalSim doesn’t yet have any image artifacts that +would be appropriate to do something with here. So this is mostly just a placeholder for +when we eventually add defects, saturation, etc.

+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_process.html b/docs/_build/html/config_process.html new file mode 100644 index 00000000000..fabd1bedcb2 --- /dev/null +++ b/docs/_build/html/config_process.html @@ -0,0 +1,1838 @@ + + + + + + + Config Processing From Python — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Processing From Python

+

It is also possible to run the config processing from a Python script, rather than using +The galsim Executable. An example of this can be found in +demo8 .

+
+

Running the Whole Script

+

The following functions are relevant to running the whole config script from Python:

+
+
+galsim.config.ReadConfig(config_file, file_type=None, logger=None)[source]
+

Read in a configuration file and return the corresponding dicts.

+

A YAML file is allowed to define several dicts using multiple documents. The GalSim parser +treats this as a set of multiple jobs to be done. The first document is taken to be a “base” +dict that has common definitions for all the jobs. Then each subsequent document has the +(usually small) modifications to the base dict for each job. See demo6.yaml, demo8.yaml and +demo9.yaml in the GalSim/examples directory for example usage.

+

On output, the returned list will have an entry for each job to be done. If there are +multiple documents, then the first dict is a merge of the first two documents, the +second a merge of the first and third, and so on. Each job includes the first document +merged with each subseqent document in turn. If there is only one document defined, +the returned list will have one element, which is this dict.

+

A JSON file does not have this feature, but to be consistent, we always return a list, +which would only have one element in this case.

+
+
Parameters:
+
    +
  • config_file – The name of the configuration file to read.

  • +
  • file_type – If given, the type of file to read. [default: None, which mean +infer the file type from the extension.]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

list of config dicts

+
+
+
+ +
+
+galsim.config.CopyConfig(config)[source]
+

If you want to use a config dict for multiprocessing, you need to deep copy +the gal, psf, and pix fields, since they cache values that are not picklable. +If you don’t do the deep copy, then python balks when trying to send the updated +config dict back to the root process. We do this a few different times, so encapsulate +the copy semantics once here.

+
+
Parameters:
+

config – The configuration dict to copy.

+
+
Returns:
+

a deep copy of the config dict.

+
+
+
+ +
+
+galsim.config.ImportModules(config, gdict=None)[source]
+

Import any modules listed in config[‘modules’].

+

These won’t be brought into the running scope of the config processing, but any side +effects of the import statements will persist. In particular, these are allowed to +register additional custom types that can then be used in the current config dict.

+
+
Parameters:
+

config – The configuration dict.

+
+
+
+ +
+
+galsim.config.ProcessTemplate(config, base, logger=None)[source]
+

If the config dict has a ‘template’ item, read in the appropriate file and +make any requested updates.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.ProcessAllTemplates(config, logger=None, base=None)[source]
+

Check through the full config dict and process any fields that have a ‘template’ item.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • base – The base configuration dict. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.Process(config, logger=None, njobs=1, job=1, new_params=None, except_abort=False)[source]
+

Do all processing of the provided configuration dict. In particular, this +function handles processing the output field, calling other functions to +build and write the specified files. The input field is processed before +building each file.

+

Sometimes, it can be helpful to split up a processing jobs over multiple machines +(i.e. not just multiple processes, which can be handled natively with the output.nproc +or image.nproc options). In this case, you can ask the Process command to split up +the total amount of work into njobs and only do one of those jobs here. To do this, +set njobs to be the number of jobs total and job to be which job should be done here.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • njobs – The total number of jobs to split the work into. [default: 1]

  • +
  • job – Which job should be worked on here (1..njobs). [default: 1]

  • +
  • new_params – A dict of new parameter values that should be used to update the config +dict after any template loading (if any). [default: None]

  • +
  • except_abort – Whether to abort processing when a file raises an exception (True) +or just report errors and continue on (False). [default: False]

  • +
+
+
Returns:
+

the final config dict that was used.

+
+
+
+ +
+
+

Building Files

+

The following functions are relevant to building one or more files as specified by a config +dict:

+
+
+galsim.config.BuildFiles(nfiles, config, file_num=0, logger=None, except_abort=False)[source]
+

Build a number of output files as specified in config.

+
+
Parameters:
+
    +
  • nfiles – The number of files to build.

  • +
  • config – A configuration dict.

  • +
  • file_num – If given, the first file_num. [default: 0]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • except_abort – Whether to abort processing when a file raises an exception (True) +or just report errors and continue on (False). [default: False]

  • +
+
+
Returns:
+

the final config dict that was used.

+
+
+
+ +
+
+galsim.config.BuildFile(config, file_num=0, image_num=0, obj_num=0, logger=None)[source]
+

Build an output file as specified in config.

+
+
Parameters:
+
    +
  • config – A configuration dict.

  • +
  • file_num – If given, the current file_num. [default: 0]

  • +
  • image_num – If given, the current image_num. [default: 0]

  • +
  • obj_num – If given, the current obj_num. [default: 0]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

(file_name, t), a tuple of the file name and the time taken to build file +Note: t==0 indicates that this file was skipped.

+
+
+
+ +
+
+galsim.config.GetNFiles(config, logger=None)[source]
+

Get the number of files that will be made, based on the information in the config dict.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the number of files

+
+
+
+ +
+
+galsim.config.GetNImagesForFile(config, file_num, logger=None)[source]
+

Get the number of images that will be made for the file number file_num, based on the +information in the config dict.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • file_num – The current file number.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the number of images

+
+
+
+ +
+
+galsim.config.GetNObjForFile(config, file_num, image_num, logger=None, approx=False)[source]
+

Get the number of objects that will be made for each image built as part of the file file_num, +which starts at image number image_num, based on the information in the config dict.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • file_num – The current file number.

  • +
  • image_num – The current image number.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • approx – Whether an approximate/overestimate is ok [default: False]

  • +
+
+
Returns:
+

a list of the number of objects in each image [ nobj0, nobj1, nobj2, … ]

+
+
+
+ +
+
+galsim.config.SetupConfigFileNum(config, file_num, image_num, obj_num, logger=None)[source]
+

Do the basic setup of the config dict at the file processing level.

+

Includes: +- Set config[‘file_num’] = file_num +- Set config[‘image_num’] = image_num +- Set config[‘obj_num’] = obj_num +- Set config[‘index_key’] = ‘file_num’ +- Set config[‘start_image_num’] = image_num +- Set config[‘start_obj_num’] = obj_num +- Make sure config[‘output’] exists +- Set default config[‘output’][‘type’] to ‘Fits’ if not specified +- Check that the specified output type is valid.

+
+
Parameters:
+
    +
  • config – A configuration dict.

  • +
  • file_num – The current file_num. (If file_num=None, then don’t set file_num or +start_obj_num items in the config dict.)

  • +
  • image_num – The current image_num.

  • +
  • obj_num – The current obj_num.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+

Building Images

+

The following functions are relevant to building one or more images as specified by a config +dict:

+
+
+galsim.config.BuildImages(nimages, config, image_num=0, obj_num=0, logger=None)[source]
+

Build a number of postage stamp images as specified by the config dict.

+
+
Parameters:
+
    +
  • nimages – How many images to build.

  • +
  • config – The configuration dict.

  • +
  • image_num – If given, the current image number. [default: 0]

  • +
  • obj_num – If given, the first object number in the image. [default: 0]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

a list of images

+
+
+
+ +
+
+galsim.config.BuildImage(config, image_num=0, obj_num=0, logger=None)[source]
+

Build an Image according to the information in config.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • image_num – If given, the current image number. [default: 0]

  • +
  • obj_num – If given, the first object number in the image. [default: 0]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the final image

+
+
+
+ +
+
+galsim.config.SetupConfigImageSize(config, xsize, ysize, logger=None)[source]
+

Do some further setup of the config dict at the image processing level based on +the provided image size.

+
    +
  • Set config[‘image_xsize’], config[‘image_ysize’] to the size of the image

  • +
  • Set config[‘image_origin’] to the origin of the image

  • +
  • Set config[‘image_center’] to the center of the image

  • +
  • Set config[‘image_bounds’] to the bounds of the image

  • +
  • Build the WCS based on either config[‘image’][‘wcs’] or config[‘image’][‘pixel_scale’]

  • +
  • Set config[‘wcs’] to be the built wcs

  • +
  • If wcs.isPixelScale(), also set config[‘pixel_scale’] for convenience.

  • +
  • Set config[‘world_center’] to either a given value or based on wcs and image_center

  • +
  • Create a blank image if possible and store as config[‘current_image’]

  • +
+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • xsize – The size of the image in the x-dimension.

  • +
  • ysize – The size of the image in the y-dimension.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.SetupConfigImageNum(config, image_num, obj_num, logger=None)[source]
+

Do the basic setup of the config dict at the image processing level.

+

Includes: +- Set config[‘image_num’] = image_num +- Set config[‘obj_num’] = obj_num +- Set config[‘index_key’] = ‘image_num’ +- Make sure config[‘image’] exists +- Set default config[‘image’][‘type’] to ‘Single’ if not specified +- Check that the specified image type is valid.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • image_num – The current image number.

  • +
  • obj_num – The first object number in the image.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.GetNObjForImage(config, image_num, logger=None, approx=False)[source]
+

Get the number of objects that will be made for the image number image_num based on +the information in the config dict.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • image_num – The current image number.

  • +
  • logger – If given, a logger object to log progress.

  • +
  • approx – Whether an approximate/overestimate is ok [default: False]

  • +
+
+
Returns:
+

the number of objects

+
+
+
+ +
+
+galsim.config.FlattenNoiseVariance(config, full_image, stamps, current_vars, logger)[source]
+

This is a helper function to bring the noise level up to a constant value +across the image. If some of the galaxies are RealGalaxy objects and noise whitening +(or symmetrizing) is turned on, then there will already be some noise in the +stamps that get built. This function goes through and figures out what the maximum +current variance is anywhere in the full image and adds noise to the other pixels +to bring everything up to that level.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • full_image – The full image onto which the noise should be added.

  • +
  • stamps – A list of the individual postage stamps.

  • +
  • current_vars – A list of the current variance in each postage stamps.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the final variance in the image

+
+
+
+ +
+
+galsim.config.BuildWCS(config, key, base, logger=None)[source]
+

Read the wcs parameters from config[key] and return a constructed wcs object.

+
+
Parameters:
+
    +
  • config – A dict with the configuration information. (usually base[‘image’])

  • +
  • key – The key name in config indicating which object to build.

  • +
  • base – The base dict of the configuration.

  • +
  • logger – Optionally, provide a logger for logging debug statements. [default: None]

  • +
+
+
Returns:
+

a BaseWCS instance

+
+
+
+ +
+
+galsim.config.AddSky(config, im)[source]
+

Add the sky level to the image

+
+
Parameters:
+
    +
  • config – The (base) configuration dict

  • +
  • im – The image onto which to add the sky

  • +
+
+
+
+ +
+
+galsim.config.AddNoise(config, im, current_var=0.0, logger=None)[source]
+

Add noise to an image according to the noise specifications in the noise dict.

+
+
Parameters:
+
    +
  • config – The (base) configuration dict

  • +
  • im – The image onto which to add the noise

  • +
  • current_var – The current noise variance present in the image already [default: 0]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

Variance added to the image (units are ADU if gain != 1)

+
+
+
+ +
+
+galsim.config.CalculateNoiseVariance(config, full=False)[source]
+

Calculate the noise variance from the noise specified in the noise dict.

+
+
Parameters:
+
    +
  • config – The (base) configuration dict

  • +
  • full – If the noise is variable across the image, return the full image with the +noise variance at every pixel. Otherwise, just return the value at the center.

  • +
+
+
Returns:
+

the noise variance (units are ADU if gain != 1)

+
+
+
+ +
+
+galsim.config.AddNoiseVariance(config, im, include_obj_var=False, logger=None)[source]
+

Add the noise variance to an image according to the noise specifications in the noise dict. +Typically, this is used for building a weight map, which is typically the inverse variance.

+
+
Parameters:
+
    +
  • config – The (base) configuration dict

  • +
  • im – The image onto which to add the variance values

  • +
  • include_obj_var – Whether to add the variance from the object photons for noise +models that have a component based on the number of photons. +Note: if this is True, the returned variance will not include this +contribution to the noise variance. [default: False]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the variance in the image (units are ADU if gain != 1)

+
+
+
+ +
+
+galsim.config.GetSky(config, base, logger=None, full=False)[source]
+

Parse the sky information and return either a float value for the sky level per pixel +or an image, as needed.

+

If an image is required (because wcs is not uniform) then it will use the presence of +base[‘image_pos’] to determine what size image to return (stamp or full). If there is +a current image_pos, then we are doing a stamp. Otherwise a full image.

+
+
Parameters:
+
    +
  • config – The configuration field with the sky specification, which can be either +base[‘image’] or base[‘image’][‘noise’]

  • +
  • base – The base configuration dict

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • full – If the sky level is variable across the image, return the full +image with the sky at every pixel. Otherwise, just return the +sky at the image center.

  • +
+
+
Returns:
+

sky, either a float value or an Image. (The latter only if full=True)

+
+
+
+ +
+
+

Building Stamps

+

The following functions are relevant to building one or more stamps as specified by a config +dict:

+
+
+galsim.config.BuildStamps(nobjects, config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None)[source]
+

Build a number of postage stamp images as specified by the config dict.

+
+
Parameters:
+
    +
  • nobjects – How many postage stamps to build.

  • +
  • config – A configuration dict.

  • +
  • obj_num – If given, the current obj_num. [default: 0]

  • +
  • xsize – The size of a single stamp in the x direction. [default: 0, +which means to look first for config.stamp.xsize, then for +config.image.stamp_xsize, and if neither are given, then use +automatic sizing.]

  • +
  • ysize – The size of a single stamp in the y direction. [default: 0, +which means to look first for config.stamp.ysize, then for +config.image.stamp_ysize, and if neither are given, then use +automatic sizing.]

  • +
  • do_noise – Whether to add noise to the image (according to config[‘noise’]). +[default: True]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the tuple (images, current_vars). Both are themselves tuples.

+
+
+
+ +
+
+galsim.config.BuildStamp(config, obj_num=0, xsize=0, ysize=0, do_noise=True, logger=None)[source]
+

Build a single stamp image using the given config file

+
+
Parameters:
+
    +
  • config – A configuration dict.

  • +
  • obj_num – If given, the current obj_num [default: 0]

  • +
  • xsize – The xsize of the stamp to build (if known). [default: 0]

  • +
  • ysize – The ysize of the stamp to build (if known). [default: 0]

  • +
  • do_noise – Whether to add noise to the image (according to config[‘noise’]). +[default: True]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the tuple (image, current_var)

+
+
+
+ +
+
+galsim.config.SetupConfigStampSize(config, xsize, ysize, image_pos, world_pos, logger=None)[source]
+

Do further setup of the config dict at the stamp (or object) processing level reflecting +the stamp size and position in either image or world coordinates.

+

Note: This is now a StampBuilder method. So this function just calls StampBuilder.locateStamp.

+
+
Parameters:
+
    +
  • config – A configuration dict.

  • +
  • xsize – The size of the stamp in the x-dimension. [may be 0 if unknown]

  • +
  • ysize – The size of the stamp in the y-dimension. [may be 0 if unknown]

  • +
  • image_pos – The position of the stamp in image coordinates. [may be None]

  • +
  • world_pos – The position of the stamp in world coordinates. [may be None]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.SetupConfigObjNum(config, obj_num, logger=None)[source]
+

Do the basic setup of the config dict at the stamp (or object) processing level.

+

Includes:

+
    +
  • Set config[‘obj_num’] = obj_num

  • +
  • Set config[‘index_key’] = ‘obj_num’

  • +
  • Make sure config[‘stamp’] exists

  • +
  • Set default config[‘stamp’][‘type’] to ‘Basic’

  • +
  • Copy over values from config[‘image’] that are allowed there, but really belong +in config[‘stamp’].

  • +
  • Set config[‘stamp’][‘draw_method’] to ‘auto’ if not given.

  • +
+
+
Parameters:
+
    +
  • config – A configuration dict.

  • +
  • obj_num – The current obj_num.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.DrawBasic(prof, image, method, offset, config, base, logger, **kwargs)[source]
+

The basic implementation of the draw command

+

This function is provided as a free function, rather than just the base class implementation +in StampBuilder to make it easier for classes derived from StampBuilder to use to help +implement their draw functions. The base class, StampBuilder, just calls this function +for its draw method.

+

This version also allows for additional kwargs, which are passed on to the drawImage function. +e.g. you can add add_to_image=True or setup_only=True if these are helpful.

+
+
Parameters:
+
    +
  • prof – The profile to draw.

  • +
  • image – The image onto which to draw the profile (which may be None).

  • +
  • method – The method to use in drawImage.

  • +
  • offset – The offset to apply when drawing.

  • +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • logger – A logger object to log progress.

  • +
  • **kwargs – Any additional kwargs are passed along to the drawImage function.

  • +
+
+
Returns:
+

the resulting image

+
+
+
+ +
+
+

Building Objects

+

The following functions are relevant to building individual objects as specified by a config +dict:

+
+
+galsim.config.BuildGSObject(config, key, base=None, gsparams={}, logger=None)[source]
+

Build a GSObject from the parameters in config[key].

+
+
Parameters:
+
    +
  • config – A dict with the configuration information.

  • +
  • key – The key name in config indicating which object to build.

  • +
  • base – The base dict of the configuration. [default: config]

  • +
  • gsparams – Optionally, provide non-default GSParams items. Any gsparams specified +at this level will be added to the list. This should be a dict with +whatever kwargs should be used in constructing the GSParams object. +[default: {}]

  • +
  • logger – Optionally, provide a logger for logging debug statements. +[default: None]

  • +
+
+
Returns:
+

the tuple (gsobject, safe), where gsobject is the built object, and safe is +a bool that says whether it is safe to use this object again next time.

+
+
+
+ +
+
+galsim.config.UpdateGSParams(gsparams, config, base)[source]
+

Add additional items to the gsparams dict based on config[‘gsparams’].

+
+
Parameters:
+
    +
  • gsparams – A dict with whatever kwargs should be used in constructing the GSParams object.

  • +
  • config – A dict with the configuration information.

  • +
  • base – The base dict of the configuration.

  • +
+
+
Returns:
+

an updated gsparams dict

+
+
+
+ +
+
+galsim.config.TransformObject(gsobject, config, base, logger)[source]
+

Applies ellipticity, rotation, gravitational shearing and centroid shifting to a +supplied GSObject, in that order.

+
+
Parameters:
+
    +
  • gsobject – The GSObject to be transformed.

  • +
  • config – A dict with the tranformation information for this object.

  • +
  • base – The base dict of the configuration.

  • +
  • logger – A logger for logging debug statements.

  • +
+
+
Returns:
+

transformed GSObject.

+
+
+
+ +
+
+class galsim.config.SkipThisObject(message=None)[source]
+

A class that a builder can throw to indicate that nothing went wrong, but for some +reason, this particular object should be skipped and just move onto the next object. +The constructor takes an optional message that will be output to the logger if +logging is active.

+
+ +
+
+

Generating Values

+

The following functions are relevant to generating and accessing individual values as specified by +a config dict:

+
+
+galsim.config.ParseValue(config, key, base, value_type)[source]
+

Read or generate a parameter value from config.

+
+
Parameters:
+
    +
  • config – The config dict from which to parse the value.

  • +
  • key – The key value in the dict to parse.

  • +
  • base – The base config dict. [default: None, which means use base=config]

  • +
  • value_type – The value_type expected. [default: None, which means it won’t check +that the value is the right type.]

  • +
+
+
Returns:
+

the tuple (value, safe).

+
+
+
+ +
+
+galsim.config.GetCurrentValue(key, config, value_type=None, base=None)[source]
+

Get the current value of another config item given the key name.

+
+
Parameters:
+
    +
  • key – The (extended) key value in the dict to get the current value of.

  • +
  • config – The config dict from which to get the key.

  • +
  • value_type – The value_type expected. [default: None, which means it won’t check +that the value is the right type.]

  • +
  • base – The base config dict. [default: None, which means use base=config]

  • +
+
+
Returns:
+

the current value

+
+
+
+ +
+
+galsim.config.EvaluateCurrentValue(key, config, base, value_type=None)[source]
+

Helper function to evaluate the current value at config[key] where key is no longer +an extended key, and config is the local dict where it is relevant.

+
+
Parameters:
+
    +
  • key – The key value in the dict to get the current value of.

  • +
  • config – The config dict from which to get the key.

  • +
  • base – The base config dict.

  • +
  • value_type – The value_type expected. [default: None, which means it won’t check +that the value is the right type.]

  • +
+
+
+
+ +
+
+galsim.config.SetDefaultIndex(config, num)[source]
+

When the number of items in a list is known, we allow the user to omit some of +the parameters of a Sequence or Random and set them automatically based on the +size of the list, catalog, etc.

+
+
Parameters:
+
    +
  • config – The config dict with the field to be updated.

  • +
  • num – The number to use for the length of the index Sequence if appropriate.

  • +
+
+
+
+ +
+
+galsim.config.CheckAllParams(config, req={}, opt={}, single=[], ignore=[])[source]
+

Check that the parameters for a particular item are all valid

+
+
Parameters:
+
    +
  • config – The config dict to check

  • +
  • req – The required items [default: {}]

  • +
  • opt – The optional items [default: {}]

  • +
  • single – List of items where exactly one is required [default: []]

  • +
  • ignore – Items to ignore [default: []]

  • +
+
+
Returns:
+

a dict, get, with get[key] = value_type for all keys to get.

+
+
+
+ +
+
+galsim.config.GetAllParams(config, base, req={}, opt={}, single=[], ignore=[])[source]
+

Check and get all the parameters for a particular item

+
+
Parameters:
+
    +
  • config – The config dict from which to get items.

  • +
  • req – The required items [default: {}]

  • +
  • opt – The optional items [default: {}]

  • +
  • single – List of items where exactly one is required [default: []]

  • +
  • ignore – Items to ignore [default: []]

  • +
+
+
Returns:
+

the tuple (kwargs, safe).

+
+
+
+ +
+
+galsim.config.ParseWorldPos(config, param_name, base, logger)[source]
+

A helper function to parse the ‘world_pos’ value.

+

The world_pos can be specified either as a regular RA, Dec (which in GalSim is known as a +CelestialCoord) or as Euclidean coordinates in the local tangent plane relative to the +image center (a PositionD).

+
    +
  1. For the RA/Dec option, the world_pos field should use the type RADec, which includes two +values named ra and dec, each of which should be an Angle type. e.g.:

    +
    world_pos:
    +    type : RADec
    +    ra : 37 hours
    +    dec: -23 degrees
    +
    +
    +

    Technically, any other type that results in a CelestialCoord is valid, but RADec is the +only one that is defined natively in GalSim.

    +
  2. +
  3. For the relative position in the local tangent plane (where 0,0 is the position of the +image center), you can use any PositionD type. e.g.:

    +
    world_pos:
    +    type : RandomCircle
    +    radius : 12       # arcsec
    +    inner_radius : 3  # arcsec
    +
    +
    +
  4. +
+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • param_name – The name of the field in the config dict to parse as a world_pos. +Normally, this is just ‘world_pos’.

  • +
  • base – The base configuration dict.

  • +
+
+
Returns:
+

either a CelestialCoord or a PositionD instance.

+
+
+
+ +
+
+

Using Input Fields

+

The following functions are relevant to processing and using input fields in a config dict:

+
+
+galsim.config.ProcessInput(config, logger=None, file_scope_only=False, safe_only=False)[source]
+

Process the input field, reading in any specified input files or setting up +any objects that need to be initialized.

+

Each item registered as a valid input type will be built and available at the top level +of config in config[‘_input_objs’]. Since there is allowed to be more than one of each type +of input object (e.g. multilpe catalogs or multiple dicts), these are actually lists. +If there is only one e.g. catalog entry in config[‘input’], then this list will have one +element.

+

e.g. config[‘_input_objs’][‘catalog’][0] holds the first catalog item defined in +config[‘input’][‘catalog’] (if any).

+
+
Parameters:
+
    +
  • config – The configuration dict to process

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • file_scope_only – If True, only process the input items that are marked as being +possibly relevant for file- and image-level items. [default: False]

  • +
  • safe_only – If True, only process the input items whose construction parameters +are not going to change every file, so it can be made once and +used by multiple processes if appropriate. [default: False]

  • +
+
+
+
+ +
+
+galsim.config.ProcessInputNObjects(config, logger=None, approx=False)[source]
+

Process the input field, just enough to determine the number of objects.

+

Some input items are relevant for determining the number of objects in a file or image. +This means we need to have them processed before splitting up jobs over multiple processes +(since the seed increments based on the number of objects). So this function builds +the input items that have a getNObjects() method and returns the number of objects.

+
+
Caveat: This function tries each input type in galsim.config.valid_input_types in

order and returns the nobjects for the first one that works. If multiple input +items have nobjects and they are inconsistent, this function may return a +number of objects that isn’t what you wanted. In this case, you should explicitly +set nobjects or nimages in the configuration dict, rather than relying on this +galsim.config “magic”.

+
+
+
+
Parameters:
+
    +
  • config – The configuration dict to process

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • approx – Whether an approximate count is ok. [default: False]

  • +
+
+
Returns:
+

the number of objects to use.

+
+
+
+ +
+
+galsim.config.SetupInput(config, logger=None)[source]
+

Process the input field if it hasn’t been processed yet.

+

This is mostly useful if the user isn’t running through the full processing and just starting +at BuildImage say. This will make sure the input objects are set up in the way that they +normally would have been by the first level of processing in a galsim config_file run.

+
+
Parameters:
+
    +
  • config – The configuration dict in which to setup the input items.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.SetupInputsForImage(config, logger=None)[source]
+

Do any necessary setup of the input items at the start of an image.

+
+
Parameters:
+
    +
  • config – The configuration dict to process

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.GetInputObj(input_type, config, base, param_name, num=0)[source]
+

Get the input object needed for generating a particular value

+
+
Parameters:
+
    +
  • input_type – The type of input object to get

  • +
  • config – The config dict for this input item

  • +
  • base – The base config dict

  • +
  • param_name – The type of value that we are trying to construct (only used for +error messages).

  • +
  • num – Which number in the list of this key, if needed. [default: 0]

  • +
+
+
+
+ +
+
+

Processing Extra Outputs

+

The following functions are relevant to processing extra output fields in a config dict:

+
+
+galsim.config.SetupExtraOutput(config, logger=None)[source]
+

Set up the extra output items as necessary, including building Managers for the work +space so they can work safely in multi-processing mode. Each builder will be placed in +config[‘extra_builder’][key] where key is the key in galsim.config.valid_extra_outputs.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.SetupExtraOutputsForImage(config, logger=None)[source]
+

Perform any necessary setup for the extra output items at the start of a new image.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.ProcessExtraOutputsForStamp(config, skip, logger=None)[source]
+

Run the appropriate processing code for any extra output items that need to do something +at the end of building each object.

+

This gets called after all the object flux is added to the stamp, but before the sky level +and noise are added.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • skip – Was the drawing of this object skipped?

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.ProcessExtraOutputsForImage(config, logger=None)[source]
+

Run the appropriate processing code for any extra output items that need to do something +at the end of building each image

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.WriteExtraOutputs(config, main_data, logger=None)[source]
+

Write the extra output objects to files.

+

This gets run at the end of the functions for building the regular output files.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • main_data – The main file data in case it is needed.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.AddExtraOutputHDUs(config, main_data, logger=None)[source]
+

Write the extra output objects to either HDUS or images as appropriate and add them +to the existing data.

+

This gets run at the end of the functions for building the regular output files.

+

Note: the extra items must have hdu numbers ranging continuously (in any order) starting +at len(data). Typically first = 1, since the main image is the primary HDU, numbered 0.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • main_data – The main file data as a list of images. Usually just [image] where +image is the primary image to be written to the output file.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

data with additional hdus added

+
+
+
+ +
+
+galsim.config.CheckNoExtraOutputHDUs(config, output_type, logger=None)[source]
+

Check that none of the extra output objects want to add to the HDU list.

+

Raises an exception if one of them has an hdu field.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • output_type – A string to use in the error message to indicate which output type +had a problem.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+
+galsim.config.GetFinalExtraOutput(key, config, main_data=[], logger=None)[source]
+

Get the finalized output object for the given extra output key

+
+
Parameters:
+
    +
  • key – The name of the output field in config[‘output’]

  • +
  • config – The configuration dict.

  • +
  • main_data – The main file data in case it is needed. [default: []]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the final data to be output.

+
+
+
+ +
+
+

Config Utilities

+

The following functions are used internally by the various galsim.config functions, +but they might be useful for some users.

+
+
+class galsim.config.LoggerWrapper(logger)[source]
+

A wrap around a Logger object that checks whether a debug or info or warn call will +actually produce any output before calling the functions.

+

This seems like a gratuitous wrapper, and it is if the object being wrapped is a real +Logger object. However, we use it to wrap proxy objects (returned from GetLoggerProxy) +that would otherwise send the arguments of logger.debug(…) calls through a multiprocessing +pipe before (typically) being ignored. Here, we check whether the call will actually +produce any output before calling the functions.

+
+
Parameters:
+

logger – The logger object to wrap.

+
+
+
+ +
+
+galsim.config.ReadYaml(config_file)[source]
+

Read in a YAML configuration file and return the corresponding dicts.

+

A YAML file is allowed to define several dicts using multiple documents. The GalSim parser +treats this as a set of multiple jobs to be done. The first document is taken to be a “base” +dict that has common definitions for all the jobs. Then each subsequent document has the +(usually small) modifications to the base dict for each job. See demo6.yaml, demo8.yaml and +demo9.yaml in the GalSim/examples directory for example usage.

+

The return value will be a list of dicts, one dict for each job to be done.

+
+
Parameters:
+

config_file – The name of the configuration file to read.

+
+
Returns:
+

list of config dicts

+
+
+
+ +
+
+galsim.config.ReadJson(config_file)[source]
+

Read in a JSON configuration file and return the corresponding dicts.

+

A JSON file only defines a single dict. However to be parallel to the functionality of +ReadYaml, the output is a list with a single item, which is the dict defined by the JSON file.

+
+
Parameters:
+

config_file – The name of the configuration file to read.

+
+
Returns:
+

[config_dict]

+
+
+
+ +
+
+galsim.config.MergeConfig(config1, config2, logger=None)[source]
+

Merge config2 into config1 such that it has all the information from either config1 or +config2 including places where both input dicts have some of a field defined.

+

e.g. If config1 has image.pixel_scale, and config2 has image.noise, then the returned dict +will have both.

+

For real conflicts (the same value in both cases), config1’s value takes precedence

+
+ +
+
+galsim.config.ConvertNones(config)[source]
+

Convert any items whose value is ‘None’ to None.

+

To allow some parameters to be set to None in the config dict (e.g. in a list, where only +some values need to be None), we convert all values == ‘None’ to None.

+
+
Parameters:
+

config – The config dict to process

+
+
+
+ +
+
+galsim.config.RemoveCurrent(config, keep_safe=False, type=None, index_key=None)[source]
+

Remove any “current” values stored in the config dict at any level.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • keep_safe – Should current values that are marked as safe be preserved? +[default: False]

  • +
  • type – If provided, only clear the current value of objects that use this +particular type. [default: None, which means to remove current values +of all types.]

  • +
  • index_key – If provided, only clear the current value of objects that use this +index_key (or start with this index_key, so obj_num also does +obj_num_in_file). [default: None]

  • +
+
+
+
+ +
+
+galsim.config.GetLoggerProxy(logger)[source]
+

Make a proxy for the given logger that can be passed into multiprocessing Processes +and used safely.

+
+
Parameters:
+

logger – The logger to make a copy of

+
+
Returns:
+

a proxy for the given logger

+
+
+
+ +
+
+galsim.config.UpdateNProc(nproc, ntot, config, logger=None)[source]
+

Update nproc

+
    +
  • If nproc < 0, set nproc to ncpu

  • +
  • Make sure nproc <= ntot

  • +
+
+
Parameters:
+
    +
  • nproc – The nominal number of processes from the config dict

  • +
  • ntot – The total number of files/images/stamps to do, so the maximum number of +processes that would make sense.

  • +
  • config – The configuration dict to copy.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the number of processes to use.

+
+
+
+ +
+
+galsim.config.ParseRandomSeed(config, param_name, base, seed_offset)[source]
+

Parse the random_seed field, converting an integer (initial) seed value into a +Sequence.

+
+ +
+
+galsim.config.PropagateIndexKeyRNGNum(config, index_key=None, rng_num=None, rng_index_key=None, key=None)[source]
+

Propagate any index_key or rng_num specification in a dict to all sub-fields

+
+ +
+
+galsim.config.SetupConfigRNG(config, seed_offset=0, logger=None)[source]
+

Set up the RNG in the config dict.

+
    +
  • Setup config[‘image’][‘random_seed’] if necessary

  • +
  • Set config[‘rng’] and other related values based on appropriate random_seed

  • +
+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • seed_offset – An offset to use relative to what config[‘image’][‘random_seed’] gives. +[default: 0]

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

the seed used to initialize the RNG.

+
+
+
+ +
+
+galsim.config.ParseExtendedKey(config, key)[source]
+

Traverse all but the last item in an extended key and return the resulting config, key.

+

If key is an extended key like gal.items.0.ellip.e, then this will return the tuple. +(config[‘gal’][‘items’][0][‘ellip’], ‘e’).

+

If key is a regular string, then is just returns the original (config, key).

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • key – The possibly extended key.

  • +
+
+
Returns:
+

the equivalent (config, key) where key is now a regular non-extended key.

+
+
+
+ +
+
+galsim.config.GetFromConfig(config, key)[source]
+

Get the value for the (possibly extended) key from a config dict.

+

If key is a simple string, then this is equivalent to config[key]. +However, key is allowed to be a chain of keys such as ‘gal.items.0.ellip.e’, in which +case this function will return config[‘gal’][‘items’][0][‘ellip’][‘e’].

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • key – The possibly extended key.

  • +
+
+
Returns:
+

the value of that key from the config.

+
+
+
+ +
+
+galsim.config.SetInConfig(config, key, value, logger=None)[source]
+

Set the value of a (possibly extended) key in a config dict.

+

If key is a simple string, then this is equivalent to config[key] = value. +However, key is allowed to be a chain of keys such as ‘gal.items.0.ellip.e’, in which +case this function will set config[‘gal’][‘items’][0][‘ellip’][‘e’] = value.

+
+
Parameters:
+
    +
  • config – The configuration dict.

  • +
  • key – The possibly extended key.

  • +
+
+
Returns:
+

the value of that key from the config.

+
+
+
+ +
+
+galsim.config.UpdateConfig(config, new_params, logger=None)[source]
+

Update the given config dict with additional parameters/values.

+
+
Parameters:
+
    +
  • config – The configuration dict to update.

  • +
  • new_params – A dict of parameters to update. The keys of this dict may be +chained field names, such as gal.first.dilate, which will be +parsed to update config[‘gal’][‘first’][‘dilate’].

  • +
+
+
+
+ +
+
+galsim.config.MultiProcess(nproc, config, job_func, tasks, item, logger=None, timeout=900, done_func=None, except_func=None, except_abort=True)[source]
+

A helper function for performing a task using multiprocessing.

+

A note about the nomenclature here. We use the term “job” to mean the job of building a single +file or image or stamp. The output of each job is gathered into the list of results that +is returned. A task is a collection of one or more jobs that are all done by the same +processor. For simple cases, each task is just a single job, but for things like a Ring +test, the task needs to have the jobs for a full ring.

+

The tasks argument is a list of tasks. +Each task in that list is a list of jobs. +Each job is a tuple consisting of (kwargs, k), where kwargs is the dict of kwargs to pass to +the job_func and k is the index of this job in the full list of jobs.

+
+
Parameters:
+
    +
  • nproc – How many processes to use.

  • +
  • config – The configuration dict.

  • +
  • job_func

    The function to run for each job. It will be called as:

    +
    result = job_func(**kwargs)
    +
    +
    +

    where kwargs is from one of the jobs in the task list.

    +

  • +
  • tasks – A list of tasks to run. Each task is a list of jobs, each of which is +a tuple (kwargs, k).

  • +
  • item – A string indicating what is being worked on.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • timeout – How many seconds to allow for each task before timing out. [default: 900]

  • +
  • done_func

    A function to run upon completion of each job. It will be called as:

    +
    done_func(logger, proc, k, result, t)
    +
    +
    +

    where proc is the process name, k is the index of the job, result is +the return value of that job, and t is the time taken. [default: None]

    +

  • +
  • except_func

    A function to run if an exception is encountered. It will be called as:

    +
    except_func(logger, proc, k, ex, tr)
    +
    +
    +

    where proc is the process name, k is the index of the job that failed, +ex is the exception caught, and tr is the traceback. [default: None]

    +

  • +
  • except_abort – Whether an exception should abort the rest of the processing. +If False, then the returned results list will not include anything +for the jobs that failed. [default: True]

  • +
+
+
Returns:
+

a list of the outputs from job_func for each job

+
+
+
+ +
+
+galsim.config.GetIndex(config, base, is_sequence=False)[source]
+

Return the index to use for the current object or parameter and the index_key.

+

First check for an explicit index_key value given by the user. +Then if base[index_key] is other than obj_num, use that. +Finally, if this is a sequence, default to ‘obj_num_in_file’, otherwise ‘obj_num’.

+
+
Returns:
+

index, index_key

+
+
+
+ +
+
+galsim.config.GetRNG(config, base, logger=None, tag='')[source]
+

Get the appropriate current rng according to whatever the current index_key is.

+

If a logger is provided, then it will emit a warning if there is no current rng setup.

+
+
Parameters:
+
    +
  • config – The configuration dict for the current item being worked on.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
  • tag – If given, an appropriate name for the current item to use in the +warning message. [default: ‘’]

  • +
+
+
Returns:
+

either the appropriate rng for the current index_key or None

+
+
+
+ +
+
+galsim.config.CleanConfig(config, keep_current=False)[source]
+

Return a “clean” config dict without any leading-underscore values

+

GalSim config dicts store a lot of ancillary information internally to help improve +efficiency. However, some of these are actually pointers to other places in the dict, so +printing a config dict, or even what should be a small portion of one, can have infinite loops.

+

This helper function is useful when debugging config processing to strip out all of these +leading-underscore values, so that printing the dict is reasonable.

+
>>> print(galsim.config.CleanConfig(config_dict))
+
+
+
+ +
+
+galsim.config.SetDefaultExt(config, default_ext)[source]
+

Set a default ext in a config ‘file_name’ field if appropriate.

+
+
Parameters:
+
    +
  • config – The configuration dict for the item that might need to be given +a default ‘ext’ value.

  • +
  • default_ext – The default extension to set in the config dict if one is not set.

  • +
+
+
+
+ +
+
+galsim.config.RetryIO(func, args, ntries, file_name, logger)[source]
+

A helper function to retry I/O commands

+
+ +
+
+galsim.config.MakeImageTasks(config, jobs, logger)[source]
+

Turn a list of jobs into a list of tasks.

+

See the doc string for galsim.config.MultiProcess for the meaning of this distinction.

+

For most image types, there is just one job per task, so the tasks list is just:

+
+

tasks = [ [ (job, k) ] for k, job in enumerate(jobs) ]

+
+

But some image types may need groups of jobs to be done sequentially by the same process. +The image type=Single for instance uses whatever grouping is needed for the stamp type.

+
+
Parameters:
+
    +
  • config – The configuration dict

  • +
  • jobs – A list of jobs to split up into tasks. Each job in the list is a +dict of parameters that includes ‘image_num’ and ‘obj_num’.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

a list of tasks

+
+
+
+ +
+
+galsim.config.MakeStampTasks(config, jobs, logger)[source]
+

Turn a list of jobs into a list of tasks.

+

See the doc string for galsim.config.MultiProcess for the meaning of this distinction.

+

For the Basic stamp type, there is just one job per task, so the tasks list is just:

+
tasks = [ [ (job, k) ] for k, job in enumerate(jobs) ]
+
+
+

But other stamp types may need groups of jobs to be done sequentially by the same process. +cf. stamp type=Ring.

+
+
Parameters:
+
    +
  • config – The configuration dict

  • +
  • jobs – A list of jobs to split up into tasks. Each job in the list is a +dict of parameters that includes ‘obj_num’.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

a list of tasks

+
+
+
+ +
+
+galsim.config.RegisterInputConnectedType(input_type, type_name)[source]
+

Register that some gsobject or value type is connected to a given input type.

+
+
Parameters:
+
    +
  • input_type – The name of the type in config[‘input’]

  • +
  • type_name – The name of the type that uses this input object.

  • +
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_special.html b/docs/_build/html/config_special.html new file mode 100644 index 00000000000..c1fd4c922a2 --- /dev/null +++ b/docs/_build/html/config_special.html @@ -0,0 +1,354 @@ + + + + + + + Special Fields — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Special Fields

+

There are a couple of other top level fields that act more in a support role, rather than being +part of the main processing.

+
+

modules

+

Almost all aspects of the file building can be customized by the user if the existing GalSim +types do not do precisely what you need. How to do this is described in the pages about +each of the different top-level fields. In all cases, you need to tell GalSim what Python +modules to load at the start of processing to get the implementations of your custom types. +That is what this field is for.

+

The modules field should contain a list of modules that GalSim should import before +processing the rest of the config file. These modules can be either in the current directory +where you are running the code or installed in your Python distro. (Or technically, they +need to be located in a directory in sys.path.)

+

See:

+ +

for some examples of this field.

+
+
+

eval_variables

+

Sometimes, it can be useful to define some configuration parameters right at the top of the +config file that might be used farther down in the file somewhere to highlight them. +Or sometimes, there are calculations that are needed by several different values in the +config file, which you only want to calculate once.

+

You can put such values in a top-level eval_variables field. They work just like +variables that you define for Eval +items, but they can be placed separately from those evaluations.

+

For examples of this field, see:

+ +
+
+

template

+

This feature directs the config processing to first load in some other file (or specific +field with that file) and then possibly modify some components of that dict.

+

To load in some other config file named config.yaml, you would write:

+
template: config.yaml
+
+
+

If you only want to load a particular field from that file, say the image field, you could +write:

+
template: config.yaml:image
+
+
+

The template field may appear anywhere in the config file. Wherever it appears, the contents +of the other file will be a starting point for that part of the current config dict, +but you can replace +or add values by specifying new values for some of the fields. Fields that are not at +the top level are specified using a . to proceed down the levels of the config hierarchy. +e.g. image.noise.sky_level refers to config['image']['noise']['sky_level'].

+

For example, if you have a simulation defined in my_sim.yaml, and you want to make another +simulation that is identical, except you want Sersic galaxies instead of Exponential galaxies say, +you could write a new file that looks something like this:

+
template : my_sim.yaml
+gal:
+    type : Sersic
+    n : { type : Random, min : 1, max: 4 }
+    half_light_radius :
+        template : my_sim.yaml:gal.half_light_radius
+    flux : 1000
+output.dir : sersic_sim
+
+
+

This will load in the file my_sim.yaml first, then replace the whole config['gal'] field +as well as config['output']['dir'] (leaving the rest of config['output'] unchanged). +The new config['gal'] field will use the same half_light_radius specification from +the other file (which might be some complicated random variate that you did not want to +duplicate here).

+

If the template field is not at the top level of the config dict, the adjustments should be +made relative to that level of the dictionary:

+
psf :
+    template: cgc.yaml:psf
+    index_key : obj_num
+    items.0.ellip.e.max : 0.05
+    items.1.nstruts : 1
+    items.1.strut_angle : { type : Random }
+
+
+

Note that the modifications do not start with psf., since the template processing is being done +within the psf field.

+

Finally, if you want to use a different field from the current config dict as a template, you can +use the colon notation without the file. +E.g. To have a bulge plus disk that have the same kinds of parameters, except that the overall type is a DeVaucouleurs and Exponential respectively, you could do:

+
gal:
+    type: Sum
+    items:
+        -
+            type: DeVaucouleurs
+            half_light_radius: { type: Random, min: 0.2, max: 0.8 }
+            flux: { type: Random, min: 100, max: 1000 }
+            ellip:
+                type: Eta1Eta2
+                eta1: { type: RandomGaussian, sigma: 0.2 }
+                eta2: { type: RandomGaussian, sigma: 0.2 }
+        -
+            template: :gal.items.0
+            type: Exponential
+
+
+

This would generate different values for the size, flux, and shape of each component. But the way those numbers are drawn would be the same for each.

+

It is also possible for modules to register a name for a template file, so users can use that name +rather than the actual file location. For instance, if a module has a template yaml file that is +installed with the python code, it will typically be in an obscure location in a Python +site-packages directory somewhere. But the installed module would be able to know this location +and register it by name. E.g. “my_default_sim”. Then users who want to +start with that canonical config file and maybe modify a few things can write:

+
modules:
+    - my_module
+
+template: my_default_sim
+
+image.random_seed: 12345
+image.nobjects: 500
+output.file_name: objs_500.fits
+
+
+

To use this feature, the module (i.e. my_module in the example here) should register the name to the correct file name using the RegisterTemplate function:

+
module_dir = os.path.dirname(__file__)
+default_sim_file = os.path.join(module_dir, 'my_default_sim.yaml')
+galsim.config.RegisterTemplate('my_default_sim', default_sim_file)
+
+
+
+
+galsim.config.RegisterTemplate(template_name, file_name)[source]
+

Register a template config file with the given named alias.

+

There are currently no named templates shipped with GalSim, but this function +provides a mechanism for modules to register a config file with a more user-friendly +name for a module-provided configuration that may be stored in an awkward location +on disk.

+

E.g. LSSTDESC.imSim has a few configurations that include most of the default recommended +modules for producing various simulations of LSST images. They are stored in the imSim +data directory, but users can just use the named value as a more convenient alias.

+
+
Parameters:
+
    +
  • template_name – The name to allow in a ‘template’ field in lieu of the file name.

  • +
  • file_name – The actual file name on disk with the configuration file.

  • +
+
+
+
+ +

See:

+ +

for more examples of this feature.

+
+
+
+

Special Specifications

+

A few specifications may be used almost anywhere in the config to adjust how the values in those +fields are processed. They are automatically propagated to lower levels in the dictionary. +For instance, if you set index_key : image_num in the psf field, then all values +generated for any aspect of the psf will be constant for a whole image and only change +when the processing goes on to the next image.

+
+

index_key

+

This specifies the cadence on which to generate a new value for each non-constant value. +There are default cadences for each of the major top-level fields, but if you want to specify +a different cadence for some value or field, then you can override it.

+

Options are:

+
+
    +
  • ‘file_num’ Update the values for each new file. This is the default for items in the input and output fields.

  • +
  • ‘image_num’ Update the values for each new image. This is the default for items in the image field that apply to the full image (i.e. not including random_seed, image_pos, world_pos, etc.).

  • +
  • ‘obj_num’ Update the values for each object. This is the default for the other items in image, and also for items in stamp, gal, and psf.

  • +
  • ‘obj_num_in_file’ For this purpose, equivalent to ‘obj_num’. (For ‘Sequence’ value types, there is an important distinction between the two. See its description in Config Values for more details.)

  • +
+
+

It is also possible for a custom module to add additional valid values here by adding to galsim.config.valid_index_keys, which is a list of strings, which are allowed.

+
+
+

rng_index_key

+

Each index_key has its own random number generator to use for generating values that need an rng object. Normally you want these to match up, but this lets you specify to use the rng for a different key than is used for the actual sequencing.

+

For instance, if you set rng_index_key = 'image_num' for a gal value, then it will use the rng normally used for image_num items, but it will still generate a new value for each obj_num.

+
+
+

rng_num

+

Normally you specify a single random number seed, which spawns a sequence of rng objects that +update according to the above index keys. So an rng for each object is stored in obj_num_rng, +one for image_num values is in image_num_rng, etc.

+

However, you are allowed to specify this seed sequence manually, and in particular, you can +have it be a list of several different sequences which update at different rates, and may +repeat. For instance, this may be useful to have some galaxy properties repeat for several +exposures, while other properties of the observations are different for each exposure.

+

You would specify which random number you want to use from such a list using rng_num in a +field. See the description of random_seed in Image Field Attributes for more information.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_stamp.html b/docs/_build/html/config_stamp.html new file mode 100644 index 00000000000..3a0e97489f0 --- /dev/null +++ b/docs/_build/html/config_stamp.html @@ -0,0 +1,873 @@ + + + + + + + Config Stamp Field — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Stamp Field

+

The stamp field defines some properties about how to draw the postage-stamp images of each +object. It is often unneccessary to explicitly include this top-level field. The default +stamp type, called ‘Basic’, is often what you want.

+
+

Stamp Field Attributes

+

Some attributes that are allowed for all stamp types are:

+
    +
  • draw_method = str_value (default = ‘auto’) Valid options are:

    +
    +
      +
    • ‘auto’ The default is normally equivalent to ‘fft’. However, if the object being rendered is simple (no convolution) and has hard edges (e.g. a Box or a truncated Moffat or Sersic), then it will switch to ‘real_space’, since that is often both faster and more accurate in these cases (due to ringing in Fourier space).

    • +
    • ‘fft’ This method will convolve by the pixel (as well as convolving the galaxy and PSF if you have both) using a fast Fourier transform.

    • +
    • ‘real_space’ This uses real-space integration to integrate over the pixel. This is only possible if there is only _either_ a PSF or galaxy, not both. Also, it cannot involve a Convolution internally. If GalSim is unable to do the real-space integration, this will revert to ‘fft’.

    • +
    • ‘phot’ Use photon shooting, which treats the profile as a probability distribution, draws photons from that distribution, and then “shoots” them at the image. The flux of each photon is added to whichever pixel it hits. This automatically handles the integration over the pixel, but it cannot be used with Deconvolutions (including RealGalaxy objects) and the result will necessarily have Poisson noise from the finite number of photons being shot.

      +
      +
        +
      • max_extra_noise = float_value (optional) If the image is sky noise dominated, then it is efficient to stop shooting photons when the photon noise of the galaxy is much less than the sky noise. This parameter specifies how much extra noise, as a fraction of the sky noise, is permissible in any pixel.

      • +
      • n_photons = int_value (optional; default is to assume the object flux is given in photons and use that) Specifies the total number of photons to shoot as a hard, fixed number. If both n_photons and max_extra_noise are specified in the options, max_extra_noise is ignored and a warning is generated.

      • +
      • poisson_flux = bool_value (default = True, unless n_photons is given, in which case the default is False) Whether to allow the total object flux to vary according to Poisson statistics for the number of photons being shot.

      • +
      +
      +
    • +
    • ‘no_pixel’ This will not integrate the flux over the pixel response. Rather, it just samples the surface brightness distribution at the pixel centers and multiplies by the pixel area. This is appropriate if the PSF already has the pixel response included (e.g. from an observed image of a PSF).

    • +
    • ‘sb’ This is similar to ‘no_pixel’, except that the image values will simply be the sampled surface brightness, not multiplied by the pixel area. This does not correspond to any real observing scenario, but it could be useful if you want to view the surface brightness profile of an object directly, without including the pixel integration.

    • +
    +
    +
  • +
  • offset = pos_value (optional) An offset in chip coordinates (i.e. pixel units) to apply when drawing the object on the postage stamp.

  • +
  • gsparams = dict (optional) A dict of (non-default) GSParams items that you want applied to the constructed object.

  • +
  • retry_failures = int_value (default = 0) How many times to retry the construction of a GSObject if there is any kind of failure. For example, you might have a random shear value that technically may come back with \(|g| > 1\), but it should be very rare. So you might set it to retry once or twice in that case. If this is > 0, then after a failure, the code will try again up to this many times.

  • +
  • skip_failures = bool_value (default = False) Whether to skip an object if some aspect of the processing failes (i.e. an exception is raised). This is similar in spirit to the above retry_failures, but more appropriate when the input is not a random value that sometimes causes problems, but rather an input catalog that might have invalid values.

  • +
  • world_pos = pos_value or sky_value (only one of world_pos and image_pos is allowed) The position in world coordinates at which to center the object. This is often defined in the image field, but it can be overridden in the stamp field.

  • +
  • image_pos = pos_value (only one of world_pos and image_pos is allowed) The position on the full image at which to center the object. This is often defined in the image field, but it can be overridden in the stamp field. Note: the object is always centered as nearly as possible on the postage stamp being drawn (unless an explicit offset is given), but the image_pos or world_pos determines where in the larger image this stamp is placed.

  • +
  • sky_pos = sky_value (default = world_pos) Normally this is just world_pos, but if you are using a Euclidean WCS, then this allows for the ability to specify a location on the sky in case some other type needs it for a calculation.

  • +
  • skip = bool_value (default = False) Skip this stamp.

  • +
  • quick_skip = bool_value (default = False) Skip this stamp before doing any work, even making the rng or calculating the position. (Usually used by some other part of the processing to precalculate objects that are not worth doing for some reason.)

  • +
  • obj_rng = bool_value (default = True) Whether to make a fresh random number generator for each object. If set to False, all objects will use the same rng, which will be the one used for image-level calculations.

  • +
  • photon_ops See Photon Operators List below.

  • +
+
+
+

Stamp Types

+

The default stamp type is ‘Basic’, which constructs a galaxy object based on the gal field +(if present) and a PSF object from the psf field (again, if present), convolves them +together, and draws the object onto a postage stamp. This is often what you need, but +there is also a ‘Ring’ type, and you can define your own custom stamp type if you want +to customize any aspect of the stamp-building process.

+
    +
  • ‘Basic’ The postage stamp contains a single gal object convolved by a psf object, assuming both fields are given. If only one of the two is given, that one is drawn.

    +
    +
      +
    • size = int_value (optional) If you want square postage stamps for each object (common), you just need to set this one value and the images will be size x size. The default is for GalSim to automatically determine a good size for the image that will encompass most of the flux of the object, but note that the image type may define the stamp size (e.g. ‘Tiled’), in which case that will be used.

    • +
    • xsize = int_value (default = size) If you want non-square postage stamps, you can specify xsize and ysize separately instead. It is an error for only one of them to be non-zero.

    • +
    • ysize = int_value (default = size)

    • +
    • min_flux_frac = float_value (optional) If the rendered stamp (before noise is applied) has less than this fraction of the nominal flux of the object, reject it and start over (presumably choosing new random values for size, flux, etc.). This counts as a “failure” for the purpose of the retry_failures count.

    • +
    • min_snr = float_value (optional) If the measured signal-to-noise ratio (using the optimal matched filter definition of S/N, measured using the signal on the stamp before noise is applied) is less than this, then reject it and start over. This counts as a “failure” for the purpose of the retry_failures count.

    • +
    • max_snr = float_value (optional) If the measured signal-to-noise ratio is higher than this, then reject it and start over. This counts as a “failure” for the purpose of the retry_failures count.

    • +
    • reject = bool_value (optional) If this evaluates to true, then reject the current stamp and start over. Typically, this would be a custom function that would perform some measurement on the pre-noise image. +See cgc.yaml for an examples of such a custom function. This counts as a “failure” for the purpose of the retry_failures count.

    • +
    +
    +
  • +
  • ‘Ring’ Generate galaxies in a ring for a ring test. (Use num=2 to get pairs of 90 degree rotated galaxies.)

    +
    +
      +
    • size, xsize, ysize = int_value (optional) Same meaning as for ‘Basic’ type.

    • +
    • num = int_value (required) How many objects to include in the ring.

    • +
    • full_rotation = angle_value (default = 180 degrees) What angle should be spanned by the full rotation? The default of 180 degrees is appropriate for the typical case of a rotationally symmetric galaxy (e.g. a sheared Exponential), but if the first profile does not have rotational symmetry, then you probably want to set this to 360 degrees.

    • +
    • index = int_value (default = ‘Sequence’ from 0 to num-1) Which item in the Ring is this.

    • +
    • min_flux_frac = float_value (optional) Equivalent to Basic, but only applies to the first stamp in the ring.

    • +
    • min_snr = float_value (optional) Equivalent to Basic, but only applies to the first stamp in the ring.

    • +
    • max_snr = float_value (optional) Equivalent to Basic, but only applies to the first stamp in the ring.

    • +
    • reject = bool_value (optional) Equivalent to Basic, but only applies to the first stamp in the ring.

    • +
    • shear = shear_value (optional) Shear the galaxy profile by a given shear. Normally shear goes in the gal field. But for ring simulations, where we rotate the base galaxy by some amount, one typically wants the shear to be applied after the rotation. So any shear (or other transformation) item that is in the gal field is applied before the ring rotation. Then any shear (or again, any other transformation) that is in the stamp field is applied after the ring rotation.

    • +
    +
    +
  • +
+
+
+

Custom Stamp Types

+

To define your own stamp type, you will need to write an importable Python module +(typically a file in the current directory where you are running galsim, but it could also +be something you have installed in your Python distro) with a class that will be used +to build the stamp.

+

The class should be a subclass of galsim.config.StampBuilder, which is the class used for +the default ‘Basic’ type. There are a number of class methods, and you only need to override +the ones for which you want different behavior than that of the ‘Basic’ type.

+
+
+class galsim.config.StampBuilder[source]
+

A base class for building stamp images of individual objects.

+

The base class defines the call signatures of the methods that any derived class should follow. +It also includes the implementation of the default stamp type: Basic.

+
+
+addNoise(config, base, image, current_var, logger)[source]
+

Add the sky level and the noise to the stamp.

+
+
Note: This only gets called if the image type requests that the noise be added to each

stamp individually, rather than to the full image and the end.

+
+
+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • image – The current image.

  • +
  • current_var – The current noise variance present in the image already.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

the new values of image, current_var

+
+
+
+ +
+
+applySNRScale(image, prof, scale_factor, method, logger)[source]
+

Apply the scale_factor from getSNRScale to the image and profile.

+

The default implementaion just multiplies each of them, but if prof is not a regular +GSObject, then you might need to do something different.

+
+
Parameters:
+
    +
  • image – The current image.

  • +
  • prof – The profile that was drawn.

  • +
  • scale_factor – The factor by which to scale both image and prof.

  • +
  • method – The method used by drawImage.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

image, prof (after being properly scaled)

+
+
+
+ +
+
+buildPSF(config, base, gsparams, logger)[source]
+

Build the PSF object.

+

For the Basic stamp type, this builds a PSF from the base[‘psf’] dict, if present, +else returns None.

+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • gsparams – A dict of kwargs to use for a GSParams. More may be added to this +list by the galaxy object.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

the PSF

+
+
+
+ +
+
+buildProfile(config, base, psf, gsparams, logger)[source]
+

Build the surface brightness profile (a GSObject) to be drawn.

+

For the Basic stamp type, this builds a galaxy from the base[‘gal’] dict and convolves +it with the psf (if given). If either the psf or the galaxy is None, then the other one +is returned as is.

+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • psf – The PSF, if any. This may be None, in which case, no PSF is convolved.

  • +
  • gsparams – A dict of kwargs to use for a GSParams. More may be added to this +list by the galaxy object.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

the final profile

+
+
+
+ +
+
+draw(prof, image, method, offset, config, base, logger)[source]
+

Draw the profile on the postage stamp image.

+
+
Parameters:
+
    +
  • prof – The profile to draw.

  • +
  • image – The image onto which to draw the profile (which may be None).

  • +
  • method – The method to use in drawImage.

  • +
  • offset – The offset to apply when drawing.

  • +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

the resulting image

+
+
+
+ +
+
+getDrawMethod(config, base, logger)[source]
+

Determine the draw method to use.

+

@param config The configuration dict for the stamp field. +@param base The base configuration dict. +@param logger A logger object to log progress.

+

@returns method

+
+ +
+
+getOffset(config, base, logger)[source]
+

Determine the offset to use.

+

The base class version adds the stamp_offset, which comes from calculations related to +world_pos and image_pos, to the field stamp.offset if any.

+

@param config The configuration dict for the stamp field. +@param base The base configuration dict. +@param logger A logger object to log progress.

+

@returns offset

+
+ +
+
+getSNRScale(image, config, base, logger)[source]
+

Calculate the factor by which to rescale the image based on a desired S/N level.

+
+
Note: The default implementation does this for the gal or psf field, so if a custom

stamp builder uses some other way to get the profiles, this method should +probably be overridden.

+
+
+
+
Parameters:
+
    +
  • image – The current image.

  • +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

scale_factor

+
+
+
+ +
+
+getSkip(config, base, logger)[source]
+

Initial check of whether to skip this object based on the stamp.skip field.

+

@param config The configuration dict for the stamp field. +@param base The base configuration dict. +@param logger A logger object to log progress.

+

@returns skip

+
+ +
+
+locateStamp(config, base, xsize, ysize, image_pos, world_pos, logger)[source]
+

Determine where and how large the stamp should be.

+

The base class version does the followin:

+
    +
  • If given, set base[‘stamp_xsize’] = xsize

  • +
  • If given, set base[‘stamp_ysize’] = ysize

  • +
  • If only image_pos or world_pos is given, compute the other from base[‘wcs’]

  • +
  • Set base[‘index_pos’] = image_pos

  • +
  • Set base[‘world_pos’] = world_pos

  • +
  • Calculate the appropriate value of the center of the stamp, to be used with the +command: stamp_image.setCenter(stamp_center). Save this as base[‘stamp_center’]

  • +
  • Calculate the appropriate offset for the position of the object from the center of +the stamp due to just the fractional part of the image position, not including +any base[‘stamp’][‘offset’] item that may be present in the base dict. +Save this as base[‘stamp_offset’]

  • +
+

@param config The configuration dict for the stamp field. +@param base The base configuration dict. +@param xsize The size of the stamp in the x-dimension. [may be 0 if unknown] +@param ysize The size of the stamp in the y-dimension. [may be 0 if unknown] +@param image_pos The position of the stamp in image coordinates. [may be None] +@param world_pos The position of the stamp in world coordinates. [may be None] +@param logger A logger object to log progress.

+
+ +
+
+makeStamp(config, base, xsize, ysize, logger)[source]
+

Make the initial empty postage stamp image, if possible.

+

If we don’t know xsize, ysize, return None, in which case the stamp will be created +automatically by the drawImage command based on the natural size of the profile.

+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • xsize – The xsize of the image to build (if known).

  • +
  • ysize – The ysize of the image to build (if known).

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

the image

+
+
+
+ +
+
+makeTasks(config, base, jobs, logger)[source]
+

Turn a list of jobs into a list of tasks.

+

For the Basic stamp type, there is just one job per task, so the tasks list is just:

+
+

tasks = [ [ (job, k) ] for k, job in enumerate(jobs) ]

+
+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • jobs – A list of jobs to split up into tasks. Each job in the list is a +dict of parameters that includes ‘obj_num’.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

a list of tasks

+
+
+
+ +
+
+quickSkip(config, base)[source]
+

Check whether this object should be skipped before doing any work.

+

The base class looks for stamp.quick_skip and returns True if it is preset and +evaluates to True.

+

@param config The configuration dict for the stamp field. +@param base The base configuration dict.

+

@returns skip

+
+ +
+
+reject(config, base, prof, psf, image, logger)[source]
+

Check to see if this object should be rejected.

+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • prof – The profile that was drawn.

  • +
  • psf – The psf that was used to build the profile.

  • +
  • image – The postage stamp image. No noise is on it yet at this point.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

whether to reject this object

+
+
+
+ +
+
+reset(base, logger)[source]
+

Reset some aspects of the config dict so the object can be rebuilt after rejecting the +current object.

+
+
Parameters:
+
    +
  • base – The base configuration dict.

  • +
  • logger – A logger object to log progress.

  • +
+
+
+
+ +
+
+setup(config, base, xsize, ysize, ignore, logger)[source]
+

Do the initialization and setup for building a postage stamp.

+

In the base class, we check for and parse the appropriate size and position values in +config (aka base[‘stamp’] or base[‘image’].

+

Values given in base[‘stamp’] take precedence if these are given in both places (which +would be confusing, so probably shouldn’t do that, but there might be a use case where it +would make sense).

+
+
Parameters:
+
    +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • xsize – The xsize of the image to build (if known).

  • +
  • ysize – The ysize of the image to build (if known).

  • +
  • ignore – A list of parameters that are allowed to be in config that we can +ignore here. i.e. it won’t be an error if these parameters are present.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

xsize, ysize, image_pos, world_pos

+
+
+
+ +
+
+setupRNG(config, base, logger)[source]
+

Setup the RNG for this object.

+

@param config The configuration dict for the stamp field. +@param base The base configuration dict.

+
+ +
+
+updateOrigin(config, base, image)[source]
+

Update the image origin to be centered at the right place

+

@param config The configuration dict for the stamp field. +@param base The base configuration dict. +@param image The postage stamp image.

+
+ +
+
+updateSkip(prof, image, method, offset, config, base, logger)[source]
+

Before drawing the profile, see whether this object can be trivially skipped.

+

The base method checks if the object is completely off the main image, so the +intersection bounds will be undefined. In this case, don’t bother drawing the +postage stamp for this object.

+
+
Parameters:
+
    +
  • prof – The profile to draw.

  • +
  • image – The image onto which to draw the profile (which may be None).

  • +
  • method – The method to use in drawImage.

  • +
  • offset – The offset to apply when drawing.

  • +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

whether to skip drawing this object.

+
+
+
+ +
+
+whiten(prof, image, config, base, logger)[source]
+

If appropriate, whiten the resulting image according to the requested noise profile +and the amount of noise originally present in the profile.

+
+
Parameters:
+
    +
  • prof – The profile to draw.

  • +
  • image – The image onto which to draw the profile.

  • +
  • config – The configuration dict for the stamp field.

  • +
  • base – The base configuration dict.

  • +
  • logger – A logger object to log progress.

  • +
+
+
Returns:
+

the variance of the resulting whitened (or symmetrized) image.

+
+
+
+ +
+ +

The base parameter is the original full configuration dict that is being used for running the +simulation. The config parameter is the local portion of the full dict that defines the stamp +being built, which would typically be base['stamp'].

+

Then, in the Python module, you need to register this function with some type name, which will +be the value of the type attribute that triggers the use of this Builder object:

+
galsim.config.RegisterStampType('CustomStamp', CustomStampBuilder())
+
+
+

Note that we register an instance of the class, not the class itself. This opens up the +possibility of having multiple stamp types use the same class instantiated with different +initialization parameters. This is not used by the GalSim stamp types, but there may be use +cases where it would be useful for custom stamp types.

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_stamp.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_stamp
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_stamp, +which will read your file and execute the registration command to add the builder to the list +of valid stamp types.

+

Then you can use this as a valid stamp type:

+
stamp:
+    type: CustomStamp
+    ...
+
+
+

For examples of custom stamps, see

+ +

which use custom stamp types Blend and BlendSet defined in blend.py .

+

It may also be helpful to look at the GalSim implementation of the include Ring type: +(click on the [source] link):

+
+
+class galsim.config.stamp_ring.RingBuilder[source]
+

Bases: StampBuilder

+

This performs the tasks necessary for building a Ring stamp type.

+

It uses the regular Basic functions for most things. +It specializes the setup, buildProfile, reject, and makeTasks functions.

+
+ +
+
+

Photon Operators List

+

When drawing with method='phot', there are a number of operators you can apply to the +photon array before accumulating the photons on the sensor. You can specify these using +photon_ops in the stamp field. This directive should be a list of dicts, each +specifying a PhotonOp in the order in which the operators should be applied to the photons.

+

The photon operator types defined by GalSim are:

+
    +
  • ‘WavelengthSampler’ assigns wavelengths to the photons based on an SED and the current Bandpass.

    +
    +
      +
    • sed = SED (required) The SED to use. To use the galaxy SED (which would be typical), +you can use @gal.sed for this.

    • +
    • npoints = int_value (optional) The number of points DistDeviate should use for its +interpolation table.

    • +
    +
    +
  • +
  • ‘FRatioAngles’ assigns incidence angles (in terms of their tangent, dxdz and dydz) to the +photons randomly given an f/ratio and an obscuration.

    +
    +
      +
    • fratio = float_vale (required) The f/ratio of the telescope.

    • +
    • obscuration = float_value (default = 0.0) The linear dimension of the central +obscuration as a fraction of the aperture size.

    • +
    +
    +
  • +
  • ‘PhotonDCR’ adjusts the positions of the photons according to the effect of differential +chromatic refraction in the atmosphere. There are several ways one can define the +parallactic angle needed to compute the DCR effect. One of the following is required.

    +
      +
    1. zenith_angle and parallactic_angle

    2. +
    3. zenith_angle alone, implicitly taking parallactic_angle = 0.

    4. +
    5. zenith_coord along with either sky_pos in the stamp field or using a +CelestialWCS so GalSim can determine the sky position from the image coordinates.

    6. +
    7. HA and latitude along with either sky_pos in the stamp field or using a +CelestialWCS so GalSim can determine the sky position from the image coordinates.

    8. +
    +
    +
      +
    • base_wavelength = float_value (required) The wavelength (in nm) for the fiducial +photon positions.

    • +
    • scale_unit = str_value (default = ‘arcsec’) The scale unit for the photon positions.

    • +
    • alpha = float_value (default = 0.0) A power law index for wavelength-dependent +seeing. This should only be used if doing a star-only simulation. It is not correct when +drawing galaxies.

    • +
    • zenith_angle = angle_value (optional; see above) the angle from the object to zenith.

    • +
    • parallactic_angle = angle_value (option; see above) the parallactic angle.

    • +
    • zenith_coord = sky_value (optional; see above) the celestial coordinates of the zenith.

    • +
    • HA = angle_value (optional; see above) the local hour angle.

    • +
    • latitude = angle_value (optional; see above) the latitude of the telescope.

    • +
    • pressure = float_value or quantity_value (default = 69.328) the pressure in kPa.

    • +
    • temperature = float_value or quantity_value (default = 293.15) the temperature in Kelvin.

    • +
    • H2O_pressure = float_value or quantity_value (default = 1.067) the water vapor pressure in kPa.

    • +
    +
    +
  • +
  • ‘FocusDepth’ adjusts the positions of the photons at the surface of the sensor to account for +the nominal focus being either above or below the sensor surface. The depth value is typically +negative, since the best focus is generally somewhere in the bulk of the sensor (although for +short wavelengths it is often very close to the surface).

    +
    +
      +
    • depth = float_value (required) The distance (in pixels) above the surface where the photons are +nominally in focus. A negative value means the focus in below the surface of the sensor.

    • +
    +
    +
  • +
  • ‘Refraction’ adjusts the incidence angles to account for refraction at the surface of the +sensor.

    +
    +

    Note

    +
    +

    If this is combined with FocusDepth, then the order of the two operators is important. +If FocusDepth is before Refraction, then the depth refers to the distance the sensor +would need to move for the bundle to be in focus at the surface. +If FocusDepth is after Refraction, then the depth refers to the physical distance +below the surface of the sensor where the photons actually come to a focus.

    +
    +
      +
    • index_ratio = float_value (required) The ratio of the index of refraction of the +sensor material to that of the air.

    • +
    +
    +
  • +
  • ‘PupilImageSampler’ assigns pupil positions to the photons randomly given an image of the +pupil plane.

    +
    +
      +
    • diam = float_value (required) The diameter of the pupil aperture.

    • +
    • lam = float_value (optional). The wavelength in nanometers.

    • +
    • circular_pupil = bool_value (default = True) Whether the pupil should be circular (True, the default) or square (False).

    • +
    • obscuration = float_value (default = 0) The linear dimension of a central obscuration as a fraction of the pupil linear dimension.

    • +
    • oversampling = float_value (default = 1.5) How much oversampling of the internal image is needed relative to the Nyquist scale of the corresponding Airy profile. The more aberrated the PSF, the higher this needs to be.

    • +
    • pad_factor = float_value (default = 1.5) How much padding to put around the edge of the internal image of the PSF.

    • +
    • nstruts = int_value (default = 0) How many support struts to include.

    • +
    • strut_thick = float_value (default = 0.05) How thick the struts should be as a fraction of the pupil diameter.

    • +
    • strut_angle = angle_value (default = 0 degrees) The counter-clockwise angle between the vertical and one of the struts. The rest will be spaced equally from there.

    • +
    • pupil_plane_im = str_value (optional) Instead of using strut-related parameters to define the pupil plane geometry, you can use this parameter to specify a file-name containing an image of the pupil plane.

    • +
    • pupil_angle = angle_value (default = 0 degrees) When specifying a pupil_plane_im, use this parameter to rotate it by some angle defined counter-clockwise with respect to the vertical.

    • +
    • pupil_plane_scale = float_value (optional) Sampling interval in meters to use for the pupil plane array.

    • +
    • pupil_plane_size = float_value (optional) Size in meters to use for the pupil plane array.

    • +
    +
    +
  • +
  • +
  • ‘PupilAnnulusSampler’ assigns pupil positions to the photons randomly within an annular +entrance pupil.

    +
    +
      +
    • R_outer = float_value (required) The outer radius of the pupil annulus in meters.

    • +
    • R_inner = float_value (default = 0) The inner radius in meters.

    • +
    +
    +
  • +
  • ‘TimeSampler’ gives the photons random time values uniformly within some interval.

    +
    +
      +
    • t0 = float_value (default = 0) The nominal start time of the observation in seconds.

    • +
    • exptime = float_value (default = 0) The exposure time in seconds.

    • +
    +
    +
  • +
+

You may also define your own custom PhotonOp type in the usual way +with an importable module where you define a custom Builder class and register it with GalSim. +The class should be a subclass of galsim.config.PhotonOpBuilder.

+
+
+class galsim.config.PhotonOpBuilder[source]
+

A base class for building PhotonOp objects.

+

The base class defines the call signatures of the methods that any derived class should follow.

+
+
+buildPhotonOp(config, base, logger)[source]
+

Build the PhotonOp based on the specifications in the config dict.

+

Note: Sub-classes must override this function with a real implementation.

+
+
Parameters:
+
    +
  • config – The configuration dict for the PhotonOp

  • +
  • base – The base configuration dict.

  • +
  • logger – If provided, a logger for logging debug statements.

  • +
+
+
Returns:
+

the constructed PhotonOp object.

+
+
+
+ +
+ +

Then, as usual, you need to register this type using:

+
galsim.config.RegisterPhotonOpType('CustomPhotonOp', CustomPhotonOpBuilder())
+
+
+
+
+galsim.config.RegisterPhotonOpType(photon_op_type, builder, input_type=None)[source]
+

Register a photon_op type for use by the config apparatus.

+
+
Parameters:
+
    +
  • photon_op_type – The name of the config type to register

  • +
  • builder – A builder object to use for building the PhotonOp object. It should +be an instance of a subclass of PhotonOpBuilder.

  • +
  • input_type – If the PhotonOp builder utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

and tell the config parser the name of the module to load at the start of processing.

+
modules:
+    - my_custom_photon_op
+
+
+

Then you can use this as a valid photon operator type:

+
stamp:
+    photon_ops:
+        -
+            type: CustomPhotonOp
+            ...
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_top.html b/docs/_build/html/config_top.html new file mode 100644 index 00000000000..b4bbb1a605c --- /dev/null +++ b/docs/_build/html/config_top.html @@ -0,0 +1,210 @@ + + + + + + + Top Level Fields — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Top Level Fields

+

At the top level, there are 6 basic fields:

+
    +
  • psf defines what kind of PSF profile to use.

  • +
  • gal defines what kind of galaxy profile to use.

  • +
  • stamp defines parameters related to building the postage stamp image of each object.

  • +
  • image defines parameters related to the full images to be drawn.

  • +
  • input defines any necessary input files or things that need some kind of initialization.

  • +
  • output defines the names and format of the output files.

  • +
+

None of these are technically required, although it is an error to have _neither_ psf nor +gal. (If you don’t want to draw anything but noise, you need to let GalSim know that this is intentional by using type: None for one of these.) But the most common usage would be to use +psf, gal, image and output. +It is not uncommon for there to be no input files, so you will often omit the input field. +And sometimes you will omit the gal field to draw an image with just stars. +Most simulations will use the default stamp type (called ‘Basic’), which involves drawing +a galaxy convolved by a PSF (or just a PSF image if gal is omitted) on each postage stamp, +so this field will very often be omitted as well.

+

We will go through each one in turn. As we do, some values will be called float_value, +int_value, etc. These can either be a value directly (e.g. float_value could just be 1.5), +or they can be a dict that describes how the value should be generated each time (e.g. a random +number or a value read from an input catalog). +See Config Values for more information about how to specify these values.

+

In addition each value will have one of (required) or (optional) or (default = _something_) to +indicate whether the item is required or if there is some sensible default value. The (optional) +tag usually means that the action in question will not be done at all, rather than done using some +default value. Also, sometimes no item is individually required, but one of several is.

+

psf:

+

The psf field defines the profile of the point-spread function (PSF). +Any object type is allowed for +the psf type, although some types are obviously more appropriate to use as a PSF than others. +For a list of all the available object types, see Config Objects.

+

If this field is omitted, the PSF will effectively be a delta function. I.e. the ideal +galaxy surface brightness profiles will be drawn directly on the image without any convolution.

+

gal:

+

The gal field defines the profile of the galaxy. +As for the psf field, any object type is allowed for +the gal type, although some types are obviously more appropriate to use as a galaxy than others. +For a list of all the available object types, see Config Objects.

+

Technically, the gal field is not +fundamental; its usage is defined by the stamp type. One could for instance define a +stamp type that looked for a gal_set field instead that might give a list of galaxies +to draw onto a single stamp. However, all of the stamp types defined natively in GalSim +use the gal field, so it will be used by most users of the code.

+

If this field is omitted, the default stamp type = ‘Basic’ will draw the PSF surface brightness +profiles directly according to the psf field. +Other stamp types may require this field or may require some other field instead.

+

stamp:

+

The stamp field defines the relevant properties and parameters of the stamp-building process. +For a list of all the available stamp types, see Config Stamp Field.

+

This field is often omitted, in which case the ‘Basic’ stamp type will be assumed.

+

image:

+

The image field defines the relevant properties and parameters of the full image-building +process. +For a list of all the available image types, see Config Image Field.

+

If this field is omitted, the ‘Single’ image type will be assumed.

+

input:

+

The input field indicates where to find any files that you want to use in building the images +or how to set up any objects that require initialization. +For a list of all the available input types, see Config Input Field.

+

This field is only required if you use object types +or value types that use an input object. +Such types will indicate this requirement in their descriptions.

+

output:

+

The output field indicates where to write the output files and what kind of output format they +should have. +For a list of all the available output types, see Config Output Field.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/config_values.html b/docs/_build/html/config_values.html new file mode 100644 index 00000000000..f445bccc217 --- /dev/null +++ b/docs/_build/html/config_values.html @@ -0,0 +1,1479 @@ + + + + + + + Config Values — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Config Values

+

There are seven kinds of values that are used within the configuration apparatus, which we +have been designating using the following italic names:

+ +

Each of the Python types can be given as a constant value using the normal Python conventions +for how to specify such a value. The GalSim angle_value and pos_value also have +direct specification options. In addition, all of them may be a dict with a type attribute +defining how to generate the value in question. These are defined below for each kind of +value.

+

One special type that any value may use is ‘Eval’, which uses the Python eval function +to evaluate a string. The Eval type is described in its own section.

+

In addition to all of these, you can also write your own Custom Value Types +and register them to be used by the config parser.

+
+

float_value

+

Options are:

+
    +
  • A normal float value (e.g. 1.8)

  • +
  • Anything that python can convert into a float (e.g. ‘1.8’)

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Catalog’ Read the value from an input catalog. This requires that input.catalog be specified and uses the following fields:

        +
        +
          +
        • col = int_value for ASCII catalog or str_value for FITS catalog (required)

        • +
        • index = int_value (default = ‘Sequence’ from 0 to input_cat.nobjects-1)

        • +
        • num = int_value (default = 0) If input.catalog is a list, this indicates which number catalog to use.

        • +
        +
        +
      • +
      • ‘Dict’ Read the value from an input dictionary. This requires that input.dict be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required) For specifying keys below the first level of the dictionary, the key string is split using the input.dict.key_split value (default = ‘.’) into multiple keys. e.g. key : galaxy_constants.redshift would be parsed as dict['galaxy_constants']['redshift'].

        • +
        • num = int_value (default = 0) If input.dict is a list, this indicates which number dictionary to use.

        • +
        +
        +
      • +
      • ‘FitsHeader’ Read the value from an input FITS header. This requires that input.fits_header be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required)

        • +
        • num = int_value (default = 0) If input.fits_header is a list, this indicates which number file to use.

        • +
        +
        +
      • +
      • ‘Random’ Generate random values uniformly distributed within a range.

        +
        +
          +
        • min = float_value (required)

        • +
        • max = float_value (required)

        • +
        +
        +
      • +
      • ‘RandomGaussian’ Generate random values from a Gaussian deviate.

        +
        +
          +
        • sigma = float_value (required)

        • +
        • mean = float_value (default = 0)

        • +
        • min = float_value (optional) Clip the distribution at some minimum.

        • +
        • max = float_value (optional) Clip the distribution at some maximum.

        • +
        +
        +
      • +
      • ‘RandomPoisson’ Generate random values from a Poisson deviate.

        +
        +
          +
        • mean = int_value (required) The mean value of the Poisson distribution.

        • +
        +
        +
      • +
      • ‘RandomBinomial’ Generate random values from a Binomial deviate.

        +
        +
          +
        • N = int_value (required) The number of “coin flips” for the distribution.

        • +
        • p = float_value (default = 0.5) The probability of “heads” for each “coin flip”.

        • +
        +
        +
      • +
      • ‘RandomWeibull’ Generate random values from a Weibull deviate.

        +
        +
          +
        • a = float_value (required) (Equivalent to k in the Wikipedia article)

        • +
        • b = float_value (required) (Equivalent to lambda in the Wikipedia article)

        • +
        +
        +
      • +
      • ‘RandomGamma’ Generate random values from a Gamma deviate.

        +
        +
          +
        • k = float_value (required) The shape parameter.

        • +
        • theta = float_value (required) The scale parameter.

        • +
        +
        +
      • +
      • ‘RandomChi2’ Generate random values from a Chi-square deviate.

        +
        +
          +
        • n = float_value (required) The number of degreed of freedom.

        • +
        +
        +
      • +
      • ‘RandomDistribution’ Generate random values from a given probability distribution.

        +
        +
          +
        • function = str_value (required) A string describing the function of x to use for the probability distribution. e.g. x**2.3. Alternatively, it may be a file name from which a tabulated function (with columns of x, p(x)) is read in.

        • +
        • x_min = float_value (required unless function is a file name) The minimum value of x to use for the distribution. (If function is a file name, the minimum value read in is taken as x_min.)

        • +
        • x_max = float_value (required unless function is a file name) The maximum value of x to use for the distribution. (If function is a file name, the maximum value read in is taken as x_max.)

        • +
        • npoints = int_value (default = 256) How many points to use for the cumulative probability distribution (CDF), which is used to map from a uniform deviate to the given distribution. More points will be more accurate, but slower.

        • +
        • interpolant = str_value (default = ‘Linear’) What to use for interpolating between tabulated points in the CDF. Options are ‘Nearest’, ‘Linear’, ‘Cubic’ or ‘Quintic’. (Technically, ‘Sinc’ and ‘LanczosN’ are also possible, but they do not make sense here.)

        • +
        +
        +
      • +
      • ‘PowerSpectrumMagnification’ Calculate a magnification from a given power spectrum. This requires that input.power_spectrum be specified and uses the following fields:

        +
        +
          +
        • max_mu = float_value (default = 5) The maximum magnification to allow. If the power spectrum returns a mu value greater than this or less than 0, then use max_mu instead. This is a sign of strong lensing, and other approximations are probably breaking down at this point anyway, so this keeps the object profile from going crazy.

        • +
        • num = int_value (default = 0) If input.power_spectrum is a list, this indicates which number power spectrum to use.

        • +
        +
        +
      • +
      • ‘NFWHaloMagnification’ Calculate a magnification from an NFW Halo mass. This requires that input.nfw_halo be specified and uses the following fields:

        +
        +
          +
        • gal.redshift = float_value (required) Special: The redshift item must be in the gal field, not magnification. Or if the galaxy is chromatic, it should be in the galaxy’s SED field.

        • +
        • max_mu = float_value (default = 5) The maximum magnification to allow. If NFWHalo returns a mu value greater than this or less than 0, then use max_mu instead. This is a sign of strong lensing, and other approximations are probably breaking down at this point anyway, so this keeps the object profile from going crazy.

        • +
        • num = int_value (default = 0) If input.nfw_halo is a list, this indicates which number halo to use.

        • +
        +
        +
      • +
      • ‘COSMOSValue’ Gets a value from an input COSMOS catalog. This requires that input.cosmos_catalog be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required)

        • +
        • index = int_value (required)

        • +
        • num = int_value (default = 0) If input.cosmos_catalog is a list, this indicates which number catalog to use.

        • +
        +
        +
      • +
      +
      +
        +
      • ‘SampleValue’ Gets a value from an input galaxy sample. This requires that input.galaxy_sample be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required)

        • +
        • index = int_value (required)

        • +
        • num = int_value (default = 0) If input.cosmos_catalog is a list, this indicates which number catalog to use.

        • +
        +
        +
      • +
      +
      +
        +
      • ‘Sequence’ Generate a sequence of values.

        +
        +
          +
        • first = float_value (default = 0)

        • +
        • step = float_value (default = 1) The step size between items.

        • +
        • repeat = int_value (default = 1) How many times to repeat the same value before moving on.

        • +
        • last = float_value (optional; at most one of last and nitems is allowed)

          +
          +
          +

          Note

          +

          If last is provided, once a value passes last, the sequence will +repeat starting with first again.

          +
          +
          +
        • +
        • nitems = int_value (optional; at most one of last and nitems is allowed) The number of items in the sequence before starting over again at first. The default is to just keep incrementing forever.

        • +
        • index_key = str_value (optional; see the option descriptions below for which index is used by default) Which number to use for indexing in the sequence. Valid options are:

          +
          +
            +
          • ‘file_num’ Index according to the running file number being worked on. This is the default for items in the input and output fields.

          • +
          • ‘image_num’ Index according to the running image number. This index number does not start back at 0 with each file, but rather keeps incrementing. This is the default for items in the image field that apply to the full image (i.e. not including random_seed, image_pos, world_pos, etc.).

          • +
          • ‘obj_num’ Index according to the running object number. This index number does not start back at 0 with each file or image, but rather keeps incrementing. This is the default for image.random_seed.

          • +
          • ‘obj_num_in_file’ Index according to the object number within the current file (i.e. start back at 0 again for each new file). This is the default for items in image that apply to the object – image_pos, world_pos, offset, stamp_size and related, or border and related – and also to items in psf or gal. Resetting the count back to zero at the start of each file is generally what you want when the files have different numbers of objects. E.g., when you are reading from input catalogs that contain different numbers of objects, you normally want to start back at 0 for each new catalog.

          • +
          +
          +
        • +
        +
        +
      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of float_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. This is especially useful if you need to use a value from some other calculation, but the value is a random variate, so you cannot just reproduce it. You need the actual value returned by the random number generator.

        +
        +
          +
        • key = str_value (required) The key name of the item to use. The nested layers in the dictionary should be separated by ‘.’ characters. e.g. To access the current half-light radius of the galaxy, use ‘gal.half_light_radius’. For list items, use the number in the list as a key (using the normal python 0-based counting convention). e.g. for the half-light radius of the third item in a galaxy List type, use ‘gal.items.2.half_light_radius’.

        • +
        +
        +
      • +
      • ‘Sum’ The sum of two other float_value items.

        +
        +
          +
        • items = list (required) A list of float_value items to be added together.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

int_value

+

Options are:

+
    +
  • A normal int value (e.g. 8)

  • +
  • Anything that python can convert into an int (e.g. 8.0, ‘8’)

    +
    +
    +

    Note

    +

    float values will silently drop any fractional part, so 8.7 will become 8.

    +
    +
    +
  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Catalog’ Read the value from an input catalog. This requires that input.catalog be specified and uses the following fields:

        +
        +
          +
        • col = int_value for ASCII catalog or str_value for FITS catalog (required)

        • +
        • index = int_value (default = ‘Sequence’ from 0 to input_cat.nobjects-1)

        • +
        • num = int_value (default = 0) If input.catalog is a list, this indicates which number catalog to use.

        • +
        +
        +
      • +
      • ‘Dict’ Read the value from an input dictionary. This requires that input.dict be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required) For specifying keys below the first level of the dictionary, the key string is split using the input.dict.key_split value (default = ‘.’) into multiple keys. e.g. key : galaxy_constants.redshift would be parsed as dict['galaxy_constants']['redshift'].

        • +
        • num = int_value (default = 0) If input.dict is a list, this indicates which number dictionary to use.

        • +
        +
        +
      • +
      • ‘FitsHeader’ Read the value from an input FITS header. This requires that input.fits_header be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required)

        • +
        • num = int_value (default = 0) If input.fits_header is a list, this indicates which number file to use.

        • +
        +
        +
      • +
      • ‘Random’ Generate a random value uniformly distributed within a range.

        +
        +
          +
        • min = int_value (required)

        • +
        • max = int_value (required) Note: the range includes both min and max.

        • +
        +
        +
      • +
      • ‘RandomPoisson’ Generate random values from a Poisson deviate.

        +
        +
          +
        • mean = int_value (required) The mean value of the Poisson distribution.

        • +
        +
        +
      • +
      • ‘RandomBinomial’ Generate random values from a Binomial deviate.

        +
        +
          +
        • N = int_value (required) The number of “coin flips” for the distribution.

        • +
        • p = float_value (default = 0.5) The probability of “heads” for each “coin flip”.

        • +
        +
        +
      • +
      • ‘Sequence’ Generate a sequence of values.

        +
        +
          +
        • first = int_value (default = 0)

        • +
        • step = int_value (default = 1) The step size between items.

        • +
        • repeat = int_value (default = 1) How many times to repeat the same value before moving on.

        • +
        • last = float_value (optional; at most one of last and nitems is allowed)

          +
          +
          +

          Note

          +

          if last is provided, once a value passes last, the sequence will +repeat starting with first again.

          +
          +
          +
        • +
        • nitems = int_value (optional; at most one of last and nitems is allowed) The number of items in the sequence before starting over again at first. The default is to just keep incrementing forever.

        • +
        • index_key = str_value (optional) Which number to use for indexing in the sequence. (See the description of this for float_value for more details.)

        • +
        +
        +
      • +
      +
      +
        +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of int_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      +
      +
        +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Sum’ The sum of two other int_value items.

        +
        +
          +
        • items = list (required) A list of int_value items to be added together.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

bool_value

+

Options are:

+
    +
  • A normal bool value (i.e. True or False)

  • +
  • Anything that python can convert into a bool (e.g. 1, 0.0)

  • +
  • Some reasonable (case-insensitive) strings: ‘true’/’false’, ‘yes’/’no’, ‘1’/’0’

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Catalog’ Read the value from an input catalog. This requires that input.catalog be specified and uses the following fields:

        +
        +
          +
        • col = int_value for ASCII catalog or str_value for FITS catalog (required)

        • +
        • index = int_value (default = ‘Sequence’ from 0 to input_cat.nobjects-1)

        • +
        • num = int_value (default = 0) If input.catalog is a list, this indicates which number catalog to use.

        • +
        +
        +
      • +
      • ‘Dict’ Read the value from an input dictionary. This requires that input.dict be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required) For specifying keys below the first level of the dictionary, the key string is split using the input.dict.key_split value (default = ‘.’) into multiple keys. e.g. key : galaxy_constants.redshift would be parsed as dict['galaxy_constants']['redshift'].

        • +
        • num = int_value (default = 0) If input.dict is a list, this indicates which number dictionary to use.

        • +
        +
        +
      • +
      • ‘FitsHeader’ Read the value from an input FITS header. This requires that input.fits_header be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required)

        • +
        • num = int_value (default = 0) If input.fits_header is a list, this indicates which number file to use.

        • +
        +
        +
      • +
      • ‘Random’ Generate a random bool value.

        +
        +
          +
        • p = float_value (default = 0.5) The probability of getting True. [New in v1.5]

        • +
        +
        +
      • +
      • ‘RandomBinomial’ Generate random values from a Binomial deviate with N=1.

        +
        +
        +

        Note

        +

        The default case with p = 0.5 is equivalent to the ‘Random’ type. So this +would normally be used for random booleans with a different probability of True.

        +
        +
          +
        • p = float_value (default = 0.5) The probability of True.

        • +
        +
        +
      • +
      • ‘Sequence’ Generate a sequence of values.

        +
        +
          +
        • first = bool_value (default = False) For bool, the only two values in the sequence are False and True, so step and last are not needed.

        • +
        • repeat = int_value (default = 1) How many times to repeat the same value before moving on.

        • +
        • index_key = str_value (optional) Which number to use for indexing in the sequence. (See the description of this for float_value for more details.)

        • +
        +
        +
      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of bool_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

str_value

+

Options are:

+
    +
  • A normal str value (e.g. ‘out.fits’)

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Catalog’ Read the value from an input catalog. This requires that input.catalog be specified and uses the following fields:

        +
        +
          +
        • col = int_value for ASCII catalog or str_value for FITS catalog (required)

        • +
        • index = int_value (default = ‘Sequence’ from 0 to input_cat.nobjects-1)

        • +
        • num = int_value (default = 0) If input.catalog is a list, this indicates which number catalog to use.

        • +
        +
        +
      • +
      • ‘Dict’ Read the value from an input dictionary. This requires that input.dict be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required) For specifying keys below the first level of the dictionary, the key string is split using the input.dict.key_split value (default = ‘.’) into multiple keys. e.g. key : galaxy_constants.redshift would be parsed as dict['galaxy_constants']['redshift'].

        • +
        • num = int_value (default = 0) If input.dict is a list, this indicates which number dictionary to use.

        • +
        +
        +
      • +
      • ‘FitsHeader’ Read the value from an input FITS header. This requires that input.fits_header be specified and uses the following fields:

        +
        +
          +
        • key = str_value (required)

        • +
        • num = int_value (default = 0) If input.fits_header is a list, this indicates which number file to use.

        • +
        +
        +
      • +
      • ‘NumberedFile’ Build a string that includes a number portion: rootNNNNext. e.g. file0001.fits, file0002.fits, etc.

        +
        +
          +
        • root = str_value (required) The part of the string that comes before the number.

        • +
        • num = int_value (default = ‘Sequence’ starting with 0) The number to use in the string.

        • +
        • digits = int_value (default = 0) How many digits to use (minimum) to write the number. The number will be left-padded with 0s as needed.

        • +
        • ext = str_value (default = ‘.fits’ for output.file_name and the file_name entries for sub-items within output – psf, weight, badpix –, and ‘’ for all other uses) An extension to place after the number.

        • +
        +
        +
      • +
      • ‘FormattedStr’ Build a string using a format akin to the normal python %-style formatting or C/C++ printf-style formatting.

        +
        +
          +
        • format = str_value (required) The formatting string to use. (e.g. ‘image_%f_%d.fits’)

        • +
        • items = list (required) A list of items to insert into the corresponding % items in the format string. The letter after the % indicates what kind of value each item is. So for the above example, the first item in the string should be a float_value to put into the %f spot. The second should be an int_value to put into the %d spot.

        • +
        +
        +
      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of str_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

angle_value

+

These represent Angle values.

+

Options are:

+
    +
  • A string consisting of a float followed by one of the following angle units: radians, degrees, hours, arcminutes, arcseconds. These may be abbreviated as rad, deg, hr, arcmin, arcsec. (e.g. ‘45 deg’)

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Radians’ or ‘Rad’ Use a float_value as an angle in radians.

        +
        +
          +
        • theta = float_value (required)

        • +
        +
        +
      • +
      • ‘Degrees’ or ‘Deg’ Use a float_value as an angle in degrees.

        +
        +
          +
        • theta = float_value (required)

        • +
        +
        +
      • +
      • ‘Random’ Generate a random angle uniformly distributed from 0 to 2pi radians.

      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of angle_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Sum’ The sum of two other angle_value items.

        +
        +
          +
        • items = list (required) A list of angle_value items to be added together.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

shear_value

+

These represent Shear values.

+

Options are:

+
    +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘E1E2’ Specify as a distortion in cartesian coordinates.

        +
        +
          +
        • e1 = float_value (required)

        • +
        • e2 = float_value (required)

        • +
        +
        +
      • +
      • ‘EBeta’ Specify as a distortion in polar coordinates.

        +
        +
          +
        • e = float_value (required)

        • +
        • beta = angle_value (required)

        • +
        +
        +
      • +
      • ‘G1G2’ Specify as a reduced shear in cartesian coordinates.

        +
        +
          +
        • g1 = float_value (required)

        • +
        • g2 = float_value (required)

        • +
        +
        +
      • +
      • ‘GBeta’ Specify as a reduced shear in polar coordinates.

        +
        +
          +
        • g = float_value (required)

        • +
        • beta = angle_value (required)

        • +
        +
        +
      • +
      • ‘Eta1Eta2’ Specify as a conformal shear in cartesian coordinates.

        +
        +
          +
        • eta1 = float_value (required)

        • +
        • eta2 = float_value (required)

        • +
        +
        +
      • +
      • ‘EtaBeta’ Specify as a conformal shear in polar coordinates.

        +
        +
          +
        • eta = float_value (required)

        • +
        • beta = angle_value (required)

        • +
        +
        +
      • +
      • ‘QBeta’ Specify as an axis ratio and position angle.

        +
        +
          +
        • q = float_value (required)

        • +
        • beta = angle_value (required)

        • +
        +
        +
      • +
      • ‘PowerSpectrumShear’ Calculate a shear from a given power spectrum. This requires that input.power_spectrum be specified and uses the following field:

        +
        +
          +
        • num = int_value (default = 0) If input.power_spectrum is a list, this indicates which number power spectrum to use.

        • +
        +
        +
      • +
      • ‘NFWHaloShear’ Calculate a shear from an NFW Halo mass. This requires that input.nfw_halo be specified and uses the following fields:

        +
        +
          +
        • gal.redshift = float_value (required) Special: The redshift item must be in the gal field, not shear. Or if the galaxy is chromatic, it should be in the galaxy’s SED field.

        • +
        • num = int_value (default = 0) If input.nfw_halo is a list, this indicates which number halo to use.

        • +
        +
        +
      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of shear_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Sum’ The sum of two other shear_value items.

        +
        +
        +

        Note

        +

        Unlike the other kinds of values, shears addition is not commutative. +g_a + g_b is not the same as g_b + g_a. Thus, the order of the elements +in the items list matters. The shear effects are applied from last to first, +so the effects should be listed in order from closest to the observer to farthest +along the light path. This is a somewhat standard convention for what +g_a + g_b means when applied to a galaxy. g_b would be a shear that is +close to the galaxy, and then g_a would be another shear closer to the +observer (perhaps within the telescope).

        +
        +
          +
        • items = list (required) A list of shear_value items to be added together.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

pos_value

+

These represent PositionD values, usually for a location on the image.

+

Options are:

+
    +
  • A string consisting of two floats separated by a comma and possibly white space. (e.g. ‘1.7, 3.0’)

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘XY’ Specify x and y separately.

        +
        +
          +
        • x = float_value (required)

        • +
        • y = float_value (required)

        • +
        +
        +
      • +
      • ‘RTheta’ Specify using polar coordinate.

        +
        +
          +
        • r = float_value (required)

        • +
        • theta = angle_value (required)

        • +
        +
        +
      • +
      • ‘RandomCircle’ Generate a random value uniformly distributed within a circle of a given radius.

        +
        +
        +

        Note

        +

        This is different from ‘RTheta’ with each one random, since that would +preferentially pick locations near the center of the circle.

        +
        +
          +
        • radius = float_value (required) The size of the circle within which to draw a random value.

        • +
        • inner_radius = float_value (default = 0) If desired, an inner circle may be excluded, making this an annulus rather than a full circle.

        • +
        • center = pos_value (default = 0,0) The center of the circle.

        • +
        +
        +
      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of pos_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Sum’ The sum of two other pos_value items.

        +
        +
          +
        • items = list (required) A list of pos_value items to be added together.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

sky_value

+

These represent CelestialCoord values for a location in the sky.

+

Options are:

+
    +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘RADec’ Specify ra and dec separately.

        +
        +
          +
        • ra = angle_value (required)

        • +
        • dec = angle_value (required)

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

table_value

+

These represent LookupTable values to provide some kind of unary function, although in some +cases you may be able to provide a regular function instead, e.g. via an Eval type using a lambda +function.

+

Options are:

+
    +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘File’ Read a LookupTable from a file. cf. LookupTable.from_file.

        +
        +
          +
        • file_name = str_value (required)

        • +
        • interpolant = str_value (default = ‘spline’) Which interpolant to use.

        • +
        • x_log = bool_value (default = False) Whether to use log(x) for the abscissae.

        • +
        • f_log = bool_value (default = False) Whether to use log(f) for the ordinates.

        • +
        • amplitude = float_value (default = 1.0) An optional scaling to apply to the +ordinates.

        • +
        +
        +
      • +
      • ‘List’ Select items from a list.

        +
        +
          +
        • items = list (required) A list of table_value items.

        • +
        • index = int_value (default = ‘Sequence’ from 0 to len(items)-1)

        • +
        +
        +
      • +
      • ‘Current’ Use the current value of some other item in the config file. (See the description of this for float_value for more details.)

        +
        +
          +
        • key = str_value (required) The key name of the item to use.

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

quantity_value

+

These represent astropy.units.Quantity values, which are a combination of a float and a unit (specifically, an astropy.units.Unit).

+

Options are:

+
    +
  • An astropy.units.Quantity value (e.g. ‘8.7*u.m’, where ‘u’ is the astropy.units module)

  • +
  • A string interpretable by astropy.units.Quantity (e.g. ‘8.7 m’)

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Quantity’ Specify the value and unit separately.

        +
        +
          +
        • value = float_value (required)

        • +
        • unit = unit_value (required)

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

unit_value

+

These represent astropy.units.Unit values.

+

Options are:

+
    +
  • An astropy.units.Unit value (e.g. ‘u.m’, where ‘u’ is the astropy.units module)

  • +
  • A string interpretable by astropy.units.Unit (e.g. ‘m’)

  • +
  • A dict with:

    +
    +
      +
    • type = str (required) Valid options are:

      +
      +
        +
      • ‘Unit’ Specify the unit.

        +
        +
          +
        • unit = str_value (required)

        • +
        +
        +
      • +
      • ‘Eval’ Evaluate a string. See Eval type.

      • +
      +
      +
    • +
    +
    +
  • +
  • A string that starts with ‘$’ or ‘@’. See Shorthand notation.

  • +
+
+
+

Eval type

+

Every kind of value has ‘Eval’ as one of its allowed types. This works a little bit differently +than the other types, so we describe it here in its own section.

+

The only required attribute to go along with an ‘Eval’ is str, which is the string to be +evaluated using the python eval function.

+

For example str : '800 * 1.e-9 / 4 * 206265' will evaluate to 0.041253. (This example is taken from demo3.yaml.) This might either be easier than doing a calculation +yourself or perhaps be clearer as to how the number was formed. For example, this example +calculates lam_over_diam using lambda = 800 nm, D = 4 m, converting the result into arcsec. +If you later wanted to change to a 6.5m telescope, it would be very clear what to change, +as opposed to if the value were listed as 0.041253.

+
+

Preset variables

+

The ‘Eval’ type gets even more powerful when you use variables. The file demo10.yaml has some +examples that use the pos variable, the position of the galaxy relative to the center of the +image, which GalSim will make available for you for any +‘Tiled’ or ‘Scattered’ image. The PSF fwhm is given as +'0.9 + 0.5 * (world_pos.x**2 + world_pos.y**2) / 100**2', which calculates the PSF size as a function of +position on the image.

+

Variables that GalSim will provide for you to use:

+
    +
  • image_pos = the position of the object on the image in pixels.

    +
    +
      +
    • Available if image type is ‘Tiled’ or ‘Scattered’

    • +
    • Available if image_pos or world_pos is explicitly given in the stamp field.

    • +
    • A galsim.PositionD instance

    • +
    +
    +
  • +
  • world_pos = the position of the object in world coordinates.

    +
    +
    +
    +
  • +
  • sky_pos = the position of the object in sky coordinates (RA, Dec)

    +
    +
      +
    • Available if sky_pos is explicitly given in the stamp field.

    • +
    • Available if world_pos is defined (as per above) and the WCS is a CelestialWCS.

    • +
    • A galsim.CelestialCoord instance

    • +
    +
    +
  • +
  • uv_pos = the position of the object in a tangent plane projection relative to world_center.

    +
    +
      +
    • Available if either image_pos or world_pos is defined and the wcs is defined.

    • +
    • A galsim.PositionD instance

    • +
    +
    +
  • +
  • image_center = the center of the image in pixels.

    +
    +
    +
    +
  • +
  • world_center = the world position of image_center.

    +
    +
    +
    +
  • +
  • image_origin = the origin of the image in pixels. This is the position on the image that corresponds to the lower-leftmost pixel

    +
    +
    +
    +
  • +
  • image_xsize, image_ysize = the size of the image in pixels.

  • +
  • image_bounds = the bounds of the current image.

    +
    +
    +
    +
  • +
  • stamp_xsize, stamp_ysize = the size of the postage stamp in pixels if available.

    +
    +
      +
    • Not always available, since the postage stamp is allowed to be automatically sized based on the size of final object profile.

    • +
    +
    +
  • +
  • pixel_scale = the pixel scale of the current image or postage stamp

    +
    +
      +
    • Only available if the WCS is a simple pixel scale.

    • +
    +
    +
  • +
  • wcs = the WCS of the current image or postage stamp

    +
    +
    +
    +
  • +
  • bandpass = the bandpass of the current image if defined.

    +
    +
    +
    +
  • +
  • file_num = the number of the file currently being worked on.

  • +
  • image_num = the number of the image currently being worked on.

    +
    +
      +
    • If parsing a value in input or output, this may be set to 0 or the last image number from the previous file (if any).

    • +
    +
    +
  • +
  • obj_num = the number of the object currently being worked on.

    +
    +
      +
    • If parsing a value in input, output, or image, this may be set to 0 or the last object number from the previous image (if any).

    • +
    +
    +
  • +
  • start_obj_num = the number of the first object in the current file.

  • +
  • rng = the random number generator being used for this object.

    +
    +
      +
    • A galsim.BaseDeviate instance

    • +
    • You can convert it to whatever deviate you need. e.g. galsim.GaussianDeviate(rng,1.0,0.2)()

    • +
    +
    +
  • +
+
+
+

Available Modules

+

Python modules that GalSim will import for you to use:

+
    +
  • math

  • +
  • numpy or np

  • +
  • os

  • +
  • galsim (obviously)

  • +
  • Anything in the modules field of your configuration file.

  • +
+
+
+

User-defined variables

+

It is also possible to define your own variables to use in your expression simply by +defining more attributes in addition to str. The first letter of the attribute +declares what type it should be evaluated to. Then the rest of the attribute name is +the name of your variable.

+

For example, we do not have a specific type for drawing from a Log-Normal distribution. +If you want the flux, say, to be log-normally distributed, you can write something like +the following:

+
flux :
+    type : Eval
+    str : '1.e5 * math.exp(normal)'
+    fnormal : { type : RandomGaussian , sigma : 0.2 }
+
+
+

The f at the start of fnormal indicates that the variable normal should be +evaluated as a float_value. In this case using type = ‘RandomGaussian’.

+

Another example appears in demo10.yaml. There, we define the magnitude of the ellipticity as:

+
e:
+    type : Eval
+    fr : { type : Eval , str : '(world_pos.x**2 + world_pos.y**2)**0.5' }
+    str : '0.4 * (r/100)**1.5'
+
+
+

So this declares a float variable r that evaluates as the radial distance from the center. Then the ellipticity is defined in terms of r directly rather than via world_pos.

+

Initial letters of user-defined variables for ‘Eval’:

+
    +
  • ‘f’ = float

  • +
  • ‘i’ = int

  • +
  • ‘b’ = bool

  • +
  • ‘s’ = str

  • +
  • ‘a’ = galsim.Angle

  • +
  • ‘p’ = galsim.PositionD

  • +
  • ‘g’ = galsim.Shear

  • +
  • ‘t’ = galsim.LookupTable

  • +
  • ‘d’ = dict (This takes a dict as a literal, rather than evaluating it.)

  • +
  • ‘l’ = list (Similarly, this allows for a literal list in the config file.)

  • +
+
+
+

The eval-variables field

+

Sometimes it is useful to have the same variable used by multiple Eval calculations. For such cases, Eval will look for a top-level field called eval_variables. If this field is present, then anything defined there will be accessible in all Eval calculations in addition to whatever variables are defined for each specific Eval item.

+

This is similar to the functionality that YAML provides where a value can be named by putting a variable name with an & before it before any value. Then later, you can refer to the value by that name preceded by a *, rather than write the value again. This can lead to more maintainable config files.

+

It can be convenient to combine the YAML naming scheme with our eval_variables setup in the following way:

+
eval_variables :
+    fpixel_scale : &pixel_scale 0.3
+    istamp_size : &stamp_size 100
+    infiles : &nfiles 30
+    [ ... ]
+
+
+

This can be put near the top of the YAML file to put the important values all in one place with appropriate names. Then in the rest of the file, the variables can be used with the YAML * notation:

+
image:
+    pixel_scale : *pixel_scale
+
+
+

or as part of an Eval item:

+
shift :
+    type : RTheta
+    r : { type : Eval , str : 'pixel_scale * 0.5' }
+    theta : { type : Random }
+
+
+
+
+

Module-defined variables

+

If you are using any user-defined modules (loaded in the modules field), then they are +allowed to add to the list of Preset variables. The mechanism for this is to append items +to the list galsim.config.eval_base_variables. This list includes all of the variables +that an Eval type will load into the local namespace before evaluating. For instance, +if a user-defined module wants to make available a variable called, say, coadd_wcs, +then the module could add this name to the list of avilable variables as folllows:

+
galsim.config.eval_base_variables.append('coadd_wcs')
+
+
+

This should be done at module scope, since you only want to add it once. So probably best +to add it when your module is imported.

+

Then in some processing step, you could set this variable as

+
base['coadd_wcs'] = coadd_wcs
+
+
+

Then any subsequent Eval field could use this variable as a local variable, just like the +ones listed in Preset variables.

+
+
+

Shorthand notation

+

It can be a bit cumbersome at times to write out a full dict with type : Eval and the +str item you want to evaluate. To streamline this, we also allow for a shorthand notation +for both Eval and Current types.

+
    +
  • Any string that starts with ‘$’ is taken to mean an Eval type with the rest of the string +being used as the str field.

  • +
  • Any string that starts with ‘@’ is taken to mean a Current type with the rest of the string +being used as the key field.

  • +
+

Furthermore, you may use ‘@’ style Current specifications within an Eval string, where the +text after the ‘@’ up to the next white space is used for the key. +So for the above example of a half pixel shift in some random direction, you could write:

+
shift:
+    type : RTheta
+    r : '$0.5 * @image.pixel_scale'
+    theta : { type: Random }
+
+
+

In many situations, this shorthand notation aids readability. However, because there is +no dict, you cannot define any variables with this notation. If you need to define any +variables, you will need to use the regular dict notation.

+
+
+
+

Custom Value Types

+

To define your own value type, you will need to write an importable Python module +(typically a file in the current directory where you are running galsim, but it could also +be something you have installed in your Python distro) with a function that will be used +to generate the value you want from the parameters in the config dict.

+

The generator function should have the following functional form:

+
def GenerateCustomValue(config, base, value_type):
+    """Generate some kind of custom value given some configuration parameters
+
+    @param config       The configuration dict of the value being generated.
+    @param base         The base configuration dict.
+    @param value_type   The desired output type.
+
+    @returns value, safe
+
+    The returned value should be something of type value_type, and safe is a bool
+    value that indicates whether the value is safe to reuse for future stamps
+    (i.e. it is a constant value that will not change for later stamps).
+    """
+    # If you need a random number generator, this is the one to use.
+    rng = base['rng']
+
+    # Generate the desired value.
+    # Probably something complicated that you want this function to do.
+    value = [...]
+
+    safe = False  # typically, but set to True if this value is safe to reuse.
+    return value, safe
+
+
+

The base parameter is the original full configuration dict that is being used for running the +simulation. The config parameter is the local portion of the full dict that defines the value +being generated, e.g. config might be base['gal']['flux'].

+

Normally a generator function can only produce a single kind of output type (float for instance), +in which case you can probably ignore the value_type parameter. However, if your generator +can be used for multiple kinds of values (int, float and bool maybe), then you might want +to do something different depending on what value_type is given.

+

Then, in the Python module, you need to register this function with some type name, which will +be the value of the type attribute that triggers running this function. You also need to +give a list of all valid value types that are allowed for this function:

+
galsim.config.RegisterValueType('CustomValue', GenerateCustomValue, [float, int])
+
+
+
+
+galsim.config.RegisterValueType(type_name, gen_func, valid_types, input_type=None)[source]
+

Register a value type for use by the config apparatus.

+

A few notes about the signature of the generating function:

+
    +
  1. The config parameter is the dict for the current value to be generated. So it should +be the case that config[‘type’] == type_name.

  2. +
  3. The base parameter is the original config dict being processed.

  4. +
  5. The value_type parameter is the intended type of the generated value. It should +be one of the values that you specify as valid in valid_types.

  6. +
  7. The return value of gen_func should be a tuple consisting of the value and a boolean, +safe, which indicates whether the generated value is safe to use again rather than +regenerate for subsequent objects. This will be used upstream to determine if +objects constructed using this value are safe to keep or if they have to be rebuilt.

  8. +
+

The allowed types to include in valid_types are: float, int, bool, str, galsim.Angle, +galsim.Shear, galsim.PositionD. In addition, including None in this list means that +it is valid to use this type if you don’t necessarily know what type you are expecting. +This happens when building a truth catalog where each item should already be generated +and the current value and type stored, so currently the only two types that allow +None as a valid type are Current and Eval.

+
+
Parameters:
+
    +
  • type_name – The name of the ‘type’ specification in the config dict.

  • +
  • gen_func

    A function to generate a value from the config information. +The call signature is:

    +
    value, safe = Generate(config, base, value_type)
    +
    +
    +

  • +
  • valid_types – A list of types for which this type name is valid.

  • +
  • input_type – If the generator utilises an input object, give the key name of the +input type here. (If it uses more than one, this may be a list.) +[default: None]

  • +
+
+
+
+ +

If the generator will use a particular input type, you should let GalSim know this by specifying +the input_type when registering. E.g. if the generator expects to use an input catalog +to access some ancillary information for each object, you would register this fact using:

+
galsim.config.RegisterValueType('CustomValue', GenerateCustomValue, [float, int],
+                                input_type='catalog')
+
+
+

The input object can be accessed in the build function as e.g.:

+
input_cat = galsim.config.GetInputObj('catalog', config, base, 'CustomValue')
+
+
+

The last argument is just used to help give sensible error messages if there is some problem, +but it should typically be the name of the value type being built.

+

Finally, to use this custom type in your config file, you need to tell the config parser the +name of the module to load at the start of processing. e.g. if this function is defined in the +file my_custom_value.py, then you would use the following top-level modules field +in the config file:

+
modules:
+    - my_custom_value
+
+
+

This modules field is a list, so it can contain more than one module to load if you want. +Then before processing anything, the code will execute the command import my_custom_value, +which will read your file and execute the registration command to add your type to the list +of valid value types.

+

Then you can use this as a valid value type:

+
gal:
+    flux:
+        type: CustomValue
+        ...
+
+
+

For examples of custom values, see:

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/corr_noise.html b/docs/_build/html/corr_noise.html new file mode 100644 index 00000000000..e3727b0436a --- /dev/null +++ b/docs/_build/html/corr_noise.html @@ -0,0 +1,1079 @@ + + + + + + + Correlated Noise — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Correlated Noise

+

The pixel noise in real astronomical images can be correlated, so we have some functionality +to enable simulating this effect in GalSim.

+
    +
  • BaseCorrelatedNoise defines the correlated noise according to an input GSObject. This is +the base class for the other ways of defining correlated noise, and implements most of the +useful methods of these classes.

    +
    +

    Warning

    +

    This GSObject profile must have two-fold rotational symmetry to represent a physical +correlation function, and this requirement is not enforced by GalSim. Users need to +ensure this fact in their calling code.

    +
    +
  • +
  • CorrelatedNoise computes the correlated noise of in input Image, e.g. a blank patch of sky +in an image similar to the one you want to simulate.

  • +
  • UncorrelatedNoise is a BaseCorrelatedNoise that start with no correlations. This can then +be sheared or convolved with other profiles to induce correlations.

  • +
  • getCOSMOSNoise is a function the returns a BaseCorrelatedNoise corresponding to the +correlated noise found in the HST COSMOS F814W coadd images used by RealGalaxy.

  • +
+

While adding correlated noise to images is a useful feature, this functionality was originally +implemented in GalSim in order to remove correlations. Specifically, the RealGalaxy class +treats HST images as surface brightness profiles. These images have correlated noise that resulted +from the drizzle image processing. Furthermore, when sheared or convolved by a PSF, the +noise in these images becomes even more correlated. If uncorrected, these pixel correlations +can lead to biases in weak lensing shear estimates of the rendered galaxies.

+

To produce more accurate image simulations using these galaxies as the underlying models (and +especially avoid the weak lensing shear biases), it is often useful to “whiten” the correlated +noise that results form these manipulations.

+
    +
  • Image.whitenNoise takes a BaseCorrelatedNoise instance as the estimate of the correlated +noise already in an image, and attempts to add more noise to result in uncorrelated “white” +noise.

  • +
  • Image.symmetrizeNoise similarly adds noise, but only attempts to produce a noise profile with +4-fold (or generically any order) symmetry, which results in less added noise while still +achieving the goal of not having correlated noise bias weak lensing shear measurements.

  • +
+
+
+class galsim.BaseCorrelatedNoise(rng, gsobject, wcs=galsim.PixelScale(1.0))[source]
+

Bases: object

+

A Base Class describing 2D correlated Gaussian random noise fields.

+

A BaseCorrelatedNoise will not generally be instantiated directly. This is recommended as the +current BaseCorrelatedNoise.__init__ interface does not provide any guarantee that the input +GSObject represents a physical correlation function, e.g. a profile that is an even function +(two-fold rotationally symmetric in the plane) and peaked at the origin. The proposed pattern +is that users instead instantiate derived classes, such as the CorrelatedNoise, which are +able to guarantee the above.

+

If you have the correlation function as an image on file, you can use the class method +from_file, which does confirm that the file has the appropriate symmetry.

+

The BaseCorrelatedNoise is therefore here primarily to define the way in which derived classes +(currently only CorrelatedNoise and UncorrelatedNoise) store the random deviate, noise +correlation function profile and allow operations with it, generate images containing noise with +these correlation properties, and generate covariance matrices according to the correlation +function.

+
+
Parameters:
+
    +
  • rng – A BaseDeviate instance to use for generating the random numbers.

  • +
  • gsobject

    The GSObject defining the correlation function.

    +

  • +
  • wcs – The wcs for the image to define the phyical relationship between the pixels. +[default: PixelScale(1.0)]

  • +
+
+
+
+
+applyTo(image)[source]
+

Apply this correlated Gaussian random noise field to an input Image.

+

To add deviates to every element of an image, the syntax:

+
>>> image.addNoise(correlated_noise)
+
+
+

is preferred. However, this is equivalent to calling this instance’s applyTo method as +follows:

+
>>> correlated_noise.applyTo(image)
+
+
+

On output the Image instance image will have been given additional noise according to +the given BaseCorrelatedNoise instance correlated_noise. Normally, image.scale +is used to determine the input pixel separation, and if image.scale <= 0 a pixel scale +of 1 is assumed. If the image has a non-uniform WCS, the local uniform approximation at +the center of the image will be used.

+

Note that the correlations defined in a correlated_noise object are defined in terms of +world coordinates (i.e. typically arcsec on the sky). Some care is thus required if you +apply correlated noise to an image with a non-trivial WCS. The correlations will have a +specific direction and scale in world coordinates, so if you apply them to an image with +a WCS that has a rotation or a different pixel scale than the original, the resulting +correlations will have the correct direction and scale in world coordinates, but a +different direction and/or scale in image coordinates.

+

If you want to override this behavior, you can view your image with the WCS of the +correlation function and apply the noise to that. For example:

+
>>> image = galsim.Image(nx, ny, wcs=complicated_wcs)
+>>> noise = galsim.getCOSMOSNoise(rng=rng)
+>>> image.view(wcs=noise.wcs).addNoise(noise)
+
+
+

This will create noise whose pixel-to-pixel correlations match those of the original +correlated noise image (in this case, the COSMOS images). If the input image has no WCS +set, then it will be treated as having the same WCS as the noise.

+

Note that the correlated noise field in image will be periodic across its boundaries: +this is due to the fact that the internals of the BaseCorrelatedNoise currently use a +relatively simple implementation of noise generation using the Fast Fourier Transform. +If you wish to avoid this property being present in your final image you should add the +noise to an image of greater extent than you need, and take a subset.

+
+
Parameters:
+

image – The input Image object.

+
+
+
+ +
+
+convolvedWith(gsobject, gsparams=None)[source]
+

Convolve the correlated noise model with an input GSObject.

+

The resulting correlated noise model will then give a statistical description of the noise +field that would result from convolving noise generated according to the initial correlated +noise with a kernel represented by gsobject (e.g. a PSF).

+

The practical purpose of this method is that it allows us to model what is happening to +noise in the images from Hubble Space Telescope that we use for simulating PSF convolved +galaxies with the RealGalaxy class.

+

This modifies the representation of the correlation function, but leaves the random number +generator unchanged.

+

Examples:

+

The following command simply applies a Moffat PSF with slope parameter beta=3. and +FWHM=0.7:

+
>>> cn = cn.convolvedWith(galsim.Moffat(beta=3., fwhm=0.7))
+
+
+

Often we will want to convolve with more than one function. For example, if we wanted to +simulate how a noise field would look if convolved with a ground-based PSF (such as the +Moffat above) and then rendered onto a new (typically larger) pixel grid, the following +example command demonstrates the syntax:

+
>>> cn = cn.convolvedWith(
+...    galsim.Convolve([galsim.Deconvolve(galsim.Pixel(0.03)),
+...                     galsim.Pixel(0.2), galsim.Moffat(3., fwhm=0.7),
+
+
+

Note, we also deconvolve by the original pixel, which should be the pixel size of the +image from which the correlated_noise was made. This command above is functionally +equivalent to:

+
>>> cn = cn.convolvedWith(galsim.Deconvolve(galsim.Pixel(0.03)))
+>>> cn = cn.convolvedWith(galsim.Pixel(0.2))
+>>> cn = cn.convolvedWith(galsim.Moffat(beta=3., fwhm=0.7))
+
+
+

as is demanded for a linear operation such as convolution.

+
+
Parameters:
+
    +
  • gsobject – A GSObject or derived class instance representing the function +with which the user wants to convolve the correlated noise model.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
Returns:
+

the new BaseCorrelatedNoise of the convolved profile.

+
+
+
+ +
+
+copy(rng=None)[source]
+

Returns a copy of the correlated noise model.

+

By default, the copy will share the BaseDeviate random number generator with the parent +instance. However, you can provide a new rng to use in the copy if you want with:

+
>>> cn_copy = cn.copy(rng=new_rng)
+
+
+
+ +
+
+dilate(scale)[source]
+

Apply the appropriate changes to the scale and variance for when the object has +an applied dilation.

+
+
Parameters:
+

scale – The linear dilation scale factor.

+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified dilation.

+
+
+
+ +
+
+drawImage(image=None, scale=None, wcs=None, dtype=None, add_to_image=False)[source]
+

A method for drawing profiles storing correlation functions.

+

This is a mild reimplementation of the GSObject.drawImage method. The method is +automatically set to ‘sb’ and cannot be changed, and the gain is set to unity. +Also, not all the normal parameters of the GSObject method are available.

+

If scale and wcs are not set, and the image has no wcs attribute, then this +will use the wcs of the BaseCorrelatedNoise object.

+
+
Parameters:
+
    +
  • image – If provided, this will be the image on which to draw the profile. +If image is None, then an automatically-sized Image will be +created. If image is given, but its bounds are undefined (e.g. if +it was constructed with image = galsim.Image()), then it will be +resized appropriately based on the profile’s size [default: None].

  • +
  • scale – If provided, use this as the pixel scale for the image. [default: None]

  • +
  • wcs – If provided, use this as the wcs for the image (possibly overriding any +existing image.wcs). At most one of scale or wcs may be +provided. [default: None] Note: If no WCS is provided either via +scale, wcs or image.wcs, then the noise object’s wcs will +be used.

  • +
  • dtype – The data type to use for an automatically constructed image. Only +valid if image is None. [default: None, which means to use +numpy.float32]

  • +
  • add_to_image – Whether to add flux to the existing image rather than clear out +anything in the image before drawing. +Note: This requires that image be provided and that it have defined +bounds. [default: False]

  • +
+
+
Returns:
+

an Image of the correlation function.

+
+
+
+ +
+
+drawKImage(image=None, nx=None, ny=None, bounds=None, scale=None, add_to_image=False)[source]
+

A method for drawing profiles storing correlation functions (i.e., power spectra) in +Fourier space.

+

This is a mild reimplementation of the GSObject.drawKImage method. The gain is +automatically set to unity and cannot be changed. Also, not all the normal parameters of +the GSObject method are available.

+

If scale is not set, and image has no wcs attribute, then this will use the +wcs of the BaseCorrelatedNoise object, which must be a PixelScale.

+
+
Parameters:
+
    +
  • image – If provided, this will be the Image onto which to draw the k-space +image. If image is None, then an automatically-sized image will be +created. If image is given, but its bounds are undefined, then it +will be resized appropriately based on the profile’s size. +[default: None]

  • +
  • nx – If provided and image is None, use to set the x-direction size of +the image. Must be accompanied by ny.

  • +
  • ny – If provided and image is None, use to set the y-direction size of +the image. Must be accompanied by nx.

  • +
  • bounds – If provided and image is None, use to set the bounds of the image.

  • +
  • scale – If provided, use this as the pixel scale, dk, for the images. +If scale is None and image is given, then take the provided +images’ pixel scale (which must be equal). +If scale is None and image is None, then use the Nyquist scale. +If scale <= 0 (regardless of image), then use the Nyquist scale. +[default: None]

  • +
  • add_to_image – Whether to add to the existing images rather than clear out +anything in the image before drawing. +Note: This requires that image be provided and that it has defined +bounds. [default: False]

  • +
+
+
Returns:
+

the tuple of Image instances, (re, im) (created if necessary)

+
+
+
+ +
+
+expand(scale)[source]
+

Scale the linear scale of correlations in this noise model by scale.

+

Scales the linear dimensions of the image by the factor scale, e.g. +half_light_radius <– half_light_radius * scale.

+
+
Parameters:
+

scale – The linear rescaling factor to apply.

+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified expansion.

+
+
+
+ +
+
+classmethod from_file(file_name, pixel_scale, rng=None, variance=0.0, x_interpolant=None, gsparams=None)[source]
+

Read a correlated noise profile from a file.

+

The file should contain an image of the correlation.

+
+
    +
  • The image should be square with odd size in each direction.

  • +
  • The central pixel corresponds to the zero-lag correlation, i.e. the variance. +Call this pixel element (0,0).

  • +
  • The other pixels (i,j) give the cross-correlation of the noise for pixels separated +by i pixels in the x direction (columns) and j pixels in the y direction (rows).

  • +
  • The image therefore must be 180 degree rotationally symmetric. i.e. the value at +(i,j) must be the same as at (-i,-j).

  • +
+
+

The pixel_scale is also required and defines the pixel scale of the original image.

+

The default x_interpolant is a galsim.Linear(), which uses bilinear interpolation. +The use of this interpolant is an approximation that gives good empirical results without +requiring internal convolution of the correlation function profile by a Pixel object when +applying correlated noise to images: such an internal convolution has been found to be +computationally costly in practice, requiring the Fourier transform of very large arrays.

+

The use of the bilinear interpolants means that the representation of correlated noise will +be noticeably inaccurate in at least the following two regimes:

+
    +
  1. If the pixel scale of the desired final output (e.g. the target image of +BaseCorrelatedNoise.drawImage, BaseCorrelatedNoise.applyTo or +BaseCorrelatedNoise.whitenImage) is small relative to the separation between pixels in +the image used to instantiate cn as shown above.

  2. +
  3. If the BaseCorrelatedNoise instance cn was instantiated with an image of scale +comparable to that in the final output, and cn has been rotated or otherwise +transformed (e.g. via the BaseCorrelatedNoise.rotate, BaseCorrelatedNoise.shear +methods; see below).

  4. +
+

Conversely, the approximation will work best in the case where the correlated noise used to +instantiate the cn is taken from an input image for which image.scale is smaller +than that in the desired output. This is the most common use case in the practical +treatment of correlated noise when simulating galaxies from space as observed in +ground-based surveys.

+

Changing from the default bilinear interpolant is made possible, but not recommended. +In our validation tests, we found that the Linear interpolant usually gave the most +accurate results.

+
+
Parameters:
+
    +
  • file_name – The name of the file to read.

  • +
  • pixel_scale – The pixel scale of the original image.

  • +
  • rng – If provided, a random number generator to use as the random number +generator of the resulting noise object. (may be any kind of +BaseDeviate object) [default: None, in which case, one will be +automatically created, using the time as a seed.]

  • +
  • variance – Scales the correlation function so that its point variance, equivalent +to its value at zero separation distance, matches this value. +[default: 0., which means to use the variance in the original file.]

  • +
  • x_interpolant – Forces use of a non-default interpolant for interpolation of the +internal lookup table in real space. See below for more details. +[default: galsim.Linear()]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
Returns:
+

a BaseCorrelatedNoise instance

+
+
+
+ +
+
+getVariance()[source]
+

Return the point variance of this noise field, equal to its correlation function value at +zero distance.

+

This is the variance of values in an image filled with noise according to this model.

+
+ +
+
+property gsparams
+

The GSParams for this object.

+
+ +
+
+lens(g1, g2, mu)[source]
+

Apply the appropriate changes for when the object has an applied shear and magnification.

+
+
Parameters:
+
    +
  • g1 – First component of lensing (reduced) shear to apply to the object.

  • +
  • g2 – Second component of lensing (reduced) shear to apply to the object.

  • +
  • mu – Lensing magnification to apply to the object.

  • +
+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified shear and magnification.

+
+
+
+ +
+
+magnify(mu)[source]
+

Apply the appropriate changes to the scale and variance for when the object has +an applied magnification mu.

+
+
Parameters:
+

mu – The lensing magnification

+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified magnification.

+
+
+
+ +
+
+property rng
+

The BaseDeviate for this object.

+
+ +
+
+rotate(theta)[source]
+

Apply a rotation theta to this correlated noise model.

+
+
Parameters:
+

theta – Rotation angle (Angle object, positive means anticlockwise).

+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified rotation.

+
+
+
+ +
+
+shear(*args, **kwargs)[source]
+

Apply a shear to this correlated noise model, where arguments are either a Shear, +or arguments that will be used to initialize one.

+

For more details about the allowed keyword arguments, see the Shear docstring.

+
+
Parameters:
+

shear – The shear to be applied. Or, as described above, you may instead supply +parameters do construct a shear directly. eg. corr.shear(g1=g1,g2=g2).

+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified shear.

+
+
+
+ +
+
+symmetrizeImage(image, order=4)[source]
+

Apply noise designed to impose N-fold symmetry on the existing noise in a (square) input +Image.

+

On input, The Image, image, is assumed to have correlated noise described by this +BaseCorrelatedNoise instance.

+

On output image will have been given additional (correlated) noise designed to +symmetrize the noise profile.

+

When called for a non-square image, this method will raise an exception, unlike the noise +whitening routines.

+

The order of the symmetry can be supplied as a keyword argument, with the default being +4 because this is presumably the minimum required for the anisotropy of noise correlations +to not affect shear statistics.

+

Note: the syntax image.symmetrizeNoise(noise, order) is preferred, but it is equivalent +to:

+
>>> correlated_noise.symmetrizeImage(image, order=order)
+
+
+

If the image originally contained noise with a correlation function described by the +correlated_noise instance, the combined noise after using the symmetrizeImage() method +will have a noise correlation function with N-fold symmetry, where N=order.

+

Note that the code doesn’t check that the “if” above is true: the user MUST make sure this +is the case for the final noise correlation function to be symmetric in the requested way.

+

Normally, image.scale is used to determine the input pixel separation, and if +image.wcs is None, it will use the wcs of the noise. If the image has a non-uniform +WCS, the local uniform approximation at the center of the image will be used.

+

If you are interested in a theoretical calculation of the variance in the final noise field +after imposing symmetry, the symmetrizeImage() method in fact returns this variance. +For example:

+
>>> variance = correlated_noise.symmetrizeImage(image, order=order)
+
+
+

For context, in comparison with the whitenImage method for the case of noise +correlation functions that are roughly like those in the COSMOS HST data, the amount of +noise added to impose N-fold symmetry is usually much less than what is added to fully +whiten the noise. The usage of symmetrizeImage() is totally analogous to the usage of +whitenImage.

+
+
Parameters:
+
    +
  • image – The square input Image object.

  • +
  • order – The order at which to require the noise to be symmetric. All noise fields +are already 2-fold symmetric, so order should be an even integer >2. +[default: 4].

  • +
+
+
Returns:
+

the theoretically calculated variance of the combined noise fields in the updated image.

+
+
+
+ +
+
+transform(dudx, dudy, dvdx, dvdy)[source]
+

Apply an arbitrary jacobian transformation to this correlated noise model.

+
+
Parameters:
+
    +
  • dudx – du/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dudy – du/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdx – dv/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdy – dv/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
+
+
Returns:
+

a new BaseCorrelatedNoise object with the specified transformation.

+
+
+
+ +
+
+whitenImage(image)[source]
+

Apply noise designed to whiten correlated Gaussian random noise in an input Image.

+

On input, The Image, image, is assumed to have correlated noise described by this +BaseCorrelatedNoise instance.

+

On output image will have been given additional (correlated) noise designed to whiten +the noise profile.

+

Note: the syntax image.whitenNoise(noise) is normally preferred, but it is equivalent +to:

+
>>> correlated_noise.whitenImage(image)
+
+
+

If the image originally contained noise with a correlation function described by the +correlated_noise instance, the combined noise after using the whitenImage() method +will be approximately uncorrelated. Tests using COSMOS noise fields suggest ~0.3% residual +off-diagonal covariances after whitening, relative to the variance, although results may +vary depending on the precise correlation function of the noise field. +(See devel/external/hst/compare_whitening_subtraction.py for the COSMOS tests.)

+

Note that the code doesn’t check that the “if” above is true: the user MUST make sure this +is the case for the final noise to be uncorrelated.

+

Normally, image.scale is used to determine the input Image pixel separation, and if +image.wcs is None, it will use the wcs of the noise. If the image has a non-uniform +WCS, the local uniform approximation at the center of the image will be used.

+

If you are interested in a theoretical calculation of the variance in the final noise field +after whitening, the whitenImage() method in fact returns this variance. For example:

+
>>> variance = correlated_noise.whitenImage(image)
+
+
+

Example:

+

To see noise whitening in action, let us use a model of the correlated noise in COSMOS +as returned by the getCOSMOSNoise function. Let’s initialize and add noise to an image:

+
>>> cn = galsim.getCOSMOSNoise()
+>>> image = galsim.ImageD(256, 256, scale=0.03)
+>>> # The scale should match the COSMOS default since didn't specify another
+>>> image.addNoise(cn)
+
+
+

The image will then contain a realization of a random noise field with COSMOS-like +correlation. Using the whitenImage() method, we can now add more noise to image +with a power spectrum specifically designed to make the combined noise fields uncorrelated:

+
>>> cn.whitenImage(image)
+
+
+

Of course, this whitening comes at the cost of adding further noise to the image, but +the algorithm is designed to make this additional noise (nearly) as small as possible.

+
+
Parameters:
+

image – The input Image object.

+
+
Returns:
+

the theoretically calculated variance of the combined noise fields in the updated image.

+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+
+ +
+
+withScaledVariance(variance_ratio)[source]
+

Scale the entire correlated noise field by the given factor.

+

This is equivalent to cn * variance_ratio.

+
+
Parameters:
+

variance_ratio – The factor by which to scale the variance of the correlation +function profile.

+
+
Returns:
+

a BaseCorrelatedNoise object whose variance and covariances have been scaled up by +the given factor.

+
+
+
+ +
+
+withVariance(variance)[source]
+

Set the point variance of the noise field, equal to its correlation function value at +zero distance, to an input variance. The rest of the correlated noise field is scaled +proportionally.

+
+
Parameters:
+

variance – The desired point variance in the noise.

+
+
Returns:
+

a BaseCorrelatedNoise object with the new variance.

+
+
+
+ +
+ +
+
+class galsim.CorrelatedNoise(image, rng=None, scale=None, wcs=None, x_interpolant=None, correct_periodicity=True, subtract_mean=False, gsparams=None)[source]
+

Bases: BaseCorrelatedNoise

+

A class that represents 2D correlated noise fields calculated from an input Image.

+

This class stores an internal representation of a 2D, discrete correlation function, and allows +a number of subsequent operations including interpolation, shearing, magnification and rendering +of the correlation function profile into an output Image.

+

The class also allows correlated Gaussian noise fields to be generated according to the +correlation function, and added to an Image: see BaseCorrelatedNoise.applyTo.

+

It also provides methods for whitening or imposing N-fold symmetry on pre-existing noise that +shares the same spatial correlations: see BaseCorrelatedNoise.whitenImage and +BaseCorrelatedNoise.symmetrizeImage, respectively.

+

It also allows the combination of multiple correlation functions by addition, and for the +scaling of the total variance they represent by scalar factors.

+
+
Parameters:
+
    +
  • image – The image from which to derive the correlated noise profile

  • +
  • rng – A BaseDeviate instance to use for generating the random numbers.

  • +
  • scale – If provided, use this as the pixel scale. Normally, the scale (or wcs) +is taken from the image.wcs field, but you may override that by +providing either scale or wcs. [default: use image.wcs if defined, +else 1.0, unless wcs is provided]

  • +
  • wcs – If provided, use this as the wcs for the image. At most one of +scale or wcs may be provided. [default: None]

  • +
  • x_interpolant – The interpolant to use for interpolating the image of the correlation +function. (See below.) [default: galsim.Linear()]

  • +
  • correct_periodicity – Whether to correct for the effects of periodicity. (See below.) +[default: True]

  • +
  • subtract_mean – Whether to subtract off the mean value from the image before computing +the correlation function. [default: False]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+

Basic example:

+
>>> cn = galsim.CorrelatedNoise(image, rng=rng)
+
+
+

Instantiates a CorrelatedNoise using the pixel scale information contained in image.scale +(assumes the scale is unity if image.scale <= 0.) by calculating the correlation function +in the input image. The input rng must be a BaseDeviate or derived class instance, +setting the random number generation for the noise.

+

Optional Inputs:

+
>>> cn = galsim.CorrelatedNoise(image, rng=rng, scale=0.2)
+
+
+

The example above instantiates a CorrelatedNoise, but forces the use of the pixel scale +scale to set the units of the internal lookup table.:

+
>>> cn = galsim.CorrelatedNoise(image, rng=rng, x_interpolant=galsim.Lanczos(5))
+
+
+

The example above instantiates a CorrelatedNoise, but forces use of a non-default interpolant +for interpolation of the internal lookup table in real space.

+

The default x_interpolant is galsim.Linear(), which uses bilinear interpolation. +The use of this interpolant is an approximation that gives good empirical results without +requiring internal convolution of the correlation function profile by a Pixel object when +applying correlated noise to images: such an internal convolution has been found to be +computationally costly in practice, requiring the Fourier transform of very large arrays.

+

The use of the bilinear interpolants means that the representation of correlated noise will be +noticeably inaccurate in at least the following two regimes:

+
+
    +
  1. If the pixel scale of the desired final output (e.g. the target image of +BaseCorrelatedNoise.drawImage, BaseCorrelatedNoise.applyTo or +BaseCorrelatedNoise.whitenImage) is small relative to the separation between pixels +in the image used to instantiate cn as shown above.

  2. +
  3. If the CorrelatedNoise instance cn was instantiated with an image of scale comparable +to that in the final output, and cn has been rotated or otherwise transformed (e.g. +via the BaseCorrelatedNoise.rotate, BaseCorrelatedNoise.shear methods; see below).

  4. +
+
+

Conversely, the approximation will work best in the case where the correlated noise used to +instantiate the cn is taken from an input image for which image.scale is smaller than +that in the desired output. This is the most common use case in the practical treatment of +correlated noise when simulating galaxies from space telescopes, such as COSMOS.

+

Changing from the default bilinear interpolant is made possible, but not recommended. +In our validation tests, we found that the Linear interpolant usually gave the most +accurate results.

+

There is also an option to switch off an internal correction for assumptions made about the +periodicity in the input noise image. If you wish to turn this off you may, e.g.:

+
>>> cn = galsim.CorrelatedNoise(image, rng=rng, correct_periodicity=False)
+
+
+

The default and generally recommended setting is correct_periodicity=True.

+

Users should note that the internal calculation of the discrete correlation function in +image will assume that image is periodic across its boundaries, introducing a dilution +bias in the estimate of inter-pixel correlations that increases with separation. Unless you +know that the noise in image is indeed periodic (perhaps because you generated it to be so), +you will not generally wish to use the correct_periodicity=False option.

+

By default, the image is not mean subtracted before the correlation function is estimated. To +do an internal mean subtraction, you can set the subtract_mean keyword to True, e.g.:

+
>>> cn = galsim.CorrelatedNoise(image, rng=rng, subtract_mean=True)
+
+
+

Using the subtract_mean option will introduce a small underestimation of variance and other +correlation function values due to a bias on the square of the sample mean. This bias reduces +as the input image becomes larger, and in the limit of uncorrelated noise tends to the constant +term variance/N**2 for an N x N sized image.

+

It is therefore recommended that a background/sky subtraction is applied to the image before +it is given as an input to the CorrelatedNoise, allowing the default subtract_mean=False. +If such a background model is global or based on large regions on sky then assuming that the +image has a zero population mean will be reasonable, and won’t introduce a bias in covariances +from an imperfectly-estimated sample mean subtraction. If this is not possible, just be aware +that subtract_mean=True will bias the correlation function low to some level.

+

You may also specify a gsparams argument. See the docstring for GSParams for more +information about this option.

+

Methods:

+

The main way that a CorrelatedNoise is used is to add correlated noise to an image. +The syntax:

+
>>> image.addNoise(cn)
+
+
+

is preferred, although:

+
>>> cn.applyTo(image)
+
+
+

is equivalent. See the Image.addNoise method docstring for more information. The +image.scale is used to get the pixel scale of the input image unless this is <= 0, in which +case a scale of 1 is assumed.

+

A number of methods familiar from GSObject instances have also been implemented directly as +cn methods, so that the following commands are all legal:

+
>>> image = cn.drawImage(im, scale)
+>>> cn = cn.shear(s)
+>>> cn = cn.expand(m)
+>>> cn = cn.rotate(theta * galsim.degrees)
+>>> cn = cn.transform(dudx, dudy, dvdx, dvdy)
+
+
+

See the individual method docstrings for more details. The shift method is not available +since a correlation function must always be centred and peaked at the origin.

+

The methods:

+
>>> var = cn.getVariance()
+>>> cn1 = cn.withVariance(variance)
+>>> cn2 = cn.withScaledVariance(variance_ratio)
+
+
+

can be used to get and set the point variance of the correlated noise, equivalent to the zero +separation distance correlation function value.

+

The BaseCorrelatedNoise.withVariance method scales the whole internal correlation function so +that its point variance matches variance.

+

Similarly, BaseCorrelatedNoise.withScaledVariance scales the entire function by the given +factor.

+

Arithmetic Operators:

+

Addition, multiplication and division operators are defined to work in an intuitive way for +correlation functions.

+

Addition works simply to add the internally-stored correlation functions, so that:

+
>>> cn3 = cn2 + cn1
+>>> cn4 += cn5
+
+
+

provides a representation of the correlation function of two linearly summed fields represented +by the individual correlation function operands.

+

What happens to the internally stored random number generators in the examples above? For all +addition operations it is the BaseDeviate belonging to the instance on the left-hand side +of the operator that is retained.

+

In the example above therefore, it is the random number generator from cn2 that will be +stored and used by cn3, and cn4 will retain its random number generator after in-place +addition of cn5. The random number generator of cn5 is not affected by the operation.

+

The multiplication and division operators, e.g.:

+
>>> cn1 /= 3.
+>>> cn2 = cn1 * 3
+
+
+

scale the overall correlation function by a scalar operand. The random number generators are +not affected by these scaling operations.

+
+ +
+
+class galsim.UncorrelatedNoise(variance, rng=None, scale=None, wcs=None, gsparams=None)[source]
+

Bases: BaseCorrelatedNoise

+

A class that represents 2D correlated noise fields that are actually (at least initially) +uncorrelated. Subsequent applications of things like BaseCorrelatedNoise.shear or +BaseCorrelatedNoise.convolvedWith will induce correlations.

+

The noise is characterized by a variance in each image pixel and a pixel size and shape. +The variance value refers to the noise variance in each pixel. If the pixels are square +(the usual case), you can specify the size using the scale parameter. If not, they +are effectively specified using the local wcs function that defines the pixel shape. i.e.:

+
>>> world_pix = wcs.toWorld(Pixel(1.))
+
+
+

should return the pixel profile in world coordinates.

+
+
Parameters:
+
    +
  • variance – The noise variance value to model as being uniform and uncorrelated +over the whole image.

  • +
  • rng – If provided, a random number generator to use as the random number +generator of the resulting noise object. (may be any kind of +BaseDeviate object) [default: None, in which case, one will be +automatically created, using the time as a seed.]

  • +
  • scale – If provided, use this as the pixel scale. [default: 1.0, unless wcs is +provided]

  • +
  • wcs – If provided, use this as the wcs for the image. At most one of scale +or wcs may be provided. [default: None]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+
+ +
+ +
+
+galsim.getCOSMOSNoise(file_name=None, rng=None, cosmos_scale=0.03, variance=0.0, x_interpolant=None, gsparams=None)[source]
+

Returns a representation of correlated noise in the HST COSMOS F814W unrotated science coadd +images.

+

See http://cosmos.astro.caltech.edu/astronomer/hst.html for information about the COSMOS survey, +and Leauthaud et al (2007) for detailed information about the unrotated F814W coadds used for +weak lensing science.

+

This function uses a stacked estimate of the correlation function in COSMOS noise fields. +The correlation function was computed by the GalSim team as described in:

+
GalSim/devel/external/hst/make_cosmos_cfimage.py
+
+
+

The resulting file is distributed with GalSim as:

+
os.path.join('galsim.meta_data.share_dir', 'acs_I_unrot_sci_20_cf.fits')
+
+
+
+
Parameters:
+
    +
  • file_name – If provided, override the usual location of the file with the given +file name. [default: None]

  • +
  • rng – If provided, a random number generator to use as the random number +generator of the resulting noise object. (may be any kind of +BaseDeviate object) [default: None, in which case, one will be +automatically created, using the time as a seed.]

  • +
  • cosmos_scale – COSMOS ACS F814W coadd image pixel scale in the units you are using to +describe GSObject instances and image scales in GalSim. [default: 0.03 +(arcsec), see below for more information.]

  • +
  • variance – Scales the correlation function so that its point variance, equivalent +to its value at zero separation distance, matches this value. +[default: 0., which means to use the variance in the original COSMOS +noise fields.]

  • +
  • x_interpolant – Forces use of a non-default interpolant for interpolation of the +internal lookup table in real space. See below for more details. +[default: galsim.Linear()]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
Returns:
+

a BaseCorrelatedNoise instance representing correlated noise in F814W COSMOS images.

+
+
+

The default x_interpolant is a galsim.Linear(), which uses bilinear interpolation. +The use of this interpolant is an approximation that gives good empirical results without +requiring internal convolution of the correlation function profile by a Pixel object when +applying correlated noise to images: such an internal convolution has been found to be +computationally costly in practice, requiring the Fourier transform of very large arrays.

+

The use of the bilinear interpolants means that the representation of correlated noise will be +noticeably inaccurate in at least the following two regimes:

+
    +
  1. If the pixel scale of the desired final output (e.g. the target image of +BaseCorrelatedNoise.drawImage, BaseCorrelatedNoise.applyTo or +BaseCorrelatedNoise.whitenImage) is small relative to the separation between pixels in +the image used to instantiate cn as shown above.

  2. +
  3. If the BaseCorrelatedNoise instance cn was instantiated with an image of scale +comparable to that in the final output, and cn has been rotated or otherwise transformed +(e.g. via the BaseCorrelatedNoise.rotate, BaseCorrelatedNoise.shear methods; see below).

  4. +
+

Conversely, the approximation will work best in the case where the correlated noise used to +instantiate the cn is taken from an input image for which image.scale is smaller than +that in the desired output. This is the most common use case in the practical treatment of +correlated noise when simulating galaxies from COSMOS, for which this function is expressly +designed.

+

Changing from the default bilinear interpolant is made possible, but not recommended. +In our validation tests, we found that the Linear interpolant usually gave the most +accurate results.

+

You may also specify a gsparams argument. See the docstring for GSParams for more +information about this option.

+
+

Note

+

The ACS coadd images in COSMOS have a pixel scale of 0.03 arcsec, and so the pixel scale +cosmos_scale adopted in the representation of of the correlation function takes a +default value cosmos_scale = 0.03

+

If you wish to use other units, ensure that the input keyword cosmos_scale takes the +value corresponding to 0.03 arcsec in your chosen system.

+
+

Example:

+

The following commands use this function to generate a 300 pixel x 300 pixel image of noise with +HST COSMOS correlation properties (substitute in your own file and path for the filestring):

+
>>> rng = galsim.BaseDeviate(123456)
+>>> noise = galsim.getCOSMOSNoise(rng=rng)
+>>> image = galsim.ImageD(nx, ny, scale=0.03)
+>>> image.addNoise(cf)
+
+
+

If your image has some other pixel scale or a complicated WCS, then the applied noise will +have the correct correlations in world coordinates, which may not be what you wanted if you +expected the pixel-to-pixel correlations to match the COSMOS noise profile. However, in +this case, you would want to view your image with the COSMOS pixel scale when you apply +the noise:

+
>>> image = galsim.Image(nx, ny, wcs=complicated_wcs)
+>>> noise = galsim.getCOSMOSNoise(rng=rng)
+>>> image.view(wcs=noise.wcs).addNoise(noise)
+
+
+

The FITS file out.fits should then contain an image of randomly-generated, COSMOS-like noise.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp.html b/docs/_build/html/cpp.html new file mode 100644 index 00000000000..f6edf7a7448 --- /dev/null +++ b/docs/_build/html/cpp.html @@ -0,0 +1,247 @@ + + + + + + + C++ Layer — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

C++ Layer

+

While GalSim is primarily a Python package, much of the implementation is done in C++ +for improved speed for the hard-core numerical calculations.

+

If you would like to use the C++-layer functions in your own C++ project, you can do +so with the caveat that these are officially implementation details, so we don’t +strictly enforce semantic versioning for the C++-layer API. That is, function signatures +may change on minor version updates (e.g. 2.3.x to 2.4.0). We don’t often make huge +changes to the C++ API, so most of the time you will be fine upgrading, but you should +be prepared that you may need to update your code when upgrading GalSim. (We do +guarantee that we won’t change the C++ API for bugfix updates, e.g. 2.4.1 to 2.4.2.)

+
+

Note

+

While we don’t intend to break ABI compatibility on bugfix releases, we don’t +currently have any mechanisms in place to ensure ABI compatibility. So it is +possible that you will need to recompile your code when updating to a new +GalSim version.

+
+

The other caveat is that we haven’t put much energy into documenting the C++ layer +functions. The following comes from a combination of Doxygen and Breathe to shoehorn +it into the Sphinx structure. But the docs are pretty bare bones in places. Sorry +about that. If you use these and want to pretty up these docs, a PR doing so would be +much appreciated. :)

+

When compiling your code, all of the public functionality should be included simply +by using #include "GalSim.h" with the appropriate -I directive when compiling +to find the GalSim include directory. The appropriate directory name is accessible +from python by running the command:

+
python -c "import galsim; print(galsim.include_dir)"
+
+
+ +
+

Linking Your Code

+

If you install GalSim using conda (see Installing With Conda), then the appropriate +C++ library file is included in the conda packaging. It should be installed into either +the main conda lib directory (e.g. /anaconda/lib) or the one for your conda environment +(e.g. /anaconda/envs/myenv/lib).

+

If you don’t use conda, then you will need to build the lib file yourself, since we don’t +include it in the pip package. +See Installing the C++ Shared Library for instructions on installing it.

+

There are both versioned and unversioned copies of the library. On OSX, these are +libgalsim.M.m.dylib and libgalsim.dylib. On Linux, they are +libgalsim.M.m.so and libgalsim.so.

+

You should link by specifying the appropriate directory with -L and link with -lgalsim.

+
+
+

Version control

+

We provide a number of functions to help you ensure that your code remains compatible with +updates to GalSim.

+

First, there are 3 MACROS that you can use to make a compile-time assert that you are +coding to the right version:

+
+
+GALSIM_MAJOR
+
+ +
+
+GALSIM_MINOR
+
+ +
+
+GALSIM_REVISION
+
+ +

Then there are three functions that return the compiled versions of those same numbers:

+
+
+int galsim::major_version()
+
+ +
+
+int galsim::minor_version()
+
+ +
+
+int galsim::revision()
+
+ +

One can also get the full three-number version as a string (e.g. “1.4.2”)

+
+
+std::string galsim::version()
+
+ +

And finally, we provide a fucntion that checks that the header file being included matches +the compiled values in the library being linked to:

+
+
+inline bool galsim::check_version()
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_bounds.html b/docs/_build/html/cpp_bounds.html new file mode 100644 index 00000000000..e6b23a7b438 --- /dev/null +++ b/docs/_build/html/cpp_bounds.html @@ -0,0 +1,550 @@ + + + + + + + Positions and Bounds — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Positions and Bounds

+
+
+template<class T>
class Position
+

Class for storing 2d position vectors in an (x, y) format.

+
+

Public Functions

+
+
+inline Position()
+

Default Constructor = (0,0)

+
+ +
+
+inline Position(const T xin, const T yin)
+

Constructor.

+
+ +
+
+inline Position(const Position<T> &rhs)
+

Copy Constructor.

+
+ +
+
+template<typename T2>
inline Position(const Position<T2> &rhs)
+
+ +
+
+inline Position &operator=(const Position<T> &rhs)
+

Assignment.

+
+ +
+
+template<typename T2>
inline Position &operator=(const Position<T2> &rhs)
+
+ +
+
+template<typename T2>
inline Position<typename SelfPromotion<T, T2>::type> &operator+=(const Position<T2> &rhs)
+

Overloaded += operator, following standard vector algebra rules.

+
+ +
+
+template<typename T2>
inline Position<typename SelfPromotion<T, T2>::type> &operator-=(const Position<T2> &rhs)
+

Overloaded -= operator, following standard vector algebra rules.

+
+ +
+
+inline Position<T> &operator*=(const T rhs)
+

Overloaded *= operator for scalar multiplication.

+
+ +
+
+inline Position<T> &operator/=(const T rhs)
+

Overloaded /= operator for scalar division.

+
+ +
+
+inline Position<T> operator*(const T rhs) const
+

Overloaded * operator for scalar on rhs.

+
+ +
+
+inline Position<T> operator/(const T rhs) const
+

Overloaded / operator for scalar on rhs.

+
+ +
+
+inline Position<T> operator-() const
+

Unary negation (x, y) -> (-x, -y).

+
+ +
+
+template<typename T2>
inline Position<typename Promotion<T, T2>::type> operator+(const Position<T2> &rhs) const
+

Overloaded vector + addition operator with a Position on the rhs.

+
+ +
+
+template<typename T2>
inline Position<typename Promotion<T, T2>::type> operator-(const Position<T2> &rhs) const
+

Overloaded vector - subtraction operator with a Position on the rhs.

+
+ +
+
+inline bool operator==(const Position<T> &rhs) const
+

Overloaded == relational equality operator.

+
+ +
+
+inline bool operator!=(const Position<T> &rhs) const
+

Overloaded != relational non-equality operator.

+
+ +
+
+inline void write(std::ostream &fout) const
+

Write (x, y) position to output stream.

+
+ +
+
+inline void read(std::istream &fin)
+

Read (x, y) position from input istream.

+
+ +
+
+

Public Members

+
+
+T x
+

Publicly visible x & y attributes of the position.

+
+ +
+
+T y
+
+ +
+
+

Friends

+
+
+inline friend Position<T> operator*(const T lhs, const Position<T> &rhs)
+

Allow T * Position as well.

+
+ +
+
+ +
+
+template<class T>
class Bounds
+

Class for storing image bounds, essentially the vertices of a rectangle.

+

This is used to keep track of the bounds of catalogs and fields. You can set values, but generally you just keep including positions of each galaxy or the bounds of each catalog respectively using the += operators.

+

The bounds are stored as four numbers in each instance, (xmin, ymin, xmax, ymax), with an additional boolean switch to say whether or not the Bounds rectangle has been defined.

+

Rectangle is undefined if min>max in either direction.

+
+

Public Functions

+
+
+inline Bounds(const T x1, const T x2, const T y1, const T y2)
+

Constructor using four scalar positions (xmin, xmax, ymin, ymax).

+
+ +
+
+inline Bounds(const Position<T> &pos)
+

Constructor using a single Position vector x/ymin = x/ymax.

+
+ +
+
+inline Bounds(const Position<T> &pos1, const Position<T> &pos2)
+

Constructor using two Positions, first for x/ymin, second for x/ymax.

+
+ +
+
+inline Bounds()
+

Constructor for empty Bounds, .isDefined() method will return false.

+
+ +
+
+inline ~Bounds()
+

Destructor.

+
+ +
+
+inline Bounds<T> copy() const
+

Make a copy of this Bounds object.

+
+ +
+
+inline void setXMin(const T x)
+

Set the xmin of the Bounds rectangle.

+
+ +
+
+inline void setXMax(const T x)
+

Set the xmax of the Bounds rectangle.

+
+ +
+
+inline void setYMin(const T y)
+

Set the ymin of the Bounds rectangle.

+
+ +
+
+inline void setYMax(const T y)
+

Set the ymax of the Bounds rectangle.

+
+ +
+
+inline T getXMin() const
+

Get the xmin of the Bounds rectangle.

+
+ +
+
+inline T getXMax() const
+

Get the xmax of the Bounds rectangle.

+
+ +
+
+inline T getYMin() const
+

Get the ymin of the Bounds rectangle.

+
+ +
+
+inline T getYMax() const
+

Get the ymax of the Bounds rectangle.

+
+ +
+
+inline bool isDefined() const
+

Query whether the Bounds rectangle is defined.

+
+ +
+
+inline Position<T> origin() const
+

Return the origin of the image (xmin, ymin)

+
+ +
+
+Position<T> center() const
+

Return the nominal center of the image.

+

This is the position of the pixel that is considered to be (0,0)

+
+ +
+
+Position<double> trueCenter() const
+

Return the true center of the image.

+

For even-sized, integer bounds, this will not be an integer, since the center in that case falls between two pixels.

+
+ +
+
+void operator+=(const Position<T> &pos)
+

expand bounds to include this point

+
+ +
+
+inline Bounds<T> operator+(const Position<T> &pos) const
+
+ +
+
+void operator+=(const Bounds<T> &rec)
+

expand bounds to include these bounds

+
+ +
+
+inline Bounds<T> operator+(const Bounds<T> &rec) const
+
+ +
+
+void addBorder(const T d)
+

add a border of size d around existing bounds

+
+ +
+
+inline void operator+=(const T d)
+
+ +
+
+inline Bounds<T> withBorder(const T d) const
+
+ +
+
+inline Bounds<T> operator+(const T d) const
+
+ +
+
+void expand(const double m)
+

expand bounds by a factor m around the current center.

+
+ +
+
+inline Bounds<T> makeExpanded(const double m) const
+
+ +
+
+const Bounds<T> operator&(const Bounds<T> &rhs) const
+

find the intersection of two bounds

+
+ +
+
+inline void shift(const T dx, const T dy)
+

shift the bounding box by some amount.

+
+ +
+
+inline void shift(const Position<T> &delta)
+
+ +
+
+inline Bounds<T> makeShifted(const T dx, const T dy) const
+
+ +
+
+inline Bounds<T> makeShifted(const Position<T> &delta) const
+
+ +
+
+template<typename T2>
inline bool includes(const Position<T2> &pos) const
+

return whether the bounded region includes a given point

+
+ +
+
+template<typename T2>
inline bool includes(const T2 x, const T2 y) const
+
+ +
+
+inline bool includes(const Bounds<T> &rhs) const
+

return whether the bounded region includes all of the given bounds

+
+ +
+
+inline bool operator==(const Bounds<T> &rhs) const
+

check equality of two bounds

+
+ +
+
+inline bool operator!=(const Bounds<T> &rhs) const
+
+ +
+
+inline bool isSameShapeAs(const Bounds<T> &rhs) const
+

Check if two bounds have same shape, but maybe different origin.

+
+ +
+
+inline T area() const
+

Return the area of the enclosed region.

+

The area is a bit different for integer-type Bounds and float-type bounds. For floating point types, it is simply (xmax-xmin)*(ymax-ymin). However, for integer type, we need to add 1 to each size to correctly count the number of pixels being described by the bounding box.

+
+ +
+
+std::vector<Bounds<T>> divide(int nx, int ny) const
+

divide the current bounds into (nx x ny) sub-regions

+
+ +
+
+inline void write(std::ostream &fout) const
+

write out to a file

+
+ +
+
+inline void read(std::istream &fin)
+

read in from a file

+
+ +
+
+ +

.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_hsm.html b/docs/_build/html/cpp_hsm.html new file mode 100644 index 00000000000..783d89c1391 --- /dev/null +++ b/docs/_build/html/cpp_hsm.html @@ -0,0 +1,577 @@ + + + + + + + HSM Implementation — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

HSM Implementation

+
+

Primary Interface

+
+
+template<typename T, typename U>
void galsim::hsm::EstimateShearView(ShapeData &results, const BaseImage<T> &gal_image, const BaseImage<U> &PSF_image, const BaseImage<int> &gal_mask_image, float sky_var = 0.0, const char *shear_est = "REGAUSS", const char *recompute_flux = "FIT", double guess_sig_gal = 5.0, double guess_sig_PSF = 3.0, double precision = 1.0e-6, galsim::Position<double> guess_centroid = galsim::Position<double>(-1000., -1000.), const HSMParams &hsmparams = HSMParams())
+

Carry out PSF correction directly using Images.

+

A template function to carry out one of the multiple possible methods of PSF correction using the HSM package, directly accessing the input Images. The input arguments get repackaged before calling general_shear_estimator, and results for the shape measurement are returned as ShapeData. There are two arguments that have default values, namely shear_est (the type of shear estimator) and recompute_flux (for the REGAUSS method only).

+
+
Parameters:
+
    +
  • [in/out] – results A ShapeData object to hold the results

  • +
  • gal_image[in] The BaseImage for the galaxy being measured

  • +
  • PSF_image[in] The BaseImage for the PSF

  • +
  • gal_mask_image[in] The BaseImage for the mask image to be applied to the galaxy being measured (integer array, 1=use pixel and 0=do not use pixel).

  • +
  • sky_var[in] The variance of the sky level, used for estimating uncertainty on the measured shape; default 0.

  • +
  • *shear_est[in] A string indicating the desired method of PSF correction: REGAUSS, LINEAR, BJ, or KSB; default REGAUSS.

  • +
  • *recompute_flux[in] A string indicating whether to recompute the object flux, which should be NONE (for no recomputation), SUM (for recomputation via an unweighted sum over unmasked pixels), or FIT (for recomputation using the Gaussian + quartic fit).

  • +
  • guess_sig_gal[in] Optional argument with an initial guess for the Gaussian sigma of the galaxy, default 5.0 (pixels).

  • +
  • guess_sig_PSF[in] Optional argument with an initial guess for the Gaussian sigma of the PSF, default 3.0 (pixels).

  • +
  • precision[in] The convergence criterion for the moments; default 1e-6.

  • +
  • guess_centroid[in] Optional argument with an initial guess for the centroid of the galaxy; if not set, then the code will try the center of the image.

  • +
  • hsmparams[in] Optional argument to specify parameters to be used for shape measurement routines, as an HSMParams object.

  • +
+
+
+
+ +
+
+template<typename T>
void galsim::hsm::FindAdaptiveMomView(ShapeData &results, const BaseImage<T> &object_image, const BaseImage<int> &object_mask_image, double guess_sig = 5.0, double precision = 1.0e-6, galsim::Position<double> guess_centroid = galsim::Position<double>(-1000., -1000.), bool round_moments = false, const HSMParams &hsmparams = HSMParams())
+

Measure the adaptive moments of an object directly using Images.

+

This function repackages the input BaseImage in a format that find_ellipmom_2 accepts, in order to iteratively compute the adaptive moments of an object. The key result is the best-fit elliptical Gaussian to the object, which is computed by initially guessing a circular Gaussian that is used as a weight function, computing the weighted moments, recomputing the moments using the result of the previous step as the weight function, and so on until the moments that are measured are the same as those used for the weight function.

+
+
Parameters:
+
    +
  • [in/out] – A ShapeData object to hold the results

  • +
  • object_image[in] The BaseImage for the object being measured.

  • +
  • object_mask_image[in] The BaseImage for the mask image to be applied to the object being measured (integer array, 1=use pixel and 0=do not use pixel).

  • +
  • guess_sig[in] Optional argument with an initial guess for the Gaussian sigma of the object, default 5.0 (pixels).

  • +
  • precision[in] The convergence criterion for the moments; default 1e-6.

  • +
  • guess_centroid[in] Optional argument with an initial guess for the centroid of the galaxy; if not set, then the code will try the center of the image.

  • +
  • hsmparams[in] Optional argument to specify parameters to be used for shape measurement routines, as an HSMParams object.

  • +
+
+
+
+ +
+
+struct HSMParams
+
+

Public Functions

+
+
+inline HSMParams(double _nsig_rg, double _nsig_rg2, double _max_moment_nsig2, int _regauss_too_small, int _adapt_order, double _convergence_threshold, long _max_mom2_iter, long _num_iter_default, double _bound_correct_wt, double _max_amoment, double _max_ashift, int _ksb_moments_max, double _ksb_sig_weight, double _ksb_sig_factor, double _failed_moments)
+
+ +
+
+inline HSMParams()
+

A reasonable set of default values

+
+ +
+
+

Public Members

+
+
+double nsig_rg
+
+ +
+
+double nsig_rg2
+
+ +
+
+double max_moment_nsig2
+
+ +
+
+int regauss_too_small
+
+ +
+
+int adapt_order
+
+ +
+
+double convergence_threshold
+
+ +
+
+long max_mom2_iter
+
+ +
+
+long num_iter_default
+
+ +
+
+double bound_correct_wt
+
+ +
+
+double max_amoment
+
+ +
+
+double max_ashift
+
+ +
+
+int ksb_moments_max
+
+ +
+
+double ksb_sig_weight
+
+ +
+
+double ksb_sig_factor
+
+ +
+
+double failed_moments
+
+ +
+
+ +
+
+class HSMError : public std::runtime_error
+

Exception class thrown by the adaptive moment and shape measurement routines in the hsm namespace.

+
+

Public Functions

+
+
+inline HSMError(const std::string &m)
+
+ +
+
+ +
+
+

Helper Functions

+
+
+unsigned int galsim::hsm::general_shear_estimator(ConstImageView<double> gal_image, ConstImageView<double> PSF_image, ObjectData &gal_data, ObjectData &PSF_data, const char *shear_est, unsigned long flags, const HSMParams &hsmparams)
+

Carry out PSF correction.

+

Carry out one of the multiple possible methods of PSF correction using the HSM package. Results for the shape measurement are returned by modifying the galaxy data directly. The flags parameter is only used for the REGAUSS shape measurement method, and is defined as follows: 0x1=recompute galaxy flux by summing unmasked pixels, 0x2=recompute galaxy flux from Gaussian-quartic fit, 0x4=cut off Gaussian approximator at NSIG_RG sigma to save time, 0x8=cut off PSF residual at NSIG_RG2 to save time.

+
+
Parameters:
+
    +
  • gal_image[in] The galaxy Image.

  • +
  • PSF_image[in] The PSF Image.

  • +
  • gal_data[in] The ObjectData object for the galaxy

  • +
  • PSF_data[in] The ObjectData object for the PSF

  • +
  • shear_est[in] A string indicating the desired method of PSF correction: REGAUSS, LINEAR, BJ, or KSB.

  • +
  • flags[in] A parameter for REGAUSS (typical usage is 0xe).

  • +
  • hsmparams[in] Optional argument to specify parameters to be used for shape measurement routines, as an HSMParams object.

  • +
+
+
Returns:
+

A status flag that should be zero if the measurement was successful.

+
+
+
+ +
+
+void galsim::hsm::find_ellipmom_2(ConstImageView<double> data, double &A, double &x0, double &y0, double &Mxx, double &Mxy, double &Myy, double &rho4, double convergence_threshold, int &num_iter, const HSMParams &hsmparams)
+

Measure the adaptive moments of an object.

+

This function iteratively computes the adaptive moments of an image, and tells the user the results plus other diagnostic information. The key result is the best-fit elliptical Gaussian to the object, which is computed by initially guessing a circular Gaussian that is used as a weight function, computing the weighted moments, recomputing the moments using the result of the previous step as the weight function, and so on until the moments that are measured are the same as those used for the weight function.

+
+
Parameters:
+
    +
  • data[in] The Image for the object being measured.

  • +
  • A[out] The amplitude of the best-fit elliptical Gaussian (total image intensity for the Gaussian is 2A).

  • +
  • x0[out] The x centroid of the best-fit elliptical Gaussian.

  • +
  • y0[out] The y centroid of the best-fit elliptical Gaussian.

  • +
  • Mxx[out] The xx component of the moment matrix.

  • +
  • Mxy[out] The xy component of the moment matrix.

  • +
  • Myy[out] The yy component of the moment matrix.

  • +
  • rho4[out] The weighted radial fourth moment.

  • +
  • convergence_threshold[in] The required level of accuracy.

  • +
  • num_iter[out] The number of iterations needed to converge.

  • +
  • hsmparams[in] Optional argument to specify parameters to be used for shape measurement routines, as an HSMParams object.

  • +
+
+
+
+ +
+
+struct ShapeData
+

Struct containing information about the shape of an object.

+

This representation of an object shape contains information about observed shapes and shape estimators after PSF correction. It also contains information about what PSF correction was used; if no PSF correction was carried out and only the observed moments were measured, the PSF correction method will be ‘None’. Note that observed shapes are bounded to lie in the range |e| < 1 or |g| < 1, so they can be represented using a Shear object. In contrast, the PSF-corrected distortions and shears are not bounded at a maximum of 1 since they are shear estimators, and placing such a bound would bias the mean. Thus, the corrected results are not represented using Shear objects, since it may not be possible to make a meaningful per-object conversion from distortion to shear (e.g., if |e|>1).

+
+

Public Functions

+
+
+inline ShapeData()
+

Constructor, setting defaults.

+
+ +
+
+

Public Members

+
+
+Bounds<int> image_bounds
+

galsim::Bounds object describing the image of the object

+
+ +
+
+int moments_status
+

Status after measuring adaptive moments; -1 indicates no attempt to measure them.

+
+ +
+
+float observed_e1
+

The observed shape e1,e2.

+
+ +
+
+float observed_e2
+
+ +
+
+float moments_sigma
+

Size sigma = (det M)^(1/4) from the adaptive moments, in units of pixels; -1 if not measured.

+
+ +
+
+float moments_amp
+

Total image intensity for best-fit elliptical Gaussian from adaptive moments; note that this is not flux, since flux = (total image intensity)*(pixel scale)^2.

+
+ +
+
+Position<double> moments_centroid
+

Centroid of best-fit elliptical Gaussian.

+
+ +
+
+double moments_rho4
+

The weighted radial fourth moment of the image.

+
+ +
+
+int moments_n_iter
+

Number of iterations needed to get adaptive moments; 0 if not measured.

+
+ +
+
+int correction_status
+

Status after carrying out PSF correction; -1 indicates no attempt to do so.

+
+ +
+
+float corrected_e1
+

Estimated e1 after correcting for effects of the PSF, for methods that return a distortion. Default value -10 if no correction carried out.

+
+ +
+
+float corrected_e2
+

Estimated e2 after correcting for effects of the PSF, for methods that return a distortion. Default value -10 if no correction carried out.

+
+ +
+
+float corrected_g1
+

Estimated g1 after correcting for effects of the PSF, for methods that return a shear. Default value -10 if no correction carried out.

+
+ +
+
+float corrected_g2
+

Estimated g2 after correcting for effects of the PSF, for methods that return a shear. Default value -10 if no correction carried out.

+
+ +
+
+std::string meas_type
+

‘e’ for PSF correction methods that return a distortion, ‘g’ for methods that return a shear. “None” if PSF correction was not done.

+
+ +
+
+float corrected_shape_err
+

Shape measurement uncertainty sigma_gamma (not sigma_e) per component.

+
+ +
+
+std::string correction_method
+

String indicating PSF-correction method; “None” if PSF correction was not done.

+
+ +
+
+float resolution_factor
+

Resolution factor R_2; 0 indicates object is consistent with a PSF, 1 indicates perfect resolution; default -1.

+
+ +
+
+float psf_sigma
+

PSF size sigma = (det M)^(1/4) from the adaptive moments, in units of pixels; default -1.

+
+ +
+
+float psf_e1
+

PSF shape from the adaptive moments.

+
+ +
+
+float psf_e2
+
+ +
+
+std::string error_message
+

A string containing any error messages from the attempted measurements, to facilitate proper error handling in both C++ and python.

+
+ +
+
+ +
+
+struct ObjectData
+

Characterizes the shape and other parameters of objects.

+

Describe the hsm shape-related parameters of some object (usually galaxy) before and after PSF correction. All ellipticities are defined as (1-q^2)/(1+q^2), with the 1st component aligned with the pixel grid and the 2nd aligned at 45 degrees with respect to it. There are two choices for measurement type: ‘e’ = Bernstein & Jarvis (2002) ellipticity (or distortion), ‘g’ = shear estimator = shear*responsivity. The sigma is defined based on the observed moments M_xx, M_xy, and M_yy as sigma = (Mxx Myy - M_xy^2)^(1/4) = [ det(M) ]^(1/4).

+
+

Public Functions

+
+
+inline ObjectData()
+
+ +
+
+

Public Members

+
+
+double x0
+

x centroid position within the postage stamp, in units of pixels

+
+ +
+
+double y0
+

y centroid position within the postage stamp, in units of pixels

+
+ +
+
+double sigma
+

size parameter

+
+ +
+
+double flux
+

total flux

+
+ +
+
+double e1
+

first ellipticity component

+
+ +
+
+double e2
+

second ellipticity component

+
+ +
+
+double responsivity
+

responsivity of ellipticity estimator

+
+ +
+
+char meas_type
+

type of measurement (see function description)

+
+ +
+
+double resolution
+

resolution factor (0=unresolved, 1=resolved)

+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_image.html b/docs/_build/html/cpp_image.html new file mode 100644 index 00000000000..6d354b1887b --- /dev/null +++ b/docs/_build/html/cpp_image.html @@ -0,0 +1,880 @@ + + + + + + + C++ Images — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

C++ Images

+
+

Image Classes

+
+
+template<typename T>
class AssignableToImage
+

AssignableToImage is a base class for anything that can be assigned to an Image.

+

It is the base class for both BaseImage, which has all the real image types and various composite types that are used to make im3 = im1 + im2 efficient.

+

Subclassed by galsim::BaseImage< T >

+
+

Public Functions

+
+
+inline virtual ~AssignableToImage()
+

Destructor is public and virtual.

+
+ +
+
+virtual void assignTo(ImageView<T> rhs) const = 0
+

Assign values to an ImageView.

+
+ +
+
+inline const Bounds<int> &getBounds() const
+

Return the bounding box of the image.

+
+ +
+
+ +
+
+template<typename T>
class BaseImage : public galsim::AssignableToImage<T>
+

BaseImage class defines the interface of what you can do with Images without modifying pixels.

+

BaseImage is the base class for the other Image types:

+

+

You should never need to declare an object of this class directly, using instead the various derived classes. However, if you are using an image as a parameter to a function in a non-modifying context, then it is convenient to declare the parameter “const BaseImage<T>&” so that any of the various image types can be used without requiring any casts.

+

Subclassed by galsim::ConstImageView< T >, galsim::ImageAlloc< T >, galsim::ImageView< T >

+
+

Public Functions

+
+
+inline virtual ~BaseImage()
+

Destructor is virtual and public.

+

Note: There are no public constructors, since this is an abstract base class. But the destructor should be public (and virtual).

+

Nothing special needs to be done here, since shared_ptr takes care of deleting the data if this is the last thing to own the data.

+
+ +
+
+inline shared_ptr<T> getOwner() const
+

Return a shared pointer that manages the lifetime of the image’s pixels.

+

The actual pointer will point to a parent image rather than the image itself if this is a subimage.

+
+ +
+
+inline const T *getData() const
+

Return a pointer to the first pixel in the image.

+
+ +
+
+inline const T *getMaxPtr() const
+
+ +
+
+inline bool ok_ptr(const T *p) const
+

Check that the ptr is valid, properly accounting for step, stride.

+
+ +
+
+inline ptrdiff_t getNElements() const
+

Return how many data elements are currently allocated in memory.

+

This is usually the same as getBounds().area(), but it may not be if the image has been resized.

+
+ +
+
+inline int getStride() const
+

Return the number of elements between rows in memory.

+
+ +
+
+inline int getStep() const
+

Return the number of elements between cols in memory.

+
+ +
+
+inline int getNCol() const
+

Return the number of columns in the image.

+
+ +
+
+inline int getNRow() const
+

Return the number of rows in the image.

+
+ +
+
+inline int getNSkip() const
+

Return the number of columns to skip at the end of each row when iterating.

+
+ +
+
+inline bool isContiguous() const
+

Return whether the data is contiguous in memory.

+

Shorthand for: (getStride() == getNCol()) or equivalently (getNSkip() == 0)

+
+ +
+
+inline ImageAlloc<T> copy() const
+

Deep copy the image.

+

The returned image will have the same bounding box and pixel values as this, but will they will not share data.

+
+ +
+
+inline ConstImageView<T> view() const
+

Create a new view of the image.

+

The returned image will share data with this image

+
+ +
+
+ConstImageView<T> subImage(const Bounds<int> &bounds) const
+

New image that is a subimage of this (shares pixels)

+
+ +
+
+inline ConstImageView<T> operator[](const Bounds<int> &bounds) const
+

im[bounds] is another syntax for making a sub-image

+
+ +
+
+Bounds<int> nonZeroBounds() const
+

Return the smallest bounds that includes all non-zero elements of the image.

+
+ +
+
+inline void shift(const Position<int> &delta)
+

Shift the bounding box of the image, changing the logical location of the pixels.

+

xmin_new = xmin + dx xmax_new = xmax + dx ymin_new = ymin + dy ymax_new = ymax + dy

+
+ +
+
+inline int getXMin() const
+

Convenience accessors for the bounding box corners.

+
+ +
+
+inline int getXMax() const
+
+ +
+
+inline int getYMin() const
+
+ +
+
+inline int getYMax() const
+
+ +
+
+inline const T &operator()(int xpos, int ypos) const
+

Unchecked element access.

+
+ +
+
+inline const T &operator()(const Position<int> &pos) const
+
+ +
+
+const T &at(int xpos, int ypos) const
+

Element access - checked.

+
+ +
+
+inline const T &at(const Position<int> &pos) const
+
+ +
+
+inline const T *getPtr(int x, int y) const
+

Return a pointer to the data at an arbitrary pixel.

+
+ +
+
+inline const T *getPtr(const Position<int> &pos) const
+
+ +
+
+inline virtual void assignTo(ImageView<T> rhs) const
+

BaseImage’s assignTo just uses the normal copyFrom method.

+
+ +
+
+T sumElements() const
+

Return the sum of the elements in the image.

+
+ +
+
+Traits<T>::real_type maxAbsElement() const
+

Return the maximum absolute value in the image.

+
+ +
+
+ +
+
+template<typename T>
class ImageAlloc : public galsim::BaseImage<T>
+

ImageAlloc class.

+

The ImageAlloc class is a 2-d array with pixel values stored contiguously in memory along rows (but not necessarily between rows). An image’s pixel values may be shared between multiple image objects (with reference counting), and a subimage may share data with its parent and multiple siblings. ImageAllocs may also share pixel values with NumPy arrays when the allocation happens in the C++ layer.

+

An ImageAlloc also contains a bounding box; its origin need not be (0,0) or (1,1).

+

The const semantics for this are pretty normal. You cannot change either the pixel values or the ancillary information (like bounds) for a const ImageAlloc, while you can change things about a non-const ImageAlloc.

+

ImageAlloc templates for uint16_t, uint32_t, int16_t, int32_t, float, and double are explicitly instantiated in Image.cpp.

+
+

Public Functions

+
+
+inline ImageAlloc()
+

Default constructor leaves the image’s data pointer as null.

+
+ +
+
+ImageAlloc(int ncol, int nrow)
+

Create a new image with origin at (1,1).

+

An exception is thrown if ncol or nrow <= 0

+
+ +
+
+ImageAlloc(int ncol, int nrow, T init_value)
+

Create a new image with origin at (1,1), intialized with some init_value.

+

An exception is thrown if ncol or nrow <= 0

+
+ +
+
+ImageAlloc(const Bounds<int> &bounds)
+

Create a new image with the given bounding box.

+
+ +
+
+ImageAlloc(const Bounds<int> &bounds, T init_value)
+

Create a new image with the given bounding box and initial value.

+
+ +
+
+inline ImageAlloc(const ImageAlloc<T> &rhs)
+

Deep copy constructor.

+
+ +
+
+inline ImageAlloc(const AssignableToImage<T> &rhs)
+

Can construct from any AssignableToImage.

+
+ +
+
+template<typename U>
inline ImageAlloc(const BaseImage<U> &rhs)
+

If rhs is a BaseImage, then type doesn’t have to match.

+
+ +
+
+inline ImageAlloc<T> &operator=(const AssignableToImage<T> &rhs)
+

Deep assignment operator.

+

The bounds must be commensurate (i.e. the same shape). If not, an exception will be thrown.

+
+ +
+
+inline ImageAlloc<T> &operator=(const ImageAlloc<T> &rhs)
+

Repeat for ImageAlloc to prevent compiler from making the default op=.

+
+ +
+
+template<typename U>
inline ImageAlloc<T> &operator=(const BaseImage<U> &rhs)
+

Copy from BaseImage allowed for different types.

+
+ +
+
+inline void fill(T x)
+

Assignment with a scalar.

+
+ +
+
+inline ImageAlloc<T> &operator=(T x)
+
+ +
+
+inline void setZero()
+
+ +
+
+inline void invertSelf()
+

Set each element to its inverse: im(i,j) = 1/im(i,j)

+

Note that if an element is zero, then this function quietly returns its inverse as zero.

+
+ +
+
+void resize(const Bounds<int> &new_bounds, bool release = true)
+

Resize the image to a new bounds. The values are left uninitialized.

+

Any views that share data with this ImageAlloc are still valid and still share data with each other, but the tie to this ImageAlloc is severed.

+

This typically allocates new memory for the array. The only exception is if the new size is the same as the current size and are there are no other views of the data. Then it just updates the bounds to the new bounds and keeps the current array.

+

You can also optionally keep the current array if you are shrinking the bounds to a smaller size, with the same limit of there not being other views. To get this behavior, set release=false, and it will not release the allocated memory when shrinking the size of the image.

+
+ +
+
+inline T *getData()
+

Return a pointer to the first pixel in the image.

+
+ +
+
+inline const T *getData() const
+
+ +
+
+inline const T *getMaxPtr() const
+
+ +
+
+inline ImageView<T> view()
+

Make a view of this image.

+
+ +
+
+inline ConstImageView<T> view() const
+
+ +
+
+inline ImageView<T> subImage(const Bounds<int> &bounds)
+

New image that is a subimage of this (shares pixels)

+
+ +
+
+inline ConstImageView<T> subImage(const Bounds<int> &bounds) const
+
+ +
+
+inline ImageView<T> operator[](const Bounds<int> &bounds)
+

im[bounds] is another syntax for making a sub-image

+
+ +
+
+inline ConstImageView<T> operator[](const Bounds<int> &bounds) const
+
+ +
+
+inline T &operator()(int xpos, int ypos)
+

Unchecked access.

+
+ +
+
+inline const T &operator()(int xpos, int ypos) const
+
+ +
+
+inline T &operator()(const Position<int> &pos)
+
+ +
+
+inline const T &operator()(const Position<int> &pos) const
+
+ +
+
+T &at(int xpos, int ypos)
+

Element access - checked.

+
+ +
+
+const T &at(int xpos, int ypos) const
+
+ +
+
+inline T &at(const Position<int> &pos)
+
+ +
+
+inline const T &at(const Position<int> &pos) const
+
+ +
+
+inline void setValue(int x, int y, T value)
+

Another way to set a value. Equivalent to im(x,y) = value.

+

The python layer can’t implement the im(x,y) = value syntax, so we need something else to set a single pixel. This function is unnecessary at the C++ level, but in the interest of trying to keep the two layers as close as possible, we might as well include it.

+

Note: This uses the checked element access.

+
+ +
+
+template<typename U>
inline void copyFrom(const BaseImage<U> &rhs)
+

Deep-copy pixel values from rhs to this.

+

The bounds must be commensurate (i.e. the same shape). If not, an exception will be thrown.

+
+ +
+
+ +
+
+template<typename T>
class ImageView : public galsim::BaseImage<T>
+

ImageView class is a mutable view of an Image.

+

The copy constructor is shallow, so ImageView’s can be cheaply returned by value. The data values persist until the last view of some data goes out of scope.

+

The op= is deep, though. This is the intuitive behavior, so you can write something like

+

im1[bounds] = im2

+

and the data in im2 will be copied to the sub-image of im1.

+

Also note that through the python interface, we can make an ImageView that views a numpy array rather than anything that was created as an ImageAlloc. We have some tricky stuff in pysrc/Image.cpp to get the C++ shared_ptr to interact correctly with numpy’s reference counting so the data are deleted when the last numpy array or ImageView finally goes out of scope.

+

You could do the same thing within the C++ layer too. You would just have to provide a shared_ptr explicitly to set up the ownership.

+
+

Public Functions

+
+
+inline ImageView(T *data, const T *maxptr, ptrdiff_t nElements, const shared_ptr<T> &owner, int step, int stride, const Bounds<int> &b)
+

Direct constructor given all the necessary information.

+
+ +
+
+inline ImageView(const ImageView<T> &rhs)
+

Shallow copy constructor.

+

The original image and its copy will share pixel values, but their bounding boxes will not be shared (even though they will be set to the same values initially).

+
+ +
+
+inline ImageView(ImageAlloc<T> &rhs)
+

Shallow copy constructor from ImageAlloc.

+

The original image and its copy will share pixel values, but their bounding boxes will not be shared (even though they will be set to the same values initially).

+
+ +
+
+inline ImageView<T> &operator=(const AssignableToImage<T> &rhs)
+

Deep assignment operator.

+

The bounds must be commensurate (i.e. the same shape). If not, an exception will be thrown.

+
+ +
+
+inline ImageView<T> &operator=(const ImageView<T> &rhs)
+

Repeat for ImageView to prevent compiler from making the default op=.

+
+ +
+
+template<typename U>
inline ImageView<T> &operator=(const BaseImage<U> &rhs)
+

Allow copy from a different type.

+
+ +
+
+void fill(T x)
+

Assignment with a scalar.

+
+ +
+
+inline ImageView<T> &operator=(T x)
+
+ +
+
+inline void setZero()
+
+ +
+
+void invertSelf()
+

Set each element to its inverse: im(i,j) = 1/im(i,j)

+

Note that if an element is zero, then this function quietly returns its inverse as zero.

+
+ +
+
+void depixelizeSelf(const double *unit_integrals, const int n)
+

Make a depixelized version of the image.

+
+ +
+
+inline T *getData()
+

Return a pointer to the first pixel in the image.

+

This overrides the version in BaseImage, since this one returns a non-const pointer. (T*, not const T*)

+
+ +
+
+inline const T *getMaxPtr()
+
+ +
+
+inline ImageView<T> view()
+

View just returns itself.

+
+ +
+
+ImageView<T> subImage(const Bounds<int> &bounds)
+

New image that is a subimage of this (shares pixels)

+
+ +
+
+inline ImageView<T> operator[](const Bounds<int> &bounds)
+

im[bounds] is another syntax for making a sub-image

+
+ +
+
+inline T &operator()(int xpos, int ypos)
+

Unchecked access.

+
+ +
+
+inline T &operator()(const Position<int> &pos)
+
+ +
+
+T &at(int xpos, int ypos)
+

Element access - checked.

+
+ +
+
+inline T &at(const Position<int> &pos)
+
+ +
+
+inline void setValue(int x, int y, T value)
+

Another way to set a value. Equivalent to im(x,y) = value.

+

The python layer can’t implement the im(x,y) = value syntax, so we need something else to set a single pixel. This function is unnecessary at the C++ level, but in the interest of trying to keep the two layers as close as possible, we might as well include it.

+

Note: This uses the checked element access.

+
+ +
+
+void copyFrom(const BaseImage<T> &rhs)
+

Deep-copy pixel values from rhs to this.

+

The bounds must be commensurate (i.e. the same shape). If not, an exception will be thrown.

+
+ +
+
+template<class U>
inline void copyFrom(const BaseImage<U> &rhs)
+

Deep copy may be from a different type of image.

+

Do this inline, so we don’t have to worry about instantiating all pairs of types.

+
+ +
+
+ +
+
+template<typename T>
class ConstImageView : public galsim::BaseImage<T>
+

ConstImageView class views an Image in a read-only context.

+

Read-only only refers to the data values. The bounds may be changed.

+
+

Public Functions

+
+
+inline ConstImageView(T *data, const T *maxptr, ptrdiff_t nElements, const shared_ptr<T> &owner, int step, int stride, const Bounds<int> &b)
+

Direct constructor given all the necessary information.

+
+ +
+
+inline ConstImageView(const BaseImage<T> &rhs)
+

Copy Constructor from a BaseImage makes a new view of the same data.

+
+ +
+
+inline ConstImageView(const ConstImageView<T> &rhs)
+

Repeat the same copy constructor functionality for ConstImageView to make sure the compiler doesn’t make the default one.

+
+ +
+
+inline ConstImageView<T> view() const
+

View just returns itself.

+
+ +
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_interp.html b/docs/_build/html/cpp_interp.html new file mode 100644 index 00000000000..7106d126785 --- /dev/null +++ b/docs/_build/html/cpp_interp.html @@ -0,0 +1,1436 @@ + + + + + + + Interpolation Tools — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Interpolation Tools

+
+

C++ Interpolants

+
+
+class Interpolant
+

Base class representing one-dimensional interpolant functions.

+

One-dimensional interpolant function. X units are in pixels and the frequency-domain u values are in cycles per pixel. This differs from the usual definition of k in radians per arcsec, hence the different letter variable.

+

All Interpolants are assumed symmetric so that frequency-domain values are real.

+

Subclassed by galsim::Cubic, galsim::Delta, galsim::Lanczos, galsim::Linear, galsim::Nearest, galsim::Quintic, galsim::SincInterpolant

+
+

Public Functions

+
+
+inline Interpolant(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline Interpolant(const Interpolant &rhs)
+

Copy constructor: does not copy photon sampler, will need to rebuild.

+
+ +
+
+inline virtual ~Interpolant()
+

Destructor.

+
+ +
+
+virtual double xrange() const = 0
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+virtual int ixrange() const = 0
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+virtual double urange() const = 0
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+virtual double xval(double x) const = 0
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double xvalWrapped(double x, int N) const
+

Value of interpolant, wrapped at period N.

+

This returns sum_{j=-inf}^{inf} xval(x + jN):

+
+
Parameters:
+
    +
  • x[in] Distance from sample (pixels)

  • +
  • N[in] Wrapping period (pixels)

  • +
+
+
Returns:
+

Value of interpolant after wrapping

+
+
+
+ +
+
+void xvalMany(double *x, int N) const
+

Calculate xval for array of input values x.

+
+
Parameters:
+
    +
  • [in/out] – Each x[i] is replaces by xval[x[i]]

  • +
  • How[in] many x values to calculate

  • +
+
+
+
+ +
+
+virtual double uval(double u) const = 0
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+void uvalMany(double *u, int N) const
+

Calculate uval for array of input values u.

+
+
Parameters:
+
    +
  • [in/out] – Each u[i] is replaces by uval[u[i]]

  • +
  • How[in] many u values to calculate

  • +
+
+
+
+ +
+
+inline virtual bool isExactAtNodes() const
+

Report whether interpolation will reproduce values at samples.

+

This will return true if the interpolant is exact at nodes, meaning that F(0)=1 and F(n)=0 for non-zero integer n. Right now this is true for every implementation.

+
+
Returns:
+

True if samples are returned exactly.

+
+
+
+ +
+
+inline virtual double getPositiveFlux() const
+

Return the integral of the positive portions of the kernel.

+

Should return 1 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of positive portions of kernel

+
+
+
+ +
+
+inline virtual double getNegativeFlux() const
+

Return the (absolute value of) integral of the negative portions of the kernel.

+

Should return 0 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of abs value of negative portions of kernel

+
+
+
+ +
+
+double getPositiveFlux2d() const
+

Return the positive and negative flux for a 2d application of the interpolant.

+
+ +
+
+double getNegativeFlux2d() const
+
+ +
+
+inline virtual void shoot(PhotonArray &photons, UniformDeviate ud) const
+

Return array of displacements drawn from this kernel.

+

Since Interpolant is 1d, will use only x array of PhotonArray. It will be assumed that photons returned are randomly ordered (no need to shuffle them). Also assumed that all photons will have nearly equal absolute value of flux. Total flux returned may not equal 1 due to shot noise in negative/positive photons, and small fluctuations in photon weights.

+
+
Parameters:
+
    +
  • N[in] number of photons to shoot

  • +
  • ud[in] UniformDeviate used to generate random values

  • +
+
+
Returns:
+

a PhotonArray containing the vector of displacements for interpolation kernel.

+
+
+
+ +
+
+virtual std::string makeStr() const = 0
+
+ +
+
+ +
+
+class Delta : public galsim::Interpolant
+

Delta-function interpolant in 1d.

+

The interpolant for when you do not want to interpolate between samples. It is not really intended to be used for any analytic drawing because it is infinite in the x domain at the location of samples, and it extends to infinity in the u domain. But it could be useful for photon-shooting, where it is trivially implemented as no displacements. The argument in the constructor is used to make a crude box approximation to the x-space delta function and to give a large but finite urange.

+
+

Public Functions

+
+
+inline Delta(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline ~Delta()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+inline virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+inline virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+inline virtual double getPositiveFlux() const
+

Return the integral of the positive portions of the kernel.

+

Should return 1 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of positive portions of kernel

+
+
+
+ +
+
+inline virtual double getNegativeFlux() const
+

Return the (absolute value of) integral of the negative portions of the kernel.

+

Should return 0 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of abs value of negative portions of kernel

+
+
+
+ +
+
+virtual void shoot(PhotonArray &photons, UniformDeviate ud) const
+

Return array of displacements drawn from this kernel.

+

Since Interpolant is 1d, will use only x array of PhotonArray. It will be assumed that photons returned are randomly ordered (no need to shuffle them). Also assumed that all photons will have nearly equal absolute value of flux. Total flux returned may not equal 1 due to shot noise in negative/positive photons, and small fluctuations in photon weights.

+
+
Parameters:
+
    +
  • N[in] number of photons to shoot

  • +
  • ud[in] UniformDeviate used to generate random values

  • +
+
+
Returns:
+

a PhotonArray containing the vector of displacements for interpolation kernel.

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+class Nearest : public galsim::Interpolant
+

Nearest-neighbor interpolation: boxcar.

+

The nearest-neighbor interpolant performs poorly as a k-space or x-space interpolant for SBInterpolatedImage. (See paper by Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) The objection to its use in Fourier space does not apply when shooting photons to generate an image; in that case, the nearest-neighbor interpolant is quite efficient (but not necessarily the best choice in terms of accuracy).

+

Tolerance determines how far onto sinc wiggles the uval will go. Very far, by default!

+
+

Public Functions

+
+
+inline Nearest(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline ~Nearest()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+inline virtual double getPositiveFlux() const
+

Return the integral of the positive portions of the kernel.

+

Should return 1 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of positive portions of kernel

+
+
+
+ +
+
+inline virtual double getNegativeFlux() const
+

Return the (absolute value of) integral of the negative portions of the kernel.

+

Should return 0 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of abs value of negative portions of kernel

+
+
+
+ +
+
+virtual void shoot(PhotonArray &photons, UniformDeviate ud) const
+

Return array of displacements drawn from this kernel.

+

Since Interpolant is 1d, will use only x array of PhotonArray. It will be assumed that photons returned are randomly ordered (no need to shuffle them). Also assumed that all photons will have nearly equal absolute value of flux. Total flux returned may not equal 1 due to shot noise in negative/positive photons, and small fluctuations in photon weights.

+
+
Parameters:
+
    +
  • N[in] number of photons to shoot

  • +
  • ud[in] UniformDeviate used to generate random values

  • +
+
+
Returns:
+

a PhotonArray containing the vector of displacements for interpolation kernel.

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+class Linear : public galsim::Interpolant
+

Linear interpolant.

+

The linear interpolant is a poor choice for FFT-based operations on SBInterpolatedImage, as it rings to high frequencies. (See paper by Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) This objection does not apply when shooting photons, in which case the linear interpolant is quite efficient (but not necessarily the best choice in terms of accuracy).

+
+

Public Functions

+
+
+inline Linear(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline ~Linear()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+inline virtual double getPositiveFlux() const
+

Return the integral of the positive portions of the kernel.

+

Should return 1 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of positive portions of kernel

+
+
+
+ +
+
+inline virtual double getNegativeFlux() const
+

Return the (absolute value of) integral of the negative portions of the kernel.

+

Should return 0 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of abs value of negative portions of kernel

+
+
+
+ +
+
+virtual void shoot(PhotonArray &photons, UniformDeviate ud) const
+

Return array of displacements drawn from this kernel.

+

Since Interpolant is 1d, will use only x array of PhotonArray. It will be assumed that photons returned are randomly ordered (no need to shuffle them). Also assumed that all photons will have nearly equal absolute value of flux. Total flux returned may not equal 1 due to shot noise in negative/positive photons, and small fluctuations in photon weights.

+
+
Parameters:
+
    +
  • N[in] number of photons to shoot

  • +
  • ud[in] UniformDeviate used to generate random values

  • +
+
+
Returns:
+

a PhotonArray containing the vector of displacements for interpolation kernel.

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+class Cubic : public galsim::Interpolant
+

Cubic interpolator exact to 3rd order Taylor expansion.

+

From R. G. Keys, IEEE Trans. Acoustics, Speech, & Signal Proc 29, p 1153, 1981

+

The cubic interpolant is a reasonable choice for a four-point interpolant for SBInterpolatedImage. (See paper by Bernstein & Gruen, http://arxiv.org/abs/1401.2636.)

+
+

Public Functions

+
+
+Cubic(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline ~Cubic()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+inline virtual double getPositiveFlux() const
+

Return the integral of the positive portions of the kernel.

+

Should return 1 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of positive portions of kernel

+
+
+
+ +
+
+inline virtual double getNegativeFlux() const
+

Return the (absolute value of) integral of the negative portions of the kernel.

+

Should return 0 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of abs value of negative portions of kernel

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+class Quintic : public galsim::Interpolant
+

Piecewise-quintic polynomial interpolant, ideal for Fourier-space interpolation.

+

See paper by Bernstein & Gruen, http://arxiv.org/abs/1401.2636.

+
+

Public Functions

+
+
+Quintic(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline ~Quintic()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+inline virtual double getPositiveFlux() const
+

Return the integral of the positive portions of the kernel.

+

Should return 1 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of positive portions of kernel

+
+
+
+ +
+
+inline virtual double getNegativeFlux() const
+

Return the (absolute value of) integral of the negative portions of the kernel.

+

Should return 0 unless the kernel has negative portions. Default is to ask the numerical sampler for its stored value.

+
+
Returns:
+

Integral of abs value of negative portions of kernel

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+class SincInterpolant : public galsim::Interpolant
+

Sinc interpolation: inverse of Nearest-neighbor.

+

The Sinc interpolant (K(x) = sin(pi x)/(pi x)) is mathematically perfect for band-limited data, introducing no spurious frequency content beyond kmax = pi/dx for input data with pixel scale dx. However, it is formally infinite in extent and, even with reasonable trunction, is still quite large. It will give exact results in SBInterpolatedImage::kValue() when it is used as a k-space interpolant, but is extremely slow. The usual compromise between sinc accuracy vs. speed is the Lanczos interpolant (see its documentation for details).

+
+

Public Functions

+
+
+inline SincInterpolant(const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+

gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

+
+
+
+ +
+
+inline ~SincInterpolant()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double xvalWrapped(double x, int N) const
+

Value of interpolant, wrapped at period N.

+

This returns sum_{j=-inf}^{inf} xval(x + jN):

+
+
Parameters:
+
    +
  • x[in] Distance from sample (pixels)

  • +
  • N[in] Wrapping period (pixels)

  • +
+
+
Returns:
+

Value of interpolant after wrapping

+
+
+
+ +
+
+virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+virtual void shoot(PhotonArray &photons, UniformDeviate ud) const
+

Return array of displacements drawn from this kernel.

+

Since Interpolant is 1d, will use only x array of PhotonArray. It will be assumed that photons returned are randomly ordered (no need to shuffle them). Also assumed that all photons will have nearly equal absolute value of flux. Total flux returned may not equal 1 due to shot noise in negative/positive photons, and small fluctuations in photon weights.

+
+
Parameters:
+
    +
  • N[in] number of photons to shoot

  • +
  • ud[in] UniformDeviate used to generate random values

  • +
+
+
Returns:
+

a PhotonArray containing the vector of displacements for interpolation kernel.

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+class Lanczos : public galsim::Interpolant
+

The Lanczos interpolation filter, nominally sinc(x)*sinc(x/n), truncated at +/-n.

+

The Lanczos filter is an approximation to the band-limiting sinc filter with a smooth cutoff at high x. Order n Lanczos has a range of +/- n pixels. It typically is a good compromise between kernel size and accuracy.

+

Note that pure Lanczos, when interpolating a set of constant-valued samples, does not return this constant. Setting conserve_dc in the constructor tweaks the function so that it approximately conserves the value of constant (DC) input data (accurate to better than 1.e-5 when used in two dimensions).

+
+

Public Functions

+
+
+Lanczos(int n, bool conserve_dc, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • n[in] Filter order; must be given on input and cannot be changed.

  • +
  • conserve_dc[in] Set true to adjust filter to be more nearly correct for constant inputs.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of operations, if different from the default.

  • +
+
+
+
+ +
+
+inline ~Lanczos()
+
+ +
+
+inline virtual double xrange() const
+

Maximum extent of interpolant from origin in x space (pixels)

+
+
Returns:
+

Range of non-zero values of interpolant.

+
+
+
+ +
+
+inline virtual int ixrange() const
+

The total range as an integer. Typically xrange() == 0.5 * ixrange().

+
+ +
+
+inline virtual double urange() const
+

Maximum extent of interpolant from origin in u space (cycles per pixel)

+
+
Returns:
+

Range of non-zero values of interpolant in u space

+
+
+
+ +
+
+inline int getN() const
+
+ +
+
+inline bool conservesDC() const
+
+ +
+
+virtual double xval(double x) const
+

Value of interpolant in real space.

+
+
Parameters:
+

x[in] Distance from sample (pixels)

+
+
Returns:
+

Value of interpolant

+
+
+
+ +
+
+virtual double uval(double u) const
+

Value of interpolant in frequency space.

+
+
Parameters:
+

u[in] Frequency for evaluation (cycles per pixel)

+
+
Returns:
+

Value of interpolant, normalized so uval(0) = 1 for flux-conserving interpolation.

+
+
+
+ +
+
+virtual std::string makeStr() const
+
+ +
+
+ +
+
+

C++ Lookup Tables

+
+
+class Table : public galsim::FluxDensity
+

A class to represent lookup tables for a function y = f(x).

+

Subclassed by galsim::TableBuilder

+
+

Public Types

+
+
+enum interpolant
+

Values:

+
+
+enumerator linear
+
+ +
+
+enumerator floor
+
+ +
+
+enumerator ceil
+
+ +
+
+enumerator nearest
+
+ +
+
+enumerator spline
+
+ +
+
+enumerator gsinterp
+
+ +
+ +
+
+

Public Functions

+
+
+Table(const double *args, const double *vals, int N, interpolant in)
+

Table from args, vals.

+
+ +
+
+Table(const double *args, const double *vals, int N, const Interpolant *gsinterp)
+
+ +
+
+double argMin() const
+
+ +
+
+double argMax() const
+
+ +
+
+size_t size() const
+
+ +
+
+virtual double operator()(double a) const
+

interp, return double(0) if beyond bounds This is a virtual function from FluxDensity, which lets a Table be a FluxDensity.

+
+ +
+
+double lookup(double a) const
+

interp, but exception if beyond bounds

+
+ +
+
+void interpMany(const double *argvec, double *valvec, int N) const
+

interp many values at once

+
+ +
+
+double integrate(double xmin, double xmax) const
+
+ +
+
+double integrateProduct(const Table &g, double xmin, double xmax, double xfact) const
+
+ +
+
+ +
+
+class TableBuilder : public galsim::Table
+
+

Public Functions

+
+
+inline TableBuilder(interpolant in)
+
+ +
+
+inline TableBuilder(const Interpolant *gsinterp)
+
+ +
+
+inline bool finalized() const
+
+ +
+
+inline double lookup(double a) const
+
+ +
+
+inline void addEntry(double x, double f)
+

Insert an (x, y(x)) pair into the table.

+
+ +
+
+void finalize()
+
+ +
+
+ +
+
+class Table2D
+

A class to represent lookup tables for a function z = f(x, y).

+
+

Public Types

+
+
+enum interpolant
+

Values:

+
+
+enumerator linear
+
+ +
+
+enumerator floor
+
+ +
+
+enumerator ceil
+
+ +
+
+enumerator nearest
+
+ +
+
+enumerator spline
+
+ +
+
+enumerator gsinterp
+
+ +
+ +
+
+

Public Functions

+
+
+Table2D(const double *xargs, const double *yargs, const double *vals, int Nx, int Ny, interpolant in)
+

Table from xargs, yargs, vals.

+
+ +
+
+Table2D(const double *xargs, const double *yargs, const double *vals, int Nx, int Ny, const double *dfdx, const double *dfdy, const double *d2fdxdy)
+
+ +
+
+Table2D(const double *xargs, const double *yargs, const double *vals, int Nx, int Ny, const Interpolant *gsinterp)
+
+ +
+
+double lookup(double x, double y) const
+

interp

+
+ +
+
+void interpMany(const double *xvec, const double *yvec, double *valvec, int N) const
+

interp many values at once

+
+ +
+
+void interpGrid(const double *xvec, const double *yvec, double *valvec, int Nx, int Ny) const
+
+ +
+
+void gradient(double x, double y, double &dfdx, double &dfdy) const
+

Estimate df/dx, df/dy at a single location.

+
+ +
+
+void gradientMany(const double *xvec, const double *yvec, double *dfdxvec, double *dfdyvec, int N) const
+

Estimate many df/dx and df/dy values.

+
+ +
+
+void gradientGrid(const double *xvec, const double *yvec, double *dfdxvec, double *dfdyvec, int Nx, int Ny) const
+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_math.html b/docs/_build/html/cpp_math.html new file mode 100644 index 00000000000..aeb986657cf --- /dev/null +++ b/docs/_build/html/cpp_math.html @@ -0,0 +1,656 @@ + + + + + + + Math — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Math

+
+

Nonlinear solver

+
+
+template<class F, class T = double>
class Solve
+

A class that solves a provided function for a zero.

+

The first template argument, F, is the type of the function. Typically you would simple function object that defines the operator() method and use that for F.

+

The second template argument (optional: default = double) is the type of the arguments to the function.

+

The solving process is in two parts:

+

    +
  1. First the solution needs to be bracketed.

    +

    This can be done in several ways:

    +

    a) If you know the appropriate range, you can set it in the constructor:

    +
    Solve<MyFunc> solver(func, x_min, x_max);
    +
    +
    +

    b) If you don’t know the range, you can set an initial guess range and let Solve expand the range until it finds a range that brackets the solution:

    +
    Solve<MyFunc> solver(func, x_min, x_max);
    +solver.bracket()
    +
    +
    +

    c) If you know one end of the range, but not the other you can expand only one side of the range from the initial guess:

    +
    Solve<MyFunc> solver(func, 0, r_max);
    +solver.bracketUpper()
    +
    +
    +

    (There is also a corresponding bracketLower() method.)

    +

    d) Sometimes there is a fundamental limit past which you want to make sure that the range doesn’t go. e.g. For values that must be positive, you might want to make sure the range doesn’t extend past 0.

    +

    For this case, there are the following two functions

    solver.bracketUpperWithLimit(upper_limit)
    +solver.bracketLowerWithLimit(lower_limit)
    +
    +
    +

    +

    Finally, note that there is nothing in the code that enforces x_min < x_max. If the two limits bracket the code in reverse order, that’s ok.

    +
  2. +
  3. The next step is to solve for the root within this range. There are currently two algorithms for doing that: Bisect and Brent. You can tell Solve which one to use with:

    +
    solver.setMethod(Bisect)
    +solver.setMethod(Brent)
    +
    +
    +

    (In fact, the former is unnecessary, since Bisect is the default.)

    +

    Then the method root() will solve for the root in that range.

    +
  4. +
+

+

Typical usage:

+
struct MyFunc {
+    double operator()(double x) { return [...] }
+};
+
+MyFunc func;
+Solve<MyFunc> solver(func, xmin, xmax);
+solver.bracket();         // If necessary
+solver.setMethod(Brent);  // If desired
+double result = solver.root()
+
+
+
+

Public Functions

+
+
+inline Solve(const F &func_, T lb_, T ub_)
+

Constructor taking the function to solve, and the range (if known).

+
+ +
+
+inline void setMaxSteps(int m)
+

Set the maximum number of steps for the solving algorithm to use.

+
+ +
+
+inline void setMethod(Method m_)
+

Set which method to use: Bisect or Brent.

+
+ +
+
+inline void setXTolerance(T tol)
+

Set the tolerance to define when the root is close enough to 0. (abs(x) < tol)

+
+ +
+
+inline T getXTolerance() const
+

Get the current tolerance.

+
+ +
+
+inline void setBounds(T lb, T ub)
+

Set the bounds for the search to new values.

+
+ +
+
+inline T getLowerBound() const
+

Get the current search bounds.

+
+ +
+
+inline T getUpperBound() const
+
+ +
+
+inline void evaluateBounds() const
+

Make sure the current bounds have corresponding flower, fupper.

+
+ +
+
+inline void bracket()
+

Hunt for bracket, geometrically expanding range.

+

This version assumes that the root is to the side of the end point that is closer to 0. This will be true if the function is monotonic.

+
+ +
+
+inline bool bracket1(T &a, T &b, T &fa, T &fb)
+
+ +
+
+inline void bracketUpper()
+

Hunt for bracket, geometrically expanding range.

+

This one only expands to the right for when you know that the lower bound is definitely to the left of the root, but the upper bound might not bracket it.

+
+ +
+
+inline void bracketLower()
+

Hunt for bracket, geometrically expanding range.

+

The opposite of bracketUpper &#8212; only expand to the left.

+
+ +
+
+inline bool bracket1WithLimit(T &a, T &b, T &fa, T &fb, T &c)
+
+ +
+
+inline void bracketUpperWithLimit(T upper_limit)
+

Hunt for upper bracket, with an upper limit to how far it can go.

+
+ +
+
+inline void bracketLowerWithLimit(T lower_limit)
+

Hunt for lower bracket, with a lower limit to how far it can go.

+
+ +
+
+inline T root() const
+

Find the root according the the method currently set.

+
+ +
+
+inline T bisect() const
+

A simple bisection root-finder.

+
+ +
+
+inline T zbrent() const
+

A more sophisticated root-finder using Brent’s algorithm.

+
+ +
+
+ +
+ +
+

Other mathematical functions

+
+
+void galsim::math::sincos(double theta, double &sint, double &cost)
+
+ +
+
+double galsim::math::gamma_p(double a, double x)
+
+ +
+
+double galsim::math::sinc(double x)
+
+ +
+
+double galsim::math::Si(double x)
+
+ +
+
+double galsim::math::Ci(double x)
+
+ +
+
+

Horner’s method for polynomial evaluation

+
+
+void galsim::math::Horner(const double *x, const int nx, const double *coef, const int nc, double *result)
+
+ +
+
+void galsim::math::Horner2D(const double *x, const double *y, const int nx, const double *coef, const int ncx, const int ncy, double *result, double *temp)
+
+ +
+
+

C++ Integration Functions

+
+
+template<class T>
struct IntRegion
+

A type that encapsulates everything known about the integral in a region.

+

The constructor of IntRegion takes the minimum and maximum values for the region, along with an optional ostream for outputing diagnostic information.

+

After the integration is done, the IntRegion will also hold the estimate of the integral’s value over the region, along with an estimate of the error.

+
+

Public Functions

+
+
+inline IntRegion(const T a, const T b, std::ostream *dbgout_ = 0, std::map<T, T> *fxmap_ = 0)
+

Constructor taking the bounds of the region and (optionally) an ostream for outputing diagnostic information.

+

Note that normally a < b. However, a > b is also valid. If a > b, then the integral will be from right to left, which is just the negative of the integral from b to a.

+
+
Parameters:
+
    +
  • a – The left end of the region

  • +
  • b – The right end of the region

  • +
  • dbgout_ – An optional ostream for diagnostic info

  • +
  • fxmap_ – Known results

  • +
+
+
+
+ +
+
+inline bool operator<(const IntRegion<T> &r2) const
+

op< sorts by the error estimate

+
+ +
+
+inline bool operator>(const IntRegion<T> &r2) const
+

op> sorts by the error estimate

+
+ +
+
+inline void subDivide(std::vector<IntRegion<T>> &children)
+

Subdivide a region according to the current split points or biset.

+

If there are no split points set yet, then this will bisect the region. However, you may also set split points using addSplit(x). This is worth doing if you know about any discontinuities, zeros or poles in the function you are integrating.

+
+ +
+
+inline void bisect()
+

Set a split point at the bisection.

+
+ +
+
+inline void findZeroCrossings()
+

Search for zero crossings based on values stored in fxmap.

+
+ +
+
+inline void addSplit(const T x)
+

Add a split point to the current list to be used by the next subDivide call.

+

This is worth doing if you know about any discontinuities, zeros or poles in the function you are integrating.

+
+ +
+
+inline size_t getNSplit() const
+

Get the number of split points currently set.

+
+ +
+
+inline const T &left() const
+

Get the left end of the region.

+
+ +
+
+inline const T &right() const
+

Get the right end of the region.

+
+ +
+
+inline const T &getErr() const
+

Get the current error estimate.

+
+ +
+
+inline const T &getArea() const
+

Get the current estimate of the integral over the region.

+
+ +
+
+inline void setArea(const T &a, const T &e)
+

Set a new estimate of the area and error.

+
+ +
+
+inline void useFXMap()
+

Setup an fxmap for this region.

+
+ +
+
+

Public Members

+
+
+std::ostream *dbgout
+
+ +
+
+std::map<T, T> *fxmap
+
+ +
+
+ +
+
+template<class UF>
inline double galsim::integ::int1d(const UF &func, double min, double max, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 1-dimensional integral using simple min/max values for the region.

+
+
Parameters:
+
    +
  • func – The function to be integrated (may be a function object)

  • +
  • min – The lower bound of the integration

  • +
  • max – The upper bound of the integration

  • +
  • relerr – The target relative error

  • +
  • abserr – The target absolute error

  • +
+
+
+
+ +
+
+template<class UF>
inline double galsim::integ::int1d(const UF &func, IntRegion<double> &reg, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 1-dimensional integral using an IntRegion.

+
+
Parameters:
+
    +
  • func – The function to be integrated(may be a function object)

  • +
  • reg – The region of the integration

  • +
  • relerr – The target relative error

  • +
  • abserr – The target absolute error

  • +
+
+
+
+ +
+
+template<class BF>
inline double galsim::integ::int2d(const BF &func, double xmin, double xmax, double ymin, double ymax, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 2-dimensional integral using simple min/max values for borh regions (i.e. the integral is over a square)

+
+ +
+
+template<class BF, class YREG>
inline double galsim::integ::int2d(const BF &func, IntRegion<double> &reg, const YREG &yreg, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 2-dimensional integral.

+
+
Parameters:
+
    +
  • func – The function to be integrated (may be a function object)

  • +
  • reg – The region of the inner integral

  • +
  • yreg – yreg(x) is the region of the outer integral at x

  • +
  • relerr – The target relative error

  • +
  • abserr – The target absolute error

  • +
+
+
+
+ +
+
+template<class BF>
inline double galsim::integ::int2d(const BF &func, IntRegion<double> &reg, IntRegion<double> &yreg, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 3-dimensional integral using constant IntRegions for both regions (i.e. the integral is over a square)

+
+ +
+
+template<class TF>
inline double galsim::integ::int3d(const TF &func, double xmin, double xmax, double ymin, double ymax, double zmin, double zmax, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 3-dimensional integral using simple min/max values for all regions (i.e. the integral is over a cube)

+
+ +
+
+template<class TF, class YREG, class ZREG>
inline double galsim::integ::int3d(const TF &func, IntRegion<double> &reg, const YREG &yreg, const ZREG &zreg, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 3-dimensional integral.

+
+
Parameters:
+
    +
  • func – The function to be integrated (may be a function object)

  • +
  • reg – The region of the inner integral

  • +
  • yreg – yreg(x) is the region of the middle integral at x

  • +
  • zreg – zreg(x)(y) is the region of the outer integral at x,y

  • +
  • relerr – The target relative error

  • +
  • abserr – The target absolute error

  • +
+
+
+
+ +
+
+template<class TF>
inline double galsim::integ::int3d(const TF &func, IntRegion<double> &reg, IntRegion<double> &yreg, IntRegion<double> &zreg, const double relerr = DEFRELERR, const double abserr = DEFABSERR)
+

Perform a 3-dimensional integral using constant IntRegions for all regions (i.e. the integral is over a cube)

+
+ +
+
+double galsim::math::hankel_trunc(const std::function<double(double)> f, double k, double nu, double maxr, double relerr = 1.e-6, double abserr = 1.e-12, int nzeros = 10)
+
+ +
+
+double galsim::math::hankel_inf(const std::function<double(double)> f, double k, double nu, double relerr = 1.e-6, double abserr = 1.e-12, int nzeros = 10)
+
+ +
+
+

Misc Utilities

+
+
+template<typename T>
bool galsim::math::isNan(T x)
+
+ +
+
+int galsim::SetOMPThreads(int num_threads)
+
+ +
+
+int galsim::GetOMPThreads()
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_noise.html b/docs/_build/html/cpp_noise.html new file mode 100644 index 00000000000..6d15d34f38d --- /dev/null +++ b/docs/_build/html/cpp_noise.html @@ -0,0 +1,1145 @@ + + + + + + + Noise-related Functionality — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_photon.html b/docs/_build/html/cpp_photon.html new file mode 100644 index 00000000000..249dd667910 --- /dev/null +++ b/docs/_build/html/cpp_photon.html @@ -0,0 +1,619 @@ + + + + + + + Photons and Sensor Effects — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Photons and Sensor Effects

+
+

C++ Photon Array

+
+
+class PhotonArray
+

Class to hold a list of “photon” arrival positions.

+

Class holds arrays of information about photon arrivals: x and y positions, dxdz and dydz inclination “angles” (really slopes), a flux, and a wavelength carried by each photon. It is the intention that fluxes of photons be nearly equal in absolute value so that noise statistics can be estimated by counting number of positive and negative photons.

+
+

Unnamed Group

+
+
+inline double *getXArray()
+

Accessors that provide access as numpy arrays in Python layer.

+
+ +
+
+inline double *getYArray()
+
+ +
+
+inline double *getFluxArray()
+
+ +
+
+inline double *getDXDZArray()
+
+ +
+
+inline double *getDYDZArray()
+
+ +
+
+inline double *getWavelengthArray()
+
+ +
+
+inline const double *getXArray() const
+
+ +
+
+inline const double *getYArray() const
+
+ +
+
+inline const double *getFluxArray() const
+
+ +
+
+inline const double *getDXDZArray() const
+
+ +
+
+inline const double *getDYDZArray() const
+
+ +
+
+inline const double *getWavelengthArray() const
+
+ +
+
+inline bool hasAllocatedAngles() const
+
+ +
+
+inline bool hasAllocatedWavelengths() const
+
+ +
+
+

Public Functions

+
+
+PhotonArray(int N)
+

Construct a PhotonArray of the given size, allocating the arrays locally.

+

Note: PhotonArrays made this way can only be used locally in the C++ layer, not returned back to Python. Also, only x,y,flux will be allocated.

+
+
Parameters:
+

N[in] Size of array

+
+
+
+ +
+
+inline PhotonArray(size_t N, double *x, double *y, double *flux, double *dxdz, double *dydz, double *wave, bool is_corr)
+

Construct a PhotonArray of the given size with the given arrays, which should be allocated separately (in Python typically).

+

If angles or wavelengths are not set, these may be 0.

+
+
Parameters:
+
    +
  • N[in] Size of array

  • +
  • x[in] An array of the initial x values

  • +
  • y[in] An array of the initial y values

  • +
  • flux[in] An array of the initial flux values

  • +
  • dxdz[in] An array of the initial dxdz values (may be 0)

  • +
  • dydz[in] An array of the initial dydz values (may be 0)

  • +
  • wave[in] An array of the initial wavelength values (may be 0)

  • +
  • is_corr[in] A boolean indicating whether the current values are correlated.

  • +
+
+
+
+ +
+
+inline size_t size() const
+

Accessor for array size.

+
+
Returns:
+

Array size

+
+
+
+ +
+
+inline void setPhoton(int i, double x, double y, double flux)
+

Set characteristics of a photon that are decided during photon shooting (i.e. only x,y,flux)

+
+
Parameters:
+
    +
  • i[in] Index of desired photon (no bounds checking)

  • +
  • x[in] x coordinate of photon

  • +
  • y[in] y coordinate of photon

  • +
  • flux[in] flux of photon

  • +
+
+
+
+ +
+
+inline double getX(int i) const
+

Access x coordinate of a photon.

+
+
Parameters:
+

i[in] Index of desired photon (no bounds checking)

+
+
Returns:
+

x coordinate of photon

+
+
+
+ +
+
+inline double getY(int i) const
+

Access y coordinate of a photon.

+
+
Parameters:
+

i[in] Index of desired photon (no bounds checking)

+
+
Returns:
+

y coordinate of photon

+
+
+
+ +
+
+inline double getFlux(int i) const
+

Access flux of a photon.

+
+
Parameters:
+

i[in] Index of desired photon (no bounds checking)

+
+
Returns:
+

flux of photon

+
+
+
+ +
+
+inline double getDXDZ(int i) const
+

Access dxdz of a photon.

+
+
Parameters:
+

i[in] Index of desired photon (no bounds checking)

+
+
Returns:
+

dxdz of photon

+
+
+
+ +
+
+inline double getDYDZ(int i) const
+

Access dydz coordinate of a photon.

+
+
Parameters:
+

i[in] Index of desired photon (no bounds checking)

+
+
Returns:
+

dydz coordinate of photon

+
+
+
+ +
+
+inline double getWavelength(int i) const
+

Access wavelength of a photon.

+
+
Parameters:
+

i[in] Index of desired photon (no bounds checking)

+
+
Returns:
+

wavelength of photon

+
+
+
+ +
+
+double getTotalFlux() const
+

Return sum of all photons’ fluxes.

+
+
Returns:
+

flux of photon

+
+
+
+ +
+
+void setTotalFlux(double flux)
+

Rescale all photon fluxes so that total flux matches argument.

+

If current total flux is zero, no rescaling is done.

+
+
Parameters:
+

flux[in] desired total flux of all photons.

+
+
+
+ +
+
+void scaleFlux(double scale)
+

Rescale all photon fluxes by the given factor.

+
+
Parameters:
+

scale[in] Scaling factor for all fluxes

+
+
+
+ +
+
+void scaleXY(double scale)
+

Rescale all photon positions by the given factor.

+
+
Parameters:
+

scale[in] Scaling factor for all positions

+
+
+
+ +
+
+void assignAt(int istart, const PhotonArray &rhs)
+

Assign the contents of another array to a portion of this one.

+
+
Parameters:
+
    +
  • istart[in] The starting index at which to assign the contents of rhs

  • +
  • rhs[in] PhotonArray whose contents to assign into this one

  • +
+
+
+
+ +
+
+void convolve(const PhotonArray &rhs, BaseDeviate ud)
+

Convolve this array with another.

+

Convolution of two arrays is defined as adding the coordinates on a photon-by-photon basis and multiplying the fluxes on a photon-by-photon basis. Output photons’ flux is renormalized so that the expectation value of output total flux is product of two input totals, if the two photon streams are uncorrelated.

+
+
Parameters:
+
    +
  • rhs[in] PhotonArray to convolve with this one. Must be same size.

  • +
  • rng[in] A BaseDeviate in case we need to shuffle.

  • +
+
+
+
+ +
+
+void convolveShuffle(const PhotonArray &rhs, BaseDeviate rng)
+

Convolve this array with another, shuffling the order in which photons are combined.

+

Same convolution behavior as convolve(), but the order in which the photons are multiplied into the array is randomized to destroy any flux or position correlations.

+
+
Parameters:
+
    +
  • rhs[in] PhotonArray to convolve with this one. Must be same size.

  • +
  • rng[in] A BaseDeviate used to shuffle the input photons.

  • +
+
+
+
+ +
+
+template<class T>
double addTo(ImageView<T> target) const
+

Add flux of photons to an image by binning into pixels.

+

Photon in this PhotonArray are binned into the pixels of the input Image and their flux summed into the pixels. Image is assumed to represent surface brightness, so photons’ fluxes are divided by image pixel area. Photons past the edges of the image are discarded.

+
+
Parameters:
+

target[in] the Image to which the photons’ flux will be added.

+
+
Returns:
+

The total flux of photons the landed inside the image bounds.

+
+
+
+ +
+
+template<class T>
int setFrom(const BaseImage<T> &image, double maxFlux, BaseDeviate ud)
+

Set photon positions based on flux in an image.

+

The flux in each non-zero pixel will be turned into 1 or more photons according to the maxFlux parameter which sets an upper limit for the absolute value of the flux of any photon. Pixels with abs values > maxFlux will spawn multiple photons.

+

The positions of the photons will be random within the area of each pixel. TODO: This corresponds to the Nearest interpolant. Consider implementing other interpolation options here.

+
+
Parameters:
+
    +
  • image – The image to use for the photon fluxes and positions.

  • +
  • maxFlux – The maximum flux that any photon should have.

  • +
  • rng – A BaseDeviate in case we need to shuffle.

  • +
+
+
Returns:
+

the total number of photons set.

+
+
+
+ +
+
+inline bool isCorrelated() const
+

Check if the current array has correlated photons.

+
+ +
+
+inline void setCorrelated(bool is_corr = true)
+

Set whether the current array has correlated photons.

+
+ +
+
+ +
+
+

Silicon Sensor

+
+
+class Silicon
+
+

Public Functions

+
+
+Silicon(int numVertices, double numElec, int nx, int ny, int qDist, double diffStep, double pixelSize, double sensorThickness, double *vertex_data, const Table &tr_radial_table, Position<double> treeRingCenter, const Table &abs_length_table, bool transpose)
+
+ +
+
+~Silicon()
+
+ +
+
+bool insidePixel(int ix, int iy, double x, double y, double zconv, Bounds<int> &targetBounds, bool *off_edge, int emptypolySize, Bounds<double> *pixelInnerBoundsData, Bounds<double> *pixelOuterBoundsData, Position<float> *horizontalBoundaryPointsData, Position<float> *verticalBoundaryPointsData, Position<double> *emptypolyData) const
+
+ +
+
+void scaleBoundsToPoly(int i, int j, int nx, int ny, const Polygon &emptypoly, Polygon &result, double factor) const
+
+ +
+
+double calculateConversionDepth(bool photonsHasAllocatedWavelengths, const double *photonsWavelength, const double *abs_length_table_data, bool photonsHasAllocatedAngles, const double *photonsDXDZ, const double *photonsDYDZ, int i, double randomNumber) const
+
+ +
+
+template<typename T>
void updatePixelDistortions(ImageView<T> target)
+
+ +
+
+void calculateTreeRingDistortion(int i, int j, Position<int> orig_center, Polygon &poly) const
+
+ +
+
+void calculateTreeRingDistortion(int i, int j, Position<int> orig_center, int nx, int ny, int i1, int j1)
+
+ +
+
+template<typename T>
void addTreeRingDistortions(ImageView<T> target, Position<int> orig_center)
+
+ +
+
+template<typename T>
void subtractDelta(ImageView<T> target)
+
+ +
+
+template<typename T>
void addDelta(ImageView<T> target)
+
+ +
+
+template<typename T>
void initialize(ImageView<T> target, Position<int> orig_center)
+
+ +
+
+void finalize()
+
+ +
+
+template<typename T>
double accumulate(const PhotonArray &photons, int i1, int i2, BaseDeviate rng, ImageView<T> target)
+
+ +
+
+template<typename T>
void update(ImageView<T> target)
+
+ +
+
+double pixelArea(int i, int j, int nx, int ny) const
+
+ +
+
+template<typename T>
void fillWithPixelAreas(ImageView<T> target, Position<int> orig_center, bool use_flux)
+
+ +
+
+ +
+
+

Charge Deflection Correction

+
+
+void galsim::ApplyCD(int n, double *x, double *y, const double *cd)
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/cpp_sb.html b/docs/_build/html/cpp_sb.html new file mode 100644 index 00000000000..c0cd5559f37 --- /dev/null +++ b/docs/_build/html/cpp_sb.html @@ -0,0 +1,1926 @@ + + + + + + + C++ Surface Brightness Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

C++ Surface Brightness Profiles

+
+

SBProfile Base Class

+
+
+class SBProfile
+

A base class representing all of the 2D surface brightness profiles that we know how to draw.

+

The SBProfile class is a representation of a surface brightness distribution across a 2-dimensional image plane, with real and/or Fourier-domain models of a wide variety of galaxy shapes, point-spread functions (PSFs), and their convolutions. There are several realizations of the SBProfile classes: There are the “atomic” classes that represent specific analytic profiles: (SBDeltaFunction, SBGaussian, SBSersic, SBAiry, SBExponential, SBBox, SBDeVaucouleurs and SBMoffat). SBInterpolatedImage represents a pattern defined by a grid of pixel values and a chosen interpolation scheme between pixel centers. SBTransform represents any affine transformation (shear, magnification, rotation, translation, and/or flux rescaling) of any other SBProfile. SBAdd represents the sum of any number of SBProfiles. SBConvolve represents the convolution of any number of SBProfiles, and SBDeconvolve is the deconvolution of one SBProfile with another.

+

Every SBProfile knows how to draw an Image<float> of itself in real and k space. Each also knows what is needed to prevent aliasing or truncation of itself when drawn. The classes model the surface brightness of the object, so the xValue routine returns the value of the surface brightness at a given point. Usually we define this as flux/arcsec^2.

+

However, when drawing an SBProfile, any distance measures used to define the SBProfile will be taken to be in units of pixels. Thus the image, which is nominally a surface brightness image is also a flux image. The flux in each pixel is the flux/pixel^2. The python layer takes care of the typical case of defining an SBProfile in terms of arcsec, then converting the image to pixels (scaling the size and flux appropriately), so that the draw command here has the correct flux.

+

This isn’t an abstract base class. An SBProfile is a concrete object which internally has a pointer to the implementation details (which is an abstract base class). Furthermore, all SBProfiles are immutable objects. Any changes are made through modifiers that return a new object. (e.g. setFlux, shear, shift, etc.) This means that we can safely make SBProfiles use shallow copies, since that will never be confusing, which in turn means that SBProfiles can be safely returned by value, used in containers (e.g. list<SBProfile>), etc.

+

The only constructor for SBProfile is the copy constructor. All SBProfiles need to be created as one of the derived types that have real constructors.

+

Well, technically, there is also a default constructor to make it easier to use containers of SBProfiles. However, it is an error to use an SBProfile that has been default constructed for any purpose.

+

The assignment operator does a shallow copy, replacing the current contents of the SBProfile with that of the rhs profile.

+

Subclassed by galsim::SBAdd, galsim::SBAiry, galsim::SBAutoConvolve, galsim::SBAutoCorrelate, galsim::SBBox, galsim::SBConvolve, galsim::SBDeconvolve, galsim::SBDeltaFunction, galsim::SBExponential, galsim::SBFourierSqrt, galsim::SBGaussian, galsim::SBInclinedExponential, galsim::SBInclinedSersic, galsim::SBInterpolatedImage, galsim::SBInterpolatedKImage, galsim::SBKolmogorov, galsim::SBMoffat, galsim::SBSecondKick, galsim::SBSersic, galsim::SBShapelet, galsim::SBSpergel, galsim::SBTopHat, galsim::SBTransform, galsim::SBVonKarman

+
+

Public Functions

+
+
+SBProfile()
+

Default constructor for convenience only. Do not use!

+

This constructor is only provided so you can do things like:

std::list<SBProfile> prof_list;
+prof_list.push_back(psf);
+prof_list.push_back(gal);
+prof_list.push_back(pix);
+
+
+ The default constructor for std::list strangely requires a default constructor for the argument type, even though it isn’t ever really used.

+
+ +
+
+SBProfile(const SBProfile &rhs)
+

Only legitimate public constructor is a copy constructor.

+
+ +
+
+SBProfile &operator=(const SBProfile &rhs)
+

operator= replaces the current contents with those of the rhs.

+
+ +
+
+~SBProfile()
+

Destructor isn’t virtual, since derived classes don’t have anything to cleanup.

+
+ +
+
+GSParams getGSParams() const
+

Get the GSParams object for this SBProfile Makes a copy, since this is used for python pickling, so the current SBProfile may go out of scope before we need to use the returned GSParams.

+
+ +
+
+double xValue(const Position<double> &p) const
+

Return value of SBProfile at a chosen 2D position in real space.

+

Assume all are real-valued. xValue() may not be implemented for derived classes (SBConvolve) that require an FFT to determine real-space values. In this case, an SBError will be thrown.

+
+
Parameters:
+

p[in] 2D position in real space.

+
+
+
+ +
+
+std::complex<double> kValue(const Position<double> &k) const
+

Return value of SBProfile at a chosen 2D position in k space.

+
+
Parameters:
+

k[in] 2D position in k space.

+
+
+
+ +
+
+void getXRange(double &xmin, double &xmax, std::vector<double> &splits) const
+

Define the range over which the profile is not trivially zero.

+

These values are used when a real-space convolution is requested to define the appropriate range of integration. The implementation here is +- infinity for both x and y. Derived classes may override this if they a have different range.

+
+ +
+
+void getYRange(double &ymin, double &ymax, std::vector<double> &splits) const
+
+ +
+
+void getYRangeX(double x, double &ymin, double &ymax, std::vector<double> &splits) const
+
+ +
+
+double maxK() const
+

Value of k beyond which aliasing can be neglected.

+
+ +
+
+inline double nyquistDx() const
+

Real-space image pixel spacing that does not alias maxK.

+
+ +
+
+double stepK() const
+

Sampling in k-space necessary to avoid folding too much of image in x space.

+
+ +
+
+int getGoodImageSize(double dx) const
+

Determine a good size for a drawn image based on dx and stepK()

+

+The basic formula is 2pi / (dx * stepK()). But then we round up to the next even integer value.

+
+
Parameters:
+

dx[in] The pixel scale of the image

+
+
Returns:
+

the recommended image size.

+
+
+
+ +
+
+bool isAxisymmetric() const
+

Check whether the SBProfile is known to have rotational symmetry about x=y=0.

+

If the SBProfile has rotational symmetry, certain calculations can be simplified.

+
+ +
+
+bool hasHardEdges() const
+

The presence of hard edges help determine whether real space convolution might be a better choice.

+
+ +
+
+bool isAnalyticX() const
+

Check whether the SBProfile is analytic in the real domain.

+

An SBProfile is “analytic” in the real domain if values can be determined immediately at any position through formula or a stored table (no DFT); this makes certain calculations more efficient.

+
+ +
+
+bool isAnalyticK() const
+

Check whether the SBProfile is analytic in the Fourier domain.

+

An SBProfile is “analytic” in the k domain if values can be determined immediately at any position through formula or a stored table (no DFT); this makes certain calculations more efficient.

+
+ +
+
+Position<double> centroid() const
+

Returns (X, Y) centroid of SBProfile.

+
+ +
+
+double getFlux() const
+

Get the total flux of the SBProfile.

+
+ +
+
+double maxSB() const
+

Get an estimate of the maximum surface brightness.

+
+ +
+
+SBTransform scaleFlux(double fluxRatio) const
+

Multiply the flux by fluxRatio.

+
+ +
+
+SBTransform expand(double scale) const
+

Apply an overall scale change to the profile, preserving surface brightness.

+
+ +
+
+SBTransform rotate(double theta) const
+

Apply a given rotation in radians.

+
+ +
+
+SBTransform transform(double dudx, double dudy, double dvdx, double dvdy) const
+

Apply a transformation given by an arbitrary Jacobian matrix.

+
+ +
+
+SBTransform shift(const Position<double> &delta) const
+

Apply a translation.

+
+ +
+
+void shoot(PhotonArray &photons, BaseDeviate rng) const
+

Shoot photons through this SBProfile.

+

Returns an array of photon coordinates and fluxes that are drawn from the light distribution of this SBProfile. Absolute value of each photons’ flux should be approximately equal, but some photons can be negative as needed to represent negative regions. Note that the ray-shooting method is not intended to produce a randomized value of the total object flux, so do not assume that there will be sqrt(N) error on the flux. In fact most implementations will return a PhotonArray with exactly correct flux, with only the distribution of flux on the sky that will definitely have sampling noise.

+

The one definitive gaurantee is that, in the limit of large number of photons, the surface brightness distribution of the photons will converge on the SB pattern defined by the object.

+

Objects with regions of negative flux will result in creation of photons with negative flux. Absolute value of negative photons’ flux should be nearly equal to the standard flux of positive photons. Shot-noise fluctuations between the number of positive and negative photons will produce noise in the total net flux carried by the output PhotonArray.

+

The typical implementation will be to take the integral of the absolute value of flux, and divide it nearly equally into N photons. The photons are then drawn from the distribution of the absolute value of flux. If a photon is drawn from a region of negative flux, then that photon’s flux is negated. Because of cancellation, this means that each photon will carry more than getFlux()/N flux if there are negative-flux regions in the object. It also means that during convolution, addition, or interpolation, positive- and negative-flux photons can be contributing to the same region of the image. Their cancellation means that the shot noise may be substantially higher than you would expect if you had only positive-flux photons.

+

The photon flux may also vary slightly as a means of speeding up photon-shooting, as an alternative to rejection sampling. See OneDimensionalDeviate documentation.

+
+
Parameters:
+
    +
  • photons[in] PhotonArray in which to write the photon information

  • +
  • rng[in] BaseDeviate that will be used to draw photons from distribution.

  • +
+
+
+
+ +
+
+double getPositiveFlux() const
+

Return expectation value of flux in positive photons when shoot() is called.

+

Returns expectation value of flux returned in positive-valued photons when shoot() is called for this object. Default implementation is to return getFlux(), if it is positive, or 0 otherwise, which will be the case when the SBProfile is constructed entirely from elements of the same sign.

+

It should be generally true that getPositiveFlux() - getNegativeFlux() returns the same thing as getFlux(). Small difference may accrue from finite numerical accuracy in cases involving lookup tables, etc.

+
+
Returns:
+

Expected positive-photon flux.

+
+
+
+ +
+
+double getNegativeFlux() const
+

Return expectation value of absolute value of flux in negative photons from shoot()

+

Returns expectation value of (absolute value of) flux returned in negative-valued photons when shoot() is called for this object. Default implementation is to return getFlux() if it is negative, 0 otherwise, which will be the case when the SBProfile is constructed entirely from elements that have the same sign.

+

It should be generally true that getPositiveFlux() - getNegativeFlux() returns the same thing as getFlux(). Small difference may accrue from finite numerical accuracy in cases involving lookup tables, etc.

+
+
Returns:
+

Expected absolute value of negative-photon flux.

+
+
+
+ +
+
+template<typename T>
void draw(ImageView<T> image, double dx, double *jac, double xoff, double yoff, double flux_ratio) const
+

Draw an image of the SBProfile in real space forcing the use of real methods where we have a formula for x values. For SBProfiles without an analytic real-space representation, an exception will be thrown.

+

The image is not cleared out before drawing. So this profile will be added to anything already on the input image.

+
+
Parameters:
+
    +
  • image[inout] (any of ImageViewF, ImageViewD, ImageViewS, ImageViewI)

  • +
  • dx, the[in] pixel scale

  • +
+
+
+
+ +
+
+template<typename T>
void drawK(ImageView<std::complex<T>> image, double dk, double *jac) const
+

Draw an image of the SBProfile in k space.

+

For drawing in k space: routines are analagous to real space, except the image is complex. The image is normalized such that I(0,0) is the total flux.

+
+
Parameters:
+
    +
  • image[inout] in k space (must be an ImageViewC)

  • +
  • dk, the[in] step size in k space

  • +
+
+
+
+ +
+
+ +
+
+

Simple C++ Profiles

+
+
+class SBGaussian : public galsim::SBProfile
+

Gaussian Surface Brightness Profile.

+

The Gaussian Surface Brightness Profile is characterized by two properties, its flux and the characteristic size sigma where the radial profile of the circular Gaussian drops off as exp[-r^2 / (2. * sigma^2)]. The maxK() and stepK() are for the SBGaussian are chosen to extend to 4 sigma in both real and k domains, or more if needed to reach the folding_threshold spec.

+
+

Public Functions

+
+
+SBGaussian(double sigma, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • sigma[in] characteristic size, surface brightness scales as exp[-r^2 / (2. * sigma^2)].

  • +
  • flux[in] flux of the Surface Brightness Profile.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBGaussian(const SBGaussian &rhs)
+

Copy constructor.

+
+ +
+
+~SBGaussian()
+

Destructor.

+
+ +
+
+double getSigma() const
+

Returns the characteristic size sigma of the Gaussian profile.

+
+ +
+
+ +
+
+class SBDeltaFunction : public galsim::SBProfile
+

Delta Function Surface Brightness Profile.

+

The Delta Function Surface Brightness Profile is characterized by a single property, its ‘flux’.

+

Note that the DeltaFunction SBP cannot be drawn by itself. Instead, it should be applied as part of a convolution first.

+
+

Public Functions

+
+
+SBDeltaFunction(double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • flux[in] flux of the Surface Brightness Profile.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBDeltaFunction(const SBDeltaFunction &rhs)
+

Copy constructor.

+
+ +
+
+~SBDeltaFunction()
+

Destructor.

+
+ +
+
+ +
+
+class SBBox : public galsim::SBProfile
+

Surface Brightness Profile for the Boxcar function.

+

The boxcar function is a rectangular box. Convolution with a Boxcar function of dimensions width x height and sampling at pixel centres is equivalent to pixelation (i.e. Surface Brightness integration) across rectangular pixels of the same dimensions. This class is therefore useful for pixelating SBProfiles.

+
+

Public Functions

+
+
+SBBox(double width, double height, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • width[in] width of Boxcar function along x.

  • +
  • height[in] height of Boxcar function along y.

  • +
  • flux[in] flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBBox(const SBBox &rhs)
+

Copy constructor.

+
+ +
+
+~SBBox()
+

Destructor.

+
+ +
+
+double getWidth() const
+

Returns the x dimension width of the Boxcar.

+
+ +
+
+double getHeight() const
+

Returns the y dimension width of the Boxcar.

+
+ +
+
+ +
+
+class SBTopHat : public galsim::SBProfile
+

Surface Brightness Profile for the TopHat function.

+

The tophat function is much like the boxcar, but a circular plateau, rather than a rectangle. It is defined by a radius and a flux.

+
+

Public Functions

+
+
+SBTopHat(double radius, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • radius[in] radius of TopHat function

  • +
  • flux[in] flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBTopHat(const SBTopHat &rhs)
+

Copy constructor.

+
+ +
+
+~SBTopHat()
+

Destructor.

+
+ +
+
+double getRadius() const
+

Returns the radius of the TopHat.

+
+ +
+
+ +
+
+

PSF C++ Profiles

+
+
+class SBMoffat : public galsim::SBProfile
+

Surface Brightness for the Moffat Profile (an approximate description of ground-based PSFs).

+

The Moffat surface brightness profile is I(R) propto [1 + (r/r_scale)^2]^(-beta). The SBProfile representation of a Moffat profile also includes an optional truncation beyond a given radius.

+
+

Public Functions

+
+
+SBMoffat(double beta, double scale_radius, double trunc, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • beta[in] Moffat beta parameter for profile [1 + (r / rD)^2]^beta.

  • +
  • scale_radius[in] Scale radius, rD.

  • +
  • trunc[in] Outer truncation radius in same physical units as size, trunc = 0. for no truncation.

  • +
  • flux[in] Flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBMoffat(const SBMoffat &rhs)
+

Copy constructor.

+
+ +
+
+~SBMoffat()
+

Destructor.

+
+ +
+
+double getBeta() const
+

Returns beta of the Moffat profile [1 + (r / rD)^2]^beta.

+
+ +
+
+double getFWHM() const
+

Returns the FWHM of the Moffat profile.

+
+ +
+
+double getScaleRadius() const
+

Returns the scale radius rD of the Moffat profile [1 + (r / rD)^2]^beta.

+
+ +
+
+double getHalfLightRadius() const
+

Returns the half-light radius of the Moffat profile.

+
+ +
+
+double getTrunc() const
+

Returns the truncation radius.

+
+ +
+
+ +
+
+class SBAiry : public galsim::SBProfile
+

Surface Brightness Profile for the Airy disk (perfect diffraction-limited PSF for a circular aperture), with central obscuration.

+

maxK() is set at the hard limit for Airy disks, stepK() makes transforms go to at least 5 lam/D or EE>(1-folding_threshold). Schroeder (10.1.18) gives limit of EE at large radius. This stepK could probably be relaxed, it makes overly accurate FFTs. Note x & y are in units of lambda/D here. Integral over area will give unity in this normalization.

+
+

Public Functions

+
+
+SBAiry(double lam_over_D, double obscuration, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • lam_over_D[in] lam_over_D = (lambda * focal length) / (telescope diam) if arg is focal plane position, else lam_over_D = lambda / (telescope diam) if arg is in radians of field angle.

  • +
  • obscuration[in] linear dimension of central obscuration as fraction of pupil dimension.

  • +
  • flux[in] flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBAiry(const SBAiry &rhs)
+

Copy constructor.

+
+ +
+
+~SBAiry()
+

Destructor.

+
+ +
+
+double getLamOverD() const
+

Returns lam_over_D param of the SBAiry.

+
+ +
+
+double getObscuration() const
+

Returns obscuration param of the SBAiry.

+
+ +
+
+ +
+
+class SBKolmogorov : public galsim::SBProfile
+

Surface Brightness Profile for a Kolmogorov turbulent spectrum.

+
+

Public Functions

+
+
+SBKolmogorov(double lam_over_r0, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • lam_over_r0[in] lambda / r0 in the physical units adopted (user responsible for consistency), where r0 is the Fried parameter. The FWHM of the Kolmogorov PSF is ~0.976 lambda/r0 (e.g., Racine 1996, PASP 699, 108). Typical values for the Fried parameter are on the order of 10 cm for most observatories and up to 20 cm for excellent sites. The values are usually quoted at lambda = 500 nm and r0 depends on wavelength as [r0 ~ lambda^(6/5)].

  • +
  • flux[in] Flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBKolmogorov(const SBKolmogorov &rhs)
+

Copy constructor.

+
+ +
+
+~SBKolmogorov()
+

Destructor.

+
+ +
+
+double getLamOverR0() const
+

Returns lam_over_r0 param of the SBKolmogorov.

+
+ +
+
+ +
+
+class SBVonKarman : public galsim::SBProfile
+
+

Public Functions

+
+
+SBVonKarman(double lam, double r0, double L0, double flux, double scale, bool doDelta, const GSParams &gsparams, double force_stepk)
+

Constructor.

+
+
Parameters:
+
    +
  • lam[in] Wavelength in nm.

  • +
  • r0[in] Fried parameter in m (at given wavelength lam).

  • +
  • L0[in] Outer scale in m.

  • +
  • flux[in] Flux.

  • +
  • scale[in] Scale of ‘x’ in xValue in arcsec.

  • +
  • doDelta[in] Whether or not to include delta-function contribution to encircled energy when computing stepk/maxk/HLR.

  • +
  • gsparams[in] GSParams.

  • +
+
+
+
+ +
+
+SBVonKarman(const SBVonKarman &rhs)
+

Copy constructor.

+
+ +
+
+~SBVonKarman()
+

Destructor.

+
+ +
+
+double getLam() const
+

Getters.

+
+ +
+
+double getR0() const
+
+ +
+
+double getL0() const
+
+ +
+
+double getScale() const
+
+ +
+
+bool getDoDelta() const
+
+ +
+
+double getDelta() const
+
+ +
+
+double getHalfLightRadius() const
+
+ +
+
+double structureFunction(double) const
+
+ +
+
+

Friends

+
+
+friend class VKXIntegrand
+
+ +
+
+ +
+
+class SBSecondKick : public galsim::SBProfile
+
+

Public Functions

+
+
+SBSecondKick(double lam_over_r0, double kcrit, double flux, const GSParamsPtr &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • lam_over_r0[in] lambda/r0, equivalent to the same in SBKolmogorov

  • +
  • kcrit[in] Critical turbulence Fourier mode in units of r0.

  • +
  • flux[in] Flux.

  • +
  • gsparams[in] GSParams.

  • +
+
+
+
+ +
+
+SBSecondKick(const SBSecondKick &rhs)
+

Copy constructor.

+
+ +
+
+~SBSecondKick()
+

Destructor.

+
+ +
+
+double getLamOverR0() const
+

Getters.

+
+ +
+
+double getKCrit() const
+
+ +
+
+double getDelta() const
+
+ +
+
+double kValue(double) const
+

Alternate versions of x/k Value for testing purposes.

+
+ +
+
+double kValueRaw(double) const
+
+ +
+
+double xValue(double) const
+
+ +
+
+double xValueRaw(double) const
+
+ +
+
+double xValueExact(double) const
+
+ +
+
+double structureFunction(double) const
+
+ +
+
+ +
+
+

Galaxy C++ Profiles

+
+
+class SBExponential : public galsim::SBProfile
+

Exponential Surface Brightness Profile.

+

Surface brightness profile with I(r) propto exp[-r/r_0] for some scale-length r_0. This is a special case of the Sersic profile, but is given a separate class since the Fourier transform has closed form and can be generated without lookup tables.

+
+

Public Functions

+
+
+SBExponential(double r0, double flux, const GSParams &gsparams)
+

Constructor - note that r0 is scale length, NOT half-light radius re as in SBSersic.

+
+
Parameters:
+
    +
  • r0[in] scale length for the profile that scales as exp[-(r / r0)], NOT the half-light radius re.

  • +
  • flux[in] flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBExponential(const SBExponential &rhs)
+

Copy constructor.

+
+ +
+
+~SBExponential()
+

Destructor.

+
+ +
+
+double getScaleRadius() const
+

Returns the scale radius of the Exponential profile.

+
+ +
+
+ +
+
+class SBSersic : public galsim::SBProfile
+

Sersic Surface Brightness Profile.

+

The Sersic Surface Brightness Profile is characterized by three properties: its Sersic index n, its flux, and the half-light radius re (or scale radius r0). Given these properties, the surface brightness profile scales as I(r) propto exp[-(r/r0)^{1/n}], or I(r) propto exp[-b*(r/re)^{1/n}]. The code is limited to 0.3 <= n <= 6.2, with an exception thrown for values outside that range.

+

The SBProfile representation of a Sersic profile also includes an optional truncation beyond a given radius, by the parameter trunc. Internal Sersic information are cached according to the (n, trunc/r0) pair. All internal calculations are based on the scale radius r0. If the profile is specified by the half-light radius re, the corresponding scale radius r0 is calculated, and vice versa.

+

When the Sersic profile is specfied by the scale radius with truncation, the normalization is adjusted such that the truncated profile has the specified flux (its half-light radius will differ from an equivalent Sersic without truncation). Similarly, when the Sersic profile is specified by the half-light radius with truncation, SBSersic generates a profile whose flux and half-light radius is as specified, by adjusting its normalization and scale radius.

+

Another optional parameter, flux_untruncated = true, allows the setting of the flux to the untruncated Sersic, while generating a truncated Sersic (i.e., the normalizaton is the same with respect to the untruncated case). This facilitates the comparison of truncated and untruncated Sersic, as the amplitude (as well as the scale radius r0, if half-light radius is specified) changes when a truncated Sersic is specified with flux_untruncated = false. The flux_untruncated variable is ignored if trunc = 0.

+

Note that when trunc > 0. and flux_untruncated == true, the actual flux will not be the same as the specified value; its true flux is returned by the getFlux() method. Similarly for the half-light radius, when the Sersic profile is specified by the half-light radius; the getHalfLightRadius() method will return the true half-light radius. The scale radius will remain at the same value, if this quantity was used to specify the profile.

+

There are two special cases of the Sersic profile that have their own SBProfiles: n=1 (SBExponential), n=0.5 (SBGaussian). These special cases use several simplifications in all calculations, whereas for general n, the Fourier transform must be treated numerically.

+
+

Public Functions

+
+
+SBSersic(double n, double scale_radius, double flux, double trunc, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • n[in] Sersic index.

  • +
  • scale_radius[in] Scale radius

  • +
  • flux[in] Flux.

  • +
  • trunc[in] Outer truncation radius in same physical units as size; trunc = 0. for no truncation.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBSersic(const SBSersic &rhs)
+

Copy constructor.

+
+ +
+
+~SBSersic()
+

Destructor.

+
+ +
+
+double getN() const
+

Returns the Sersic index n of the profile.

+
+ +
+
+double getScaleRadius() const
+

Returns the scale radius r0 of the Sersic profile exp[-(r/r_0)^(1/n)].

+
+ +
+
+double getHalfLightRadius() const
+

Returns the half light radius of the Sersic profile.

+
+ +
+
+double getTrunc() const
+

Returns the truncation radius.

+
+ +
+
+ +
+
+class SBInclinedExponential : public galsim::SBProfile
+

Inclined exponential surface brightness profile.

+

Surface brightness profile based on the distribution I(R,z) propto sech^2(z/Hs)*exp(-R/Rs), where Hs is the scale height of the disk, Rs is the scale radius, z is the distance along the minor axis, and R is the distance perpendicular to it. This profile is determined by four parameters: The inclination angle, the scale radius, the scale height, and the flux.

+

Note that the position angle is always zero. A profile with a different position angle can be obtained through the rotate() method of the corresponding Python class.

+
+

Public Functions

+
+
+SBInclinedExponential(double inclination, double scale_radius, double scale_height, double flux, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • inclination[in] Inclination angle, where 0 = face-on, pi/2 = edge-on

  • +
  • scale_radius[in] Scale radius of the exponential disk.

  • +
  • scale_height[in] Scale height of the exponential disk.

  • +
  • flux[in] Flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBInclinedExponential(const SBInclinedExponential &rhs)
+

Copy constructor.

+
+ +
+
+~SBInclinedExponential()
+

Destructor.

+
+ +
+
+double getInclination() const
+

Returns the inclination angle of the profile in radians.

+
+ +
+
+double getScaleRadius() const
+

Returns the scale radius r0 of the disk profile.

+
+ +
+
+double getScaleHeight() const
+

Returns the scale height h0 of the disk profile.

+
+ +
+
+ +
+
+class SBInclinedSersic : public galsim::SBProfile
+

Inclined Sersic Surface Brightness Profile.

+

This class is similar to a Sersic profile, additionally allowing inclination relative to the line of sight. The true profile is assumed to follow a Sersic distribution in R, multiplied by sech^2(z/Hs), where Hs is the scale height of the disk and z is the distance along the minor axis. The inclination angle determines how elliptical the profile appears.

+

See the documentation for the SBSersic class for further details on Sersic profiles.

+

Note that the position angle is always zero. A profile with a different position angle can be obtained through the rotate() method of the corresponding Python class.

+

If the inclination will always be zero (face-on), the SBSersic class can instead be used as a slightly faster alternative. If no truncation radius will be applied and n=1, the SBInclinedExponential class can be used as a much faster alternative.

+
+

Public Functions

+
+
+SBInclinedSersic(double n, double inclination, double scale_radius, double height, double flux, double trunc, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • n[in] Sersic index.

  • +
  • inclination[in] Inclination of the disk relative to line of sight, in radians, where 0 = face-on and pi/2 = edge-on.

  • +
  • scale_radius[in] Scale radius

  • +
  • height[in] Scale height

  • +
  • flux[in] Flux.

  • +
  • trunc[in] Outer truncation radius in same physical units as size; trunc = 0. for no truncation.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBInclinedSersic(const SBInclinedSersic &rhs)
+

Copy constructor.

+
+ +
+
+~SBInclinedSersic()
+

Destructor.

+
+ +
+
+double getN() const
+

Returns the Sersic index n of the profile.

+
+ +
+
+double getInclination() const
+

Returns the inclination angle of the profile in radians.

+
+ +
+
+double getScaleRadius() const
+

Returns the scale radius r0 of the Sersic profile.

+
+ +
+
+double getHalfLightRadius() const
+

Returns the half light radius of the Sersic profile (if it were face-on).

+
+ +
+
+double getScaleHeight() const
+

Returns the scale height h0 of the disk profile.

+
+ +
+
+double getTrunc() const
+

Returns the truncation radius.

+
+ +
+
+ +
+
+class SBSpergel : public galsim::SBProfile
+

Spergel Surface Brightness Profile.

+

Surface brightness profile with I(r) propto (r/r_c)^{nu} K_{nu}(r/r_c) for some scale-length r_c = r_0 / c_{nu}, where K_{nu}(u) is the modified Bessel function of the second kind (also confusingly referred to as the ‘spherical modified Bessel function of the third kind’) and nu > -1. For different parameters nu this profile can approximate Sersic profiles with different indices.

+

Reference: D. N. Spergel, “ANALYTICAL GALAXY PROFILES FOR PHOTOMETRIC AND LENSING ANALYSIS,”” ASTROPHYS J SUPPL S 191(1), 58-65 (2010) [doi:10.1088/0067-0049/191/1/58].

+
+

Public Functions

+
+
+SBSpergel(double nu, double scale_radius, double flux, const GSParams &gsparams)
+

Constructor - note that r0 is scale length, NOT half-light radius re as in SBSersic.

+
+
Parameters:
+
    +
  • nu[in] index parameter setting the logarithmic slope of the profile.

  • +
  • scale_radius[in] scale radius

  • +
  • flux[in] flux.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBSpergel(const SBSpergel &rhs)
+

Copy constructor.

+
+ +
+
+~SBSpergel()
+

Destructor.

+
+ +
+
+double getNu() const
+

Returns the Spergel index nu of the profile.

+
+ +
+
+double getScaleRadius() const
+

Returns the scale radius of the Spergel profile.

+
+ +
+
+double calculateIntegratedFlux(double r) const
+

Return integrated flux of circular profile.

+
+ +
+
+double calculateFluxRadius(double f) const
+

Return radius enclosing flux f.

+
+ +
+
+ +
+
+

Arbitrary C++ Profiles

+
+
+class SBInterpolatedImage : public galsim::SBProfile
+

Surface Brightness Profile represented by interpolation over one or more data tables/images.

+

The SBInterpolatedImage class represents an arbitrary surface brightness profile (supplied as an image), including rules for how to interpolate the profile between the supplied pixel values.

+

It is assumed that input images oversample the profiles they represent. maxK() is set at the Nyquist frequency of the input image, although it should be noted that interpolants other than the ideal sinc function may make the max frequency higher than this. The output is required to be periodic on a scale > original image extent + kernel footprint, and stepK() is set accordingly.

+

The normal way to make an SBInterpolatedImage is to provide the image to interpolate and the interpolation scheme. See Interpolant.h for more about the different kind of interpolation.

+

You can provide different interpolation schemes for real and Fourier space (passed as xInterp and kInterp respectively). These are required, but there are sensible defaults in the python layer wrapper class, InterpolatedImage.

+

The ideal k-space interpolant is a sinc function; however, the quintic interpolant is the default, based on detailed investigations on the tradeoffs between accuracy and speed. Note that, as in Bernstein & Gruen (2012), the accuracy achieved by this interpolant is dependent on our choice of 4x pad factor. Users who do not wish to pad the arrays to this degree may need to use a higher-order Lanczos interpolant instead, but this is not the recommended usage. (Note: this padding is done by the python layer now, not here.)

+

The surface brightness profile will be in terms of the image pixels. The python layer InterpolatedImage class takes care of converting between these units and the arcsec units that are usually desired.

+
+

Public Functions

+
+
+SBInterpolatedImage(const BaseImage<double> &image, const Bounds<int> &init_bounds, const Bounds<int> &nonzero_bounds, const Interpolant &xInterp, const Interpolant &kInterp, double stepk, double maxk, const GSParams &gsparams)
+

Initialize internal quantities and allocate data tables based on a supplied 2D image.

+
+
Parameters:
+
    +
  • image[in] Input Image (ImageF or ImageD).

  • +
  • init_bounds[in] The bounds of the original unpadded image.

  • +
  • nonzero_bounds[in] The bounds in which the padded image is non-zero.

  • +
  • xInterp[in] Interpolation scheme to adopt between pixels

  • +
  • kInterp[in] Interpolation scheme to adopt in k-space

  • +
  • stepk[in] If > 0, force stepk to this value.

  • +
  • maxk[in] If > 0, force maxk to this value.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering.

  • +
+
+
+
+ +
+
+SBInterpolatedImage(const SBInterpolatedImage &rhs)
+

Copy Constructor.

+
+ +
+
+~SBInterpolatedImage()
+

Destructor.

+
+ +
+
+const Interpolant &getXInterp() const
+
+ +
+
+const Interpolant &getKInterp() const
+
+ +
+
+double getPadFactor() const
+
+ +
+
+void calculateStepK(double max_stepk = 0.) const
+

Refine the value of stepK if the input image was larger than necessary.

+
+
Parameters:
+

max_stepk[in] Optional maximum value of stepk if you have some a priori knowledge about an appropriate maximum.

+
+
+
+ +
+
+void calculateMaxK(double max_maxk = 0.) const
+

Refine the value of maxK if the input image had a smaller scale than necessary.

+
+
Parameters:
+

max_maxk[in] Optional maximum value of maxk if you have some a priori knowledge about an appropriate maximum.

+
+
+
+ +
+
+ConstImageView<double> getPaddedImage() const
+
+ +
+
+ConstImageView<double> getNonZeroImage() const
+
+ +
+
+ConstImageView<double> getImage() const
+
+ +
+
+ +
+
+class SBInterpolatedKImage : public galsim::SBProfile
+
+

Public Functions

+
+
+SBInterpolatedKImage(const BaseImage<std::complex<double>> &kimage, double stepk, const Interpolant &kInterp, const GSParams &gsparams)
+

Initialize internal quantities and allocate data tables based on a supplied 2D image.

+
+
Parameters:
+
    +
  • kimage[in] Input Fourier-space Image (ImageC).

  • +
  • stepk[in] If > 0, force stepk to this value.

  • +
  • kInterp[in] Interpolation scheme to adopt in k-space

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering.

  • +
+
+
+
+ +
+
+SBInterpolatedKImage(const BaseImage<double> &data, double stepk, double maxk, const Interpolant &kInterp, const GSParams &gsparams)
+
+ +
+
+SBInterpolatedKImage(const SBInterpolatedKImage &rhs)
+

Copy Constructor.

+
+ +
+
+~SBInterpolatedKImage()
+

Destructor.

+
+ +
+
+const Interpolant &getKInterp() const
+
+ +
+
+ConstImageView<double> getKData() const
+
+ +
+
+ +
+
+class SBShapelet : public galsim::SBProfile
+

Class for describing polar shapelet surface brightness profiles.

+
+

Public Functions

+
+
+SBShapelet(double sigma, LVector bvec, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • sigma[in] scale size of Gauss-Laguerre basis set.

  • +
  • bvec[in] bvec[n,m] contains flux information for the (n, m) basis function.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBShapelet(const SBShapelet &rhs)
+

Copy Constructor.

+
+ +
+
+~SBShapelet()
+

Destructor.

+
+ +
+
+double getSigma() const
+
+ +
+
+const LVector &getBVec() const
+
+ +
+
+void rotate(double theta)
+
+ +
+
+ +
+
+

Composite C++ Profiles

+
+
+class SBAdd : public galsim::SBProfile
+

Sums SBProfiles.

+

The SBAdd class can be used to add arbitrary numbers of SBProfiles together.

+
+

Public Functions

+
+
+SBAdd(const std::list<SBProfile> &slist, const GSParams &gsparams)
+

Constructor, list of inputs.

+
+
Parameters:
+
    +
  • slist[in] List of SBProfiles.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBAdd(const SBAdd &rhs)
+

Copy constructor.

+
+ +
+
+~SBAdd()
+

Destructor.

+
+ +
+
+std::list<SBProfile> getObjs() const
+

Get the list of SBProfiles that are being added together.

+
+ +
+
+ +
+
+class SBConvolve : public galsim::SBProfile
+

Convolve SBProfiles.

+

Convolve two, three or more SBProfiles together.

+

The profiles to be convolved may be provided either as the first 2 or 3 parameters in the constructor, or as a std::list<SBProfile>.

+

The convolution will normally be done using discrete Fourier transforms of each of the component profiles, multiplying them together, and then transforming back to real space. The nominal flux of the resulting SBConvolve is the product of the fluxes of each of the component profiles. Thus, when using the SBConvolve to convolve a galaxy of some desired flux with a PSF, it is important to normalize the flux in the PSF to 1 beforehand.

+

The stepK used for the k-space image will be (Sum 1/stepK()^2)^(-1/2) where the sum is over all the components being convolved. Since the size of the convolved image scales roughly as the quadrature sum of the components, this should be close to Pi/Rmax where Rmax is the radius that encloses all but (1-folding_threshold) of the flux in the final convolved image.

+

The maxK used for the k-space image will be the minimum of the maxK() calculated for each component. Since the k-space images are multiplied, if one of them is essentially zero beyond some k value, then that will be true of the final image as well.

+

There is also an option to do the convolution as integrals in real space. Each constructor has an optional boolean parameter, real_space, that comes immediately after the list of profiles to convolve. Currently, the real-space integration is only enabled for 2 profiles. If you try to use it for more than 2 profiles, an exception will be thrown.

+

The real-space convolution is normally slower than the DFT convolution. The exception is if both component profiles have hard edges (e.g. a truncated Moffat with a Box). In that case, the maxK for each component is quite large since the ringing dies off fairly slowly. So it can be quicker to use real-space convolution instead.

+
+

Public Functions

+
+
+SBConvolve(const std::list<SBProfile> &slist, bool real_space, const GSParams &gsparams)
+

Constructor, list of inputs.

+
+
Parameters:
+
    +
  • slist[in] Input: list of SBProfiles.

  • +
  • real_space[in] Do convolution in real space?

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBConvolve(const SBConvolve &rhs)
+

Copy constructor.

+
+ +
+
+~SBConvolve()
+

Destructor.

+
+ +
+
+std::list<SBProfile> getObjs() const
+

Get the list of SBProfiles that are being convolved.

+
+ +
+
+bool isRealSpace() const
+

Return whether the convolution should be done in real space.

+
+ +
+
+ +
+
+class SBAutoConvolve : public galsim::SBProfile
+
+

Public Functions

+
+
+SBAutoConvolve(const SBProfile &s, bool real_space, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • s[in] SBProfile to be convolved with itself.

  • +
  • real_space[in] Do convolution in real space?

  • +
  • gsparams[in] GSParams to use, if different from the default.

  • +
+
+
+
+ +
+
+SBAutoConvolve(const SBAutoConvolve &rhs)
+

Copy constructor.

+
+ +
+
+~SBAutoConvolve()
+

Destructor.

+
+ +
+
+SBProfile getObj() const
+

Get the SBProfile being convolved.

+
+ +
+
+bool isRealSpace() const
+

Return whether the convolution should be done in real space.

+
+ +
+
+ +
+
+class SBAutoCorrelate : public galsim::SBProfile
+
+

Public Functions

+
+
+SBAutoCorrelate(const SBProfile &s, bool real_space, const GSParams &gsparams)
+

Constructor.

+
+
Parameters:
+
    +
  • s[in] SBProfile to be correlated with itself.

  • +
  • real_space[in] Do convolution in real space?

  • +
  • gsparams[in] GSParams to use, if different from the default.

  • +
+
+
+
+ +
+
+SBAutoCorrelate(const SBAutoCorrelate &rhs)
+

Copy constructor.

+
+ +
+
+~SBAutoCorrelate()
+

Destructor.

+
+ +
+
+SBProfile getObj() const
+

Get the SBProfile being conrrelated.

+
+ +
+
+bool isRealSpace() const
+

Return whether the convolution should be done in real space.

+
+ +
+
+ +
+
+

Transformed C++ Profiles

+
+
+class SBTransform : public galsim::SBProfile
+

An affine transformation of another SBProfile.

+

Origin of original shape will now appear at _cen. Flux is NOT conserved in transformation - surface brightness is preserved. We keep track of all distortions in a 2x2 matrix M = [(A B), (C D)] = [row1, row2] plus a 2-element Positon object cen for the shift, and a flux scaling, in addition to the scaling implicit in the matrix M = abs(det(M)).

+
+

Public Functions

+
+
+SBTransform(const SBProfile &sbin, const double *jac, const Position<double> &cen, double ampScaling, const GSParams &gsparams)
+

General constructor.

+
+
Parameters:
+
    +
  • obj[in] SBProfile being transformed

  • +
  • jac[in] 4-element array (A,B,C,D) of 2x2 distortion matrix M = [(A B), (C D)]

  • +
  • cen[in] 2-element (x, y) Position for the translational shift.

  • +
  • ampScaling[in] Amount by which the SB amplitude should be multiplied.

  • +
  • gsparams[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

  • +
+
+
+
+ +
+
+SBTransform(const SBTransform &rhs)
+

Copy constructor.

+
+ +
+
+~SBTransform()
+

Destructor.

+
+ +
+
+SBProfile getObj() const
+
+ +
+
+void getJac(double &mA, double &mB, double &mC, double &mD) const
+
+ +
+
+Position<double> getOffset() const
+
+ +
+
+double getFluxScaling() const
+
+ +
+
+ +
+
+class SBDeconvolve : public galsim::SBProfile
+

SBProfile adapter which inverts its subject in k space to effect a deconvolvution.

+
+
Param adaptee:
+

[in] SBProfile for which to effect a deconvolution by k space inversion.

+
+
Param gsparams:
+

[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

+
+
+
+

Public Functions

+
+
+SBDeconvolve(const SBProfile &adaptee, const GSParams &gsparams)
+

Constructor.

+
+ +
+
+SBDeconvolve(const SBDeconvolve &rhs)
+

Copy constructor.

+
+ +
+
+~SBDeconvolve()
+

Destructor.

+
+ +
+
+SBProfile getObj() const
+

Get the SBProfile being deconvolved.

+
+ +
+
+ +
+
+class SBFourierSqrt : public galsim::SBProfile
+

SBProfile adapter which computes the square root of its subject in k space.

+
+
Param adaptee:
+

[in] SBProfile to compute the Fourier-space square root of.

+
+
Param gsparams:
+

[in] GSParams object storing constants that control the accuracy of image operations and rendering, if different from the default.

+
+
+
+

Public Functions

+
+
+SBFourierSqrt(const SBProfile &adaptee, const GSParams &gsparams)
+

Constructor.

+
+ +
+
+SBFourierSqrt(const SBFourierSqrt &rhs)
+

Copy constructor.

+
+ +
+
+SBProfile getObj() const
+

Get the SBProfile being operated on.

+
+ +
+
+~SBFourierSqrt()
+

Destructor.

+
+ +
+
+ +
+
+

C++ GSParams

+
+
+struct GSParams
+
+

Public Functions

+
+
+GSParams(int _minimum_fft_size, int _maximum_fft_size, double _folding_threshold, double _stepk_minimum_hlr, double _maxk_threshold, double _kvalue_accuracy, double _xvalue_accuracy, double _table_spacing, double _realspace_relerr, double _realspace_abserr, double _integration_relerr, double _integration_abserr, double _shoot_accuracy)
+

A set of numbers that govern how SBProfiles make various speed/accuracy tradeoff decisions.

+

These parameters can be broadly split into two groups: i) parameters that affect the rendering of objects by Discrete Fourier Transform (DFT) and by real space convolution; and ii) parameters that affect rendering by photon shooting.

+

The DFT and real space convolution relevant params are:

+

+The Photon Shooting relevant params are:

+
+
Parameters:
+
    +
  • minimum_fft_size – Constant giving minimum FFT size we’re willing to do.

  • +
  • maximum_fft_size – Constant giving maximum FFT size we’re willing to do.

  • +
  • folding_threshold – A threshold parameter used for setting the stepK value for FFTs. The FFT’s stepK is set so that at most a fraction folding_threshold of the flux of any profile is folded.

  • +
  • stepk_minimum_hlr – In addition to the above constraint for aliasing, also set stepk such that pi/stepk is at least stepk_minimum_hlr times the profile’s half-light radius (for profiles that have a well-defined half-light radius).

  • +
  • maxk_threshold – A threshold parameter used for setting the maxK value for FFTs. The FFT’s maxK is set so that the k-values that are excluded off the edge of the image are less than maxk_threshold.

  • +
  • kvalue_accuracy – Accuracy of values in k-space. If a k-value is less than kvalue_accuracy, then it may be set to zero. Similarly, if an alternate calculation has errors less than kvalue_accuracy, then it may be used instead of an exact calculation. Note: This does not necessarily imply that all kvalues are this accurate. There may be cases where other choices we have made lead to errors greater than this. But whenever we do an explicit calculation about this, this is the value we use. This should typically be set to a lower, more stringent value than maxk_threshold.

  • +
  • xvalue_accuracy – Accuracy of values in real space. If a value in real space is less than xvalue_accuracy, then it may be set to zero. Similarly, if an alternate calculation has errors less than xvalue_accuracy, then it may be used instead of an exact calculation.

  • +
  • table_spacing – Several profiles use lookup tables for either the Hankel transform (Sersic, truncated Moffat) or the real space radial function (Kolmogorov). We try to estimate a good spacing between values in the lookup tables based on either xvalue_accuracy or kvalue_accuracy as appropriate. However, you may change the spacing with table_spacing. Using table_spacing < 1 will use a spacing value that much smaller than the default, which should produce more accurate interpolations.

  • +
  • realspace_relerr – The target relative accuracy for real-space convolution.

  • +
  • realspace_abserr – The target absolute accuracy for real-space convolution.

  • +
  • integration_relerr – Target relative accuracy for integrals (other than real-space convolution).

  • +
  • integration_abserr – Target absolute accuracy for integrals (other than real-space convolution).

  • +
  • shoot_accuracy – Accuracy of total flux for photon shooting. The photon shooting algorithm sometimes needs to sample the radial profile out to some value. We choose the outer radius such that the integral encloses at least (1-shoot_accuracy) of the flux.

  • +
+
+
+
+ +
+
+inline GSParams()
+

A reasonable set of default values

+
+ +
+
+bool operator==(const GSParams &rhs) const
+
+ +
+
+bool operator<(const GSParams &rhs) const
+
+ +
+
+

Public Members

+
+
+int minimum_fft_size
+
+ +
+
+int maximum_fft_size
+
+ +
+
+double folding_threshold
+
+ +
+
+double stepk_minimum_hlr
+
+ +
+
+double maxk_threshold
+
+ +
+
+double kvalue_accuracy
+
+ +
+
+double xvalue_accuracy
+
+ +
+
+double table_spacing
+
+ +
+
+double realspace_relerr
+
+ +
+
+double realspace_abserr
+
+ +
+
+double integration_relerr
+
+ +
+
+double integration_abserr
+
+ +
+
+double shoot_accuracy
+
+ +
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/dcr.html b/docs/_build/html/dcr.html new file mode 100644 index 00000000000..77b0df201e9 --- /dev/null +++ b/docs/_build/html/dcr.html @@ -0,0 +1,251 @@ + + + + + + + Differential Chromatic Refraction — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Differential Chromatic Refraction

+

These utilities are used for our various classes and functions that implement differential +chromatic refraction (DCR).

+
+
+galsim.dcr.air_refractive_index_minus_one(wave, pressure=69.328, temperature=293.15, H2O_pressure=1.067)[source]
+

Return the refractive index of air as function of wavelength.

+

Uses the formulae given in Filippenko (1982), which appear to come from Edlen (1953), +and Coleman, Bozman, and Meggers (1960). The units of the original formula are non-SI, +being mmHg for pressure (and water vapor pressure), and degrees C for temperature. This +function accepts SI units, however, and transforms them when plugging into the formula.

+

The default values for temperature, pressure and water vapor pressure are expected to be +appropriate for LSST at Cerro Pachon, Chile, but they are broadly reasonable for most +observatories.

+
+
Parameters:
+
    +
  • wave – Wavelength array in nanometers

  • +
  • pressure – Air pressure in kiloPascals.

  • +
  • temperature – Temperature in Kelvins.

  • +
  • H2O_pressure – Water vapor pressure in kiloPascals.

  • +
+
+
Returns:
+

the refractive index minus 1.

+
+
+
+ +
+
+galsim.dcr.get_refraction(wave, zenith_angle, **kwargs)[source]
+

Compute the angle of refraction for a photon entering the atmosphere.

+

Photons refract when transitioning from space, where the refractive index n = 1.0 exactly, to +air, where the refractive index is slightly greater than 1.0. This function computes the +change in zenith angle for a photon with a given wavelength. Output is a positive number of +radians, even though the apparent zenith angle technically decreases due to this effect.

+
+
Parameters:
+
    +
  • wave – Wavelength array in nanometers

  • +
  • zenith_angleAngle from object to zenith

  • +
  • **kwargs – Keyword arguments to pass to air_refractive_index() to override default +pressure, temperature, and/or H2O_pressure.

  • +
+
+
Returns:
+

the absolute value of change in zenith angle in radians.

+
+
+
+ +
+
+galsim.dcr.zenith_parallactic_angles(obj_coord, zenith_coord=None, HA=None, latitude=None)[source]
+

Compute the zenith angle and parallactic angle of a celestial coordinate, given either +the celestial coordinate of the zenith, or equivalently, the hour angle of the coordinate and +the latitude of the observer. This is useful for the function ChromaticAtmosphere.

+
+
Parameters:
+
    +
  • obj_coord – A CelestialCoord object for which the zenith and parallactic +angles will be computed.

  • +
  • zenith_coord – A CelestialCoord indicating the coordinates of the zenith.

  • +
  • HA – The hour angle (as an Angle) of the coordinate for which the +zenith and parallactic angles will be computed.

  • +
  • latitude – The observer’s latitude, as an Angle.

  • +
+
+
Returns:
+

the tuple (zenith_angle, parallactic_angle), each of which is an Angle.

+
+
+
+ +
+
+galsim.dcr.parse_dcr_angles(**kwargs)[source]
+

Parse the various options for specifying the zenith angle and parallactic angle +in ChromaticAtmosphere.

+
+
Parameters:
+
    +
  • zenith_angleAngle from object to zenith [default: 0]

  • +
  • parallactic_angle – Parallactic angle, i.e. the position angle of the zenith, measured +from North through East. [default: 0]

  • +
  • obj_coord – Celestial coordinates of the object being drawn as a +CelestialCoord. [default: None]

  • +
  • zenith_coord – Celestial coordinates of the zenith as a CelestialCoord. +[default: None]

  • +
  • HA – Hour angle of the object as an Angle. [default: None]

  • +
  • latitude – Latitude of the observer as an Angle. [default: None]

  • +
  • **kwargs – For convenience, any other kwargs are returned back for further +processing.

  • +
+
+
Returns:
+

zenith_angle, parallactic_angle, kw, where kw is any other kwargs that aren’t relevant.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/des.html b/docs/_build/html/des.html new file mode 100644 index 00000000000..52cae1a0a46 --- /dev/null +++ b/docs/_build/html/des.html @@ -0,0 +1,536 @@ + + + + + + + The DES Module — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The DES Module

+

The galsim.des module contains some functionality specific developed for the use of GalSim in +simulations of the Dark Energy Survey. However, both PSFEx and MEDS files are used for other +surveys besides DES, so both DES_PSFEx and MEDSBuilder may be relevant to users outside of +DES.

+
+

Note

+

To use this module, you must separately import galsim.des. These functions are +not automatically imported when you import galsim.

+
+
+

DES PSF models

+
+
+class galsim.des.DES_PSFEx(file_name, image_file_name=None, wcs=None, dir=None)[source]
+

Bases: object

+

Class that handles DES files describing interpolated principal component images +of the PSF. These are usually stored as *_psfcat.psf files.

+

PSFEx is software written by Emmanuel Bertin. If you want more detail about PSFEx, please +check out the web site:

+

http://www.astromatic.net/software/psfex

+

It builds PSF objects from images of stars in a given exposure, finds a reasonable basis +set to describe those images, and then fits the coefficient of these bases as a function +of the (x,y) position on the image.

+

Note that while the interpolation is done in image coordinates, GalSim usually deals with +object profiles in world coordinates. However, PSFEx does not consider the WCS of the +image when building its bases. The bases are built in image coordinates. So there are +two options to get GalSim to handle this difference.

+
    +
  1. Ignore the WCS of the original image. In this case, the *.psf files have all the +information you need:

    +
    >>> des_psfex = galsim.des.DES_PSFEx(fitpsf_file_name)
    +>>> image_pos = galsim.PositionD(image_x, image_y)    # position in pixels on the image
    +>>>                                                   # NOT in arcsec on the sky!
    +>>> psf = des_psfex.getPSF(image_pos)      # profile is in image coordinates
    +
    +
    +

    The psf profile that is returned will be in image coordinates. Therefore, it should be +drawn onto an image with no wcs. (Or equivalently, one with scale = 1.) If you want +to use this to convolve a galaxy profile, you would want to either project the galaxy +(typically constructed in world coordinates) to the correct image coordinates or project +the PSF up into world coordinates.

    +
  2. +
  3. Build the PSF in world coordinates directly. The DES_PSFEx constructor can take an +extra argument, either image_file_name or wcs, to tell GalSim what WCS to use for +the coversion between image and world coordinates. The former option is the name of +the file from which to read the WCS, which will often be more convenient, but you can +also just pass in a WCS object directly.

    +
    >>> des_psfex = galsim.des.DES_PSFEx(fitpsf_file_name, image_file_name)
    +>>> image_pos = galsim.PositionD(image_x, image_y)    # position in pixels on the image
    +>>>                                                   # NOT in arcsec on the sky!
    +>>> psf = des_psfex.getPSF(image_pos)      # profile is in world coordinates
    +
    +
    +

    This time the psf profile that is returned will already be in world coordinates as +GalSim normally expects, so you can use it in the normal ways. If you want to draw it +(or a convolved object) onto an image with the original WCS at that location, you can use +des_psfex.getLocalWCS(image_pos) for the local wcs at the location of the PSF.

    +
  4. +
+

Note that the returned psf here already includes the pixel. This is what is sometimes +called an “effective PSF”. Thus, you should not convolve by the pixel profile again +(nor integrate over the pixel). This would effectively include the pixel twice!

+

In GalSim, you should always pass method='no_pixel' when drawing images of objects +convolved with PSFs produced with this class. Other drawing methods, such as photon shooting +(method='phot') or an FFT (method='fft'), will result in convolving the pixel twice.

+
+
Parameters:
+
    +
  • file_name – The file name to be read in, or a pyfits HDU in which case it is +used directly instead of being opened.

  • +
  • image_file_name – The name of the fits file of the original image (needed for the +WCS information in the header). If unavailable, you may omit this +(or use None), but then the returned profiles will be in image +coordinates, not world coordinates. [default: None]

  • +
  • wcs – Optional way to provide the WCS if you already have it loaded from +the image file. [default: None]

  • +
  • dir – Optionally a directory name can be provided if the file_name +does not already include it. (The image file is assumed to be in +the same directory.) [default: None].

  • +
+
+
+
+
+getLocalWCS(image_pos)[source]
+

If the original image was provided to the constructor, this will return the local +WCS at a given location in that original image. If not, this will return None.

+
+
Parameter:

image_pos (Position): The position in pixels in the image.

+
+
+
+
Returns:
+

A LocalWCS or None

+
+
+
+ +
+
+getPSF(image_pos, gsparams=None)[source]
+

Returns the PSF at position image_pos.

+
+
Parameters:
+
    +
  • image_pos – The position in image coordinates at which to build the PSF.

  • +
  • gsparams – A GSParams instance to pass to the constructed GSObject. +[defualt: None]

  • +
+
+
Returns:
+

the PSF as a GSObject

+
+
+
+ +
+
+getPSFArray(image_pos)[source]
+

Returns the PSF image as a numpy array at position image_pos in image coordinates.

+
+ +
+ +
+
+class galsim.des.DES_Shapelet(file_name, dir=None)[source]
+

Bases: object

+

Class that handles DES files describing interpolated polar shapelet decompositions. +These are stored as *_fitpsf.fits files. They are not used in DES anymore, so this +class is at best of historical interest

+

The shapelet PSFs measure a shapelet decomposition of each star and interpolate the shapelet +coefficients over the image positions.

+

Unlike PSFEx, these PSF models are built directly in world coordinates. The shapelets know +about the WCS, so they are able to fit the shapelet model directly in terms of arcsec. +Thus, the getPSF function always returns a profile in world coordinates.

+

Typical usage:

+
>>> des_shapelet = galsim.des.DES_Shapelet(fitpsf_file_name)
+>>> image_pos = galsim.PositionD(image_x, image_y)    # position in pixels on the image
+>>>                                                   # NOT in arcsec on the sky!
+>>> psf = des_shapelet.getPSF(image_pos)   # profile is in world coordinates
+
+
+

Note that the returned psf here already includes the pixel. This is what is sometimes +called an “effective PSF”. Thus, you should not convolve by the pixel profile again +(nor integrate over the pixel). This would effectively include the pixel twice!

+

This class will only interpolate within the defining bounds. It won’t extrapolate +beyond the bounding box of where the stars defined the interpolation. +If you try to use it with an invalid position, it will throw an IndexError. +You can check whether a position is valid with

+
>>> if des_shapelet.bounds.includes(pos):
+>>>     psf = des_shapelet.getPSF(pos)
+>>> else:
+>>>     [...skip this object...]
+
+
+
+
Parameters:
+
    +
  • file_name – The name of the file to be read in.

  • +
  • dir – Optionally a directory name can be provided if the file names do not +already include it. [default: None]

  • +
+
+
+
+
+getB(pos)[source]
+

Get the B vector as a numpy array at position pos

+
+ +
+
+getPSF(image_pos, gsparams=None)[source]
+

Returns the PSF at position image_pos

+
+
Parameters:
+
    +
  • image_pos – The position in pixel units for which to build the PSF.

  • +
  • gsparams – An optional GSParams instance to pass to the constructed GSObject. +[default: None]

  • +
+
+
Returns:
+

the PSF as a galsim.Shapelet instance

+
+
+
+ +
+
+read_fits()[source]
+

Read in a DES_Shapelet stored in FITS file.

+
+ +
+ +
+
+

Writing to MEDS Files

+

This module defines the MultiExposureObject class for representing multiple exposure data for a single object. The WriteMEDS function can be used to write a list of MultiExposureObject instances to a single MEDS file.

+

Importing this module also adds these data structures to the config framework, so that MEDS file output can subsequently be simulated directly using a config file.

+
+
+class galsim.des.MultiExposureObject(images, weight=None, badpix=None, seg=None, psf=None, wcs=None, id=0, cutout_row=None, cutout_col=None)[source]
+

A class containing exposures for single object, along with other information.

+

The MultiExposureObject class represents multiple exposure data for a single object for the +purpose of writing the images to a MEDS file.

+

The WriteMEDS function can be used to write a list of MultiExposureObjects to a MEDS file.

+
+
Parameters:
+
    +
  • images – List of images of the object.

  • +
  • weight – List of weight images. [default: None]

  • +
  • badpix – List of bad pixel masks. [default: None]

  • +
  • seg – List of segmentation maps. [default: None]

  • +
  • psf – List of psf images. [default: None]

  • +
  • wcs – List of WCS transformations. [default: None]

  • +
  • id – Galaxy id. [default: 0]

  • +
+
+
Attributes:
+
    +
  • self.images – List of images of the object.

  • +
  • self.weight – List of weight maps.

  • +
  • self.seg – List of segmentation maps.

  • +
  • self.psf – List of psf images.

  • +
  • self.wcs – List of WCS transformations.

  • +
  • self.n_cutouts – Number of exposures.

  • +
  • self.box_size – Size of each exposure image.

  • +
+
+
+
+ +
+
+class galsim.des.MEDSBuilder[source]
+

Bases: OutputBuilder

+

This class lets you use the MultiExposureObject very simply as a special output +type when using config processing.

+

It requires the use of galsim.des in the config files modules section.

+
+
+buildImages(config, base, file_num, image_num, obj_num, ignore, logger)[source]
+

Build a meds file as specified in config.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • file_num – The current file_num.

  • +
  • image_num – The current image_num.

  • +
  • obj_num – The current obj_num.

  • +
  • ignore – A list of parameters that are allowed to be in config that we can ignore +here.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

A list of MultiExposureObjects.

+
+
+
+ +
+
+getNImages(config, base, file_num, logger=None)[source]
+

Returns the number of images to be built for a given file_num.

+

In the base class, we only build a single image, so it returns 1.

+
+
Parameters:
+
    +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • file_num – The current file number.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
Returns:
+

the number of images to build.

+
+
+
+ +
+
+writeFile(data, file_name, config, base, logger)[source]
+

Write the data to a file. In this case a MEDS file.

+
+
Parameters:
+
    +
  • data – The data to write. Here, this is a list of MultiExposureObject.

  • +
  • file_name – The file_name to write to.

  • +
  • config – The configuration dict for the output field.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress.

  • +
+
+
+
+ +
+ +
+
+class galsim.des.OffsetBuilder[source]
+

Bases: ExtraOutputBuilder

+

This “extra” output builder saves the stamp offset values for later use.

+

It is used as a meds_get_offset field in the output section.

+
+
+finalize(config, base, main_data, logger)[source]
+

Perform any final processing at the end of all the image processing.

+

This function will be called after all images have been built.

+

It returns some sort of final version of the object. In the base class, it just returns +self.data, but depending on the meaning of the output object, something else might be +more appropriate.

+
+
Parameters:
+
    +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • main_data – The main file data in case it is needed.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
Returns:
+

The final version of the object.

+
+
+
+ +
+
+processStamp(obj_num, config, base, logger)[source]
+

Perform any necessary processing at the end of each stamp construction.

+

This function will be called after each stamp is built, but before the noise is added, +so the existing stamp image has the true surface brightness profile (unless photon shooting +was used, in which case there will necessarily be noise from that process).

+

Remember, these stamps may be processed out of order. Saving data to the scratch dict +is safe, even if multiprocessing is being used.

+
+
Parameters:
+
    +
  • obj_num – The object number

  • +
  • config – The configuration field for this output object.

  • +
  • base – The base configuration dict.

  • +
  • logger – If given, a logger object to log progress. [default: None]

  • +
+
+
+
+ +
+ +
+
+galsim.des.WriteMEDS(obj_list, file_name, clobber=True)[source]
+

Writes a MEDS file from a list of MultiExposureObject instances.

+
+
Parameters:
+
    +
  • obj_list – List of MultiExposureObject instances

  • +
  • file_name – Name of meds file to be written

  • +
  • clobber – Setting clobber=True when file_name is given will silently overwrite +existing files. [default True]

  • +
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/deviate.html b/docs/_build/html/deviate.html new file mode 100644 index 00000000000..12a212ff1d2 --- /dev/null +++ b/docs/_build/html/deviate.html @@ -0,0 +1,931 @@ + + + + + + + Random Deviates — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Random Deviates

+

GalSim can produce random values according to a variety of probability distributions:

+
    +
  • UniformDeviate implements \(p(x) = 1\) for \(0 \le x < 1\).

  • +
  • GaussianDeviate implements \(p(x) = \frac{1}{\sqrt{2\pi\sigma}} e^{-\frac{(x-\mu)^2}{2\sigma^2}}\).

  • +
  • PoissonDeviate implements \(p(x) = \frac{e^{-\mu}\mu^x}{x!}\) for integer \(x > 0\).

  • +
  • BinomialDeviate implements \(p(x) = {N \choose x}p^k(1-p)^{N-x}\) for integer \(0 \le x \le N\).

  • +
  • Chi2Deviate implements \(p(x) = \frac{x^{(n/2)-1}e^{-x/2}}{\Gamma(n/2)2^{n/2}}\) for \(x > 0\).

  • +
  • GammaDeviate implements \(p(x) = x^{k-1}\frac{e^{-x/\theta}}{\theta^k\Gamma(k)}\) for \(x > 0\).

  • +
  • WeibullDeviate implements \(p(x) = \frac{a}{b}\left(\frac{x}{b}\right)^{a-1}e^{-\left(\frac{x}{b}\right)^a}\) for \(x \ge 0\).

  • +
  • DistDeviate implements any arbitrary, user-supplied \(p(x)\).

  • +
+

These are all subclasses of the base class BaseDeviate, which implements the underlying +pseudo-random number generator using the Boost libraries Mersenne twister.

+

We have fixed the implementation of this to Boost version 1.48.0, the relevant files of which are +bundled with the GalSim distribution, so that random numbers produced by GalSim simulations are +deterministic across different user platforms and operating systems. These Boost files are +included with GalSim, so the user does not need to have Boost installed on their system.

+

There are ways to connect various different deviate objects to use the same underlying +BaseDeviate, which is often important for producing deterministic simulations given a particular +random number seed. See the docstring of BaseDeviate for details.

+
+

Note

+

We have put some care into the way we seed the random number generator such that it is +safe to start several random number sequences seeded by sequential seeds. This is already +supposed to be the case for the Boost Mersenne Twister implementation, but we add some extra +(probably overly paranoid) steps to ensure this by seeding one pseudo-rng, skip a few values, +and then use that to seed the actual pseudo-rng that we will use.

+

This means you can start the rngs for sequential images or even galaxies with sequential seed +values and there will not be any measurable correlations in the results. This can greatly +ease the ability to split work across multiple processes and still achieve deterministic +results.

+
+
+
+class galsim.BaseDeviate(seed=None)[source]
+

Base class for all the various random deviates.

+

This holds the essential random number generator that all the other classes use.

+

All deviates take an initial seed argument that is used to seed the underlying random number +generator. It has three different kinds of behavior.

+
    +
  1. An integer value can be provided to explicitly seed the random number generator with a +particular value. This is useful to have deterministic behavior. If you seed with an +integer value, the subsequent series of “random” values will be the same each time you +run the program.

  2. +
  3. A seed of 0 or None means to pick some arbitrary value that will be different each time +you run the program. Currently, this tries to get a seed from /dev/urandom if possible. +If that doesn’t work, then it creates a seed from the current time. You can also get this +behavior by omitting the seed argument entirely. (i.e. the default is None.)

  4. +
  5. Providing another BaseDeviate object as the seed will make the new BaseDeviate share the +same underlying random number generator as the other BaseDeviate. So you can make one +BaseDeviate (of any type), and seed it with a particular deterministic value. Then if you +pass that BaseDeviate to any other one you make, they will both be using the same RNG and +the series of “random” values will be deterministic.

  6. +
+

Usage:

+

The only kind of random number you can obtain from a pure BaseDeviate (i.e. one that is +not actually one of the variosu subclasses) is a “raw” value. This is an unsigned 32-bit +integer that behind the scenes is used by all sub-classes to generate floating point values +with various distributions.

+
>>> rng = galsim.BaseDeviate(215324)
+>>> rng.raw()
+3559052779
+
+
+

Most other usage is effected by creating sub-classes corresponding to particular random +deviates with various distributions. E.g. UniformDeviate generates random values following +a uniform distribution between 0 and 1.

+
>>> rng = galsim.BaseDeviate(215324)
+>>> ud = galsim.UniformDeviate(rng)
+>>> ud()
+0.58736140513792634
+>>> ud2 = galsim.UniformDeviate(215324)
+>>> ud2()
+0.58736140513792634
+
+
+
+
+_seed(seed=0)[source]
+

Equivalent to seed, but without any type checking.

+
+ +
+
+_reset(rng)[source]
+

Equivalent to reset, but rng must be a BaseDeviate (not an int), and there +is no type checking.

+
+ +
+
+add_generate(array)[source]
+

Generate many pseudo-random values, adding them to the values of a numpy array.

+
+ +
+
+as_numpy_generator()[source]
+

Return a numpy.random.Generator object that uses the current BaseDeviate for the +underlying bit generations.

+

This allows you to use the (probably) more familiar numpy functions, while maintaining +GalSim’s guarantees about random number stability across platforms.

+

Example:

+
>>> rng = galsim.BaseDeviate(1234)
+>>> gen = rng.as_numpy_generator()
+>>> uniform = gen.uniform(1, 10, size=10)
+>>> norm = gen.normal(0, 3, size=20)
+
+
+

There is also a shorthand syntax that may be convenient. +The property np is equivalent to this method, so you can also write:

+
>>> uniform = rng.np.uniform(1, 10, size=10)
+>>> norm = rng.np.normal(0, 3, size=20)
+
+
+
+ +
+
+clearCache()[source]
+

Clear the internal cache of the BaseDeviate, if any. This is currently only relevant +for GaussianDeviate, since it generates two values at a time, saving the second one to +use for the next output value.

+
+ +
+
+discard(n, suppress_warnings=False)[source]
+

Discard n values from the current sequence of pseudo-random numbers.

+

This is typically used to keep two random number generators in sync when one of them +is used to generate some random values. The other can quickly discard the same number +of random values to stay in sync with the first one.

+
+
Parameters:
+
    +
  • n – The number of raw random numbers to discard.

  • +
  • suppress_warnings – Whether to suppress warnings related to detecting when this +action is not likely to reliably keep two random number +generators in sync. [default: False]

  • +
+
+
+
+ +
+
+duplicate()[source]
+

Create a duplicate of the current BaseDeviate object.

+

The subsequent series from each copy of the BaseDeviate will produce identical values:

+
>>> u = galsim.UniformDeviate(31415926)
+>>> u()
+0.17100770119577646
+>>> u2 = u.duplicate()
+>>> u()
+0.49095047544687986
+>>> u()
+0.10306670609861612
+>>> u2()
+0.49095047544687986
+>>> u2()
+0.10306670609861612
+>>> u2()
+0.13129289541393518
+>>> u()
+0.13129289541393518
+
+
+
+ +
+
+generate(array)[source]
+

Generate many pseudo-random values, filling in the values of a numpy array.

+
+ +
+
+property generates_in_pairs
+

Indicates whether the generator uses 2 rngs values per 2 returned values.

+

GaussianDeviate has a slight wrinkle to the BaseDeviate.has_reliable_discard story. +It always uses two rng values to generate two Gaussian deviates. So if the number of +generated values is even, then discard can keep things in sync. However, if an odd +number of values are generated, then you to generate one more value (and discard it) +to keep things synced up.

+

This is only True for GaussianDeviate.

+
+ +
+
+property has_reliable_discard
+

Indicates whether the generator always uses 1 rng per value.

+

If it does, then discard can reliably be used to keep two generators in sync when one +of them generated some values and the other didn’t.

+

This is False for PoissonDeviate, Chi2Deviate, and GammaDeviate.

+

See also BaseDeviate.generates_in_pairs.

+
+ +
+
+property np
+

Shorthand for self.as_numpy_generator()

+
+ +
+
+raw()[source]
+

Generate the next pseudo-random number and rather than return the appropriate kind +of random deviate for this class, just return the raw integer value that would have been +used to generate this value.

+
+ +
+
+reset(seed=None)[source]
+

Reset the pseudo-random number generator, severing connections to any other deviates. +Providing another BaseDeviate object as the seed connects this deviate with the other +one, so they will both use the same underlying random number generator.

+
+
Parameters:
+

seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using None means to generate a seed from the system. +[default: None]

+
+
+
+ +
+
+seed(seed=0)[source]
+

Seed the pseudo-random number generator with a given integer value.

+
+
Parameters:
+

seed – An int value to be used to seed the random number generator. Using 0 +means to generate a seed from the system. [default: 0]

+
+
+
+ +
+ +
+
+class galsim.UniformDeviate(seed=None)[source]
+

Bases: BaseDeviate

+

Pseudo-random number generator with uniform distribution in interval [0.,1.).

+

Successive calls to u() generate pseudo-random values distributed uniformly in the interval +[0., 1.):

+
>>> u = galsim.UniformDeviate(31415926)
+>>> u()
+0.17100770119577646
+>>> u()
+0.49095047544687986
+
+
+
+
Parameters:
+

seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a uniform deviate between 0 and 1.

+
+ +
+ +
+
+class galsim.GaussianDeviate(seed=None, mean=0.0, sigma=1.0)[source]
+

Bases: BaseDeviate

+

Pseudo-random number generator with Gaussian distribution.

+

See http://en.wikipedia.org/wiki/Gaussian_distribution for further details.

+

Successive calls to g() generate pseudo-random values distributed according to a Gaussian +distribution with the provided mean, sigma:

+
>>> g = galsim.GaussianDeviate(31415926)
+>>> g()
+0.5533754000847082
+>>> g()
+1.0218588970190354
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • mean – Mean of Gaussian distribution. [default: 0.]

  • +
  • sigma – Sigma of Gaussian distribution. [default: 1.; Must be > 0]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a Gaussian deviate with the given mean and sigma.

+
+ +
+
+generate_from_variance(array)[source]
+

Generate many Gaussian deviate values using the existing array values as the +variance for each.

+
+ +
+
+property generates_in_pairs
+

Indicates whether the generator uses 2 rngs values per 2 returned values.

+

GaussianDeviate has a slight wrinkle to the BaseDeviate.has_reliable_discard story. +It always uses two rng values to generate two Gaussian deviates. So if the number of +generated values is even, then discard can keep things in sync. However, if an odd +number of values are generated, then you to generate one more value (and discard it) +to keep things synced up.

+

This is only True for GaussianDeviate.

+
+ +
+
+property mean
+

The mean of the Gaussian distribution.

+
+ +
+
+property sigma
+

The sigma of the Gaussian distribution.

+
+ +
+ +
+
+class galsim.PoissonDeviate(seed=None, mean=1.0)[source]
+

Bases: BaseDeviate

+

Pseudo-random Poisson deviate with specified mean.

+

The input mean sets the mean and variance of the Poisson deviate. An integer deviate with +this distribution is returned after each call. +See http://en.wikipedia.org/wiki/Poisson_distribution for more details.

+

Successive calls to p() generate pseudo-random integer values distributed according to a +Poisson distribution with the specified mean:

+
>>> p = galsim.PoissonDeviate(31415926, mean=100)
+>>> p()
+94
+>>> p()
+106
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • mean – Mean of the distribution. [default: 1; Must be > 0]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a Poisson deviate with the given mean.

+
+ +
+
+generate_from_expectation(array)[source]
+

Generate many Poisson deviate values using the existing array values as the +expectation value (aka mean) for each.

+
+ +
+
+property has_reliable_discard
+

Indicates whether the generator always uses 1 rng per value.

+

If it does, then discard can reliably be used to keep two generators in sync when one +of them generated some values and the other didn’t.

+

This is False for PoissonDeviate, Chi2Deviate, and GammaDeviate.

+

See also BaseDeviate.generates_in_pairs.

+
+ +
+
+property mean
+

The mean of the distribution.

+
+ +
+ +
+
+class galsim.BinomialDeviate(seed=None, N=1, p=0.5)[source]
+

Bases: BaseDeviate

+

Pseudo-random Binomial deviate for N trials each of probability p.

+

N is number of ‘coin flips,’ p is probability of ‘heads,’ and each call returns an +integer value where 0 <= value <= N gives the number of heads. See +http://en.wikipedia.org/wiki/Binomial_distribution for more information.

+

Successive calls to b() generate pseudo-random integer values distributed according to a +binomial distribution with the provided N, p:

+
>>> b = galsim.BinomialDeviate(31415926, N=10, p=0.3)
+>>> b()
+2
+>>> b()
+3
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • N – The number of ‘coin flips’ per trial. [default: 1; Must be > 0]

  • +
  • p – The probability of success per coin flip. [default: 0.5; Must be > 0]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a Binomial deviate with the given n and p.

+
+ +
+
+property n
+

The number of ‘coin flips’.

+
+ +
+
+property p
+

The probability of success per ‘coin flip’.

+
+ +
+ +
+
+class galsim.Chi2Deviate(seed=None, n=1.0)[source]
+

Bases: BaseDeviate

+

Pseudo-random Chi^2-distributed deviate for degrees-of-freedom parameter n.

+

See http://en.wikipedia.org/wiki/Chi-squared_distribution (note that k=n in the notation +adopted in the Boost.Random routine called by this class). The Chi^2 distribution is a +real-valued distribution producing deviates >= 0.

+

Successive calls to chi2() generate pseudo-random values distributed according to a +chi-square distribution with the specified degrees of freedom, n:

+
>>> chi2 = galsim.Chi2Deviate(31415926, n=7)
+>>> chi2()
+7.9182211987712385
+>>> chi2()
+6.644121724269535
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • n – Number of degrees of freedom for the output distribution. [default: 1; +Must be > 0]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a Chi2-distributed deviate with the given number of degrees of freedom.

+
+ +
+
+property has_reliable_discard
+

Indicates whether the generator always uses 1 rng per value.

+

If it does, then discard can reliably be used to keep two generators in sync when one +of them generated some values and the other didn’t.

+

This is False for PoissonDeviate, Chi2Deviate, and GammaDeviate.

+

See also BaseDeviate.generates_in_pairs.

+
+ +
+
+property n
+

The number of degrees of freedom.

+
+ +
+ +
+
+class galsim.GammaDeviate(seed=None, k=1.0, theta=1.0)[source]
+

Bases: BaseDeviate

+

A Gamma-distributed deviate with shape parameter k and scale parameter theta. +See http://en.wikipedia.org/wiki/Gamma_distribution. +(Note: we use the k, theta notation. If you prefer alpha, beta, use k=alpha, theta=1/beta.) +The Gamma distribution is a real valued distribution producing deviates >= 0.

+

Successive calls to g() generate pseudo-random values distributed according to a gamma +distribution with the specified shape and scale parameters k and theta:

+
>>> gam = galsim.GammaDeviate(31415926, k=1, theta=2)
+>>> gam()
+0.37508882726316
+>>> gam()
+1.3504199388358704
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • k – Shape parameter of the distribution. [default: 1; Must be > 0]

  • +
  • theta – Scale parameter of the distribution. [default: 1; Must be > 0]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a Gamma-distributed deviate with the given k and theta.

+
+ +
+
+property has_reliable_discard
+

Indicates whether the generator always uses 1 rng per value.

+

If it does, then discard can reliably be used to keep two generators in sync when one +of them generated some values and the other didn’t.

+

This is False for PoissonDeviate, Chi2Deviate, and GammaDeviate.

+

See also BaseDeviate.generates_in_pairs.

+
+ +
+
+property k
+

The shape parameter, k.

+
+ +
+
+property theta
+

The scale parameter, theta.

+
+ +
+ +
+
+class galsim.WeibullDeviate(seed=None, a=1.0, b=1.0)[source]
+

Bases: BaseDeviate

+

Pseudo-random Weibull-distributed deviate for shape parameter a and scale parameter b.

+

The Weibull distribution is related to a number of other probability distributions; in +particular, it interpolates between the exponential distribution (a=1) and the Rayleigh +distribution (a=2). +See http://en.wikipedia.org/wiki/Weibull_distribution (a=k and b=lambda in the notation adopted +in the Wikipedia article) for more details. The Weibull distribution is real valued and +produces deviates >= 0.

+

Successive calls to w() generate pseudo-random values distributed according to a Weibull +distribution with the specified shape and scale parameters a and b:

+
>>> w = galsim.WeibullDeviate(31415926, a=1.3, b=4)
+>>> w()
+1.1038481241018219
+>>> w()
+2.957052966368049
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • a – Shape parameter of the distribution. [default: 1; Must be > 0]

  • +
  • b – Scale parameter of the distribution. [default: 1; Must be > 0]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+

Returns a Weibull-distributed deviate with the given shape parameters a and b.

+
+ +
+
+property a
+

The shape parameter, a.

+
+ +
+
+property b
+

The scale parameter, b.

+
+ +
+ +
+
+class galsim.DistDeviate(seed=None, function=None, x_min=None, x_max=None, interpolant=None, npoints=None, clip_neg=False)[source]
+

Bases: BaseDeviate

+

A class to draw random numbers from a user-defined probability distribution.

+

DistDeviate is a BaseDeviate class that can be used to draw from an arbitrary probability +distribution. The probability distribution passed to DistDeviate can be given one of three +ways: as the name of a file containing a 2d ASCII array of x and P(x), as a LookupTable +mapping x to P(x), or as a callable function.

+

Once given a probability, DistDeviate creates a table of the cumulative probability and draws +from it using a UniformDeviate. The precision of its outputs can be controlled with the +keyword npoints, which sets the number of points DistDeviate creates for its internal table +of CDF(x). To prevent errors due to non-monotonicity, the interpolant for this internal table +is always linear.

+

Two keywords, x_min and x_max, define the support of the function. They must be passed +if a callable function is given to DistDeviate, unless the function is a LookupTable, which +has its own defined endpoints. If a filename or LookupTable is passed to DistDeviate, the +use of x_min or x_max will result in an error.

+

If given a table in a file, DistDeviate will construct an interpolated LookupTable to obtain +more finely gridded probabilities for generating the cumulative probability table. The default +interpolant is linear, but any interpolant understood by LookupTable may be used. We +caution against the use of splines because they can cause non-monotonic behavior. Passing the +interpolant keyword next to anything but a table in a file will result in an error.

+

Examples:

+

Some sample initialization calls:

+
>>> d = galsim.DistDeviate(function=f, x_min=x_min, x_max=x_max)
+
+
+

Initializes d to be a DistDeviate instance with a distribution given by the callable function +f(x) from x=x_min to x=x_max and seeds the PRNG using current time:

+
>>> d = galsim.DistDeviate(1062533, function=file_name, interpolant='floor')
+
+
+

Initializes d to be a DistDeviate instance with a distribution given by the data in file +file_name, which must be a 2-column ASCII table, and seeds the PRNG using the integer +seed 1062533. It generates probabilities from file_name using the interpolant ‘floor’:

+
>>> d = galsim.DistDeviate(rng, function=galsim.LookupTable(x,p))
+
+
+

Initializes d to be a DistDeviate instance with a distribution given by P(x), defined as two +arrays x and p which are used to make a callable LookupTable, and links the +DistDeviate PRNG to the already-existing random number generator rng.

+

Successive calls to d() generate pseudo-random values with the given probability +distribution:

+
>>> d = galsim.DistDeviate(31415926, function=lambda x: 1-abs(x), x_min=-1, x_max=1)
+>>> d()
+-0.4151921102709466
+>>> d()
+-0.00909781188974034
+
+
+
+
Parameters:
+
    +
  • seed – Something that can seed a BaseDeviate: an integer seed or another +BaseDeviate. Using 0 means to generate a seed from the system. +[default: None]

  • +
  • function – A callable function giving a probability distribution or the name of a +file containing a probability distribution as a 2-column ASCII table. +[required]

  • +
  • x_min – The minimum desired return value (required for non-LookupTable +callable functions; will raise an error if not passed in that case, or if +passed in any other case) [default: None]

  • +
  • x_max – The maximum desired return value (required for non-LookupTable +callable functions; will raise an error if not passed in that case, or if +passed in any other case) [default: None]

  • +
  • interpolant – Type of interpolation used for interpolating a file (causes an error if +passed alongside a callable function). Options are given in the +documentation for LookupTable. [default: ‘linear’]

  • +
  • npoints – Number of points DistDeviate should create for its internal interpolation +tables. [default: 256, unless the function is a non-log LookupTable, in +which case it uses the table’s x values]

  • +
  • clip_neg – Clip any negative input values to zero. [default: False; an error will +be raised if any negative probabilities are found.]

  • +
+
+
+
+
+__call__()[source]
+

Draw a new random number from the distribution.

+
+ +
+
+add_generate(array)[source]
+

Generate many pseudo-random values, adding them to the values of a numpy array.

+
+ +
+
+generate(array)[source]
+

Generate many pseudo-random values, filling in the values of a numpy array.

+
+ +
+
+val(p)[source]
+

Return the value \(x\) of the input function to DistDeviate such that p = +\(F(x)\), where \(F\) is the cumulattive probability distribution function:

+
+\[F(x) = \int_{-\infty}^x \mathrm{pdf}(t) dt\]
+

This function is typically called by __call__, which generates a random p +between 0 and 1 and calls self.val(p).

+
+
Parameters:
+

p – The desired cumulative probabilty p.

+
+
Returns:
+

the corresponding x such that \(p = F(x)\).

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/errors.html b/docs/_build/html/errors.html new file mode 100644 index 00000000000..354944947ed --- /dev/null +++ b/docs/_build/html/errors.html @@ -0,0 +1,407 @@ + + + + + + + Errors and Warnings — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Errors and Warnings

+

GalSim uses some custom Exception and Warning classes when it finds some exceptional +occurrence:

+
+
GalSimError

This is the base class for all other GalSim exceptions. So you can catch this +exception if you want to catch just the exceptions raised by GalSim. It is +roughly analogous to a RuntimeError.

+
+
GalSimValueError

This indicates that you provided an invalid value for an argument to a function. +It includes attributes that tell you about what value you provided and sometimes +about the allowed values.

+
+
GalSimKeyError

This indicates that you tried to access some dict-like object (e.g. FitsHeader +or Catalog) with an invalid key.

+
+
GalSimIndexError

This indicates that you tried to access some list-like object (e.g. RealGalaxyCatalog) +with an invalid index.

+
+
GalSimRangeError

This indicates that you provided a value that is outside of the allowed range. It +includes attributes indicating what value you provided and what the allowed range is.

+
+
GalSimBoundsError

This indicates that you used a Position outside of the allowed Bounds. It is +basically a GalSimRangeError, but in two dimensions. It includes attributes that +tell you the Position and the allowed Bounds.

+
+
GalSimUndefinedBoundsError

This indicates that you are trying to use an undefined Bounds instance in a context +where it must be defined.

+
+
GalSimImmutableError

This indicates that you tried to change an immutable Image in some way.

+
+
GalSimIncompatibleValuesError

This indicates that two or more values that you provided to some function are not +compatible with each other. It includes attributes telling you the two values that +are incompatible.

+
+
GalSimSEDError

This indicates that you tried to use an SED in a context where it is required to be +either spectral or dimensionless, and you provided the other kind.

+
+
GalSimHSMError

This indicates that the HSM algorithm raised some kind of exception.

+
+
GalSimFFTSizeError

This indicates that something you did requires a very large FFT, in particular one +that is larger than the relevant gsparams.maximum_fft_size parameter. It includes +attributes that tell you both the size that was required and how much memory it would +have used, so you can decide whether you want to adjust some parameters of your +simulation or to adjust the object’s GSParams options.

+
+
GalSimConfigError

This indicates that there was some kind of failure processing a configuration file.

+
+
GalSimConfigValueError

This indicates that some parameter in your configuration file is an invalid value.

+
+
GalSimNotImplementedError

This indicates that you tried to do something that is not implemented currently.

+
+
GalSimWarning

This indicates that you did something that is not necessarily an error, but we think +it is likely that you didn’t do something right.

+
+
GalSimDeprecationWarning

This indicates that you are using functionality that is currently deprecated. +Your code will generally continue to work until the next major upgrade, but you are +encouraged to update your code to the new syntax.

+
+
+
+
+class galsim.GalSimError[source]
+

The base class for GalSim-specific run-time errors.

+
+ +
+
+class galsim.GalSimValueError(message, value, allowed_values=None)[source]
+

Bases: GalSimError, ValueError

+

A GalSim-specific exception class indicating that some user-input value is invalid.

+
+
Attributes:
+
    +
  • value – The invalid value

  • +
  • allowed_values – A list of allowed values if appropriate (may be None)

  • +
+
+
+
+ +
+
+class galsim.GalSimKeyError(message, key)[source]
+

Bases: GalSimError, KeyError

+

A GalSim-specific exception class indicating an attempt to access a dict-like object +with an invalid key.

+
+
Attributes:
+

key – The invalid key

+
+
+
+ +
+
+class galsim.GalSimIndexError(message, index)[source]
+

Bases: GalSimError, IndexError

+

A GalSim-specific exception class indicating an attempt to access a list-like object +with an invalid index.

+
+
Attributes:
+

index – The invalid index

+
+
+
+ +
+
+class galsim.GalSimRangeError(message, value, min, max=None)[source]
+

Bases: GalSimError, ValueError

+

A GalSim-specific exception class indicating that some user-input value is +outside of the allowed range of values.

+
+
Attributes:
+
    +
  • value – The invalid value

  • +
  • min – The minimum allowed value (may be None)

  • +
  • max – The maximum allowed value (may be None)

  • +
+
+
+
+ +
+
+class galsim.GalSimBoundsError(message, pos, bounds)[source]
+

Bases: GalSimError, ValueError

+

A GalSim-specific exception class indicating that some user-input position is +outside of the allowed bounds.

+
+
Attributes:
+
    +
  • pos – The invalid position

  • +
  • bounds – The bounds in which it was expected to fall

  • +
+
+
+
+ +
+
+class galsim.GalSimUndefinedBoundsError[source]
+

Bases: GalSimError

+

A GalSim-specific exception class indicating an attempt to access the extent of +a Bounds instance that has not yet been defined.

+
+ +
+
+class galsim.GalSimImmutableError(message, image)[source]
+

Bases: GalSimError

+

A GalSim-specific exception class indicating an attempt to modify an immutable image.

+
+
Attributes:
+

image – The image that the user attempted to modify

+
+
+
+ +
+
+class galsim.GalSimIncompatibleValuesError(message, values={}, **kwargs)[source]
+

Bases: GalSimError, ValueError, TypeError

+

A GalSim-specific exception class indicating that 2 or more user-input values are +incompatible as given.

+
+
Attributes:
+

values – A dict of {name : value} giving the values that in combination are invalid.

+
+
+
+ +
+
+class galsim.GalSimSEDError(message, sed)[source]
+

Bases: GalSimError, TypeError

+

A GalSim-specific exception class indicating an attempt to do something invalid for the +kind of SED that is present. Typically involving a dimensionless SED where a spectral +SED is required (or vice versa).

+
+
Attributes:
+

sed – The invalid SED

+
+
+
+ +
+
+class galsim.GalSimHSMError[source]
+

Bases: GalSimError

+

A GalSim-specific exception class indicating some kind of failure of the HSM algorithms

+
+ +
+
+class galsim.GalSimFFTSizeError(message, size)[source]
+

Bases: GalSimError

+

A GalSim-specific exception class indicating that a requested FFT exceeds the relevant +maximum_fft_size.

+
+
Attributes:
+
    +
  • size – The size that was deemed too large

  • +
  • mem – The estimated memory that would be required (in GB) for the FFT.

  • +
+
+
+
+ +
+
+class galsim.GalSimConfigError[source]
+

Bases: GalSimError, ValueError

+

A GalSim-specific exception class indicating some kind of failure processing a +configuration file.

+
+ +
+
+class galsim.GalSimConfigValueError(message, value, allowed_values=None)[source]
+

Bases: GalSimValueError, GalSimConfigError

+

A GalSim-specific exception class indicating that a config entry has an invalid value.

+
+
Attributes:
+
    +
  • value – The invalid value

  • +
  • allowed_values – A list of allowed values if appropriate (may be None)

  • +
+
+
+
+ +
+
+class galsim.GalSimNotImplementedError[source]
+

Bases: GalSimError, NotImplementedError

+

A GalSim-specific exception class indicating that the feature being attempted is not +currently implemented.

+

If this is a feature you feel you need, please open an issue about it at

+
+
+

Even better, feel free to offer to contribute code to implement the feature.

+
+ +
+
+class galsim.GalSimWarning[source]
+

Bases: UserWarning

+

The base class for GalSim-emitted warnings.

+
+ +
+
+class galsim.GalSimDeprecationWarning[source]
+

Bases: GalSimWarning

+

A GalSim-specific warning class used for deprecation warnings.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/fft.html b/docs/_build/html/fft.html new file mode 100644 index 00000000000..1d506fe98af --- /dev/null +++ b/docs/_build/html/fft.html @@ -0,0 +1,361 @@ + + + + + + + Fourier Transforms — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Fourier Transforms

+

In the C++ layer we use FFTW for our 2D Fourier transforms. +This package is generally faster than numpy fft functions. So for at least a subset +of the functionality available in the numpy versions, we have implemented python functions +that call out to the backend C++ FFTW functions.

+

These should be drop in replacements for np.fft.* functions. e.g.:

+
>>> karray = galsim.fft.fft2(xarray)
+
+
+

is functionally equivalent to:

+
>>> karray = np.fft.fft2(xarray)
+
+
+

but should be a bit faster.

+
+

Note

+

The GalSim versions often only implement the normal use case without many of the +advanced options available with the numpy functions. This is mostly laziness on our part – +we only implemented the functions that we needed. If your usage requires some option available +in the numpy version, feel free to post a feature request on our GitHub page.

+
+
+
+galsim.fft.fft2(a, shift_in=False, shift_out=False)[source]
+

Compute the 2-dimensional discrete Fourier Transform.

+

For valid inputs, the result is equivalent to numpy.fft.fft2(a), but usually faster.:

+
>>> ka1 = numpy.fft.fft2(a)
+>>> ka2 = galsim.fft.fft2(a)
+
+
+

Restrictions on this version vs the numpy version:

+
+
    +
  • The input array must be 2-dimensional.

  • +
  • The size in each direction must be even. (Ideally 2^k or 3*2^k for speed, but this is +not required.)

  • +
  • If it has a real dtype, it will be coerced to numpy.float64.

  • +
  • If it has a complex dtype, it will be coerced to numpy.complex128.

  • +
+
+

The returned array will be complex with dtype numpy.complex128.

+

If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input.:

+
>>> ka1 = numpy.fft.fft2(numpy.fft.fftshift(a))
+>>> ka2 = galsim.fft.fft2(a, shift_in=True)
+
+
+

If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output.:

+
>>> ka1 = numpy.fft.fftshift(numpy.fft.fft2(a))
+>>> ka2 = galsim.fft.fft2(a, shift_out=True)
+
+
+
+
Parameters:
+
    +
  • a – The input array to be transformed

  • +
  • shift_in – Whether to shift the input array so that the center is moved to (0,0). +[default: False]

  • +
  • shift_out – Whether to shift the output array so that the center is moved to (0,0). +[default: False]

  • +
+
+
Returns:
+

a complex numpy array

+
+
+
+ +
+
+galsim.fft.ifft2(a, shift_in=False, shift_out=False)[source]
+

Compute the 2-dimensional inverse discrete Fourier Transform.

+

For valid inputs, the result is equivalent to numpy.fft.ifft2(a), but usually faster.:

+
>>> a1 = numpy.fft.ifft2(ka)
+>>> a2 = galsim.fft.ifft2(ka)
+
+
+

Restrictions on this version vs the numpy version:

+
+
    +
  • The array must be 2-dimensional.

  • +
  • The size in each direction must be even. (Ideally 2^k or 3*2^k for speed, but this is +not required.)

  • +
  • The array is assumed to be Hermitian, which means the k values with kx<0 are assumed +to be equal to the conjuate of their inverse. This will always be the case if +a is an output of fft2 (with a real input array). i.e.

    +
      +
    • for kx >= N/2, ky > 0: a[ky, kx] == a[N-ky, N-kx].conjugate()

    • +
    • for kx >= N/2, ky = 0: a[0, kx] == a[0, N-kx].conjugate()

    • +
    +

    Only the elements a[:,0:N/2+1] are accessed by this function.

    +
  • +
  • If it has a real dtype, it will be coerced to numpy.float64.

  • +
  • If it has a complex dtype, it will be coerced to numpy.complex128.

  • +
+
+

The returned array will be complex with dtype numpy.complex128.

+

If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input:

+
>>> a1 = numpy.fft.ifft2(numpy.fft.fftshift(ka))
+>>> a2 = galsim.fft.ifft2(ka, shift_in=True)
+
+
+

If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output:

+
>>> a1 = numpy.fft.fftshift(numpy.fft.ifft2(ka))
+>>> a2 = galsim.fft.ifft2(ka, shift_out=True)
+
+
+
+
Parameters:
+
    +
  • a – The input array to be transformed

  • +
  • shift_in – Whether to shift the input array so that the center is moved to (0,0). +[default: False]

  • +
  • shift_out – Whether to shift the output array so that the center is moved to (0,0). +[default: False]

  • +
+
+
Returns:
+

a complex numpy array

+
+
+
+ +
+
+galsim.fft.rfft2(a, shift_in=False, shift_out=False)[source]
+

Compute the one-dimensional discrete Fourier Transform for real input.

+

For valid inputs, the result is equivalent to numpy.fft.rfft2(a), but usually faster.:

+
>>> ka1 = numpy.fft.rfft2(a)
+>>> ka2 = galsim.fft.rfft2(a)
+
+
+

Restrictions on this version vs the numpy version:

+
+
    +
  • The input array must be 2-dimensional.

  • +
  • If it does not have dtype numpy.float64, it will be coerced to numpy.float64.

  • +
  • The size in each direction must be even. (Ideally 2^k or 3*2^k for speed, but this is +not required.)

  • +
+
+

The returned array will be complex with dtype numpy.complex128.

+

If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input.:

+
>>> ka1 = numpy.fft.rfft2(numpy.fft.fftshift(a))
+>>> ka2 = galsim.fft.rfft2(a, shift_in=True)
+
+
+

If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output.:

+
>>> ka1 = numpy.fft.fftshift(numpy.fft.rfft2(a),axes=(0,))
+>>> ka2 = galsim.fft.rfft2(a, shift_out=True)
+
+
+
+
Parameters:
+
    +
  • a – The input array to be transformed

  • +
  • shift_in – Whether to shift the input array so that the center is moved to (0,0). +[default: False]

  • +
  • shift_out – Whether to shift the output array so that the center is moved to (0,0). +[default: False]

  • +
+
+
Returns:
+

a complex numpy array

+
+
+
+ +
+
+galsim.fft.irfft2(a, shift_in=False, shift_out=False)[source]
+

Compute the 2-dimensional inverse FFT of a real array.

+

For valid inputs, the result is equivalent to numpy.fft.irfft2(a), but usually faster.:

+
>>> a1 = numpy.fft.irfft2(ka)
+>>> a2 = galsim.fft.irfft2(ka)
+
+
+

Restrictions on this version vs the numpy version:

+
+
    +
  • The array must be 2-dimensional.

  • +
  • If it does not have dtype numpy.complex128, it will be coerced to numpy.complex128.

  • +
  • It must have shape (M, N/2+1).

  • +
  • The size M must be even. (Ideally 2^k or 3*2^k for speed, but this is not required.)

  • +
+
+

The returned array will be real with dtype numpy.float64.

+

If shift_in is True, then this is equivalent to applying numpy.fft.fftshift to the input.:

+
>>> a1 = numpy.fft.irfft2(numpy.fft.fftshift(a, axes=(0,)))
+>>> a2 = galsim.fft.irfft2(a, shift_in=True)
+
+
+

If shift_out is True, then this is equivalent to applying numpy.fft.fftshift to the output.:

+
>>> a1 = numpy.fft.fftshift(numpy.fft.irfft2(a))
+>>> a2 = galsim.fft.irfft2(a, shift_out=True)
+
+
+
+
Parameters:
+
    +
  • a – The input array to be transformed

  • +
  • shift_in – Whether to shift the input array so that the center is moved to (0,0). +[default: False]

  • +
  • shift_out – Whether to shift the output array so that the center is moved to (0,0). +[default: False]

  • +
+
+
Returns:
+

a real numpy array

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/fits.html b/docs/_build/html/fits.html new file mode 100644 index 00000000000..825ffc57e43 --- /dev/null +++ b/docs/_build/html/fits.html @@ -0,0 +1,924 @@ + + + + + + + Interfacing with FITS Files — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Interfacing with FITS Files

+

As many astronomical images are stored as FITS files, GalSim includes functionality for +reading and writing these files with GalSim Image instances.

+

We include routines for reading and writing an individual Image to/from FITS files, and also +routines for handling multiple Image instances in a single FITS file.

+

We also have a wrapper around the FITS header information to make it work more like a Python +dict, called FitsHeader.

+
+

Note

+

These routines are largely wrappers of the astropy.io.fits package. They are still fairly +useful for connecting GalSim objects with the AstroPy API. However, they used to be critically +important for providing a stable API across different PyFITS and then AstroPy versions. +For instance, now the astropy.io.fits.Header API is very similar to our own FitsHeader, +but we used to have many checks for different PyFITS and AstroPy versions to call things in +different ways while maintaining an intuitive front-end user interface.

+
+
+

Reading FITS Files

+
+
+galsim.fits.read(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto', read_header=False, suppress_warning=True)[source]
+

Construct an Image from a FITS file or HDUList.

+

The normal usage for this function is to read a fits file and return the image contained +therein, automatically decompressing it if necessary. However, you may also pass it +an HDUList, in which case it will select the indicated hdu (with the hdu parameter) +from that.

+

Not all FITS pixel types are supported – only short, int, unsigned short, +unsigned int, float, and double.

+

If the FITS header has keywords that start with GS_, these will be used to initialize the +bounding box and WCS. If these are absent, the code will try to read whatever WCS is given +in the FITS header. cf. galsim.wcs.readFromFitsHeader. The default bounding box will have +(xmin,ymin) at (1,1). The default WCS, if there is no WCS information in the FITS +file, will be PixelScale(1.0).

+

This function is called as im = galsim.fits.read(...)

+
+
Parameters:
+
    +
  • file_name – The name of the file to read in. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – Either an astropy.io.fits.HDUList, astropy.io.fits.PrimaryHDU, or +astropy.io.fits..ImageHDU. In the former case, the hdu in the list +will be selected. In the latter two cases, the hdu parameter is +ignored. [Either file_name or hdu_list is required.]

  • +
  • hdu – The number of the HDU to return. [default: None, which means to return +either the primary or first extension as appropriate for the given +compression. (e.g. for ‘rice’, the first extension is the one you normally +want.)]

  • +
  • compression

    Which decompression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no decompression

    • +
    • ’rice’ = use rice decompression in tiles

    • +
    • ’gzip’ = use gzip to decompress the full file

    • +
    • ’bzip2’ = use bzip2 to decompress the full file

    • +
    • ’gzip_tile’ = use gzip decompression in tiles

    • +
    • ’hcompress’ = use hcompress decompression in tiles

    • +
    • ’plio’ = use plio decompression in tiles

    • +
    • ’auto’ = determine the decompression from the extension of the file name +(requires file_name to be given).

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
  • read_header – Whether to read the header and store it as image.header.[default: False]

  • +
  • suppress_warning – Whether to suppress a warning that the WCS could not be read from the +FITS header, so the WCS defaulted to either a PixelScale or +AffineTransform. [default: True]

  • +
+
+
Returns:
+

the image as an Image instance.

+
+
+
+ +
+
+galsim.fits.readMulti(file_name=None, dir=None, hdu_list=None, compression='auto', read_headers=False, suppress_warning=True)[source]
+

Construct a list of Image instances from a FITS file or HDUList.

+

The normal usage for this function is to read a fits file and return a list of all the images +contained therein, automatically decompressing them if necessary. However, you may also pass +it an HDUList, in which case it will build the images from these directly.

+

Not all FITS pixel types are supported – only short, int, unsigned short, +unsigned int, float, and double.

+

If the FITS header has keywords that start with GS_, these will be used to initialize the +bounding box and WCS. If these are absent, the code will try to read whatever WCS is given +in the FITS header. cf. galsim.wcs.readFromFitsHeader. The default bounding box will have +(xmin,ymin) at (1,1). The default WCS, if there is no WCS information in the FITS +file, will be PixelScale(1.0).

+

This function is called as im = galsim.fits.readMulti(...)

+
+

Note

+

This function along with writeMulti can be used to effect the equivalent of a simple +version of fpack or funpack. To Rice compress a fits file, you can call:

+
fname = 'some_image_file.fits'
+galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname+'.fz')
+
+
+

To uncompress:

+
fname = 'some_image_file.fits.fz'
+galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname[:-3])
+
+
+
+
+
Parameters:
+
    +
  • file_name – The name of the file to read in. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – An astropy.io.fits.HDUList from which to read the images. [Either +file_name or hdu_list is required.]

  • +
  • compression

    Which decompression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no decompression

    • +
    • ’rice’ = use rice decompression in tiles

    • +
    • ’gzip’ = use gzip to decompress the full file

    • +
    • ’bzip2’ = use bzip2 to decompress the full file

    • +
    • ’gzip_tile’ = use gzip decompression in tiles

    • +
    • ’hcompress’ = use hcompress decompression in tiles

    • +
    • ’plio’ = use plio decompression in tiles

    • +
    • ’auto’ = determine the decompression from the extension of the file name +(requires file_name to be given).

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
  • read_headers – Whether to read the headers and store them as image.header.[default: False]

  • +
  • suppress_warning – Whether to suppress a warning that the WCS could not be read from the +FITS header, so the WCS defaulted to either a PixelScale or +AffineTransform. [default: True]

  • +
+
+
Returns:
+

a Python list of Image instances.

+
+
+
+ +
+
+galsim.fits.readCube(file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto', suppress_warning=True)[source]
+

Construct a Python list of Image instances from a FITS data cube.

+

Not all FITS pixel types are supported – only short, int, unsigned short, +unsigned int, float, and double.

+

If the FITS header has keywords that start with GS_, these will be used to initialize the +bounding box and WCS. If these are absent, the code will try to read whatever WCS is given +in the FITS header. cf. galsim.wcs.readFromFitsHeader. The default bounding box will have +(xmin,ymin) at (1,1). The default WCS, if there is no WCS information in the FITS +file, will be PixelScale(1.0).

+

This function is called as image_list = galsim.fits.readCube(...)

+
+
Parameters:
+
    +
  • file_name – The name of the file to read in. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – Either an astropy.io.fits.HDUList, an astropy.io.fits.PrimaryHDU, or +astropy.io.fits.ImageHDU. In the former case, the hdu in the list will +be selected. In the latter two cases, the hdu parameter is ignored. +[Either file_name or hdu_list is required.]

  • +
  • hdu – The number of the HDU to return. [default: None, which means to return +either the primary or first extension as appropriate for the given +compression. (e.g. for rice, the first extension is the one you normally +want.)]

  • +
  • compression

    Which decompression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no decompression

    • +
    • ’rice’ = use rice decompression in tiles

    • +
    • ’gzip’ = use gzip to decompress the full file

    • +
    • ’bzip2’ = use bzip2 to decompress the full file

    • +
    • ’gzip_tile’ = use gzip decompression in tiles

    • +
    • ’hcompress’ = use hcompress decompression in tiles

    • +
    • ’plio’ = use plio decompression in tiles

    • +
    • ’auto’ = determine the decompression from the extension of the file name +(requires file_name to be given).

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
  • suppress_warning – Whether to suppress a warning that the WCS could not be read from the +FITS header, so the WCS defaulted to either a PixelScale or +AffineTransform. [default: True]

  • +
+
+
Returns:
+

a Python list of Image instances.

+
+
+
+ +
+
+galsim.fits.readFile(file_name, dir=None, hdu=None, compression='auto')[source]
+

Read in a Pyfits hdu_list from a FITS file, taking care of the GalSim compression options.

+

If you want to do something different with an hdu or hdu_list than one of our other read +functions, you can use this function. It handles the compression options in the standard +GalSim way and just returns the hdu (and hdu_list) for you to use as you see fit.

+

This function is called as:

+
>>> hdu, hdu_list, fin = galsim.fits.readFile(...)
+
+
+

The first item in the returned tuple is the specified hdu (or the primary if none was +specifically requested). The other two are returned so you can properly close them. +They are the full HDUList and possibly a file handle. The appropriate cleanup can be +done with:

+
>>> galsim.fits.closeHDUList(hdu_list, fin)
+
+
+
+
Parameters:
+
    +
  • file_name – The name of the file to read in.

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu – The number of the HDU to return. [default: None, which means to return +either the primary or first extension as appropriate for the given +compression. (e.g. for rice, the first extension is the one you normally +want.)]

  • +
  • compression

    Which decompression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no decompression

    • +
    • ’rice’ = use rice decompression in tiles

    • +
    • ’gzip’ = use gzip to decompress the full file

    • +
    • ’bzip2’ = use bzip2 to decompress the full file

    • +
    • ’gzip_tile’ = use gzip decompression in tiles

    • +
    • ’hcompress’ = use hcompress decompression in tiles

    • +
    • ’plio’ = use plio decompression in tiles

    • +
    • ’auto’ = determine the decompression from the extension of the file name +(requires file_name to be given).

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
+
+
Returns:
+

(hdu, hdu_list, fin).

+
+
Return type:
+

a tuple with three items

+
+
+
+ +
+
+galsim.fits.closeHDUList(hdu_list, fin)[source]
+

If necessary, close the file handle that was opened to read in the hdu_list

+
+ +
+
+

Writing FITS Files

+
+
+galsim.fits.write(image, file_name=None, dir=None, hdu_list=None, clobber=True, compression='auto')[source]
+

Write a single image to a FITS file.

+

Write the Image instance image to a FITS file, with details depending on the arguments. +This function can be called directly as galsim.fits.write(image, ...), with the image as the +first argument, or as an Image method: image.write(...).

+
+
Parameters:
+
    +
  • image – The Image to write to file. Per the description of this method, it may be +given explicitly via galsim.fits.write(image, ...) or the method may be +called directly as an image method, image.write(...). Note that if the +image has a ‘header’ attribute containing a FitsHeader, then the +FitsHeader is written to the header in the PrimaryHDU, followed by the +WCS as usual.

  • +
  • file_name – The name of the file to write to. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – An astropy.io.fits.HDUList. If this is provided instead of file_name, +then the Image is appended to the end of the HDUList as a new HDU. In +that case, the user is responsible for calling either +hdu_list.writeto(...) or galsim.fits.writeFile(...) afterwards. +[Either file_name or hdu_list is required.]

  • +
  • clobber – Setting clobber=True will silently overwrite existing files. +[default: True]

  • +
  • compression

    Which compression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no compression

    • +
    • ’rice’ = use rice compression in tiles (preserves header readability)

    • +
    • ’gzip’ = use gzip to compress the full file

    • +
    • ’bzip2’ = use bzip2 to compress the full file

    • +
    • ’gzip_tile’ = use gzip in tiles (preserves header readability)

    • +
    • ’hcompress’ = use hcompress in tiles (only valid for 2-d images)

    • +
    • ’plio’ = use plio compression in tiles (only valid for pos integer data)

    • +
    • ’auto’ = determine the compression from the extension of the file name +(requires file_name to be given):

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
+
+
+
+ +
+
+galsim.fits.writeMulti(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, compression='auto')[source]
+

Write a Python list of images to a multi-extension FITS file.

+

The details of how the images are written to file depends on the arguments.

+
+

Note

+

This function along with readMulti can be used to effect the equivalent of a simple +version of fpack or funpack. To Rice compress a fits file, you can call:

+
fname = 'some_image_file.fits'
+galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname+'.fz')
+
+
+

To uncompress:

+
fname = 'some_image_file.fits.fz'
+galsim.fits.writeMulti(galsim.fits.readMulti(fname, read_headers=True), fname[:-3])
+
+
+
+
+
Parameters:
+
    +
  • image_list – A Python list of Image instances. (For convenience, some items in this +list may be HDUs already. Any Image will be converted into an +astropy.io.fits.HDU.)

  • +
  • file_name – The name of the file to write to. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – An astropy.io.fits.HDUList. If this is provided instead of file_name, +then the Image is appended to the end of the HDUList as a new HDU. In +that case, the user is responsible for calling either +hdu_list.writeto(...) or galsim.fits.writeFile(...) afterwards. +[Either file_name or hdu_list is required.]

  • +
  • clobber – Setting clobber=True will silently overwrite existing files. +[default: True]

  • +
  • compression

    Which compression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no compression

    • +
    • ’rice’ = use rice compression in tiles (preserves header readability)

    • +
    • ’gzip’ = use gzip to compress the full file

    • +
    • ’bzip2’ = use bzip2 to compress the full file

    • +
    • ’gzip_tile’ = use gzip in tiles (preserves header readability)

    • +
    • ’hcompress’ = use hcompress in tiles (only valid for 2-d images)

    • +
    • ’plio’ = use plio compression in tiles (only valid for pos integer data)

    • +
    • ’auto’ = determine the compression from the extension of the file name +(requires file_name to be given):

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
+
+
+
+ +
+
+galsim.fits.writeCube(image_list, file_name=None, dir=None, hdu_list=None, clobber=True, compression='auto')[source]
+

Write a Python list of images to a FITS file as a data cube.

+

The details of how the images are written to file depends on the arguments. Unlike for +writeMulti, when writing a data cube it is necessary that each Image in image_list has +the same size (nx, ny). No check is made to confirm that all images have the same origin +and pixel scale (or WCS).

+

In fact, the WCS of the first image is the one that gets put into the FITS header (since only +one WCS can be put into a FITS header). Thus, if the images have different WCS functions, +only the first one will be rendered correctly by plotting programs such as ds9. The FITS +standard does not support any way to have the various images in a data cube to have different +WCS solutions.

+
+
Parameters:
+
    +
  • image_list – The image_list can also be either an array of NumPy arrays or a 3d NumPy +array, in which case this is written to the fits file directly. In the +former case, no explicit check is made that the NumPy arrays are all the +same shape, but a NumPy exception will be raised which we let pass upstream +unmolested.

  • +
  • file_name – The name of the file to write to. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – An astropy.io.fits.HDUList. If this is provided instead of file_name, +then the cube is appended to the end of the HDUList as a new HDU. In that +case, the user is responsible for calling either hdu_list.writeto(...) +or galsim.fits.writeFile(...) afterwards. [Either file_name or +hdu_list is required.]

  • +
  • clobber – Setting clobber=True will silently overwrite existing files. +[default: True]

  • +
  • compression

    Which compression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no compression

    • +
    • ’rice’ = use rice compression in tiles (preserves header readability)

    • +
    • ’gzip’ = use gzip to compress the full file

    • +
    • ’bzip2’ = use bzip2 to compress the full file

    • +
    • ’gzip_tile’ = use gzip in tiles (preserves header readability)

    • +
    • ’hcompress’ = use hcompress in tiles (only valid for 2-d images)

    • +
    • ’plio’ = use plio compression in tiles (only valid for pos integer data)

    • +
    • ’auto’ = determine the compression from the extension of the file name +(requires file_name to be given):

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
+
+
+
+ +
+
+galsim.fits.writeFile(file_name, hdu_list, dir=None, clobber=True, compression='auto')[source]
+

Write a Pyfits hdu_list to a FITS file, taking care of the GalSim compression options.

+

If you have used the write(), writeMulti() or writeCube() functions with the hdu_list +option rather than writing directly to a file, you may subsequently use the command +hdu_list.writeto(...). However, it may be more convenient to use this function, writeFile() +instead, since it treats the compression option consistently with how that option is handled in +the above functions.

+
+
Parameters:
+
    +
  • file_name – The name of the file to write to.

  • +
  • hdu_list – An astropy.io.fits.HDUList.

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • clobber – Setting clobber=True will silently overwrite existing files. +[default: True]

  • +
  • compression

    Which compression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no compression

    • +
    • ’gzip’ = use gzip to compress the full file

    • +
    • ’bzip2’ = use bzip2 to compress the full file

    • +
    • ’auto’ = determine the compression from the extension of the file name +(requires file_name to be given):

      +
        +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    Note that the other options, such as ‘rice’, that operate on the image +directly are not available at this point. If you want to use one of them, +it must be applied when writing each hdu. +[default: ‘auto’]

    +

  • +
+
+
+
+ +
+
+

FITS Headers

+
+
+class galsim.fits.FitsHeader(header=None, file_name=None, dir=None, hdu_list=None, hdu=None, compression='auto', text_file=False)[source]
+

A class storing key/value pairs from a FITS Header

+

This class works a lot like the regular read() function, but rather than returning +the image part of the FITS file, it gives you access to the header information.

+

After construction, you can access a header value by:

+
>>> value = fits_header[key]
+
+
+

or write to it with:

+
>>> fits_header[key] = value                # If you just want to set a value.
+>>> fits_header[key] = (value, comment)     # If you want to include a comment field.
+
+
+

In fact, most of the normal functions available for a dict are available::

+
>>> keys = fits_header.keys()
+>>> items = fits_header.items()
+>>> for key in fits_header:
+>>>     value = fits_header[key]
+>>> value = fits_header.get(key, default)
+>>> del fits_header[key]
+>>> etc.
+
+
+
+

Note

+

This used to be a particularly useful abstraction, since PyFITS and then AstroPy used to +keep changing their syntax for how to write to a fits header rather often, so this class +had numerous checks for which version of PPyFITS or AstroPy was installed and call things +the right way depending on the version. Thus, it was able to maintain a stable, intuitive +API that would work with any version on the backend. We no longer support PyFITS or older +versions of AstroPy, so now much of the syntax of this class is very similar in interface +to the current version of astropy.io.fits.Header. Indeed it is now a rather light wrapper +around their Header class with just a few convenience features to make it easier to work +with GalSim objects.

+
+

The underlying Header object is available as a .header attribute:

+
>>> apy_header = fits_header.header
+
+
+

A FitsHeader may be constructed from a file name, an open PyFits (or astropy.io.fits) HDUList +object, or a PyFits (or astropy.io.fits) Header object. It can also be constructed with +no parameters, in which case a blank Header will be constructed with no keywords yet if +you want to add the keywords you want by hand.:

+
>>> h1 = galsim.FitsHeader(file_name = file_name)
+>>> h2 = galsim.FitsHeader(header = header)
+>>> h3 = galsim.FitsHeader(hdu_list = hdu_list)
+>>> h4 = galsim.FitsHeader()
+
+
+

For convenience, the first parameter may be unnamed as either a header or a file_name:

+
>>> h1 = galsim.FitsHeader(file_name)
+>>> h2 = galsim.FitsHeader(header)
+
+
+
+
Parameters:
+
    +
  • header – An astropy.io.fits.Header object or in fact any dict-like object or list of +(key,value) pairs. [default: None]

  • +
  • file_name – The name of the file to read in. [default: None]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – Either an astropy.io.fits.HDUList, an astropy.io.fits.PrimaryHDU, or +astropy.io.fits.ImageHDU. In the former case, the hdu in the list will +be selected. In the latter two cases, the hdu parameter is ignored. +[default: None]

  • +
  • hdu – The number of the HDU to return. [default: None, which means to return +either the primary or first extension as appropriate for the given +compression. (e.g. for rice, the first extension is the one you normally +want.)]

  • +
  • compression

    Which decompression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no decompression

    • +
    • ’rice’ = use rice decompression in tiles

    • +
    • ’gzip’ = use gzip to decompress the full file

    • +
    • ’bzip2’ = use bzip2 to decompress the full file

    • +
    • ’gzip_tile’ = use gzip decompression in tiles

    • +
    • ’hcompress’ = use hcompress decompression in tiles

    • +
    • ’plio’ = use plio decompression in tiles

    • +
    • ’auto’ = determine the decompression from the extension of the file name +(requires file_name to be given).

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
  • text_file – Normally a file is taken to be a fits file, but you can also give it a +text file with the header information (like the .head file output from +SCamp). In this case you should set text_file = True to tell GalSim +to parse the file this way. [default: False]

  • +
+
+
+
+
+append(key, value='', comment=None, useblanks=True)[source]
+

Append an item to the end of the header.

+

This breaks convention a bit by treating the header more like a list than a dict, +but sometimes that is necessary to get the header structured the way you want it.

+
+
Parameters:
+
    +
  • key – The key of the entry to append

  • +
  • value – The value of the entry to append [default: ‘’]

  • +
  • comment – A comment field if desired [default: None]

  • +
  • useblanks – If there are blank entries currently at the end, should they be +overwritten with the new entry? [default: True]

  • +
+
+
+
+ +
+
+clear()[source]
+

Clear all values in the header. Works like dict.clear.

+
+ +
+
+comment(key)[source]
+

Get the comment field for the given key.

+
+
Parameter:

key: The header key for which to get the comment field.

+
+
+
+
Returns:
+

the comment field.

+
+
+
+ +
+
+extend(other, replace=False, useblanks=True)[source]
+

Extend this FitsHeader with items from another FitsHeader.

+

Equivalent to appending all the other’s items to the end of this one with the +exception that it ignores items that are already in self. +If you want to replace existing values rather than ignore duplicates, use +replace=True.

+
+
Parameters:
+
    +
  • other – Another FitsHeader object.

  • +
  • replace – Replace duplicate entries rather than ignore them. [default: False]

  • +
  • useblanks – If there are blank entries currently at the end, should they be +overwritten with the new entry? [default: True]

  • +
+
+
+
+ +
+
+get(key, default=None)[source]
+

Get the value of a given key. Works like dict.get.

+
+
Parameters:
+
    +
  • key – The header key for which to get the value

  • +
  • default – Optionally, A value to use if the key is not present. [default: None]

  • +
+
+
Returns:
+

the value of the given key

+
+
+
+ +
+
+items()[source]
+

Get all header items. Works like dict.items.

+
+
Returns:
+

A list of (key, value) tuples.

+
+
+
+ +
+
+iteritems()[source]
+

Synonym for self.items()

+
+ +
+
+iterkeys()[source]
+

Synonym for self.keys()

+
+ +
+
+itervalues()[source]
+

Synonym for self.values()

+
+ +
+
+keys()[source]
+

Get all header keys. Works like dict.keys

+
+
Returns:
+

A list of keys.

+
+
+
+ +
+
+pop(key, default=None)[source]
+

Pop off a value from the header. Works like dict.pop.

+
+
Parameters:
+
    +
  • key – The header key for which to get the value

  • +
  • default – Optionally, A value to use if the key is not present. [default: None]

  • +
+
+
Returns:
+

the value of the given key

+
+
+
+ +
+
+update(dict2)[source]
+

Update the header with a dict-like object. Works like dict.update.

+

If there are any items in dict2 that are duplicates of items already in the header, +the current items will ber overwritten.

+
+
Parameters:
+

dict2 – Another header or dict-like object with keys and values to update.

+
+
+
+ +
+
+values()[source]
+

Get all header values. Works like dict.values.

+
+
Returns:
+

A list of values.

+
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/gal.html b/docs/_build/html/gal.html new file mode 100644 index 00000000000..7304692d1de --- /dev/null +++ b/docs/_build/html/gal.html @@ -0,0 +1,1126 @@ + + + + + + + Galaxies — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Galaxies

+

There are a number of profiles that are designed to be used for galaxy profiles. +Again though, there is nothing restricting these classes to be used only for that purpose +if you have another use case for which one would be relevant.

+
+

Exponenatial Profile

+
+
+class galsim.Exponential(half_light_radius=None, scale_radius=None, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing an exponential profile.

+

Surface brightness profile with

+
+\[I(r) \sim e^{-r/r_0}\]
+

where \(r_0\) is the scale_radius. This is a special case of the Sersic profile, +but is given a separate class since the Fourier transform has closed form and can be generated +without lookup tables.

+

An Exponential can be initialized using one (and only one) of two possible size parameters: +scale_radius or half_light_radius. Exactly one of these two is required.

+
+
Parameters:
+
    +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • scale_radius – The scale radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property half_light_radius
+

The half-light radius of the profile.

+
+ +
+
+property scale_radius
+

The scale radius of the profile.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

De Vaucouleurs Profile

+
+
+class galsim.DeVaucouleurs(half_light_radius=None, scale_radius=None, flux=1.0, trunc=0.0, flux_untruncated=False, gsparams=None)[source]
+

Bases: Sersic

+

A class describing DeVaucouleurs profile objects.

+

Surface brightness profile with

+
+\[I(r) \sim e^{-(r/r_0)^{1/4}}\]
+

where \(r_0\) is the scale_radius. This is completely equivalent to a Sersic with n=4.

+

For more information, refer to

+

http://en.wikipedia.org/wiki/De_Vaucouleurs’_law

+

A DeVaucouleurs can be initialized using one (and only one) of two possible size parameters: +scale_radius or half_light_radius. Exactly one of these two is required.

+
+
Parameters:
+
    +
  • scale_radius – The value of scale radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • trunc – An optional truncation radius at which the profile is made to drop to +zero, in the same units as the size parameter. +[default: 0, indicating no truncation]

  • +
  • flux_untruncated – Should the provided flux and half_light_radius refer to the +untruncated profile? See the docstring for Sersic for more details. +[default: False]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Sersic Profile

+
+
+class galsim.Sersic(n, half_light_radius=None, scale_radius=None, flux=1.0, trunc=0.0, flux_untruncated=False, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a Sersic profile.

+

The Sersic surface brightness profile is characterized by three properties: its Sersic index +n, its flux, and either the half_light_radius or scale_radius. Given these +properties, the surface brightness profile scales as

+
+\[I(r) \sim e^{-(r/r_0)^{1/n}}\]
+

where \(r_0\) is the scale_radius, or

+
+\[I(r) \sim e^{-b (r/r_e)^{1/n}}\]
+

where \(r_e\) is the half_light_radius and \(b\) is calculated to give the right +half-light radius.

+

For more information, refer to

+

http://en.wikipedia.org/wiki/Sersic_profile

+

The allowed range of values for the n parameter is 0.3 <= n <= 6.2. An exception will be +thrown if you provide a value outside that range. Below n=0.3, there are severe numerical +problems. Above n=6.2, we found that the code begins to be inaccurate when sheared or +magnified (at the level of upcoming shear surveys), so we do not recommend extending beyond +this. See Issues #325 and #450 for more details.

+

Sersic profile calculations take advantage of Hankel transform tables that are precomputed for a +given value of n when the Sersic profile is initialized. Making additional objects with the +same n can therefore be many times faster than making objects with different values of n that +have not been used before. Moreover, these Hankel transforms are only cached for a maximum of +100 different n values at a time. For this reason, for large sets of simulations, it is worth +considering the use of only discrete n values rather than allowing it to vary continuously. For +more details, see https://github.com/GalSim-developers/GalSim/issues/566.

+

Note that if you are building many Sersic profiles using truncation, the code will be more +efficient if the truncation is always the same multiple of scale_radius, since it caches +many calculations that depend on the ratio trunc/scale_radius.

+

A Sersic can be initialized using one (and only one) of two possible size parameters: +scale_radius or half_light_radius. Exactly one of these two is required.

+

Flux of a truncated profile:

+

If you are truncating the profile, the optional parameter, flux_untruncated, specifies +whether the flux and half_light_radius specifications correspond to the untruncated +profile (True) or to the truncated profile (False, default). The impact of this +parameter is a little subtle, so we’ll go through a few examples to show how it works.

+

First, let’s examine the case where we specify the size according to the half-light radius. +If flux_untruncated is True (and trunc > 0), then the profile will be identical +to the version without truncation up to the truncation radius, beyond which it drops to 0. +In this case, the actual half-light radius will be different from the specified half-light +radius. The half_light_radius property will return the true half-light radius. Similarly, +the actual flux will not be the same as the specified value; the true flux is also returned +by the flux property.

+

Example:

+
>>> sersic_obj1 = galsim.Sersic(n=3.5, half_light_radius=2.5, flux=40.)
+>>> sersic_obj2 = galsim.Sersic(n=3.5, half_light_radius=2.5, flux=40., trunc=10.)
+>>> sersic_obj3 = galsim.Sersic(n=3.5, half_light_radius=2.5, flux=40., trunc=10., \\
+                                flux_untruncated=True)
+
+>>> sersic_obj1.xValue(galsim.PositionD(0.,0.))
+237.3094228615618
+>>> sersic_obj2.xValue(galsim.PositionD(0.,0.))
+142.54505376530574    # Normalization and scale radius adjusted (same half-light radius)
+>>> sersic_obj3.xValue(galsim.PositionD(0.,0.))
+237.30942286156187
+
+>>> sersic_obj1.xValue(galsim.PositionD(10.0001,0.))
+0.011776164687304694
+>>> sersic_obj2.xValue(galsim.PositionD(10.0001,0.))
+0.0
+>>> sersic_obj3.xValue(galsim.PositionD(10.0001,0.))
+0.0
+
+>>> sersic_obj1.half_light_radius
+2.5
+>>> sersic_obj2.half_light_radius
+2.5
+>>> sersic_obj3.half_light_radius
+1.9795101383056892    # The true half-light radius is smaller than the specified value
+
+>>> sersic_obj1.flux
+40.0
+>>> sersic_obj2.flux
+40.0
+>>> sersic_obj3.flux
+34.56595186009519     # Flux is missing due to truncation
+
+>>> sersic_obj1.scale_radius
+0.003262738739834598
+>>> sersic_obj2.scale_radius
+0.004754602453641744  # the scale radius needed adjustment to accommodate HLR
+>>> sersic_obj3.scale_radius
+0.003262738739834598  # the scale radius is still identical to the untruncated case
+
+
+

When the truncated Sersic scale is specified with scale_radius, the behavior between the +three cases (untruncated, flux_untruncated=True and flux_untruncated=False) will be +somewhat different from above. Since it is the scale radius that is being specified, and since +truncation does not change the scale radius the way it can change the half-light radius, the +scale radius will remain unchanged in all cases. This also results in the half-light radius +being the same between the two truncated cases (although different from the untruncated case). +The flux normalization is the only difference between flux_untruncated=True and +flux_untruncated=False in this case.

+

Example:

+
>>> sersic_obj1 = galsim.Sersic(n=3.5, scale_radius=0.05, flux=40.)
+>>> sersic_obj2 = galsim.Sersic(n=3.5, scale_radius=0.05, flux=40., trunc=10.)
+>>> sersic_obj3 = galsim.Sersic(n=3.5, scale_radius=0.05, flux=40., trunc=10., \\
+                                flux_untruncated=True)
+
+>>> sersic_obj1.xValue(galsim.PositionD(0.,0.))
+1.010507575186637
+>>> sersic_obj2.xValue(galsim.PositionD(0.,0.))
+5.786692612210923     # Normalization adjusted to accomodate the flux within trunc radius
+>>> sersic_obj3.xValue(galsim.PositionD(0.,0.))
+1.010507575186637
+
+>>> sersic_obj1.half_light_radius
+38.311372735390016
+>>> sersic_obj2.half_light_radius
+5.160062547614234
+>>> sersic_obj3.half_light_radius
+5.160062547614234     # For the truncated cases, the half-light radii are the same
+
+>>> sersic_obj1.flux
+40.0
+>>> sersic_obj2.flux
+40.0
+>>> sersic_obj3.flux
+6.985044085834393     # Flux is missing due to truncation
+
+>>> sersic_obj1.scale_radius
+0.05
+>>> sersic_obj2.scale_radius
+0.05
+>>> sersic_obj3.scale_radius
+0.05
+half_light_radius:  The half-light radius
+
+
+
+
Parameters:
+
    +
  • n – The Sersic index, n.

  • +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • scale_radius – The scale radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • trunc – An optional truncation radius at which the profile is made to drop to +zero, in the same units as the size parameter. +[default: 0, indicating no truncation]

  • +
  • flux_untruncated – Should the provided flux and half_light_radius refer to the +untruncated profile? See below for more details. [default: False]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+calculateHLRFactor()[source]
+

Calculate the half-light-radius in units of the scale radius.

+
+ +
+
+calculateIntegratedFlux(r)[source]
+

Return the fraction of the total flux enclosed within a given radius, r

+
+ +
+
+property half_light_radius
+

The half-light radius.

+
+ +
+
+property n
+

The Sersic parameter n.

+
+ +
+
+property scale_radius
+

The scale radius.

+
+ +
+
+property trunc
+

The truncation radius (if any).

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Inclined Exponential Profile

+
+
+class galsim.InclinedExponential(inclination, half_light_radius=None, scale_radius=None, scale_height=None, scale_h_over_r=None, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing an inclined exponential profile.

+

The Inclined Exponential surface brightness profile is characterized by three properties: its +inclination angle (where 0 degrees = face-on and 90 degrees = edge-on), its scale radius, and +its scale height. The 3D light distribution function is:

+
+\[I(R,z) \sim \mathrm{sech}^2 (z/h_s) \, \exp\left(-R/R_s\right)\]
+

where \(z\) is the distance along the minor axis, \(R\) is the radial distance from the +minor axis, \(R_s\) is the scale radius of the disk, and \(h_s\) is the scale height of +the disk. The 2D light distribution function is then determined from the scale height and +radius here, along with the inclination angle.

+

In this implementation, the profile is inclined along the y-axis. This means that it will likely +need to be rotated in most circumstances.

+

At present, this profile is not enabled for photon-shooting.

+

A profile can be initialized using one (and only one) of two possible size parameters: +scale_radius or half_light_radius. Exactly one of these two is required. Similarly, +at most one of scale_height and scale_h_over_r is required; if neither is given, the +default of scale_h_over_r = 0.1 will be used. Note that if half_light_radius and +scale_h_over_r are supplied (or the default value of scale_h_over_r is used), +scale_h_over_r will be assumed to refer to the scale radius, not the half-light radius.

+
+
Parameters:
+
    +
  • inclination – The inclination angle, which must be a galsim.Angle instance

  • +
  • scale_radius – The scale radius of the exponential disk. Typically given in +arcsec. This can be compared to the ‘scale_radius’ parameter of the +galsim.Exponential class, and in the face-on case, the same scale +radius will result in the same 2D light distribution as with that +class.

  • +
  • half_light_radius – The half-light radius of the exponential disk, as an alternative to +the scale radius.

  • +
  • scale_height – The scale height of the exponential disk. Typically given in arcsec. +[default: None]

  • +
  • scale_h_over_r – In lieu of the scale height, you may also specify the ratio of the +scale height to the scale radius. [default: 0.1]

  • +
  • flux – The flux (in photons) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property disk_half_light_radius
+

The half-light radius of the exponential disk.

+
+ +
+
+property inclination
+

The inclination angle.

+
+ +
+
+property scale_h_over_r
+

The ratio scale_height / scale_radius

+
+ +
+
+property scale_height
+

The scale height of the disk.

+
+ +
+
+property scale_radius
+

The scale radius of the exponential disk.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Inclined Sersic Profile

+
+
+class galsim.InclinedSersic(n, inclination, half_light_radius=None, scale_radius=None, scale_height=None, scale_h_over_r=None, flux=1.0, trunc=0.0, flux_untruncated=False, gsparams=None)[source]
+

Bases: GSObject

+

A class describing an inclined sersic profile. This class is general, and so for certain +special cases, more specialized classes will be more efficient. For the case where n==1 +with no truncation, the InclinedExponential class will be much more efficient. For the case +where the inclination angle is zero (face-on), the Sersic class will be slightly more +efficient.

+

The InclinedSersic surface brightness profile is characterized by four properties: its +Sersic index n, its inclination angle (where 0 degrees = face-on and 90 degrees = edge-on), +its scale radius, and its scale height. The 3D light distribution function is:

+
+\[I(R,z) \sim \mathrm{sech}^2 (z/h_s) \, \exp\left(-(R/R_s)^{1/n}\right)\]
+

where \(z\) is the distance along the minor axis, \(R\) is the radial distance from the +minor axis, \(R_s\) is the scale radius of the disk, and \(h_s\) is the scale height of +the disk. The 2D light distribution function is then determined from the scale height and +radius here, along with the inclination angle.

+

In this implementation, the profile is inclined along the y-axis. This means that it will likely +need to be rotated in most circumstances.

+

At present, this profile is not enabled for photon-shooting.

+

The allowed range of values for the n parameter is 0.3 <= n <= 6.2. An exception will be +thrown if you provide a value outside that range, matching the range of the Sersic profile.

+

This class shares the caching of Hankel transformations with the Sersic class; see that +class for documentation on efficiency considerations with regards to caching.

+

A profile can be initialized using one (and only one) of two possible size parameters: +scale_radius or half_light_radius. Exactly one of these two is required. Similarly, +at most one of scale_height and scale_h_over_r is required; if neither is given, the +default of scale_h_over_r = 0.1 will be used. Note that if half_light_radius and +scale_h_over_r are supplied (or the default value of scale_h_over_r is used), +scale_h_over_r will be assumed to refer to the scale radius, not the half-light radius.

+
+
Parameters:
+
    +
  • n – The Sersic index, n.

  • +
  • inclination – The inclination angle, which must be a galsim.Angle instance

  • +
  • scale_radius – The scale radius of the disk. Typically given in arcsec. +This can be compared to the ‘scale_radius’ parameter of the +galsim.Sersic class, and in the face-on case, the same scale +radius will result in the same 2D light distribution as with that +class. Exactly one of this and half_light_radius must be provided.

  • +
  • half_light_radius – The half-light radius of disk when seen face-on. Exactly one of this +and scale_radius must be provided.

  • +
  • scale_height – The scale height of the exponential disk. Typically given in arcsec. +[default: None]

  • +
  • scale_h_over_r – In lieu of the scale height, you may specify the ratio of the +scale height to the scale radius. [default: 0.1]

  • +
  • flux – The flux (in photons) of the profile. [default: 1]

  • +
  • trunc – An optional truncation radius at which the profile is made to drop to +zero, in the same units as the size parameter. +[default: 0, indicating no truncation]

  • +
  • flux_untruncated – Should the provided flux and half_light_radius refer to the +untruncated profile? See the documentation of the Sersic class for +more details. [default: False]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property disk_half_light_radius
+

The half-light radius of the exponential disk.

+
+ +
+
+property inclination
+

The inclination angle.

+
+ +
+
+property n
+

The Sersic parameter n.

+
+ +
+
+property scale_h_over_r
+

The ratio scale_height / scale_radius.

+
+ +
+
+property scale_height
+

The scale height of the disk.

+
+ +
+
+property scale_radius
+

The scale radius of the exponential disk.

+
+ +
+
+property trunc
+

The truncation radius (if any).

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Spergel Profile

+
+
+class galsim.Spergel(nu, half_light_radius=None, scale_radius=None, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a Spergel profile.

+

The Spergel surface brightness profile is characterized by three properties: its Spergel index +nu, its flux, and either the half_light_radius or scale_radius. Given these +properties, the surface brightness profile scales as

+
+\[I(r) \sim \left(\frac{r}{r_0}\right)^\nu K_\nu\left(\frac{r}{r_0}\right)\]
+

where \(r_0\) is the scale_radius and \(K_\nu\) is the modified Bessel function of +the second kind.

+

The Spergel profile is intended as a generic galaxy profile, somewhat like a Sersic profile, +but with the advantage of being analytic in both real space and Fourier space. The Spergel +index \(\nu\) plays a similar role to the Sersic index \(n\), in that it adjusts the +relative peakiness of the profile core and the relative prominence of the profile wings. +At \(\nu = 0.5\), the Spergel profile is equivalent to an Exponential profile (or +alternatively an :math`n = 1` Sersic profile). At \(\nu = -0.6\) (and in the radial +range near the half-light radius), the Spergel profile is similar to a DeVaucouleurs profile +or \(n = 4\) Sersic profile.

+

Note that for \(\nu <= 0\), the Spergel profile surface brightness diverges at the origin. +This may lead to rendering problems if the profile is not convolved by either a PSF or a pixel +and the profile center is precisely on a pixel center.

+

Due to its analytic Fourier transform and depending on the indices \(n\) and \(\nu\), +the Spergel profile can be considerably faster to draw than the roughly equivalent Sersic +profile. For example, the \(\nu = -0.6\) Spergel profile is roughly 3x faster to draw than +an \(n = 4\) Sersic profile once the Sersic profile cache has been set up. However, if +not taking advantage of the cache, for example, if drawing Sersic profiles with \(n\) +continuously varying near 4.0 and Spergel profiles with \(\nu\) continuously varying near +-0.6, then the Spergel profiles are about 50x faster to draw. At the other end of the galaxy +profile spectrum, the \(\nu = 0.5\) Spergel profile, \(n = 1\) Sersic profile, and +the Exponential profile all take about the same amount of time to draw if cached, and the +Spergel profile is about 2x faster than the Sersic profile if uncached.

+

For more information, refer to

+
+

D. N. Spergel, “ANALYTICAL GALAXY PROFILES FOR PHOTOMETRIC AND LENSING ANALYSIS,” +ASTROPHYS J SUPPL S 191(1), 58-65 (2010) [doi:10.1088/0067-0049/191/1/58].

+
+

The allowed range of values for the nu parameter is -0.85 <= nu <= 4. An exception +will be thrown if you provide a value outside that range. The lower limit is set above the +theoretical lower limit of -1 due to numerical difficulties integrating the very peaky +nu < -0.85 profiles. The upper limit is set to avoid numerical difficulties evaluating the +modified Bessel function of the second kind.

+

A Spergel profile can be initialized using one (and only one) of two possible size parameters: +scale_radius or half_light_radius. Exactly one of these two is required.

+
+
Parameters:
+
    +
  • nu – The Spergel index, nu.

  • +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • scale_radius – The scale radius of the profile. Typically given in arcsec. +[One of scale_radius or half_light_radius is required.]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+calculateFluxRadius(f)[source]
+

Return the radius within which the total flux is f

+
+ +
+
+calculateIntegratedFlux(r)[source]
+

Return the integrated flux out to a given radius, r

+
+ +
+
+property half_light_radius
+

The half-light radius

+
+ +
+
+property nu
+

The Spergel index, nu

+
+ +
+
+property scale_radius
+

The scale radius

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Knots of Star Formation

+
+
+class galsim.RandomKnots(npoints, half_light_radius=None, flux=None, profile=None, rng=None, gsparams=None)[source]
+

Bases: GSObject

+

A class for generating a set of point sources, following either a Gaussian profile or a +specified input profile.

+

Uses of this profile include representing an “irregular” galaxy, or +adding this profile to an Exponential to represent knots of star formation.

+

RandomKnots profiles have “shape noise” that depends on the number of point +sources used. For example, using the default Gaussian distribution, with +100 points, the shape noise is g~0.05, and this will decrease as more +points are added. The profile can be sheared to give additional +ellipticity, for example to follow that of an associated disk.

+

The requested half light radius (hlr) should be thought of as a rough +value. With a finite number point sources the actual realized hlr will be +noisy.

+
+

Note

+

If providing an input profile object, it must be “shoot-able”. Objects that +cannot be drawn with method='phot' cannot be used as the profile parameter here.

+
+
+
Parameters:
+
    +
  • npoints – Number of point sources to generate.

  • +
  • half_light_radius – Optional half light radius of the distribution of points. This value +is used for a Gaussian distribution if an explicit profile is not sent. +This is the mean half light radius produced by an infinite number of +points. A single instance will be noisy. [default None]

  • +
  • flux – Optional total flux in all point sources. This value is used for a +Gaussian distribution if an explicit profile is not sent. Defaults to +None if profile is sent, otherwise 1. [default: None]

  • +
  • profile – Optional profile to use for drawing points. If a profile is sent, the +half_light_radius and flux keywords are invalid. [default: None]

  • +
  • rng – Optional random number generator. Can be any galsim.BaseDeviate. If +None, the rng is created internally. [default: None]

  • +
  • gsparams – Optional GSParams for the objects representing each point source. +[default: None]

  • +
+
+
Attributes:
+
    +
  • npoints – The number of points to use as knots

  • +
  • input_half_light_radius – The input half_light_radius

  • +
  • flux – The flux

  • +
  • points – The array of x,y offsets used to create the point sources

  • +
+
+
+
+

Note

+

The algorithm was originally a modified version of that presented in +https://arxiv.org/abs/1312.5514v3. However, we now use the GalSim photon shooting +mechanism, which allows the knots to trace any profile, not just a Gaussian.

+
+
+
+calculateHLR()[source]
+

calculate the half-light radius of the generated points

+
+ +
+
+dilate(scale)[source]
+

Dilate the linear size of the profile by the given scale factor, while preserving +flux.

+

e.g. half_light_radius <– half_light_radius * scale

+

See expand() and magnify() for versions that preserve surface brightness, and thus +changes the flux.

+
+
Parameters:
+

scale – The linear rescaling factor to apply.

+
+
Returns:
+

the dilated object.

+
+
+
+ +
+
+expand(scale)[source]
+

Expand the linear size of the profile by the given scale factor, while preserving +surface brightness.

+

e.g. half_light_radius <– half_light_radius * scale

+

This doesn’t correspond to either of the normal operations one would typically want to do to +a galaxy. The functions dilate() and magnify() are the more typical usage. But this +function is conceptually simple. It rescales the linear dimension of the profile, while +preserving surface brightness. As a result, the flux will necessarily change as well.

+

See dilate() for a version that applies a linear scale factor while preserving flux.

+

See magnify() for a version that applies a scale factor to the area while preserving surface +brightness.

+
+
Parameters:
+

scale – The factor by which to scale the linear dimension of the object.

+
+
Returns:
+

the expanded object.

+
+
+
+ +
+
+property input_half_light_radius
+

Get the input half light radius (HLR).

+

Note the input HLR is not necessarily the realized HLR, +due to the finite number of points used in the profile.

+

If a profile is sent, and that profile is a Transformation object (e.g. +it has been sheared, its flux set, etc), then this value will be None.

+

You can get the calculated half light radius using the calculateHLR +method. That value will be valid in all cases.

+
+ +
+
+property npoints
+

The number of point sources.

+
+ +
+
+rotate(theta)[source]
+

Rotate this object by an Angle theta.

+
+
Parameters:
+

theta – Rotation angle (Angle object, positive means anticlockwise).

+
+
Returns:
+

the rotated object.

+
+
+
+ +
+
+shear(*args, **kwargs)[source]
+

Create a version of the current object with an area-preserving shear applied to it.

+

The arguments may be either a Shear instance or arguments to be used to initialize one.

+

For more details about the allowed keyword arguments, see the Shear docstring.

+

The shear() method precisely preserves the area. To include a lensing distortion with +the appropriate change in area, either use shear() with magnify(), or use lens(), which +combines both operations.

+
+
Parameters:
+

shear – The Shear to be applied. Or, as described above, you may instead supply +parameters do construct a shear directly. eg. obj.shear(g1=g1,g2=g2).

+
+
Returns:
+

the sheared object.

+
+
+
+ +
+
+shift(*args, **kwargs)[source]
+

Create a version of the current object shifted by some amount in real space.

+

After this call, the caller’s type will be a GSObject. +This means that if the caller was a derived type that had extra methods or properties +beyond those defined in GSObject (e.g. Gaussian.sigma), then these methods are no +longer available.

+

Note: in addition to the dx,dy parameter names, you may also supply dx,dy as a tuple, +or as a Position object.

+

The shift coordinates here are sky coordinates. GSObject profiles are always defined in +sky coordinates and only later (when they are drawn) is the connection to pixel coordinates +established (via a pixel_scale or WCS). So a shift of dx moves the object horizontally +in the sky (e.g. west in the local tangent plane of the observation), and dy moves the +object vertically (north in the local tangent plane).

+

The units are typically arcsec, but we don’t enforce that anywhere. The units here just +need to be consistent with the units used for any size values used by the GSObject. +The connection of these units to the eventual image pixels is defined by either the +pixel_scale or the wcs parameter of GSObject.drawImage.

+

Note: if you want to shift the object by a set number (or fraction) of pixels in the +drawn image, you probably want to use the offset parameter of GSObject.drawImage +rather than this method.

+
+
Parameters:
+
    +
  • dx – Horizontal shift to apply.

  • +
  • dy – Vertical shift to apply.

  • +
+
+
+

Alternatively, you may supply a single parameter as a Position instance, rather than +the two components separately if that is more convenient.

+
+
Parameter:

offset: The shift to apply, given as PositionD(dx,dy) or PositionI(dx,dy)

+
+
+
+
Returns:
+

the shifted object.

+
+
+
+ +
+
+transform(dudx, dudy, dvdx, dvdy)[source]
+

Create a version of the current object with an arbitrary Jacobian matrix transformation +applied to it.

+

This applies a Jacobian matrix to the coordinate system in which this object +is defined. It changes a profile defined in terms of (x,y) to one defined in +terms of (u,v) where:

+
+

u = dudx x + dudy y +v = dvdx x + dvdy y

+
+

That is, an arbitrary affine transform, but without the translation (which is +easily effected via the shift method).

+

Note that this function is similar to expand in that it preserves surface brightness, +not flux. If you want to preserve flux, you should also do:

+
>>> prof *= 1./abs(dudx*dvdy - dudy*dvdx)
+
+
+
+
Parameters:
+
    +
  • dudx – du/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dudy – du/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdx – dv/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdy – dv/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
+
+
Returns:
+

the transformed object

+
+
+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+
+withScaledFlux(flux_ratio)[source]
+

Create a version of the current object with the flux scaled by the given flux_ratio.

+

This function is equivalent to obj.withFlux(flux_ratio * obj.flux). Indeed, withFlux() +is implemented in terms of this one.

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location scaled by the given amount. If flux_ratio is an +SED, then the returned object is a ChromaticObject with the SED multiplied by +its current flux.

+

Note that in this case the flux attribute of the GSObject being scaled gets +interpreted as being dimensionless, instead of having its normal units of [photons/s/cm^2]. +The photons/s/cm^2 units are (optionally) carried by the SED instead, or even left out +entirely if the SED is dimensionless itself (see discussion in the ChromaticObject +docstring). The GSObject flux attribute does still contribute to the +ChromaticObject normalization, though. For example, the following are equivalent:

+
>>> chrom_obj = gsobj.withScaledFlux(sed * 3.0)
+>>> chrom_obj2 = (gsobj * 3.0).withScaledFlux(sed)
+
+
+

An equivalent, and usually simpler, way to effect this scaling is:

+
>>> obj = obj * flux_ratio
+
+
+
+
Parameters:
+

flux_ratio – The ratio by which to rescale the flux of the object when creating a new +one.

+
+
Returns:
+

the object with the new flux.

+
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/genindex.html b/docs/_build/html/genindex.html new file mode 100644 index 00000000000..a13b0dc6fbc --- /dev/null +++ b/docs/_build/html/genindex.html @@ -0,0 +1,4673 @@ + + + + + + Index — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ _ + | A + | B + | C + | D + | E + | F + | G + | H + | I + | J + | K + | L + | M + | N + | O + | P + | Q + | R + | S + | T + | U + | V + | W + | X + | Y + | Z + +
+

_

+ + + +
+ +

A

+ + + +
+ +

B

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

J

+ + + +
+ +

K

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

Q

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ +

X

+ + + +
+ +

Y

+ + + +
+ +

Z

+ + + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2023, GalSim-developers.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/gsobject.html b/docs/_build/html/gsobject.html new file mode 100644 index 00000000000..f45127d899c --- /dev/null +++ b/docs/_build/html/gsobject.html @@ -0,0 +1,1773 @@ + + + + + + + The GSObject base class — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The GSObject base class

+

This class defines most of the public API methods for how to use one of the various +surface brightness profiles like transforming it, drawing it, etc.

+

Note that not all methods are allowed to be called for all subclasses. For instance, +some classes only define the profile in Fourier space, so methods which need to access +the profile in real space may not be implemented. In such cases, a NotImplementedError +will be raised.

+
+
+class galsim.GSObject[source]
+

Base class for all GalSim classes that represent some kind of surface brightness profile.

+

A GSObject is not intended to be constructed directly. Normally, you would use whatever +derived class is appropriate for the surface brightness profile you want:

+
>>> gal = galsim.Sersic(n=4, half_light_radius=4.3)
+>>> psf = galsim.Moffat(beta=3, fwhm=2.85)
+>>> conv = galsim.Convolve([gal,psf])
+
+
+

All of these classes are subclasses of GSObject, so you should see those docstrings for +more details about how to construct the various profiles. Here we discuss attributes and +methods that are common to all GSObjects.

+

GSObjects are always defined in sky coordinates. So all sizes and other linear dimensions +should be in terms of some kind of units on the sky, arcsec for instance. Only later (when +they are drawn) is the connection to pixel coordinates established via a pixel scale or WCS. +(See the documentation for galsim.BaseWCS for more details about how to specify various kinds +of world coordinate systems more complicated than a simple pixel scale.)

+

For instance, if you eventually draw onto an image that has a pixel scale of 0.2 arcsec/pixel, +then the normal thing to do would be to define your surface brightness profiles in terms of +arcsec and then draw with pixel_scale=0.2. However, while arcsec are the usual choice of +units for the sky coordinates, if you wanted, you could instead define the sizes of all your +galaxies and PSFs in terms of radians and then use pixel_scale=0.2/206265 when you draw +them.

+

Transforming methods:

+

The GSObject class uses an “immutable” design[1], so all methods that would potentially modify +the object actually return a new object instead. This uses pointers and such behind the +scenes, so it all happens efficiently, but it makes using the objects a bit simpler, since +you don’t need to worry about some function changing your object behind your back.

+

In all cases below, we just give an example usage. See the docstrings for the methods for +more details about how to use them.:

+
>>> obj = obj.shear(shear)      # Apply a shear to the object.
+>>> obj = obj.dilate(scale)     # Apply a flux-preserving dilation.
+>>> obj = obj.magnify(mu)       # Apply a surface-brightness-preserving magnification.
+>>> obj = obj.rotate(theta)     # Apply a rotation.
+>>> obj = obj.shift(dx,dy)      # Shft the object in real space.
+>>> obj = obj.transform(dudx,dudy,dvdx,dvdy)    # Apply a general jacobian transformation.
+>>> obj = obj.lens(g1,g2,mu)    # Apply both a lensing shear and magnification.
+>>> obj = obj.withFlux(flux)    # Set a new flux value.
+>>> obj = obj * ratio           # Scale the surface brightness profile by some factor.
+
+
+

Access Methods:

+

There are some access methods and properties that are available for all GSObjects. +Again, see the docstrings for each method for more details.:

+
>>> obj.flux
+>>> obj.centroid
+>>> obj.nyquist_scale
+>>> obj.stepk
+>>> obj.maxk
+>>> obj.has_hard_edges
+>>> obj.is_axisymmetric
+>>> obj.is_analytic_x
+>>> obj.is_analytic_k
+>>> obj.xValue(x,y) or obj.xValue(pos)
+>>> obj.kValue(kx,ky) os obj.kValue(kpos)
+
+
+

Most subclasses have additional methods that are available for values that are particular to +that specific surface brightness profile. e.g. sigma = gauss.sigma. However, note +that class-specific methods are not available after performing one of the above transforming +operations.:

+
>>> gal = galsim.Gaussian(sigma=5)
+>>> gal = gal.shear(g1=0.2, g2=0.05)
+>>> sigma = gal.sigma               # This will raise an exception.
+
+
+

It is however possible to access the original object that was transformed via the +original attribute.:

+
>>> sigma = gal.original.sigma      # This works.
+
+
+

No matter how many transformations are performed, the original attribute will contain the +_original_ object (not necessarily the most recent ancestor).

+

Drawing Methods:

+

The main thing to do with a GSObject once you have built it is to draw it onto an image. +There are two methods that do this. In both cases, there are lots of optional parameters. +See the docstrings for these methods for more details.:

+
>>> image = obj.drawImage(...)
+>>> kimage = obj.drawKImage(...)
+
+
+

There two attributes that may be available for a GSObject.

+
+
Attributes:
+
    +
  • original – This was mentioned above as a way to access the original object that has +been transformed by one of the transforming methods.

  • +
  • noise – Some types, like RealGalaxy, set this attribute to be the intrinsic noise that +is already inherent in the profile and will thus be present when you draw the +object. The noise is propagated correctly through the various transforming +methods, as well as convolutions and flux rescalings. Note that the noise +attribute can be set directly by users even for GSObjects that do not naturally +have one. The typical use for this attribute is to use it to whiten the noise in +the image after drawing. See BaseCorrelatedNoise for more details.

  • +
+
+
+

GSParams:

+

All GSObject classes take an optional gsparams argument, so we document that feature here. +For all documentation about the specific derived classes, please see the docstring for each +one individually.

+

The gsparams argument can be used to specify various numbers that govern the tradeoff +between accuracy and speed for the calculations made in drawing a GSObject. The numbers are +encapsulated in a class called GSParams, and the user should make careful choices whenever +they opt to deviate from the defaults. For more details about the parameters and their default +values, please see the docstring of the GSParams class.

+

For example, let’s say you want to do something that requires an FFT larger than 4096 x 4096 +(and you have enough memory to handle it!). Then you can create a new GSParams object with a +larger maximum_fft_size and pass that to your GSObject on construction:

+
>>> gal = galsim.Sersic(n=4, half_light_radius=4.3)
+>>> psf = galsim.Moffat(beta=3, fwhm=2.85)
+>>> conv = galsim.Convolve([gal,psf])
+>>> im = galsim.Image(1000,1000, scale=0.02)        # Note the very small pixel scale!
+>>> im = conv.drawImage(image=im)                   # This uses the default GSParams.
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+  File "galsim/gsobject.py", line 1666, in drawImage
+    added_photons = prof.drawFFT(draw_image, add)
+  File "galsim/gsobject.py", line 1877, in drawFFT
+    kimage, wrap_size = self.drawFFT_makeKImage(image)
+  File "galsim/gsobject.py", line 1802, in drawFFT_makeKImage
+    raise GalSimFFTSizeError("drawFFT requires an FFT that is too large.", Nk)
+galsim.errors.GalSimFFTSizeError: drawFFT requires an FFT that is too large.
+The required FFT size would be 12288 x 12288, which requires 3.38 GB of memory.
+If you can handle the large FFT, you may update gsparams.maximum_fft_size.
+>>> big_fft_params = galsim.GSParams(maximum_fft_size=12300)
+>>> conv = galsim.Convolve([gal,psf],gsparams=big_fft_params)
+>>> im = conv.drawImage(image=im)                   # Now it works (but is slow!)
+>>> im.write('high_res_sersic.fits')
+
+
+

Note that for compound objects such as Convolution or Sum, not all GSParams can be +changed when the compound object is created. In the example given here, it is possible to +change parameters related to the drawing, but not the Fourier space parameters for the +components that go into the Convolution. To get better sampling in Fourier space, +for example, the gal and/or psf should be created with gsparams that have a +non-default value of folding_threshold. This statement applies to the threshold and +accuracy parameters.

+
+
+__add__(other)[source]
+

Add two GSObjects.

+

Equivalent to Add(self, other)

+
+ +
+
+__sub__(other)[source]
+

Subtract two GSObjects.

+

Equivalent to Add(self, -1 * other)

+
+ +
+
+__mul__(other)[source]
+

Scale the flux of the object by the given factor.

+

obj * flux_ratio is equivalent to obj.withScaledFlux(flux_ratio)

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location scaled by the given amount.

+

You can also multiply by an SED, which will create a ChromaticObject where the SED +acts like a wavelength-dependent flux_ratio.

+
+ +
+
+__rmul__(other)[source]
+

Equivalent to obj * other. See __mul__ for details.

+
+ +
+
+__div__(other)[source]
+

Equivalent to obj * (1/other). See __mul__ for details.

+
+ +
+
+_xValue(pos)[source]
+

Equivalent to xValue, but pos must be a galsim.PositionD instance

+
+
Parameters:
+

pos – The position at which you want the surface brightness of the object.

+
+
Returns:
+

the surface brightness at that position.

+
+
+
+ +
+
+_kValue(kpos)[source]
+

Equivalent to kValue, but kpos must be a galsim.PositionD instance.

+
+ +
+
+_shear(shear)[source]
+

Equivalent to GSObject.shear, but without the overhead of sanity checks or other +ways to input the shear value.

+

Also, it won’t propagate any noise attribute.

+
+
Parameters:
+

shear – The Shear to be applied.

+
+
Returns:
+

the sheared object.

+
+
+
+ +
+
+_shift(dx, dy)[source]
+

Equivalent to shift, but without the overhead of sanity checks or option +to give the shift as a PositionD.

+

Also, it won’t propagate any noise attribute.

+
+
Parameters:
+
    +
  • dx – The x-component of the shift to apply

  • +
  • dy – The y-component of the shift to apply

  • +
+
+
Returns:
+

the shifted object.

+
+
+
+ +
+
+_drawReal(image, jac=None, offset=(0.0, 0.0), flux_scaling=1.0)[source]
+

A version of drawReal without the sanity checks or some options.

+

This is nearly equivalent to the regular drawReal(image, add_to_image=False), but +the image’s dtype must be either float32 or float64, and it must have a c_contiguous array +(image.iscontiguous must be True).

+
+ +
+
+_calculate_nphotons(n_photons, poisson_flux, max_extra_noise, rng)[source]
+

Calculate how many photons to shoot and what flux_ratio (called g) each one should +have in order to produce an image with the right S/N and total flux.

+

This routine is normally called by drawPhot.

+
+
Returns:
+

n_photons, g

+
+
+
+ +
+
+_shoot(photons, rng)[source]
+

Shoot photons into the given PhotonArray.

+

This is the backend implementation of shoot once the PhotonArray has been constructed.

+
+
Parameters:
+
    +
  • photons – A PhotonArray instance into which the photons should be placed.

  • +
  • rng – A BaseDeviate instance to use for the photon shooting,

  • +
+
+
+
+ +
+
+_drawKImage(image, jac=None)[source]
+

A version of drawKImage without the sanity checks or some options.

+

Equivalent to drawKImage(image, add_to_image=False, recenter=False, add_to_image=False), +but without the option to create the image automatically.

+

The input image must be provided as a complex Image instance (dtype=complex64 or +complex128), and the bounds should be set up appropriately (e.g. with 0,0 in the center if +so desired). This corresponds to recenter=False for the normal drawKImage. And, it must +have a c_contiguous array (image.iscontiguous must be True).

+
+
Parameters:
+

image – The Image onto which to draw the k-space image. [required]

+
+
+
+ +
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Apply this surface brightness profile as a convolution to an existing photon array.

+

This method allows a GSObject to duck type as a PhotonOp, so one can apply a PSF +in a photon_ops list.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use to effect the convolution. +[default: None]

  • +
+
+
+
+ +
+
+atRedshift(redshift)[source]
+

Create a version of the current object with a different redshift.

+

For regular GSObjects, this method doesn’t do anything aside from setting a redshift +attribute with the given value. But this allows duck typing with ChromaticObjects +where this function will adjust the SED appropriately.

+
+

Warning

+

This method has been deprecated as of version 2.5.3.

+
+
+
Returns:
+

the object with the new redshift

+
+
+
+ +
+
+calculateFWHM(size=None, scale=None, centroid=None)[source]
+

Returns the full-width half-maximum (FWHM) of the object.

+

If the profile has a fwhm attribute, it will just return that, but in the general case, +we draw the profile and estimate the FWHM directly.

+

As with calculateHLR and calculateMomentRadius, this function optionally takes size and +scale values to use for the image drawing. The default is to use the the Nyquist scale +for the pixel scale and let drawImage choose a size for the stamp that will enclose at +least 99.5% of the flux. These were found to produce results accurate to well below +one percent on our internal tests, so it is unlikely that you will want to adjust +them for accuracy. However, using a smaller size than default could help speed up +the calculation, since the default is usually much larger than is needed.

+
+
Parameters:
+
    +
  • size – If given, the stamp size to use for the drawn image. [default: None, +which will let drawImage choose the size automatically]

  • +
  • scale – If given, the pixel scale to use for the drawn image. [default: +self.nyquist_scale]

  • +
  • centroid – The position to use for the centroid. [default: self.centroid]

  • +
+
+
Returns:
+

an estimate of the full-width half-maximum in physical units

+
+
+
+ +
+
+calculateHLR(size=None, scale=None, centroid=None, flux_frac=0.5)[source]
+

Returns the half-light radius of the object.

+

If the profile has a half_light_radius attribute, it will just return that, but in the +general case, we draw the profile and estimate the half-light radius directly.

+

This function (by default at least) is only accurate to a few percent, typically. +Possibly worse depending on the profile being measured. If you care about a high +precision estimate of the half-light radius, the accuracy can be improved using the +optional parameter scale to change the pixel scale used to draw the profile.

+

The default scale is half the Nyquist scale, which were found to produce results accurate +to a few percent on our internal tests. Using a smaller scale will be more accurate at +the expense of speed.

+

In addition, you can optionally specify the size of the image to draw. The default size is +None, which means drawImage will choose a size designed to contain around 99.5% of the +flux. This is overkill for this calculation, so choosing a smaller size than this may +speed up this calculation somewhat.

+

Also, while the name of this function refers to the half-light radius, in fact it can also +calculate radii that enclose other fractions of the light, according to the parameter +flux_frac. E.g. for r90, you would set flux_frac=0.90.

+

The default scale should usually be acceptable for things like testing that a galaxy +has a reasonable resolution, but they should not be trusted for very fine grain +discriminations.

+
+
Parameters:
+
    +
  • size – If given, the stamp size to use for the drawn image. [default: None, +which will let drawImage choose the size automatically]

  • +
  • scale – If given, the pixel scale to use for the drawn image. [default: +0.5 * self.nyquist_scale]

  • +
  • centroid – The position to use for the centroid. [default: self.centroid]

  • +
  • flux_frac – The fraction of light to be enclosed by the returned radius. +[default: 0.5]

  • +
+
+
Returns:
+

an estimate of the half-light radius in physical units

+
+
+
+ +
+
+calculateMomentRadius(size=None, scale=None, centroid=None, rtype='det')[source]
+

Returns an estimate of the radius based on unweighted second moments.

+

The second moments are defined as:

+

Q_ij = int( I(x,y) i j dx dy ) / int( I(x,y) dx dy ) +where i,j may be either x or y.

+

If I(x,y) is a Gaussian, then T = Tr(Q) = Qxx + Qyy = 2 sigma^2. Thus, one reasonable +choice for a “radius” for an arbitrary profile is sqrt(T/2).

+

In addition, det(Q) = sigma^4. So another choice for an arbitrary profile is det(Q)^1/4.

+

This routine can return either of these measures according to the value of the rtype +parameter. rtype='trace' will cause it to return sqrt(T/2). rtype='det' will cause +it to return det(Q)^1/4. And rtype='both' will return a tuple with both values.

+

Note that for the special case of a Gaussian profile, no calculation is necessary, and +the sigma attribute will be used in both cases. In the limit as scale->0, this +function will return the same value, but because finite pixels are drawn, the results +will not be precisely equal for real use cases. The approximation being made is that +the integral of I(x,y) i j dx dy over each pixel can be approximated as +int(I(x,y) dx dy) * i_center * j_center.

+

This function (by default at least) is only accurate to a few percent, typically. +Possibly worse depending on the profile being measured. If you care about a high +precision estimate of the radius, the accuracy can be improved using the optional +parameters size and scale to change the size and pixel scale used to draw the profile.

+

The default is to use the the Nyquist scale for the pixel scale and let drawImage +choose a size for the stamp that will enclose at least 99.5% of the flux. These +were found to produce results accurate to a few percent on our internal tests. +Using a smaller scale and larger size will be more accurate at the expense of speed.

+

The default parameters should usually be acceptable for things like testing that a galaxy +has a reasonable resolution, but they should not be trusted for very fine grain +discriminations. For a more accurate estimate, see galsim.hsm.FindAdaptiveMom.

+
+
Parameters:
+
    +
  • size – If given, the stamp size to use for the drawn image. [default: None, +which will let drawImage choose the size automatically]

  • +
  • scale – If given, the pixel scale to use for the drawn image. [default: +self.nyquist_scale]

  • +
  • centroid – The position to use for the centroid. [default: self.centroid]

  • +
  • rtype – There are three options for this parameter: +- ‘trace’ means return sqrt(T/2) +- ‘det’ means return det(Q)^1/4 +- ‘both’ means return both: (sqrt(T/2), det(Q)^1/4) +[default: ‘det’]

  • +
+
+
Returns:
+

an estimate of the radius in physical units (or both estimates if rtype == ‘both’)

+
+
+
+ +
+
+property centroid
+

The (x, y) centroid of an object as a PositionD.

+
+ +
+
+dilate(scale)[source]
+

Dilate the linear size of the profile by the given scale factor, while preserving +flux.

+

e.g. half_light_radius <– half_light_radius * scale

+

See expand() and magnify() for versions that preserve surface brightness, and thus +changes the flux.

+
+
Parameters:
+

scale – The linear rescaling factor to apply.

+
+
Returns:
+

the dilated object.

+
+
+
+ +
+
+drawFFT(image, add_to_image=False)[source]
+

Draw this profile into an Image by computing the k-space image and performing an FFT.

+

This is usually called from the drawImage function, rather than called directly by the +user. In particular, the input image must be already set up with defined bounds. The +profile will be drawn centered on whatever pixel corresponds to (0,0) with the given +bounds, not the image center (unlike drawImage). The image also must have a PixelScale +wcs. The profile being drawn should have already been converted to image coordinates via:

+
>>> image_profile = original_wcs.toImage(original_profile)
+
+
+

Note that the Image produced by drawFFT represents the profile sampled at the center +of each pixel and then multiplied by the pixel area. That is, the profile is NOT +integrated over the area of the pixel. This is equivalent to method=’no_pixel’ in +drawImage. If you want to render a profile integrated over the pixel, you can convolve +with a Pixel first and draw that.

+
+
Parameters:
+
    +
  • image – The Image onto which to place the flux. [required]

  • +
  • add_to_image – Whether to add flux to the existing image rather than clear out +anything in the image before drawing. [default: False]

  • +
+
+
Returns:
+

The total flux drawn inside the image bounds.

+
+
+
+ +
+
+drawFFT_finish(image, kimage, wrap_size, add_to_image)[source]
+

This is a helper routine for drawFFT that finishes the calculation, based on the +drawn k-space image.

+

It applies the Fourier transform to kimage and adds the result to image.

+
+
Parameters:
+
    +
  • image – The Image onto which to place the flux.

  • +
  • kimage – The k-space Image where the object was drawn.

  • +
  • wrap_size – The size of the region to wrap kimage, which must be either the same +size as kimage or smaller.

  • +
  • add_to_image – Whether to add flux to the existing image rather than clear out +anything in the image before drawing.

  • +
+
+
Returns:
+

The total flux drawn inside the image bounds.

+
+
+
+ +
+
+drawFFT_makeKImage(image)[source]
+

This is a helper routine for drawFFT that just makes the (blank) k-space image +onto which the profile will be drawn. This can be useful if you want to break +up the calculation into parts for extra efficiency. E.g. save the k-space image of +the PSF so drawing many models of the galaxy with the given PSF profile can avoid +drawing the PSF each time.

+
+
Parameters:
+

image – The Image onto which to place the flux.

+
+
Returns:
+

(kimage, wrap_size), where wrap_size is either the size of kimage or smaller if +the result should be wrapped before doing the inverse fft.

+
+
+
+ +
+
+drawImage(image=None, nx=None, ny=None, bounds=None, scale=None, wcs=None, dtype=None, method='auto', area=1.0, exptime=1.0, gain=1.0, add_to_image=False, center=None, use_true_center=True, offset=None, n_photons=0.0, rng=None, max_extra_noise=0.0, poisson_flux=None, sensor=None, photon_ops=None, n_subsample=3, maxN=None, save_photons=False, bandpass=None, setup_only=False, surface_ops=None)[source]
+

Draws an Image of the object.

+

The drawImage() method is used to draw an Image of the current object using one of several +possible rendering methods (see below). It can create a new Image or can draw onto an +existing one if provided by the image parameter. If the image is given, you can +also optionally add to the given Image if add_to_image = True, but the default is to +replace the current contents with new values.

+

Providing an input image:

+

Note that if you provide an image parameter, it is the image onto which the profile +will be drawn. The provided image will be modified. A reference to the same image +is also returned to provide a parallel return behavior to when image is None +(described above).

+

This option is useful in practice because you may want to construct the image first and +then draw onto it, perhaps multiple times. For example, you might be drawing onto a +subimage of a larger image. Or you may want to draw different components of a complex +profile separately. In this case, the returned value is typically ignored. For example:

+
>>> im1 = bulge.drawImage()
+>>> im2 = disk.drawImage(image=im1, add_to_image=True)
+>>> assert im1 is im2
+
+>>> full_image = galsim.Image(2048, 2048, scale=pixel_scale)
+>>> b = galsim.BoundsI(x-32, x+32, y-32, y+32)
+>>> stamp = obj.drawImage(image = full_image[b])
+>>> assert (stamp.array == full_image[b].array).all()
+
+
+

Letting drawImage create the image for you:

+

If drawImage() will be creating the image from scratch for you, then there are several ways +to control the size of the new image. If the nx and ny keywords are present, then +an image with these numbers of pixels on a side will be created. Similarly, if the bounds +keyword is present, then an image with the specified bounds will be created. Note that it +is an error to provide an existing Image when also specifying nx, ny, or +bounds. In the absence of nx, ny, and bounds, drawImage will decide a good +size to use based on the size of the object being drawn. Basically, it will try to use an +area large enough to include at least 99.5% of the flux.

+
+

Note

+

This value 0.995 is really 1 - folding_threshold. You can change the value of +folding_threshold for any object via GSParams.

+
+

You can set the pixel scale of the constructed image with the scale parameter, or set +a WCS function with wcs. If you do not provide either scale or wcs, then +drawImage() will default to using the Nyquist scale for the current object.

+

You can also set the data type used in the new Image with the dtype parameter that has +the same options as for the Image constructor.

+

The drawing “method”:

+

There are several different possible methods drawImage() can use for rendering the image. +This is set by the method parameter. The options are:

+
+
auto

This is the default, which will normally be equivalent to ‘fft’. However, +if the object being rendered is simple (no convolution) and has hard edges +(e.g. a Box or a truncated Moffat or Sersic), then it will switch to +‘real_space’, since that is often both faster and more accurate in these +cases (due to ringing in Fourier space).

+
+
fft

The integration of the light within each pixel is mathematically equivalent +to convolving by the pixel profile (a Pixel object) and sampling the result +at the centers of the pixels. This method will do that convolution using +a discrete Fourier transform. Furthermore, if the object (or any component +of it) has been transformed via shear(), dilate(), etc., then these +transformations are done in Fourier space as well.

+
+
real_space

This uses direct integrals (using the Gauss-Kronrod-Patterson algorithm) +in real space for the integration over the pixel response. It is usually +slower than the ‘fft’ method, but if the profile has hard edges that cause +ringing in Fourier space, it can be faster and/or more accurate. If you +use ‘real_space’ with something that is already a Convolution, then this +will revert to ‘fft’, since the double convolution that is required to also +handle the pixel response is far too slow to be practical using real-space +integrals.

+
+
phot

This uses a technique called photon shooting to render the image. +Essentially, the object profile is taken as a probability distribution +from which a finite number of photons are “shot” onto the image. Each +photon’s flux gets added to whichever pixel the photon hits. This process +automatically accounts for the integration of the light over the pixel +area, since all photons that hit any part of the pixel are counted. +Convolutions and transformations are simple geometric processes in this +framework. However, there are two caveats with this method: (1) the +resulting image will have Poisson noise from the finite number of photons, +and (2) it is not available for all object types (notably anything that +includes a Deconvolution).

+
+
no_pixel

Instead of integrating over the pixels, this method will sample the profile +at the centers of the pixels and multiply by the pixel area. If there is +a convolution involved, the choice of whether this will use an FFT or +real-space calculation is governed by the real_space parameter of the +Convolution class. This method is the appropriate choice if you are using +a PSF that already includes a convolution by the pixel response. For +example, if you are using a PSF from an observed image of a star, then it +has already been convolved by the pixel, so you would not want to do so +again. Note: The multiplication by the pixel area gets the flux +normalization right for the above use case. cf. method = 'sb'.

+
+
sb

This is a lot like ‘no_pixel’, except that the image values will simply be +the sampled object profile’s surface brightness, not multiplied by the +pixel area. This does not correspond to any real observing scenario, but +it could be useful if you want to view the surface brightness profile of an +object directly, without including the pixel integration.

+
+
+

The ‘phot’ method has a few extra parameters that adjust how it functions. The total +number of photons to shoot is normally calculated from the object’s flux. This flux is +taken to be given in photons/cm^2/s, so for most simple profiles, this times area * exptime +will equal the number of photons shot. (See the discussion in Rowe et al, 2015, for why +this might be modified for InterpolatedImage and related profiles.) However, you can +manually set a different number of photons with n_photons. You can also set +max_extra_noise to tell drawImage() to use fewer photons than normal (and so is faster) +such that no more than that much extra noise is added to any pixel. This is particularly +useful if you will be subsequently adding sky noise, and you can thus tolerate more noise +than the normal number of photons would give you, since using fewer photons is of course +faster. Finally, the default behavior is to have the total flux vary as a Poisson random +variate, which is normally appropriate with photon shooting. But you can turn this off with +poisson_flux=False. It also defaults to False if you set an explicit value for +n_photons.

+

Given the periodicity implicit in the use of FFTs, there can occasionally be artifacts due +to wrapping at the edges, particularly for objects that are quite extended (e.g., due to +the nature of the radial profile). See GSParams for parameters that you can use to reduce +the level of these artifacts, in particular folding_threshold may be helpful if you see +such artifacts in your images.

+

Setting the offset:

+

The object will by default be drawn with its nominal center at the center location of the +image. There is thus a qualitative difference in the appearance of the rendered profile +when drawn on even- and odd-sized images. For a profile with a maximum at (0,0), this +maximum will fall in the central pixel of an odd-sized image, but in the corner of the four +central pixels of an even-sized image. There are three parameters that can affect this +behavior. First, you can specify any arbitrary pixel position to center the object using +the center parameter. If this is None, then it will pick one of the two potential +“centers” of the image, either image.true_center or image.center. The latter is +an integer position, which always corresponds to the center of some pixel, which for even +sized images won’t (cannot) be the actual “true” center of the image. You can choose which +of these two centers you want to use with the use_true_center parameters, which +defaults to False. You can also arbitrarily offset the profile from the image center with +the offset parameter to handle any aribtrary offset you want from the chosen center. +(Typically, one would use only one of center or offset but it is permissible to use +both.)

+

Setting the overall normalization:

+

Normally, the flux of the object should be equal to the sum of all the pixel values in the +image, less some small amount of flux that may fall off the edge of the image (assuming you +don’t use method='sb'). However, you may optionally set a gain value, which +converts between photons and ADU (so-called analog-to-digital units), the units of the +pixel values in real images. Normally, the gain of a CCD is in electrons/ADU, but in +GalSim, we fold the quantum efficiency into the gain as well, so the units are photons/ADU.

+

Another caveat is that, technically, flux is really in units of photons/cm^2/s, not photons. +So if you want, you can keep track of this properly and provide an area and exptime +here. This detail is more important with chromatic objects where the SED is typically +given in erg/cm^2/s/nm, so the exposure time and area are important details. With achromatic +objects however, it is often more convenient to ignore these details and just consider the +flux to be the total number of photons for this exposure, in which case, you would leave the +area and exptime parameters at their default value of 1.

+

On return, the image will have an attribute added_flux, which will be set to the total +flux added to the image. This may be useful as a sanity check that you have provided a +large enough image to catch most of the flux. For example:

+
>>> obj.drawImage(image)
+>>> assert image.added_flux > 0.99 * obj.flux
+
+
+

The appropriate threshold will depend on your particular application, including what kind +of profile the object has, how big your image is relative to the size of your object, +whether you are keeping poisson_flux=True, etc.

+

The following code snippet illustrates how gain, exptime, area, and method +can all influence the relationship between the flux attribute of a GSObject and +both the pixel values and .added_flux attribute of an Image drawn with +drawImage():

+
>>> obj = galsim.Gaussian(fwhm=1)
+>>> obj.flux
+1.0
+>>> im = obj.drawImage()
+>>> im.added_flux
+0.9999630988657515
+>>> im.array.sum()
+0.99996305
+>>> im = obj.drawImage(exptime=10, area=10)
+>>> im.added_flux
+0.9999630988657525
+>>> im.array.sum()
+99.996315
+>>> im = obj.drawImage(exptime=10, area=10, method='sb', scale=0.5, nx=10, ny=10)
+>>> im.added_flux
+0.9999973790505298
+>>> im.array.sum()
+399.9989
+>>> im = obj.drawImage(exptime=10, area=10, gain=2)
+>>> im.added_flux
+0.9999630988657525
+>>> im.array.sum()
+49.998158
+
+
+

Using a non-trivial sensor:

+

Normally the sensor is modeled as an array of pixels where any photon that hits a given +pixel is accumulated into that pixel. The final pixel value then just reflects the total +number of pixels that hit each sensor. However, real sensors do not (quite) work this way.

+

In real CCDs, the photons travel some distance into the silicon before converting to +electrons. Then the electrons diffuse laterally some amount as they are pulled by the +electric field toward the substrate. Finally, previous electrons that have already been +deposited will repel subsequent electrons, both slowing down their descent, leading to +more diffusion, and pushing them laterally toward neighboring pixels, which is called +the brighter-fatter effect.

+

Users interested in modeling this kind of effect can supply a sensor object to use +for the accumulation step. See SiliconSensor for a class that models silicon-based CCD +sensors.

+

Some related effects may need to be done to the photons at the surface layer before being +passed into the sensor object. For instance, the photons may need to be given appropriate +incidence angles according to the optics of the telescope (since this matters for where the +photons are converted to electrons). You may also need to give the photons wavelengths +according to the SED of the object. Such steps are specified in a photon_ops +parameter, which should be a list of any such operations you wish to perform on the photon +array before passing them to the sensor. See FRatioAngles and WavelengthSampler for +two examples of such photon operators.

+

Since the sensor deals with photons, it is most natural to use this feature in conjunction +with photon shooting (method='phot'). However, it is allowed with FFT methods too. +But there is a caveat one should be aware of in this case. The FFT drawing is used to +produce an intermediate image, which is then converted to a PhotonArray using the +factory function PhotonArray.makeFromImage. This assigns photon positions randomly +within each pixel where they were drawn, which isn’t always a particularly good +approximation.

+

To improve this behavior, the intermediate image is drawn with smaller pixels than the +target image, so the photons are given positions closer to their true locations. The +amount of subsampling is controlled by the n_subsample parameter, which defaults to 3. +Larger values will be more accurate at the expense of larger FFTs (i.e. slower and using +more memory).

+
+
Parameters:
+
    +
  • image – If provided, this will be the image on which to draw the profile. +If image is None, then an automatically-sized Image will be +created. If image is given, but its bounds are undefined (e.g. if +it was constructed with image = galsim.Image()), then it will be +resized appropriately based on the profile’s size [default: None].

  • +
  • nx – If provided and image is None, use to set the x-direction size of +the image. Must be accompanied by ny.

  • +
  • ny – If provided and image is None, use to set the y-direction size of +the image. Must be accompanied by nx.

  • +
  • bounds – If provided and image is None, use to set the bounds of the image.

  • +
  • scale – If provided, use this as the pixel scale for the image. +If scale is None and image is given, then take the provided +image’s pixel scale. +If scale is None and image is None, then use the Nyquist scale. +If scale <= 0 (regardless of image), then use the Nyquist scale. +If scale > 0 and image is given, then override image.scale +with the value given as a keyword. [default: None]

  • +
  • wcs – If provided, use this as the wcs for the image (possibly overriding any +existing image.wcs). At most one of scale or wcs may be +provided. [default: None]

  • +
  • dtype – The data type to use for an automatically constructed image. Only +valid if image is None. [default: None, which means to use +numpy.float32]

  • +
  • method – Which method to use for rendering the image. See discussion above +for the various options and what they do. [default: ‘auto’]

  • +
  • area – Collecting area of telescope in cm^2. [default: 1.]

  • +
  • exptime – Exposure time in s. [default: 1.]

  • +
  • gain – The number of photons per ADU (“analog to digital units”, the units of +the numbers output from a CCD). [default: 1]

  • +
  • add_to_image – Whether to add flux to the existing image rather than clear out +anything in the image before drawing. +Note: This requires that image be provided and that it have defined +bounds. [default: False]

  • +
  • center – The position on the image at which to place the nominal center of the +object (usually the centroid, but not necessarily). [default: None]

  • +
  • use_true_center – If center is None, then the object is normally centered at the +true center of the image (using the property image.true_center). +If you would rather use the integer center (given by image.center), +set this to False. [default: True]

  • +
  • offset – The offset in pixel coordinates at which to center the profile being +drawn relative to either center (if given) or the center of the +image (either the true center or integer center according to the +use_true_center parameter). [default: None]

  • +
  • n_photons – If provided, the number of photons to use for photon shooting. +If not provided (i.e. n_photons = 0), use as many photons as +necessary to result in an image with the correct Poisson shot +noise for the object’s flux. For positive definite profiles, this +is equivalent to n_photons = flux. However, some profiles need +more than this because some of the shot photons are negative +(usually due to interpolants). +[default: 0]

  • +
  • rng – If provided, a random number generator to use for photon shooting, +which may be any kind of BaseDeviate object. If rng is None, one +will be automatically created. [default: None]

  • +
  • max_extra_noise – If provided, the allowed extra noise in each pixel when photon +shooting. This is only relevant if n_photons=0, so the number of +photons is being automatically calculated. In that case, if the image +noise is dominated by the sky background, then you can get away with +using fewer shot photons than the full n_photons = flux. +Essentially each shot photon can have a flux > 1, which increases +the noise in each pixel. The max_extra_noise parameter specifies +how much extra noise per pixel is allowed because of this approximation. +A typical value for this might be max_extra_noise = sky_level / 100 +where sky_level is the flux per pixel due to the sky. Note that +this uses a “variance” definition of noise, not a “sigma” definition. +[default: 0.]

  • +
  • poisson_flux – Whether to allow total object flux scaling to vary according to +Poisson statistics for n_photons samples when photon shooting. +[default: True, unless n_photons is given, in which case the default +is False]

  • +
  • sensor – An optional Sensor instance, which will be used to accumulate the +photons onto the image. [default: None]

  • +
  • photon_ops – A list of operators that can modify the photon array that will be +applied in order before accumulating the photons on the sensor. +[default: None]

  • +
  • n_subsample – The number of sub-pixels per final pixel to use for fft drawing when +using a sensor. The sensor step needs to know the sub-pixel positions +of the photons, which is lost in the fft method. So using smaller +pixels for the fft step keeps some of that information, making the +assumption of uniform flux per pixel less bad of an approximation. +[default: 3]

  • +
  • maxN

    Sets the maximum number of photons that will be added to the image +at a time. (Memory requirements are proportional to this number.)

    +
    +

    Note

    +

    Using this parameter will not necessarily produce identical +results as when not using it due to potentially different order +of various random number generations in either the photon_ops, +or the sensor, or (for method=’fft’) the conversion of the FFT +image to photons.

    +
    +

    [default: None, which means no limit]

    +

  • +
  • save_photons – If True, save the PhotonArray as image.photons. Only valid if +method is ‘phot’ or sensor is not None. [default: False]

  • +
  • bandpass – This parameter is ignored, but is allowed to enable duck typing +eqivalence between this method and the ChromaticObject.drawImage +method. [default: None]

  • +
  • setup_only – Don’t actually draw anything on the image. Just make sure the image +is set up correctly. This is used internally by GalSim, but there +may be cases where the user will want the same functionality. +[default: False]

  • +
+
+
Returns:
+

the drawn Image.

+
+
+
+ +
+
+drawKImage(image=None, nx=None, ny=None, bounds=None, scale=None, add_to_image=False, recenter=True, bandpass=None, setup_only=False)[source]
+

Draws the k-space (complex) Image of the object, with bounds optionally set by input +Image instance.

+

Normalization is always such that image(0,0) = flux. Unlike the real-space drawImage +function, the (0,0) point will always be one of the actual pixel values. For even-sized +images, it will be 1/2 pixel above and to the right of the true center of the image.

+

Another difference from drawImage is that a wcs other than a simple pixel scale is not +allowed. There is no wcs parameter here, and if the images have a non-trivial wcs (and +you don’t override it with the scale parameter), a TypeError will be raised.

+

Also, there is no convolution by a pixel. This is just a direct image of the Fourier +transform of the surface brightness profile.

+
+
Parameters:
+
    +
  • image – If provided, this will be the Image onto which to draw the k-space +image. If image is None, then an automatically-sized image will be +created. If image is given, but its bounds are undefined, then it +will be resized appropriately based on the profile’s size. +[default: None]

  • +
  • nx – If provided and image is None, use to set the x-direction size of +the image. Must be accompanied by ny.

  • +
  • ny – If provided and image is None, use to set the y-direction size of +the image. Must be accompanied by nx.

  • +
  • bounds – If provided and image is None, use to set the bounds of the image.

  • +
  • scale – If provided, use this as the pixel scale, dk, for the images. +If scale is None and image is given, then take the provided +images’ pixel scale (which must be equal). +If scale is None and image is None, then use the Nyquist scale. +If scale <= 0 (regardless of image), then use the Nyquist scale. +[default: None]

  • +
  • add_to_image – Whether to add to the existing images rather than clear out +anything in the image before drawing. +Note: This requires that image be provided and that it has defined +bounds. [default: False]

  • +
  • recenter – Whether to recenter the image to put k = 0 at the center (True) or to +trust the provided bounds (False). [default: True]

  • +
  • bandpass – This parameter is ignored, but is allowed to enable duck typing +eqivalence between this method and the ChromaticObject.drawImage +method. [default: None]

  • +
  • setup_only – Don’t actually draw anything on the image. Just make sure the image +is set up correctly. This is used internally by GalSim, but there +may be cases where the user will want the same functionality. +[default: False]

  • +
+
+
Returns:
+

an Image instance (created if necessary)

+
+
+
+ +
+
+drawPhot(image, gain=1.0, add_to_image=False, n_photons=0, rng=None, max_extra_noise=0.0, poisson_flux=None, sensor=None, photon_ops=(), maxN=None, orig_center=galsim.PositionI(x=0, y=0), local_wcs=None, surface_ops=None)[source]
+

Draw this profile into an Image by shooting photons.

+

This is usually called from the drawImage function, rather than called directly by the +user. In particular, the input image must be already set up with defined bounds. The +profile will be drawn centered on whatever pixel corresponds to (0,0) with the given +bounds, not the image center (unlike drawImage). The image also must have a PixelScale +wcs. The profile being drawn should have already been converted to image coordinates via:

+
>>> image_profile = original_wcs.toImage(original_profile)
+
+
+

Note that the image produced by drawPhot represents the profile integrated over the +area of each pixel. This is equivalent to convolving the profile by a square Pixel +profile and sampling the value at the center of each pixel, although this happens +automatically by the shooting algorithm, so you do not need to manually convolve by +a Pixel as you would for drawReal or drawFFT.

+
+
Parameters:
+
    +
  • image – The Image onto which to place the flux. [required]

  • +
  • gain – The number of photons per ADU (“analog to digital units”, the units of +the numbers output from a CCD). [default: 1.]

  • +
  • add_to_image – Whether to add to the existing images rather than clear out +anything in the image before drawing. [default: False]

  • +
  • n_photons – If provided, the number of photons to use for photon shooting. +If not provided (i.e. n_photons = 0), use as many photons as +necessary to result in an image with the correct Poisson shot +noise for the object’s flux. For positive definite profiles, this +is equivalent to n_photons = flux. However, some profiles need +more than this because some of the shot photons are negative +(usually due to interpolants). [default: 0]

  • +
  • rng – If provided, a random number generator to use for photon shooting, +which may be any kind of BaseDeviate object. If rng is None, one +will be automatically created, using the time as a seed. +[default: None]

  • +
  • max_extra_noise – If provided, the allowed extra noise in each pixel when photon +shooting. This is only relevant if n_photons=0, so the number of +photons is being automatically calculated. In that case, if the image +noise is dominated by the sky background, then you can get away with +using fewer shot photons than the full n_photons = flux. +Essentially each shot photon can have a flux > 1, which increases +the noise in each pixel. The max_extra_noise parameter specifies +how much extra noise per pixel is allowed because of this approximation. +A typical value for this might be max_extra_noise = sky_level / 100 +where sky_level is the flux per pixel due to the sky. Note that +this uses a “variance” definition of noise, not a “sigma” definition. +[default: 0.]

  • +
  • poisson_flux – Whether to allow total object flux scaling to vary according to +Poisson statistics for n_photons samples when photon shooting. +[default: True, unless n_photons is given, in which case the default +is False]

  • +
  • sensor – An optional Sensor instance, which will be used to accumulate the +photons onto the image. [default: None]

  • +
  • photon_ops – A list of operators that can modify the photon array that will be +applied in order before accumulating the photons on the sensor. +[default: ()]

  • +
  • maxN – Sets the maximum number of photons that will be added to the image +at a time. (Memory requirements are proportional to this number.) +[default: None, which means no limit]

  • +
  • orig_center – The position of the image center in the original image coordinates. +[default: (0,0)]

  • +
  • local_wcs – The local wcs in the original image. [default: None]

  • +
+
+
Returns:
+

    +
  • added_flux is the total flux of photons that landed inside the image bounds, and

  • +
  • photons is the PhotonArray that was applied to the image.

  • +
+

+
+
Return type:
+

(added_flux, photons) where

+
+
+
+ +
+
+drawReal(image, add_to_image=False)[source]
+

Draw this profile into an Image by direct evaluation at the location of each pixel.

+

This is usually called from the drawImage function, rather than called directly by the +user. In particular, the input image must be already set up with defined bounds. The +profile will be drawn centered on whatever pixel corresponds to (0,0) with the given +bounds, not the image center (unlike drawImage). The image also must have a PixelScale +wcs. The profile being drawn should have already been converted to image coordinates via:

+
>>> image_profile = original_wcs.toImage(original_profile)
+
+
+

Note that the image produced by drawReal represents the profile sampled at the center +of each pixel and then multiplied by the pixel area. That is, the profile is NOT +integrated over the area of the pixel. This is equivalent to method=’no_pixel’ in +drawImage. If you want to render a profile integrated over the pixel, you can convolve +with a Pixel first and draw that.

+
+
Parameters:
+
    +
  • image – The Image onto which to place the flux. [required]

  • +
  • add_to_image – Whether to add flux to the existing image rather than clear out +anything in the image before drawing. [default: False]

  • +
+
+
Returns:
+

The total flux drawn inside the image bounds.

+
+
+
+ +
+
+expand(scale)[source]
+

Expand the linear size of the profile by the given scale factor, while preserving +surface brightness.

+

e.g. half_light_radius <– half_light_radius * scale

+

This doesn’t correspond to either of the normal operations one would typically want to do to +a galaxy. The functions dilate() and magnify() are the more typical usage. But this +function is conceptually simple. It rescales the linear dimension of the profile, while +preserving surface brightness. As a result, the flux will necessarily change as well.

+

See dilate() for a version that applies a linear scale factor while preserving flux.

+

See magnify() for a version that applies a scale factor to the area while preserving surface +brightness.

+
+
Parameters:
+

scale – The factor by which to scale the linear dimension of the object.

+
+
Returns:
+

the expanded object.

+
+
+
+ +
+
+property flux
+

The flux of the profile.

+
+ +
+
+getGoodImageSize(pixel_scale)[source]
+

Return a good size to use for drawing this profile.

+

The size will be large enough to cover most of the flux of the object. Specifically, +at least (1-gsparams.folding_threshold) (i.e. 99.5% by default) of the flux should fall +in the image.

+

Also, the returned size is always an even number, which is usually desired in practice. +Of course, if you prefer an odd-sized image, you can add 1 to the result.

+
+
Parameters:
+

pixel_scale – The desired pixel scale of the image to be built.

+
+
Returns:
+

N, a good (linear) size of an image on which to draw this object.

+
+
+
+ +
+
+property gsparams
+

A GSParams object that sets various parameters relevant for speed/accuracy trade-offs.

+
+ +
+
+property has_hard_edges
+

Whether there are any hard edges in the profile, which would require very small k +spacing when working in the Fourier domain.

+
+ +
+
+property is_analytic_k
+

Whether the k-space values can be determined immediately at any position without +requiring a Discrete Fourier Transform.

+
+ +
+
+property is_analytic_x
+

Whether the real-space values can be determined immediately at any position without +requiring a Discrete Fourier Transform.

+
+ +
+
+property is_axisymmetric
+

Whether the profile is axially symmetric; affects efficiency of evaluation.

+
+ +
+
+kValue(*args, **kwargs)[source]
+

Returns the value of the object at a chosen 2D position in k space.

+

This function returns the amplitude of the fourier transform of the surface brightness +profile at a given position in k space. The position argument may be provided as a +Position argument, or it may be given as kx,ky (either as a tuple or as two arguments).

+

Technically, kValue() is available if and only if the given obj has obj.is_analytic_k +== True, but this is the case for all GSObject classes currently, so that should never +be an issue (unlike for xValue).

+
+
Parameters:
+

position – The position in k space at which you want the fourier amplitude.

+
+
Returns:
+

the amplitude of the fourier transform at that position.

+
+
+
+ +
+
+lens(g1, g2, mu)[source]
+

Create a version of the current object with both a lensing shear and magnification +applied to it.

+

This GSObject method applies a lensing (reduced) shear and magnification. The shear must +be specified using the g1, g2 definition of shear (see Shear for more details). +This is the same definition as the outputs of the PowerSpectrum and NFWHalo classes, which +compute shears according to some lensing power spectrum or lensing by an NFW dark matter +halo. The magnification determines the rescaling factor for the object area and flux, +preserving surface brightness.

+
+
Parameters:
+
    +
  • g1 – First component of lensing (reduced) shear to apply to the object.

  • +
  • g2 – Second component of lensing (reduced) shear to apply to the object.

  • +
  • mu – Lensing magnification to apply to the object. This is the factor by which +the solid angle subtended by the object is magnified, preserving surface +brightness.

  • +
+
+
Returns:
+

the lensed object.

+
+
+
+ +
+
+magnify(mu)[source]
+

Create a version of the current object with a lensing magnification applied to it, +scaling the area and flux by mu at fixed surface brightness.

+

This process applies a lensing magnification mu, which scales the linear dimensions of the +image by the factor sqrt(mu), i.e., half_light_radius <– +half_light_radius * sqrt(mu) while increasing the flux by a factor of mu. Thus, +magnify() preserves surface brightness.

+

See dilate() for a version that applies a linear scale factor while preserving flux.

+

See expand() for a version that applies a linear scale factor while preserving surface +brightness.

+
+
Parameters:
+

mu – The lensing magnification to apply.

+
+
Returns:
+

the magnified object.

+
+
+
+ +
+
+makePhot(n_photons=0, rng=None, max_extra_noise=0.0, poisson_flux=None, photon_ops=(), local_wcs=None, surface_ops=None)[source]
+

Make photons for a profile.

+

This is equivalent to drawPhot, except that the photons are not placed onto +an image. Instead, it just returns the PhotonArray.

+
+

Note

+

The (x,y) positions returned are in the same units as the distance units +of the GSObject being rendered. If you want (x,y) in pixel coordinates, you +should call this function for the profile in image coordinates:

+
>>> photons = image.wcs.toImage(obj).makePhot()
+
+
+

Or if you just want a simple pixel scale conversion from sky coordinates to image +coordinates, you can instead do

+
>>> photons = obj.makePhot()
+>>> photons.scaleXY(1./pixel_scale)
+
+
+
+
+
Parameters:
+
    +
  • n_photons – If provided, the number of photons to use for photon shooting. +If not provided (i.e. n_photons = 0), use as many photons as +necessary to result in an image with the correct Poisson shot +noise for the object’s flux. For positive definite profiles, this +is equivalent to n_photons = flux. However, some profiles need +more than this because some of the shot photons are negative +(usually due to interpolants). [default: 0]

  • +
  • rng – If provided, a random number generator to use for photon shooting, +which may be any kind of BaseDeviate object. If rng is None, one +will be automatically created, using the time as a seed. +[default: None]

  • +
  • max_extra_noise – If provided, the allowed extra noise in each pixel when photon +shooting. This is only relevant if n_photons=0, so the number of +photons is being automatically calculated. In that case, if the image +noise is dominated by the sky background, then you can get away with +using fewer shot photons than the full n_photons = flux. +Essentially each shot photon can have a flux > 1, which increases +the noise in each pixel. The max_extra_noise parameter specifies +how much extra noise per pixel is allowed because of this approximation. +A typical value for this might be max_extra_noise = sky_level / 100 +where sky_level is the flux per pixel due to the sky. Note that +this uses a “variance” definition of noise, not a “sigma” definition. +[default: 0.]

  • +
  • poisson_flux – Whether to allow total object flux scaling to vary according to +Poisson statistics for n_photons samples when photon shooting. +[default: True, unless n_photons is given, in which case the default +is False]

  • +
  • photon_ops – A list of operators that can modify the photon array that will be +applied in order before accumulating the photons on the sensor. +[default: ()]

  • +
  • local_wcs – The local wcs in the original image. [default: None]

  • +
+
+
Returns:
+

+

+
+
+
+ +
+
+property max_sb
+

An estimate of the maximum surface brightness of the object.

+

Some profiles will return the exact peak SB, typically equal to the value of +obj.xValue(obj.centroid). However, not all profiles (e.g. Convolution) know how to +calculate this value without just drawing the image and checking what the maximum value is. +Clearly, this would be inefficient, so in these cases, some kind of estimate is returned, +which will generally be conservative on the high side.

+

This routine is mainly used by the photon shooting process, where an overestimate of +the maximum surface brightness is acceptable.

+

Note, for negative-flux profiles, this will return the absolute value of the most negative +surface brightness. Technically, it is an estimate of the maximum deviation from zero, +rather than the maximum value. For most profiles, these are the same thing.

+
+ +
+
+property maxk
+

The value of k beyond which aliasing can be neglected.

+
+ +
+
+property negative_flux
+

Returns the expectation value of flux in negative photons.

+

Some profiles, when rendered with photon shooting, need to shoot both positive- and +negative-flux photons. For such profiles, this method returns the total absolute flux +of the negative-valued photons (i.e. as a positive value).

+

For profiles that don’t have this complication, this returns 0.

+

It should be generally true that obj.positive_flux - obj.negative_flux returns the same +thing as obj.flux. Small difference may accrue from finite numerical accuracy in +cases involving lookup tables, etc.

+
+ +
+
+property noise
+

An estimate of the noise already in the profile.

+

Some profiles have some noise already in their definition. E.g. those that come from +observations of galaxies in real data. In GalSim, RealGalaxy objects are an example of +this. In these cases, the noise attribute gives an estimate of the Noise object that +would generate noise consistent with that already in the profile.

+

It is permissible to attach a noise estimate to an existing object with:

+
>>> obj.noise = noise    # Some BaseNoise instance
+
+
+
+ +
+
+property nyquist_scale
+

The pixel spacing that does not alias maxk.

+
+ +
+
+property positive_flux
+

The expectation value of flux in positive photons.

+

Some profiles, when rendered with photon shooting, need to shoot both positive- and +negative-flux photons. For such profiles, this method returns the total flux +of the positive-valued photons.

+

For profiles that don’t have this complication, this is equivalent to getFlux().

+

It should be generally true that obj.positive_flux - obj.negative_flux returns the same +thing as obj.flux. Small difference may accrue from finite numerical accuracy in +cases involving lookup tables, etc.

+
+ +
+
+rotate(theta)[source]
+

Rotate this object by an Angle theta.

+
+
Parameters:
+

theta – Rotation angle (Angle object, positive means anticlockwise).

+
+
Returns:
+

the rotated object.

+
+
+
+ +
+
+shear(*args, **kwargs)[source]
+

Create a version of the current object with an area-preserving shear applied to it.

+

The arguments may be either a Shear instance or arguments to be used to initialize one.

+

For more details about the allowed keyword arguments, see the Shear docstring.

+

The shear() method precisely preserves the area. To include a lensing distortion with +the appropriate change in area, either use shear() with magnify(), or use lens(), which +combines both operations.

+
+
Parameters:
+

shear – The Shear to be applied. Or, as described above, you may instead supply +parameters do construct a shear directly. eg. obj.shear(g1=g1,g2=g2).

+
+
Returns:
+

the sheared object.

+
+
+
+ +
+
+shift(*args, **kwargs)[source]
+

Create a version of the current object shifted by some amount in real space.

+

After this call, the caller’s type will be a GSObject. +This means that if the caller was a derived type that had extra methods or properties +beyond those defined in GSObject (e.g. Gaussian.sigma), then these methods are no +longer available.

+

Note: in addition to the dx,dy parameter names, you may also supply dx,dy as a tuple, +or as a Position object.

+

The shift coordinates here are sky coordinates. GSObject profiles are always defined in +sky coordinates and only later (when they are drawn) is the connection to pixel coordinates +established (via a pixel_scale or WCS). So a shift of dx moves the object horizontally +in the sky (e.g. west in the local tangent plane of the observation), and dy moves the +object vertically (north in the local tangent plane).

+

The units are typically arcsec, but we don’t enforce that anywhere. The units here just +need to be consistent with the units used for any size values used by the GSObject. +The connection of these units to the eventual image pixels is defined by either the +pixel_scale or the wcs parameter of GSObject.drawImage.

+

Note: if you want to shift the object by a set number (or fraction) of pixels in the +drawn image, you probably want to use the offset parameter of GSObject.drawImage +rather than this method.

+
+
Parameters:
+
    +
  • dx – Horizontal shift to apply.

  • +
  • dy – Vertical shift to apply.

  • +
+
+
+

Alternatively, you may supply a single parameter as a Position instance, rather than +the two components separately if that is more convenient.

+
+
Parameter:

offset: The shift to apply, given as PositionD(dx,dy) or PositionI(dx,dy)

+
+
+
+
Returns:
+

the shifted object.

+
+
+
+ +
+
+shoot(n_photons, rng=None)[source]
+

Shoot photons into a PhotonArray.

+
+
Parameters:
+
    +
  • n_photons – The number of photons to use for photon shooting.

  • +
  • rng – If provided, a random number generator to use for photon shooting, +which may be any kind of BaseDeviate object. If rng is None, one +will be automatically created, using the time as a seed. +[default: None]

  • +
+
+
Returns:
+

A PhotonArray.

+
+
+
+ +
+
+property stepk
+

The sampling in k space necessary to avoid folding of image in x space.

+
+ +
+
+transform(dudx, dudy, dvdx, dvdy)[source]
+

Create a version of the current object with an arbitrary Jacobian matrix transformation +applied to it.

+

This applies a Jacobian matrix to the coordinate system in which this object +is defined. It changes a profile defined in terms of (x,y) to one defined in +terms of (u,v) where:

+
+

u = dudx x + dudy y +v = dvdx x + dvdy y

+
+

That is, an arbitrary affine transform, but without the translation (which is +easily effected via the shift method).

+

Note that this function is similar to expand in that it preserves surface brightness, +not flux. If you want to preserve flux, you should also do:

+
>>> prof *= 1./abs(dudx*dvdy - dudy*dvdx)
+
+
+
+
Parameters:
+
    +
  • dudx – du/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dudy – du/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdx – dv/dx, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
  • dvdy – dv/dy, where (x,y) are the current coords, and (u,v) are the new coords.

  • +
+
+
Returns:
+

the transformed object

+
+
+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+

You may either provide a GSParams instance:

+
>>> gsparams = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+>>> obj = obj.withGSParams(gsparams)
+
+
+

or one or more named parameters as keyword arguments:

+
>>> obj = obj.withGSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+
+
+
+

Note

+

The latter style will leave all non-named parameters at their current +values. It only updates the named parameters to the given values.

+
+
+ +
+
+withScaledFlux(flux_ratio)[source]
+

Create a version of the current object with the flux scaled by the given flux_ratio.

+

This function is equivalent to obj.withFlux(flux_ratio * obj.flux). Indeed, withFlux() +is implemented in terms of this one.

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location scaled by the given amount. If flux_ratio is an +SED, then the returned object is a ChromaticObject with the SED multiplied by +its current flux.

+

Note that in this case the flux attribute of the GSObject being scaled gets +interpreted as being dimensionless, instead of having its normal units of [photons/s/cm^2]. +The photons/s/cm^2 units are (optionally) carried by the SED instead, or even left out +entirely if the SED is dimensionless itself (see discussion in the ChromaticObject +docstring). The GSObject flux attribute does still contribute to the +ChromaticObject normalization, though. For example, the following are equivalent:

+
>>> chrom_obj = gsobj.withScaledFlux(sed * 3.0)
+>>> chrom_obj2 = (gsobj * 3.0).withScaledFlux(sed)
+
+
+

An equivalent, and usually simpler, way to effect this scaling is:

+
>>> obj = obj * flux_ratio
+
+
+
+
Parameters:
+

flux_ratio – The ratio by which to rescale the flux of the object when creating a new +one.

+
+
Returns:
+

the object with the new flux.

+
+
+
+ +
+
+xValue(*args, **kwargs)[source]
+

Returns the value of the object at a chosen 2D position in real space.

+

This function returns the surface brightness of the object at a particular position +in real space. The position argument may be provided as a PositionD or PositionI +argument, or it may be given as x,y (either as a tuple or as two arguments).

+

The object surface brightness profiles are typically defined in world coordinates, so +the position here should be in world coordinates as well.

+

Not all GSObject classes can use this method. Classes like Convolution that require a +Discrete Fourier Transform to determine the real space values will not do so for a single +position. Instead a GalSimError will be raised. The xValue() method is available if and +only if obj.is_analytic_x == True.

+

Users who wish to use the xValue() method for an object that is the convolution of other +profiles can do so by drawing the convolved profile into an image, using the image to +initialize a new InterpolatedImage, and then using the xValue() method for that new +object.

+
+
Parameters:
+

position – The position at which you want the surface brightness of the object.

+
+
Returns:
+

the surface brightness at that position.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/gsparams.html b/docs/_build/html/gsparams.html new file mode 100644 index 00000000000..e49459934a5 --- /dev/null +++ b/docs/_build/html/gsparams.html @@ -0,0 +1,277 @@ + + + + + + + The GSParams class — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The GSParams class

+

As mentioned in the GSObject docstring, all of the various GSObject classes take an optional +gsparams argument, which can be used to specify various numbers that govern the tradeoff +between accuracy and speed for the calculations made in drawing a GSObject. The numbers are +encapsulated in a class called GSParams.

+
+
+class galsim.GSParams(minimum_fft_size=128, maximum_fft_size=8192, folding_threshold=0.005, stepk_minimum_hlr=5, maxk_threshold=0.001, kvalue_accuracy=1e-05, xvalue_accuracy=1e-05, table_spacing=1, realspace_relerr=0.0001, realspace_abserr=1e-06, integration_relerr=1e-06, integration_abserr=1e-08, shoot_accuracy=1e-05, allowed_flux_variation=0.81, range_division_for_extrema=32, small_fraction_of_flux=0.0001)[source]
+

GSParams stores a set of numbers that govern how a GSObject makes various speed/accuracy +tradeoff decisions.

+

All GSObject classes can take an optional parameter named gsparams, which would be an +instance of this class. e.g.:

+
>>> gsp = galsim.GSParams(folding_threshold=1.e-3)
+>>> gal = galsim.Sersic(n=3.4, half_light_radius=3.2, flux=200, gsparams=gsp)
+
+
+

One can also update the parameters for an existing object using the method +GSObject.withGSParams. e.g.:

+
>>> gal = gal.withGSParams(kvalue_accuracy=1.e-8)
+
+
+

All parameters have reasonable default values. You only need to specify the ones you want +to change.

+
+
Parameters:
+
    +
  • minimum_fft_size – The minimum size of any FFT that may need to be performed. +[default: 128]

  • +
  • maximum_fft_size – The maximum allowed size of an image for performing an FFT. This +is more about memory use than accuracy. We have this maximum +value to help prevent the user from accidentally trying to perform +an extremely large FFT that crashes the program. Instead, GalSim +will raise an exception indicating that the image is too large, +which is often a sign of an error in the user’s code. However, if +you have the memory to handle it, you can raise this limit to +allow the calculation to happen. [default: 8192]

  • +
  • folding_threshold – This sets a maximum amount of real space folding that is allowed, +an effect caused by the periodic nature of FFTs. FFTs implicitly +use periodic boundary conditions, and a profile specified on a +finite grid in Fourier space corresponds to a real space image +that will have some overlap with the neighboring copies of the real +space profile. As the step size in k increases, the spacing +between neighboring aliases in real space decreases, increasing the +amount of folded, overlapping flux. folding_threshold is used +to set an appropriate step size in k to allow at most this +fraction of the flux to be folded. +This parameter is also relevant when you let GalSim decide how +large an image to use for your object. The image is made to be +large enough that at most a fraction folding_threshold of the +total flux is allowed to fall off the edge of the image. +[default: 5.e-3]

  • +
  • stepk_minimum_hlr – In addition to the above constraint for aliasing, also set stepk +such that pi/stepk is at least stepk_minimum_hlr times the +profile’s half-light radius (for profiles that have a well-defined +half-light radius). [default: 5]

  • +
  • maxk_threshold – This sets the maximum amplitude of the high frequency modes in +Fourier space that are excluded by truncating the FFT at some +maximum k value. Lowering this parameter can help minimize the +effect of “ringing” if you see that in your images. +[default: 1.e-3]

  • +
  • kvalue_accuracy – This sets the accuracy of values in Fourier space. Whenever there +is some kind of approximation to be made in the calculation of a +Fourier space value, the error in the approximation is constrained +to be no more than this value times the total flux. +[default: 1.e-5]

  • +
  • xvalue_accuracy – This sets the accuracy of values in real space. Whenever there is +some kind of approximation to be made in the calculation of a +real space value, the error in the approximation is constrained +to be no more than this value times the total flux. +[default: 1.e-5]

  • +
  • table_spacing – Several profiles use lookup tables for either the Hankel transform +(Sersic, Moffat) or the real space radial function (Kolmogorov). +We try to estimate a good spacing between values in the lookup +tables based on either xvalue_accuracy or kvalue_accuracy as +appropriate. However, you may change the spacing with this +parameter. Using table_spacing < 1 will use a spacing value that +is that much smaller than the default, which should produce more +accurate interpolations. [default: 1]

  • +
  • realspace_relerr – This sets the relative error tolerance for real-space integration. +[default: 1.e-4]

  • +
  • realspace_abserr – This sets the absolute error tolerance for real-space integration. +[default: 1.e-6] +The estimated integration error for the flux value in each pixel +when using the real-space rendering method (either explicitly with +method='real_space' or if it is triggered automatically with +method='auto') is constrained to be no larger than either +realspace_relerr times the pixel flux or realspace_abserr +times the object’s total flux.

  • +
  • integration_relerr – The relative error tolerance for integrations other than real-space +rendering. [default: 1.e-6]

  • +
  • integration_abserr – The absolute error tolerance for integrations other than real-space +rendering. [default: 1.e-8]

  • +
  • shoot_accuracy – This sets the relative accuracy on the total flux when photon +shooting. The photon shooting algorithm at times needs to make +approximations, such as how high in radius it needs to sample the +radial profile. When such approximations need to be made, it makes +sure that the resulting fractional error in the flux will be at +most this much. [default: 1.e-5]

  • +
+
+
+

After construction, all of the above parameters are available as read-only attributes.

+
+
+static check(gsparams, default=None, **kwargs)[source]
+

Checks that gsparams is either a valid GSParams instance or None.

+

In the former case, it returns gsparams, in the latter it returns default +(GSParams.default if no other default specified).

+
+ +
+
+static combine(gsp_list)[source]
+

Combine a list of GSParams instances using the most restrictive parameter from each.

+

Uses the minimum value for most parameters. For the following parameters, it uses the +maximum numerical value: minimum_fft_size, maximum_fft_size, stepk_minimum_hlr.

+
+ +
+
+withParams(**kwargs)[source]
+

Return a GSParams that is identical to the current one except for any keyword +arguments given here, which supersede the current value.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/history.html b/docs/_build/html/history.html new file mode 100644 index 00000000000..488a331f3b9 --- /dev/null +++ b/docs/_build/html/history.html @@ -0,0 +1,236 @@ + + + + + + + Revision History — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Revision History

+
+

Changes from v2.5 to v2.6

+

We currently support Python 3.7 through 3.12.

+
+

Dependency Changes

+
    +
  • Removed an accidental implicit dependency we had on scipy in FittedSIPWCS. (#1253, #1305)

  • +
+
+
+

API Changes

+
    +
  • Changed the behavior of random_seed. (See below) For most use cases, this is essentially a bug +fix, but if users were relying on the old behavior, you may need to change your config file to +work with the new behavior. See Image Field Attributes for more details about the new +behavior. (#1309)

  • +
+
+
+

Config Updates

+
    +
  • Changed the behavior of random_seed to be less confusing. Now the first random_seed is always +converted into a sequence based on obj_num, and later ones (if any) in a list are not. +If you want a non-standard seed sequence, you should now put it in a list somewhere after +the first item. The first item will always evaluate as an integer value and create a sequence +based on that indexed by obj_num. (#1309)

  • +
  • Added Quantity and Unit types to allow more intuitive specification of values with units +in the config file. (#1311)

  • +
+
+
+

New Features

+
    +
  • Added InterpolatedChromaticObject.from_images. (#1294, #1296)

  • +
  • Allow PosixPath instances in constructors for Bandpass and SED. (#1270, #1304)

  • +
  • Added filter information for the Prism and Grism in the roman module. (#1307)

  • +
  • Added options to give some unitful values as an astropy Quantity rather than rely on +implicit units specified in the doc string. (#1311)

  • +
+
+
+

Bug Fixes

+
    +
  • Fixed a bug in the config-layer parsing of Position items. (#1299, #1300)

  • +
  • Fixed a bug in DoubleZernike to handle integer arguments. (#1283, #1303)

  • +
  • Fixed a bug in ChromaticConvolution when one of the items is a simple GSObject +and the other has an inseparable SED. (#1302, #1306)

  • +
+
+
+
+

Older Versions

+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/hsm.html b/docs/_build/html/hsm.html new file mode 100644 index 00000000000..8de86f4c872 --- /dev/null +++ b/docs/_build/html/hsm.html @@ -0,0 +1,588 @@ + + + + + + + The HSM Module — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The HSM Module

+

Routines for adaptive moment estimation and PSF correction.

+

This module contains code for estimation of second moments of images, and for carrying out PSF +correction using a variety of algorithms. The algorithms are described in +Hirata & Seljak (2003), and were +tested/characterized using real data in +Mandelbaum et al. (2005). +Note that these routines for moment measurement and shear estimation are not accessible via config, +only via python. There are a number of default settings for the code (often governing the tradeoff +between accuracy and speed) that can be adjusting using an optional hsmparams argument as +described below.

+

The moments that are estimated are “adaptive moments” (see the first paper cited above for details); +that is, they use an elliptical Gaussian weight that is matched to the image of the object being +measured. The observed moments can be represented as a Gaussian sigma and a Shear object +representing the shape.

+

The PSF correction includes several algorithms, three that are re-implementations of methods +originated by others and one that was originated by Hirata & Seljak:

+ +

These methods return shear (or shape) estimators, which may not in fact satisfy conditions like +\(|e|<=1\), and so they are represented simply as e1/e2 or g1/g2 (depending on the method) +rather than using a Shear object, which IS required to satisfy \(|e|<=1\).

+

These methods are all based on correction of moments, but with different sets of assumptions. For +more detailed discussion on all of these algorithms, see the relevant papers above.

+

Users can find a listing of the parameters that can be adjusted using the hsmparams keyword, +along with default values, under galsim.hsm.HSMParams below.

+
+

Shape Measurement Functions

+
+
+galsim.hsm.FindAdaptiveMom(object_image, weight=None, badpix=None, guess_sig=5.0, precision=1e-06, guess_centroid=None, strict=True, check=True, round_moments=False, hsmparams=None, use_sky_coords=False)[source]
+

Measure adaptive moments of an object.

+

This method estimates the best-fit elliptical Gaussian to the object (see Hirata & Seljak 2003 +for more discussion of adaptive moments). This elliptical Gaussian is computed iteratively +by initially guessing a circular Gaussian that is used as a weight function, computing the +weighted moments, recomputing the moments using the result of the previous step as the weight +function, and so on until the moments that are measured are the same as those used for the +weight function. FindAdaptiveMom can be used either as a free function, or as a method of the +Image class.

+

By default, this routine computes moments in pixel coordinates, which generally use (x,y) +for the coordinate variables, so the underlying second moments are Ixx, Iyy, and Ixy. +If the WCS is (at least approximately) just a PixelScale, then this scale can be applied to +convert the moments’ units from pixels to arcsec. The derived shapes are unaffected by +the pixel scale.

+

However, there is also an option to apply a non-trivial WCS, which may potentially rotate +and/or shear the (x,y) moments to the local sky coordinates, which generally use (u,v) +for the coordinate variables. These coordinates are measured in arcsec and are oriented +such that +v is towards North and +u is towards West. In this case, the returned values are +all in arcsec, and are based instead on Iuu, Ivv, and Iuv. To enable this feature, use +use_sky_coords=True. See also the method ShapeData.applyWCS for more details.

+
+

Note

+

The application of the WCS implicitly assumes that the WCS is locally uniform across the +size of the object being measured. This is normally a very good approximation for most +applications of interest.

+
+

Like EstimateShear, FindAdaptiveMom works on Image inputs, and fails if the object is +small compared to the pixel scale. For more details, see EstimateShear.

+

Example:

+
>>> my_gaussian = galsim.Gaussian(flux=1.0, sigma=1.0)
+>>> my_gaussian_image = my_gaussian.drawImage(scale=0.2, method='no_pixel')
+>>> my_moments = galsim.hsm.FindAdaptiveMom(my_gaussian_image)
+
+
+

or:

+
>>> my_moments = my_gaussian_image.FindAdaptiveMom()
+
+
+

Assuming a successful measurement, the most relevant pieces of information are +my_moments.moments_sigma, which is |det(M)|^(1/4) (= sigma for a circular Gaussian) +and my_moments.observed_shape, which is a Shear. In this case, +my_moments.moments_sigma is precisely 5.0 (in units of pixels), and +my_moments.observed_shape is consistent with zero.

+

Methods of the Shear class can be used to get the distortion e, the shear g, the +conformal shear eta, and so on.

+

As an example of how to use the optional hsmparams argument, consider cases where the input +images have unusual properties, such as being very large. This could occur when measuring the +properties of a very over-sampled image such as that generated using:

+
>>> my_gaussian = galsim.Gaussian(sigma=5.0)
+>>> my_gaussian_image = my_gaussian.drawImage(scale=0.01, method='no_pixel')
+
+
+

If the user attempts to measure the moments of this very large image using the standard syntax,

+
>>> my_moments = my_gaussian_image.FindAdaptiveMom()
+
+
+

then the result will be a GalSimHSMError due to moment measurement failing because the +object is so large. While the list of all possible settings that can be changed is accessible +in the docstring of the HSMParams class, in this case we need to modify max_amoment which +is the maximum value of the moments in units of pixel^2. The following measurement, using the +default values for every parameter except for max_amoment, will be +successful:

+
>>> new_params = galsim.hsm.HSMParams(max_amoment=5.0e5)
+>>> my_moments = my_gaussian_image.FindAdaptiveMom(hsmparams=new_params)
+
+
+
+
Parameters:
+
    +
  • object_image – The Image for the object being measured.

  • +
  • weight – The optional weight image for the object being measured. Can be an int +or a float array. Currently, GalSim does not account for the variation +in non-zero weights, i.e., a weight map is converted to an image with 0 +and 1 for pixels that are not and are used. Full use of spatial +variation in non-zero weights will be included in a future version of +the code. [default: None]

  • +
  • badpix – The optional bad pixel mask for the image being used. Zero should be +used for pixels that are good, and any nonzero value indicates a bad +pixel. [default: None]

  • +
  • guess_sig – Optional argument with an initial guess for the Gaussian sigma of the +object (in pixels). [default: 5.0]

  • +
  • precision – The convergence criterion for the moments. [default: 1e-6]

  • +
  • guess_centroid – An initial guess for the object centroid (useful in case it is not +located at the center, which is used if this keyword is not set). The +convention for centroids is such that the center of the lower-left pixel +is (image.xmin, image.ymin). +[default: object_image.true_center]

  • +
  • strict – Whether to require success. If strict=True, then there will be a +GalSimHSMError exception if shear estimation fails. If set to +False, then information about failures will be silently stored in +the output ShapeData object. [default: True]

  • +
  • check – Check if the object_image, weight and badpix are in the correct format and valid. +[default: True]

  • +
  • round_moments – Use a circular weight function instead of elliptical. +[default: False]

  • +
  • hsmparams – The hsmparams keyword can be used to change the settings used by +FindAdaptiveMom when estimating moments; see HSMParams documentation +for more information. [default: None]

  • +
  • use_sky_coords – Whether to convert the measured moments to sky_coordinates. +Setting this to true is equivalent to running +applyWCS(object_image.wcs, image_pos=object_image.true_center) +on the result. [default: False]

  • +
+
+
Returns:
+

a ShapeData object containing the results of moment measurement.

+
+
+
+ +
+
+galsim.hsm.EstimateShear(gal_image, PSF_image, weight=None, badpix=None, sky_var=0.0, shear_est='REGAUSS', recompute_flux='FIT', guess_sig_gal=5.0, guess_sig_PSF=3.0, precision=1e-06, guess_centroid=None, strict=True, check=True, hsmparams=None)[source]
+

Carry out moments-based PSF correction routines.

+

Carry out PSF correction using one of the methods of the HSM package (see references in +docstring for file hsm.py) to estimate galaxy shears, correcting for the convolution by the +PSF.

+

This method works from Image inputs rather than GSObject inputs, which provides +additional flexibility (e.g., it is possible to work from an Image that was read from file and +corresponds to no particular GSObject), but this also means that users who wish to apply it to +compound GSObject classes (e.g., Convolve) must take the additional step of drawing +their GSObject into Image instances.

+

This routine assumes that (at least locally) the WCS can be approximated as a PixelScale, with +no distortion or non-trivial remapping. Any non-trivial WCS gets completely ignored.

+
+

Note

+

There is not currently an option to use_sky_coords as we have for FindAdaptiveMom. +We would welcome a PR adding this feature if someone wants it for their science case.

+
+

Note that the method will fail if the PSF or galaxy are too point-like to easily fit an +elliptical Gaussian; when running on batches of many galaxies, it may be preferable to set +strict=False and catch errors explicitly, as in the second example below.

+

This function has a number of keyword parameters, many of which a typical user will not need to +change from the default.

+

Example:

+

Typical application to a single object:

+
>>> galaxy = galsim.Gaussian(flux=1.0, sigma=1.0)
+>>> galaxy = galaxy.shear(g1=0.05, g2=0.0)  # shears the Gaussian by (0.05, 0) using the
+>>>                                         # |g| = (a-b)/(a+b) definition
+>>> psf = galsim.Kolmogorov(flux=1.0, fwhm=0.7)
+>>> final = galsim.Convolve(galaxy, psf)
+>>> final_image = final.drawImage(scale=0.2)
+>>> final_epsf_image = psf.drawImage(scale=0.2)
+>>> result = galsim.hsm.EstimateShear(final_image, final_epsf_image)
+
+
+

After running the above code, result.observed_shape is a Shear object with a value of +(0.0438925349133, -2.85747392701e-18) and result.corrected_e1, result_corrected_e2 +are (0.09934103488922119, -3.746108423463568e-10), compared with the expected (0.09975, +0) for a perfect PSF correction method.

+

The code below gives an example of how one could run this routine on a large batch of galaxies, +explicitly catching and tracking any failures:

+
>>> n_image = 100
+>>> n_fail = 0
+>>> for i=0, range(n_image):
+>>>     #...some code defining this_image, this_final_epsf_image...
+>>>     result = galsim.hsm.EstimateShear(this_image, this_final_epsf_image, strict=False)
+>>>     if result.error_message != "":
+>>>         n_fail += 1
+>>> print "Number of failures: ", n_fail
+
+
+
+
Parameters:
+
    +
  • gal_image – The Image of the galaxy being measured.

  • +
  • PSF_image – The Image for the PSF.

  • +
  • weight – The optional weight image for the galaxy being measured. Can be an int +or a float array. Currently, GalSim does not account for the variation +in non-zero weights, i.e., a weight map is converted to an image with 0 +and 1 for pixels that are not and are used. Full use of spatial +variation in non-zero weights will be included in a future version of +the code.

  • +
  • badpix – The optional bad pixel mask for the image being used. Zero should be +used for pixels that are good, and any nonzero value indicates a bad +pixel.

  • +
  • sky_var – The variance of the sky level, used for estimating uncertainty on the +measured shape. [default: 0.]

  • +
  • shear_est – A string indicating the desired method of PSF correction: ‘REGAUSS’, +‘LINEAR’, ‘BJ’, or ‘KSB’. The first three options return an e-type +distortion, whereas the last option returns a g-type shear. [default: +‘REGAUSS’]

  • +
  • recompute_flux – A string indicating whether to recompute the object flux, which +should be ‘NONE’ (for no recomputation), ‘SUM’ (for recomputation via +an unweighted sum over unmasked pixels), or ‘FIT’ (for +recomputation using the Gaussian + quartic fit). [default: ‘FIT’]

  • +
  • guess_sig_gal – Optional argument with an initial guess for the Gaussian sigma of the +galaxy (in pixels). [default: 5.]

  • +
  • guess_sig_PSF – Optional argument with an initial guess for the Gaussian sigma of the +PSF (in pixels). [default: 3.]

  • +
  • precision – The convergence criterion for the moments. [default: 1e-6]

  • +
  • guess_centroid – An initial guess for the object centroid (useful in +case it is not located at the center, which is used if this keyword is +not set). The convention for centroids is such that the center of +the lower-left pixel is (image.xmin, image.ymin). +[default: gal_image.true_center]

  • +
  • strict – Whether to require success. If strict=True, then there will be a +GalSimHSMError exception if shear estimation fails. If set to +False, then information about failures will be silently stored in +the output ShapeData object. [default: True]

  • +
  • check – Check if the object_image, weight and badpix are in the correct format and valid. +[default: True]

  • +
  • hsmparams – The hsmparams keyword can be used to change the settings used by +EstimateShear when estimating shears; see HSMParams documentation +for more information. [default: None]

  • +
+
+
Returns:
+

a ShapeData object containing the results of shape measurement.

+
+
+
+ +
+
+

HSM output

+
+
+class galsim.hsm.ShapeData(image_bounds=galsim.BoundsI(), moments_status=-1, observed_shape=galsim.Shear(0j), moments_sigma=-1.0, moments_amp=-1.0, moments_centroid=galsim.PositionD(x=0.0, y=0.0), moments_rho4=-1.0, moments_n_iter=0, correction_status=-1, corrected_e1=-10.0, corrected_e2=-10.0, corrected_g1=-10.0, corrected_g2=-10.0, meas_type='None', corrected_shape_err=-1.0, correction_method='None', resolution_factor=-1.0, psf_sigma=-1.0, psf_shape=galsim.Shear(0j), error_message='')[source]
+

A class to contain the outputs of the HSM shape and moments measurement routines.

+

The ShapeData class contains the following information about moment measurement (from either +EstimateShear or FindAdaptiveMom:

+
    +
  • image_bounds: a BoundsI object describing the image.

  • +
  • moments_status: the status flag resulting from moments measurement; -1 indicates no +attempt to measure, 0 indicates success.

  • +
  • observed_shape: a Shear object representing the observed shape based on adaptive +moments.

  • +
  • observed_e1, observed_e2: floats representing the e1 and e2 components respectively of the +observed_shape.

  • +
  • moments_sigma: size sigma=(det M)^(1/4) from the adaptive moments, in units of pixels; +-1 if not measured. (If FindAdaptiveMom is called with use_sky_coords=True, then +the units will be arcsec.)

  • +
  • moments_amp: total image intensity for best-fit elliptical Gaussian from adaptive moments. +Normally, this field is simply equal to the image flux (for objects that follow a Gaussian +light distribution, otherwise it is something approximating the flux). However, if the image +was drawn using drawImage(method='sb') then moments_amp relates to the flux via +flux=(moments_amp)*(pixel scale)^2.

  • +
  • moments_centroid: a PositionD object representing the centroid based on adaptive +moments, in units of pixels. The indexing convention is defined with respect to the BoundsI +object defining the bounds of the input Image, i.e., the center of the lower left pixel is +(image.xmin, image.ymin). An object drawn at the center of the image should generally +have moments_centroid equal to image.true_center. (If FindAdaptiveMom is called with +use_sky_coords=True, then the units will be arcsec, measured in sky coordinates with +respect to the image center.)

  • +
  • moments_rho4: the weighted radial fourth moment of the image (dimensionless).

  • +
  • moments_n_iter: number of iterations needed to get adaptive moments, or 0 if not measured.

  • +
+

If EstimateShear was used, then the following fields related to PSF-corrected shape will also +be populated:

+
    +
  • correction_status: the status flag resulting from PSF correction; -1 indicates no attempt +to measure, 0 indicates success.

  • +
  • corrected_e1, corrected_e2, corrected_g1, corrected_g2: floats representing +the estimated shear after removing the effects of the PSF. Either e1/e2 or g1/g2 will differ +from the default values of -10, with the choice of shape to use determined by the correction +method (since the correction method determines what quantity is estimated, either the shear or +the distortion). After a measurement is made, the type of shape measurement is stored in the +ShapeData structure as ‘meas_type’ (a string that equals either ‘e’ or ‘g’).

  • +
  • corrected_shape_err: shape measurement uncertainty sigma_gamma per component. The +estimate of the uncertainty will only be non-zero if an estimate of the sky variance was +passed to EstimateShear.

  • +
  • correction_method: a string indicating the method of PSF correction (will be “None” if +PSF-correction was not carried out).

  • +
  • resolution_factor: Resolution factor R_2; 0 indicates object is consistent with a PSF, 1 +indicates perfect resolution.

  • +
  • psf_sigma: size sigma=(det M)^(1/4) of the PSF from the adaptive moments, in units of +pixels; -1 if not measured.

  • +
  • psf_shape: a Shear object representing the observed PSF shape based on adaptive moments.

  • +
  • error_message: a string containing any error messages from the attempt to carry out +PSF-correction.

  • +
+

The ShapeData object can be initialized completely empty, or can be returned from the +routines that measure object moments (FindAdaptiveMom) and carry out PSF correction +(EstimateShear).

+
+
+applyWCS(wcs, image_pos)[source]
+

Convert moments in pixel coordinates to moments in sky coordinates.

+

Natively, the HSM algorithm computes second moments in (x,y) coordinates in the +sensor coordinate system. However, many applications of second moments for shape +measurements need the measurements in sky coordinates. This method converts the +moments to (u,v) coordinates, where +v is towards North and +u is towards West.

+

The values that are different from the original are:

+
    +
  • moments_sigma is changed to be in units of arcsec.

  • +
  • observed_shape is changed to be in (u,v) coordinates.

  • +
  • moments_centroid is changed to be in (u,v) coordinates relative to image_pos.

  • +
+
+

Note

+

This currently only works for the measurements from FindAdaptiveMom. +If the input ShapeData instance has any values set from EstimateShear, they will +not be present in the return value. We would welcome a PR adding the ability to +work on corrected shapes if someone wants it for their science case.

+
+
+
Parameters:
+
    +
  • wcs – The WCS to apply.

  • +
  • image_pos – The position in image coordinates (x,y) of the object whose moments +have been measured.

  • +
+
+
+
+ +
+ +
+
+

HSM parameters

+
+
+class galsim.hsm.HSMParams(nsig_rg=3.0, nsig_rg2=3.6, max_moment_nsig2=0, regauss_too_small=1, adapt_order=2, convergence_threshold=1e-06, max_mom2_iter=400, num_iter_default=-1, bound_correct_wt=0.25, max_amoment=8000.0, max_ashift=15.0, ksb_moments_max=4, ksb_sig_weight=0.0, ksb_sig_factor=1.0, failed_moments=-1000.0)[source]
+

A collection of parameters that govern how the HSM functions operate.

+

HSMParams stores a set of numbers that determine how the moments/shape estimation +routines make speed/accuracy tradeoff decisions and/or store their results.

+
+
Parameters:
+
    +
  • nsig_rg – A parameter used to optimize convolutions by cutting off the galaxy +profile. In the first step of the re-Gaussianization method of PSF +correction, a Gaussian approximation to the pre-seeing galaxy is +calculated. If nsig_rg > 0, then this approximation is cut off +at nsig_rg sigma to save computation time in convolutions. +[default: 3.0]

  • +
  • nsig_rg2 – A parameter used to optimize convolutions by cutting off the PSF +residual profile. In the re-Gaussianization method of PSF +correction, a ‘PSF residual’ (the difference between the true PSF +and its best-fit Gaussian approximation) is constructed. If +nsig_rg2 > 0, then this PSF residual is cut off at nsig_rg2 +sigma to save computation time in convolutions. [default: 3.6]

  • +
  • max_moment_nsig2 – No longer used. (Now calculated based on convergence_threshold.)

  • +
  • regauss_too_small – A parameter for how strictly the re-Gaussianization code treats +small galaxies. If this parameter is 1, then the re-Gaussianization +code does not impose a cut on the apparent resolution before trying +to measure the PSF-corrected shape of the galaxy; if 0, then it is +stricter. Using the default value of 1 prevents the +re-Gaussianization PSF correction from completely failing at the +beginning, before trying to do PSF correction, due to the crudest +possible PSF correction (Gaussian approximation) suggesting that +the galaxy is very small. This could happen for some usable +galaxies particularly when they have very non-Gaussian surface +brightness profiles – for example, if there’s a prominent bulge +that the adaptive moments attempt to fit, ignoring a more +extended disk. Setting a value of 1 is useful for keeping galaxies +that would have failed for that reason. If they later turn out to +be too small to really use, this will be reflected in the final +estimate of the resolution factor, and they can be rejected after +the fact. [default: 1]

  • +
  • adapt_order – The order to which circular adaptive moments should be calculated +for KSB method. This parameter only affects calculations using the +KSB method of PSF correction. Warning: deviating from default +value of 2 results in code running more slowly, and results have +not been significantly tested. [default: 2]

  • +
  • convergence_threshold – Accuracy (in x0, y0, and sigma, each as a fraction of sigma) +when calculating adaptive moments. [default: 1.e-6]

  • +
  • max_mom2_iter – Maximum number of iterations to use when calculating adaptive +moments. This should be sufficient in nearly all situations, with +the possible exception being very flattened profiles. [default: 400]

  • +
  • num_iter_default – Number of iterations to report in the output ShapeData structure +when code fails to converge within max_mom2_iter iterations. +[default: -1]

  • +
  • bound_correct_wt – Maximum shift in centroids and sigma between iterations for +adaptive moments. [default: 0.25]

  • +
  • max_amoment – Maximum value for adaptive second moments before throwing +exception. Very large objects might require this value to be +increased. [default: 8000]

  • +
  • max_ashift – Maximum allowed x / y centroid shift (units: pixels) between +successive iterations for adaptive moments before throwing +exception. [default: 15]

  • +
  • ksb_moments_max – Use moments up to ksb_moments_max order for KSB method of PSF +correction. [default: 4]

  • +
  • ksb_sig_weight – The width of the weight function (in pixels) to use for the KSB +method. Normally, this is derived from the measured moments of the +galaxy image; this keyword overrides this calculation. Can be +combined with ksb_sig_factor. [default: 0.0]

  • +
  • ksb_sig_factor – Factor by which to multiply the weight function width for the KSB +method (default: 1.0). Can be combined with ksb_sig_weight. +[default: 1.0]

  • +
  • failed_moments – Value to report for ellipticities and resolution factor if shape +measurement fails. [default: -1000.]

  • +
+
+
+
+

Note

+

Parameters that are given in units of pixels should still be in pixels, even if one is +calling FindAdaptiveMom with the option use_sky_coords=True.

+
+

After construction, all of the above are available as read-only attributes.

+
+
+static check(hsmparams, default=None)[source]
+

Checks that hsmparams is either a valid HSMParams instance or None.

+

In the former case, it returns hsmparams, in the latter it returns default +(HSMParams.default if no other default specified).

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/image.html b/docs/_build/html/image.html new file mode 100644 index 00000000000..f6c63164309 --- /dev/null +++ b/docs/_build/html/image.html @@ -0,0 +1,177 @@ + + + + + + + Images and Related Concepts — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/image_class.html b/docs/_build/html/image_class.html new file mode 100644 index 00000000000..5e3c4fa5817 --- /dev/null +++ b/docs/_build/html/image_class.html @@ -0,0 +1,1735 @@ + + + + + + + The Image class — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The Image class

+
+
+class galsim.Image(*args, **kwargs)[source]
+

A class for storing image data along with the pixel scale or WCS information

+

The Image class encapsulates all the relevant information about an image including a NumPy array +for the pixel values, a bounding box, and some kind of WCS that converts between pixel +coordinates and world coordinates. The NumPy array may be constructed by the Image class +itself, or an existing array can be provided by the user.

+

This class creates shallow copies unless a deep copy is explicitly requested using the copy +method. The main reason for this is that it allows users to work directly with and modify +subimages of larger images (for example, to successively draw many galaxies into one large +image). For other implications of this convention, see the description of initialization +instructions below.

+

In most applications with images, we will use (x,y) to refer to the coordinates. We adopt +the same meaning for these coordinates as most astronomy applications do: ds9, SAOImage, +SExtractor, etc. all treat x as the column number and y as the row number. However, this +is different from the default convention used by numpy. In numpy, the access is by +[row_num,col_num], which means this is really [y,x] in terms of the normal x,y values. +Users are typically insulated from this concern by the Image API, but if you access the +numpy array directly via the array attribute, you will need to be careful about this +difference.

+

There are 6 data types that the Image can use for the data values. These are numpy.uint16, +numpy.uint32, numpy.int16, numpy.int32, numpy.float32, and numpy.float64. +If you are constructing a new Image from scratch, the default is numpy.float32, but you +can specify one of the other data types.

+

There are several ways to construct an Image: +(Optional arguments are shown with their default values after the = sign.)

+
+

Image(ncol, nrow, dtype=numpy.float32, init_value=0, xmin=1, ymin=1, ...)

+
+

This constructs a new image, allocating memory for the pixel values according to +the number of columns and rows. You can specify the data type as dtype if you +want. The default is numpy.float32 if you don’t specify it. You can also +optionally provide an initial value for the pixels, which defaults to 0. +The optional xmin,ymin allow you to specify the location of the lower-left +pixel, which defaults to (1,1). Reminder, with our convention for x,y coordinates +described above, ncol is the number of pixels in the x direction, and nrow is the +number of pixels in the y direction.

+
+

Image(bounds, dtype=numpy.float32, init_value=0, ...)

+
+

This constructs a new image, allocating memory for the pixel values according to a +given Bounds object. Particularly, the bounds should be a BoundsI instance. +You can specify the data type as dtype if you want. The default is +numpy.float32 if you don’t specify it. You can also optionally provide an +initial value for the pixels, which defaults to 0.

+
+

Image(array, xmin=1, ymin=1, make_const=False, copy=False ...)

+
+

This views an existing NumPy array as an Image, where updates to either the image +or the original array will affect the other one. The data type is taken from +array.dtype, which must be one of the allowed types listed above. You can also +optionally set the origin xmin, ymin if you want it to be something other than +(1,1).

+

You can also optionally force the Image to be read-only with make_const=True, +though if the original NumPy array is modified then the contents of Image.array +will change.

+

If you want to make a copy of the input array, rather than just view the existing +array, you can force a copy with:

+
>>> image = galsim.Image(array, copy=True)
+
+
+
+

Image(image, dtype=image.dtype, copy=True)

+
+

This creates a copy of an Image, possibly changing the type. e.g.:

+
>>> image_float = galsim.Image(64, 64) # default dtype=numpy.float32
+>>> image_double = galsim.Image(image_float, dtype=numpy.float64)
+
+
+

You can see a list of valid values for dtype in galsim.Image.valid_dtypes. +Without the dtype argument, this is equivalent to image.copy(), which makes +a deep copy. If you want a copy that shares data with the original, see +the view method.

+

If you only want to enforce the image to have a given type and not make a copy +if the array is already the correct type, you can use, e.g.:

+
>>> image_double = galsim.Image(image, dtype=numpy.float64, copy=False)
+
+
+
+
+

You can specify the ncol, nrow, bounds, array, or image parameters by +keyword argument if you want, or you can pass them as simple arg as shown aboves, and the +constructor will figure out what they are.

+

The other keyword arguments (shown as … above) relate to the conversion between sky +coordinates, which is how all the GalSim objects are defined, and the pixel coordinates. +There are three options for this:

+
+
+
scale

You can optionally specify a pixel scale to use. This would normally have +units arcsec/pixel, but it doesn’t have to be arcsec. If you want to +use different units for the physical scale of your galsim objects, then +the same unit would be used here.

+
+
wcs

A WCS object that provides a non-trivial mapping between sky units and +pixel units. The scale parameter is equivalent to +wcs=PixelScale(scale). But there are a number of more complicated options. +See the WCS class for more details.

+
+
None

If you do not provide either of the above, then the conversion is undefined. +When drawing onto such an image, a suitable pixel scale will be automatically +set according to the Nyquist scale of the object being drawn.

+
+
+
+

After construction, you can set or change the scale or wcs with:

+
>>> image.scale = new_scale
+>>> image.wcs = new_wcs
+
+
+

Note that image.scale will only work if the WCS is a PixelScale. Once you set the +wcs to be something non-trivial, then you must interact with it via the wcs attribute. +The image.scale syntax will raise an exception.

+

There are also two read-only attributes:

+
>>> image.bounds
+>>> image.array
+
+
+

The array attribute is a NumPy array of the Image’s pixels. The individual elements in the +array attribute are accessed as image.array[y,x], matching the standard NumPy convention, +while the Image class’s own accessor uses either (x,y) or [x,y].

+

That is, the following are equivalent:

+
>>> ixy = image(x,y)
+>>> ixy = image[x,y]
+>>> ixy = image.array[y,x]
+>>> ixy = image.getValue(x,y)
+
+
+

Similarly, for setting individual pixel values, the following are equivalent:

+
>>> image[x,y] = new_ixy
+>>> image.array[y,x] = new_ixy
+>>> image.setValue(x,y,new_ixy)
+
+
+
+
+__getitem__(*args)[source]
+

Return either a subimage or a single pixel value.

+

For example,:

+
>>> subimage = im[galsim.BoundsI(3,7,3,7)]
+>>> value = im[galsim.PositionI(5,5)]
+>>> value = im[5,5]
+
+
+
+ +
+
+__setitem__(*args)[source]
+

Set either a subimage or a single pixel to new values.

+

For example,:

+
>>> im[galsim.BoundsI(3,7,3,7)] = im2
+>>> im[galsim.PositionI(5,5)] = 17.
+>>> im[5,5] = 17.
+
+
+
+ +
+
+_wrap(bounds, hermx, hermy)[source]
+

A version of wrap without the sanity checks.

+

Equivalent to image.wrap(bounds, hermitian='x' if hermx else 'y' if hermy else False)

+
+ +
+
+_view()[source]
+

Equivalent to view, but without some of the sanity checks and extra options.

+
+ +
+
+_shift(delta)[source]
+

Equivalent to shift, but without some of the sanity checks and delta must +be a PositionI instance.

+
+
Parameters:
+

delta – The amount to shift as a PositionI.

+
+
+
+ +
+
+__call__(*args, **kwargs)[source]
+

Get the pixel value at given position

+

The arguments here may be either (x, y) or a PositionI instance. +Or you can provide x, y as named kwargs.

+
+ +
+
+_getValue(x, y)[source]
+

Equivalent to getValue, except there are no checks that the values fall +within the bounds of the image.

+
+ +
+
+_setValue(x, y, value)[source]
+

Equivalent to setValue except that there are no checks that the values +fall within the bounds of the image, and the coordinates must be given as x, y.

+
+
Parameters:
+
    +
  • x – The x coordinate of the pixel to set.

  • +
  • y – The y coordinate of the pixel to set.

  • +
  • value – The value to set the pixel to.

  • +
+
+
+
+ +
+
+_addValue(x, y, value)[source]
+

Equivalent to addValue except that there are no checks that the values +fall within the bounds of the image, and the coordinates must be given as x, y.

+
+
Parameters:
+
    +
  • x – The x coordinate of the pixel to add to.

  • +
  • y – The y coordinate of the pixel to add to.

  • +
  • value – The value to add to this pixel.

  • +
+
+
+
+ +
+
+_fill(value)[source]
+

Equivalent to fill, except that there are no checks that the bounds are defined.

+
+ +
+
+_invertSelf()[source]
+

Equivalent to invertSelf, except that there are no checks that the bounds are defined.

+
+ +
+
+FindAdaptiveMom(weight=None, badpix=None, guess_sig=5.0, precision=1e-06, guess_centroid=None, strict=True, check=True, round_moments=False, hsmparams=None, use_sky_coords=False)
+

Measure adaptive moments of an object.

+

This method estimates the best-fit elliptical Gaussian to the object (see Hirata & Seljak 2003 +for more discussion of adaptive moments). This elliptical Gaussian is computed iteratively +by initially guessing a circular Gaussian that is used as a weight function, computing the +weighted moments, recomputing the moments using the result of the previous step as the weight +function, and so on until the moments that are measured are the same as those used for the +weight function. FindAdaptiveMom can be used either as a free function, or as a method of the +Image class.

+

By default, this routine computes moments in pixel coordinates, which generally use (x,y) +for the coordinate variables, so the underlying second moments are Ixx, Iyy, and Ixy. +If the WCS is (at least approximately) just a PixelScale, then this scale can be applied to +convert the moments’ units from pixels to arcsec. The derived shapes are unaffected by +the pixel scale.

+

However, there is also an option to apply a non-trivial WCS, which may potentially rotate +and/or shear the (x,y) moments to the local sky coordinates, which generally use (u,v) +for the coordinate variables. These coordinates are measured in arcsec and are oriented +such that +v is towards North and +u is towards West. In this case, the returned values are +all in arcsec, and are based instead on Iuu, Ivv, and Iuv. To enable this feature, use +use_sky_coords=True. See also the method ShapeData.applyWCS for more details.

+
+

Note

+

The application of the WCS implicitly assumes that the WCS is locally uniform across the +size of the object being measured. This is normally a very good approximation for most +applications of interest.

+
+

Like EstimateShear, FindAdaptiveMom works on Image inputs, and fails if the object is +small compared to the pixel scale. For more details, see EstimateShear.

+

Example:

+
>>> my_gaussian = galsim.Gaussian(flux=1.0, sigma=1.0)
+>>> my_gaussian_image = my_gaussian.drawImage(scale=0.2, method='no_pixel')
+>>> my_moments = galsim.hsm.FindAdaptiveMom(my_gaussian_image)
+
+
+

or:

+
>>> my_moments = my_gaussian_image.FindAdaptiveMom()
+
+
+

Assuming a successful measurement, the most relevant pieces of information are +my_moments.moments_sigma, which is |det(M)|^(1/4) (= sigma for a circular Gaussian) +and my_moments.observed_shape, which is a Shear. In this case, +my_moments.moments_sigma is precisely 5.0 (in units of pixels), and +my_moments.observed_shape is consistent with zero.

+

Methods of the Shear class can be used to get the distortion e, the shear g, the +conformal shear eta, and so on.

+

As an example of how to use the optional hsmparams argument, consider cases where the input +images have unusual properties, such as being very large. This could occur when measuring the +properties of a very over-sampled image such as that generated using:

+
>>> my_gaussian = galsim.Gaussian(sigma=5.0)
+>>> my_gaussian_image = my_gaussian.drawImage(scale=0.01, method='no_pixel')
+
+
+

If the user attempts to measure the moments of this very large image using the standard syntax,

+
>>> my_moments = my_gaussian_image.FindAdaptiveMom()
+
+
+

then the result will be a GalSimHSMError due to moment measurement failing because the +object is so large. While the list of all possible settings that can be changed is accessible +in the docstring of the HSMParams class, in this case we need to modify max_amoment which +is the maximum value of the moments in units of pixel^2. The following measurement, using the +default values for every parameter except for max_amoment, will be +successful:

+
>>> new_params = galsim.hsm.HSMParams(max_amoment=5.0e5)
+>>> my_moments = my_gaussian_image.FindAdaptiveMom(hsmparams=new_params)
+
+
+
+
Parameters:
+
    +
  • object_image – The Image for the object being measured.

  • +
  • weight – The optional weight image for the object being measured. Can be an int +or a float array. Currently, GalSim does not account for the variation +in non-zero weights, i.e., a weight map is converted to an image with 0 +and 1 for pixels that are not and are used. Full use of spatial +variation in non-zero weights will be included in a future version of +the code. [default: None]

  • +
  • badpix – The optional bad pixel mask for the image being used. Zero should be +used for pixels that are good, and any nonzero value indicates a bad +pixel. [default: None]

  • +
  • guess_sig – Optional argument with an initial guess for the Gaussian sigma of the +object (in pixels). [default: 5.0]

  • +
  • precision – The convergence criterion for the moments. [default: 1e-6]

  • +
  • guess_centroid – An initial guess for the object centroid (useful in case it is not +located at the center, which is used if this keyword is not set). The +convention for centroids is such that the center of the lower-left pixel +is (image.xmin, image.ymin). +[default: object_image.true_center]

  • +
  • strict – Whether to require success. If strict=True, then there will be a +GalSimHSMError exception if shear estimation fails. If set to +False, then information about failures will be silently stored in +the output ShapeData object. [default: True]

  • +
  • check – Check if the object_image, weight and badpix are in the correct format and valid. +[default: True]

  • +
  • round_moments – Use a circular weight function instead of elliptical. +[default: False]

  • +
  • hsmparams – The hsmparams keyword can be used to change the settings used by +FindAdaptiveMom when estimating moments; see HSMParams documentation +for more information. [default: None]

  • +
  • use_sky_coords – Whether to convert the measured moments to sky_coordinates. +Setting this to true is equivalent to running +applyWCS(object_image.wcs, image_pos=object_image.true_center) +on the result. [default: False]

  • +
+
+
Returns:
+

a ShapeData object containing the results of moment measurement.

+
+
+
+ +
+
+addNoise(noise)
+

Add noise to the image according to a supplied noise model.

+
+
Parameters:
+

noise – The noise (BaseNoise) model to use.

+
+
+
+ +
+
+addNoiseSNR(noise, snr, preserve_flux=False)
+

Adds noise to the image in a way that achieves the specified signal-to-noise ratio.

+

The specified snr (signal-to-noise ratio, or \(S/N\)) can be achieved either by scaling +the flux of the object while keeping the noise level fixed, or the flux can be preserved and +the noise variance changed. This choice is specified using the parameter preserve_flux, +where False means the former and True means the latter.

+

The definition of \(S/N\) is equivalent to the one used by Great08. We take the signal to +be a weighted sum of the pixel values:

+
+\[\begin{split}S &= \frac{\sum W(x,y) I(x,y)}{\sum W(x,y)} \\ +N^2 = Var(S) &= \frac{\sum W(x,y)^2 Var(I(x,y))}{(\sum W(x,y))^2}\end{split}\]
+

and assume that \(Var(I(x,y))\) is constant, \(V \equiv Var(I(x,y))\). +We then assume that we are using a matched filter for \(W\), so \(W(x,y) = I(x,y)\). +Then a few things cancel and we find that

+
+\[(S/N)^2 = \frac{\sum I(x,y)^2}{V}\]
+

and therefore, for a given \(I(x,y)\) and \(S/N\) (aka snr)

+
+\[V = \frac{\sum I(x,y)^2}{(S/N)^2}\]
+
+

Note

+

For noise models such as PoissonNoise and CCDNoise, the assumption of constant +\(Var(I(x,y))\) is only approximate, since the flux of the object adds to the Poisson +noise in those pixels. Thus, the real \(S/N\) on the final image will be slightly +lower than the target snr value, and this effect will be larger for brighter objects.

+
+

This function relies on BaseNoise.getVariance to determine how much variance the noise model +will add. Thus, it will not work for noise models that do not have a well-defined variance, +such as VariableGaussianNoise.

+
+
Parameters:
+
    +
  • noise – The noise (BaseNoise) model to use.

  • +
  • snr – The desired signal-to-noise after the noise is applied.

  • +
  • preserve_flux – Whether to preserve the flux of the object (True) or the variance of +the noise model (False) to achieve the desired snr. [default: False]

  • +
+
+
Returns:
+

the variance of the noise that was applied to the image.

+
+
+
+ +
+
+addReciprocityFailure(exp_time, alpha, base_flux)
+

Accounts for the reciprocity failure and includes it in the original Image directly.

+

Reciprocity, in the context of photography, is the inverse relationship between the incident +flux (I) of a source object and the exposure time (t) required to produce a given response (p) +in the detector, i.e., p = I*t. At very low (also at high) levels of incident flux, deviation +from this relation is observed, leading to reduced sensitivity at low flux levels. The pixel +response to a high flux is larger than its response to a low flux. This flux-dependent non- +linearity is known as ‘Reciprocity Failure’ and is known to happen in photographic films since +1893. Interested users can refer to http://en.wikipedia.org/wiki/Reciprocity_(photography)

+

CCDs are not known to suffer from this effect. HgCdTe detectors that are used for near infrared +astrometry, although to an extent much lesser than the photographic films, are found to +exhibit reciprocity failure at low flux levels. The exact mechanism of this effect is unknown +and hence we lack a good theoretical model. Many models that fit the empirical data exist and +a common relation is

+
+\[\frac{p_R}{p} = \left(1 + \alpha \log_{10}\left(\frac{p}{t}\right) + - \alpha \log_{10}\left(\frac{p^\prime}{t^\prime}\right)\right)\]
+

where \(t\) is the exposure time (in units of seconds), \(p\) is the pixel response +(in units of electrons) and \(p_R\) is the response if the reciprocity relation fails to +hold. \(p^\prime/t^\prime\) is the count rate (in electrons/second) corresponding to the +photon flux (base flux) at which the detector is calibrated to have its nominal gain. +alpha is the parameter in the model, measured in units of per decade and varies with detectors +and the operating temperature. The functional form for the reciprocity failure is motivated +empirically from the tests carried out on H2RG detectors.

+

See for reference Fig. 1 and Fig. 2 of http://arxiv.org/abs/1106.1090. Since \(p_R/p\) +remains close to unity over a wide range of flux, we convert this relation to a power law by +approximating \(p_R/p \approx 1 + \log(p_R/p)\). This gives a relation that is better +behaved than the logarithmic relation at low flux levels.

+
+\[\frac{p_R}{p} = \left(\frac{p/t}{p^\prime/t^\prime}\right)^\frac{\alpha}{\log(10)}.\]
+

Because of how this function is defined, the input image must have non-negative pixel +values for the resulting image to be well-defined. Negative pixel values result in ‘nan’s. +The image should be in units of electrons, or if it is in ADU, then the value passed to +exp_time should be the exposure time divided by the nominal gain. The image should include +both the signal from the astronomical objects as well as the background level. The addition of +nonlinearity should occur after including the effect of reciprocity failure.

+
+
Parameters:
+
    +
  • exp_time – The exposure time (t) in seconds, which goes into the expression for +reciprocity failure given in the docstring.

  • +
  • alpha – The alpha parameter in the expression for reciprocity failure, in +units of ‘per decade’.

  • +
  • base_flux – The flux (\(p^\prime/t^\prime\)) at which the gain is calibrated to have +its nominal value.

  • +
+
+
+
+ +
+
+addValue(*args, **kwargs)[source]
+

Add some amount to the pixel value at given (x,y) position

+

The arguments here may be either (x, y, value) or (pos, value) where pos is a PositionI. +Or you can provide x, y, value as named kwargs.

+

This is equivalent to self[x,y] += rhs

+
+ +
+
+applyIPC(IPC_kernel, edge_treatment='extend', fill_value=None, kernel_nonnegativity=True, kernel_normalization=True)
+

Applies the effect of interpixel capacitance to the Image instance.

+

In NIR detectors, the quantity that is sensed is not the charge as in CCDs, but a voltage that +relates to the charge present within each pixel. The voltage read at a given pixel location is +influenced by the charges present in the neighboring pixel locations due to capacitive +coupling of sense nodes.

+

This interpixel capacitance is approximated as a linear effect that can be described by a 3x3 +kernel that is convolved with the image. The kernel must be an Image instance and could be +intrinsically anisotropic. A sensible kernel must have non-negative entries and must be +normalized such that the sum of the elements is 1, in order to conserve the total charge. +The (1,1) element of the kernel is the contribution to the voltage read at a pixel from the +electrons in the pixel to its bottom-left, the (1,2) element of the kernel is the contribution +from the charges to its left and so on.

+

The argument ‘edge_treatment’ specifies how the edges of the image should be treated, which +could be in one of the three ways:

+
    +
  1. ‘extend’: The kernel is convolved with the zero-padded image, leading to a larger +intermediate image. The central portion of this image is returned. [default]

  2. +
  3. ‘crop’: The kernel is convolved with the image, with the kernel inside the image completely. +Pixels at the edges, where the center of the kernel could not be placed, are set to the +value specified by ‘fill_value’. If ‘fill_value’ is not specified or set to ‘None’, then +the pixel values in the original image are retained. The user can make the edges invalid +by setting fill_value to numpy.nan.

  4. +
  5. ‘wrap’: The kernel is convolved with the image, assuming periodic boundary conditions.

  6. +
+

The size of the image array remains unchanged in all three cases.

+
+
Parameters:
+
    +
  • IPC_kernel – A 3x3 Image instance that is convolved with the Image instance

  • +
  • edge_treatment – Specifies the method of handling edges and should be one of +‘crop’, ‘extend’ or ‘wrap’. See above for details. +[default: ‘extend’]

  • +
  • fill_value – Specifies the value (including nan) to fill the edges with when +edge_treatment is ‘crop’. If unspecified or set to ‘None’, the +original pixel values are retained at the edges. If +edge_treatment is not ‘crop’, then this is ignored.

  • +
  • kernel_nonnegativity – Specify whether the kernel should have only non-negative +entries. [default: True]

  • +
  • kernel_normalization – Specify whether to check and enforce correct normalization for +the kernel. [default: True]

  • +
+
+
+
+ +
+
+applyNonlinearity(NLfunc, *args)
+

Applies the given non-linearity function (NLfunc) on the Image instance directly.

+

This routine can transform the image in a non-linear manner specified by the user. However, +the typical kind of non-linearity one sees in astronomical images is voltage non-linearity, +also sometimes known as ‘classical non-linearity’, refers to the non-linearity in +charge-to-voltage conversion process. This arises as charge gets integrated at the junction +capacitance of the pixel node. Voltage non-linearity decreases signals at higher signal +levels, causing the attenuation of brighter pixels. The image should include both the +signal from the astronomical objects as well as the background level. Other detectors effects +such as dark current and persistence (not currently included in GalSim) would also occur +before the inclusion of nonlinearity.

+

The argument NLfunc is a callable function (for example a lambda function, a +galsim.LookupTable, or a user-defined function), possibly with arguments that need to be given +as subsequent arguments to the applyNonlinearity function (after the NLfunc argument). +NLfunc should be able to take a 2d NumPy array as input, and return a NumPy array of the +same shape. It should be defined such that it outputs the final image with nonlinearity +included (i.e., in the limit that there is no nonlinearity, the function should return the +original image, NOT zero). The image should be in units of electrons when this routine is being +used to generate classical non-linearity. When used for other purposes, the units can be in +electrons or in ADU, as found appropriate by the user.

+

Examples:

+
>>> f = lambda x: x + (1.e-7)*(x**2)
+>>> img.applyNonlinearity(f)
+
+>>> f = lambda x, beta1, beta2: x - beta1*x*x + beta2*x*x*x
+>>> img.applyNonlinearity(f, 1.e-7, 1.e-10)
+
+
+

On calling the method, the Image instance img is transformed by the user-defined function +f with beta1 = 1.e-7 and beta2 = 1.e-10.

+
+
Parameters:
+
    +
  • NLfunc – The function that maps the input image pixel values to the output image pixel +values.

  • +
  • *args – Any subsequent arguments are passed along to the NLfunc function.

  • +
+
+
+
+ +
+
+applyPersistence(imgs, coeffs)
+

Applies the effects of persistence to the Image instance.

+

Persistence refers to the retention of a small fraction of the signal after resetting the +imager pixel elements. The persistence signal of a previous exposure is left in the pixel even +after several detector resets. This effect is most likely due to charge traps in the material. +Laboratory tests on the Roman Space Telescope CMOS detectors show that if exposures and +readouts are taken in a fixed cadence, the persistence signal can be given as a linear +combination of prior pixel values that can be added to the current image.

+

This routine takes in a list of Image instances and adds them to Image weighted by the +values passed on to ‘coeffs’. The pixel values of the Image instances in the list must +correspond to the electron counts before the readout. This routine does NOT keep track of +realistic dither patterns. During the image simulation process, the user has to queue a list of +previous Image instances (imgs) outside the routine by inserting the latest image in the +beginning of the list and deleting the oldest image. The values in ‘coeffs’ tell how much of +each Image is to be added. This usually remains constant in the image generation process.

+
+
Parameters:
+
    +
  • imgs – A list of previous Image instances that still persist.

  • +
  • coeffs – A list of floats that specifies the retention factors for the corresponding +Image instances listed in ‘imgs’.

  • +
+
+
+
+ +
+
+property array
+

The underlying numpy array.

+
+ +
+
+bin(nx, ny)[source]
+

Bin the image pixels in blocks of nx x ny pixels.

+

This returns a new image that is a binned version of the current image. +Adjacent pixel values in nx x ny blocks are added together to produce the flux in each +output pixel.

+

If the current number of pixels in each direction is not a multiple of nx, ny, then the +last pixel in each direction will be the sum of fewer than nx or ny pixels as needed.

+

See also subsample, which is the opposite of this.

+

If the wcs is a Jacobian (or simpler), the output image will have its wcs set properly. +But if the wcs is more complicated, the output wcs would be fairly complicated to figure +out properly, so we leave it as None. The user should set it themselves if required.

+
+
Parameters:
+
    +
  • nx – The number of adjacent pixels in the x direction to add together into each +output pixel.

  • +
  • ny – The number of adjacent pixels in the y direction to add together into each +output pixel.

  • +
+
+
Returns:
+

a new Image

+
+
+
+ +
+
+property bounds
+

The bounds of the Image.

+
+ +
+
+calculateFWHM(center=None, Imax=0.0)[source]
+

Returns the full-width half-maximum (FWHM) of a drawn object.

+

This method is equivalent to GSObject.calculateFWHM when the object has already +been drawn onto an image. Note that the profile should be drawn using a method that +does not integrate over pixels, so either ‘sb’ or ‘no_pixel’. Also, if there is a +significant amount of noise in the image, this method may not work well.

+

If the image has a wcs other than a PixelScale, an AttributeError will be raised.

+
+
Parameters:
+
    +
  • center – The position in pixels to use for the center, r=0. +[default: self.true_center]

  • +
  • Imax – The maximum surface brightness. [default: max(self.array)] +Note: If Imax is provided, and the maximum pixel value is larger than +this value, Imax will be updated to use the larger value.

  • +
+
+
Returns:
+

an estimate of the full-width half-maximum in physical units defined by the pixel scale.

+
+
+
+ +
+
+calculateHLR(center=None, flux=None, flux_frac=0.5)[source]
+

Returns the half-light radius of a drawn object.

+

This method is equivalent to GSObject.calculateHLR when the object has already been +been drawn onto an image. Note that the profile should be drawn using a method that +integrates over pixels and does not add noise. (The default method=’auto’ is acceptable.)

+

If the image has a wcs other than a PixelScale, an AttributeError will be raised.

+
+
Parameters:
+
    +
  • center – The position in pixels to use for the center, r=0. +[default: self.true_center]

  • +
  • flux – The total flux. [default: sum(self.array)]

  • +
  • flux_frac – The fraction of light to be enclosed by the returned radius. +[default: 0.5]

  • +
+
+
Returns:
+

an estimate of the half-light radius in physical units defined by the pixel scale.

+
+
+
+ +
+
+calculateMomentRadius(center=None, flux=None, rtype='det')[source]
+

Returns an estimate of the radius based on unweighted second moments of a drawn object.

+

This method is equivalent to GSObject.calculateMomentRadius when the object has already +been drawn onto an image. Note that the profile should be drawn using a method that +integrates over pixels and does not add noise. (The default method=’auto’ is acceptable.)

+

If the image has a wcs other than a PixelScale, an AttributeError will be raised.

+
+
Parameters:
+
    +
  • center – The position in pixels to use for the center, r=0. +[default: self.true_center]

  • +
  • flux – The total flux. [default: sum(self.array)]

  • +
  • rtype

    There are three options for this parameter:

    +
      +
    • ’trace’ means return sqrt(T/2)

    • +
    • ’det’ means return det(Q)^1/4

    • +
    • ’both’ means return both: (sqrt(T/2), det(Q)^1/4)

    • +
    +

    [default: ‘det’]

    +

  • +
+
+
Returns:
+

an estimate of the radius in physical units defined by the pixel scale +(or both estimates if rtype == ‘both’).

+
+
+
+ +
+
+calculate_fft()[source]
+

Performs an FFT of an Image in real space to produce a k-space Image.

+

Note: the image will be padded with zeros as needed to make an image with bounds that +look like BoundsI(-N/2, N/2-1, -N/2, N/2-1).

+

The input image must have a PixelScale wcs. The output image will be complex (an +ImageCF or ImageCD instance) and its scale will be 2pi / (N dx), where dx is the scale +of the input image.

+
+
Returns:
+

an Image instance with the k-space image.

+
+
+
+ +
+
+calculate_inverse_fft()[source]
+

Performs an inverse FFT of an Image in k-space to produce a real-space Image.

+

The starting image is typically an ImageCD, although if the Fourier function is real +valued, then you could get away with using an ImageD or ImageF.

+

The image is assumed to be Hermitian. In fact, only the portion with x >= 0 needs to +be defined, with f(-x,-y) taken to be conj(f(x,y)).

+

Note: the k-space image will be padded with zeros and/or wrapped as needed to make an +image with bounds that look like BoundsI(0, N/2, -N/2, N/2-1). If you are building a +larger k-space image and then wrapping, you should wrap directly into an image of +this shape.

+

The input image must have a PixelScale wcs. The output image will be real (an ImageD +instance) and its scale will be 2pi / (N dk), where dk is the scale of the input image.

+
+
Returns:
+

an Image instance with the real-space image.

+
+
+
+ +
+
+property center
+

The current nominal center (xcen,ycen) of the image as a PositionI instance.

+

In terms of the rows and columns, xcen is the x value for the central column, and ycen +is the y value of the central row. For even-sized arrays, there is no central column +or row, so the convention we adopt in this case is to round up. For example:

+
>>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4)))
+>>> im.center
+galsim.PositionI(x=3, y=3)
+>>> im(im.center)
+10.0
+>>> im.setCenter(56,72)
+>>> im.center
+galsim.PositionI(x=56, y=72)
+>>> im(im.center)
+10.0
+
+
+
+ +
+
+static clear_depixelize_cache()[source]
+

Release the cached solver used by depixelize to make repeated calls more efficient.

+
+ +
+
+property conjugate
+

Return the complex conjugate of an image.

+

This works for real or complex. For real images, it acts the same as view.

+

Note that for complex images, this is not a conjugate view into the original image. +So changing the original image does not change the conjugate (or vice versa).

+
+ +
+
+copy()[source]
+

Make a copy of the Image

+
+ +
+
+copyFrom(rhs)[source]
+

Copy the contents of another image

+
+ +
+
+depixelize(x_interpolant)[source]
+

Return a depixelized version of the image.

+

Specifically, this function creates an image that could be used with InterpolatedImage +with the given x_interpolant, which when drawn with method=auto would produce the +current image.

+
>>> alt_image = image.depixelize(x_interpolant)
+>>> ii = galsim.InterpolatedImage(alt_image, x_interpolant=x_interpolant)
+>>> image2 = ii.drawImage(image.copy(), method='auto')
+
+
+

image2 will end up approximately equal to the original image.

+
+

Warning

+

This function is fairly expensive, both in memory and CPU time, so it should +only be called on fairly small images (~100x100 or smaller typically). +The memory requirement scales as Npix^2, and the execution time scales as Npix^3.

+

However, the expensive part of the calculation is independent of the image values. +It only depends on the size of the image and interpolant being used. So this part +of the calculation is cached and reused if possible. If you make repeated calls +to depixelize using the same image size and interpolant, it will be much faster +after the first call.

+

If you need to release the cache (since it can be a non-trivial amount of memory), +you may do so using Image.clear_depixelize_cache.

+
+
+
Parameters:
+

x_interpolant – The Interpolant to use in the InterpolatedImage to describe +how the profile should be interpolated between the pixel centers.

+
+
Returns:
+

an Image representing the underlying profile without the pixel convolution.

+
+
+
+ +
+
+property dtype
+

The dtype of the underlying numpy array.

+
+ +
+
+fill(value)[source]
+

Set all pixel values to the given value

+
+
Parameter:

value: The value to set all the pixels to.

+
+
+
+ +
+
+flip_lr()[source]
+

Return a version of the image flipped left to right.

+

Note: The returned image will have an undefined wcs. +If you care about the wcs, you will need to set it yourself.

+
+ +
+
+flip_ud()[source]
+

Return a version of the image flipped top to bottom.

+

Note: The returned image will have an undefined wcs. +If you care about the wcs, you will need to set it yourself.

+
+ +
+
+getValue(x, y)[source]
+

This method is a synonym for im(x,y). It is a bit faster than im(x,y), since GalSim +does not have to parse the different options available for __call__. (i.e. im(x,y) or +im(pos) or im(x=x,y=y))

+
+
Parameters:
+
    +
  • x – The x coordinate of the pixel to get.

  • +
  • y – The y coordinate of the pixel to get.

  • +
+
+
+
+ +
+
+get_pixel_centers()[source]
+

A convenience function to get the x and y values at the centers of the image pixels.

+
+
Returns:
+

(x, y), each of which is a numpy array the same shape as self.array

+
+
+
+ +
+
+classmethod good_fft_size(input_size)[source]
+

Round the given input size up to the next higher power of 2 or 3 times a power of 2.

+

This rounds up to the next higher value that is either 2^k or 3*2^k. If you are +going to be performing FFTs on an image, these will tend to be faster at performing +the FFT.

+
+ +
+
+property imag
+

Return the imaginary part of an image.

+

This is a property, not a function. So write im.imag, not im.imag().

+

This works for real or complex. For real images, the returned array is read-only and +all elements are 0.

+
+ +
+
+invertSelf()[source]
+

Set all pixel values to their inverse: x -> 1/x.

+

Note: any pixels whose value is 0 originally are ignored. They remain equal to 0 +on the output, rather than turning into inf.

+
+ +
+
+property iscomplex
+

Whether the Image values are complex.

+
+ +
+
+property isconst
+

Whether the Image is constant. I.e. modifying its values is an error.

+
+ +
+
+property iscontiguous
+

Indicates whether each row of the image is contiguous in memory.

+

Note: it is ok for the end of one row to not be contiguous with the start of the +next row. This just checks that each individual row has a stride of 1.

+
+ +
+
+property isinteger
+

Whether the Image values are integral.

+
+ +
+
+property ncol
+

The number of columns in the image

+
+ +
+
+property nrow
+

The number of rows in the image

+
+ +
+
+property origin
+

Return the origin of the image. i.e. the (x,y) position of the lower-left pixel.

+

In terms of the rows and columns, this is the (x,y) coordinate of the first column, and +first row of the array. For example:

+
>>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4)))
+>>> im.origin
+galsim.PositionI(x=1, y=1)
+>>> im(im.origin)
+0.0
+>>> im.setOrigin(23,45)
+>>> im.origin
+galsim.PositionI(x=23, y=45)
+>>> im(im.origin)
+0.0
+>>> im(23,45)
+0.0
+>>> im.bounds
+galsim.BoundsI(xmin=23, xmax=26, ymin=45, ymax=48)
+
+
+
+ +
+
+property outer_bounds
+

The bounds of the outer edge of the pixels.

+

Equivalent to galsim.BoundsD(im.xmin-0.5, im.xmax+0.5, im.ymin-0.5, im.ymax+0.5)

+
+ +
+
+quantize()
+

Rounds the pixel values in an image to integer values, while preserving the type of the data.

+

At certain stages in the astronomical image generation process, detectors effectively round to +the nearest integer. The exact stage at which this happens depends on the type of device (CCD +vs. NIR detector). For example, for H2RG detectors, quantization happens in two stages: first, +when detecting a certain number of photons, corresponding to the sum of background and signal +multiplied by the QE and including reciprocity failure. After this, a number of other processes +occur (e.g., nonlinearity, IPC, read noise) that could result in non-integer pixel values, only +rounding to an integer at the stage of analog-to-digital conversion.

+

Because we cannot guarantee that quantization will always be the last step in the process, the +quantize() routine does not actually modify the type of the image to ‘int’. However, users can +easily do so by doing:

+
>>> image.quantize()
+>>> int_image = galsim.Image(image, dtype=int)
+
+
+
+ +
+
+property real
+

Return the real part of an image.

+

This is a property, not a function. So write im.real, not im.real().

+

This works for real or complex. For real images, it acts the same as view.

+
+ +
+
+replaceNegative(replace_value=0)[source]
+

Replace any negative values currently in the image with 0 (or some other value).

+

Sometimes FFT drawing can result in tiny negative values, which may be undesirable for +some purposes. This method replaces those values with 0 or some other value if desired.

+
+
Parameters:
+

replace_value – The value with which to replace any negative pixels. [default: 0]

+
+
+
+ +
+
+resize(bounds, wcs=None)[source]
+

Resize the image to have a new bounds (must be a BoundsI instance)

+

Note that the resized image will have uninitialized data. If you want to preserve +the existing data values, you should either use subImage (if you want a smaller +portion of the current Image) or make a new Image and copy over the current values +into a portion of the new image (if you are resizing to a larger Image).

+
+
Parameters:
+
    +
  • bounds – The new bounds to resize to.

  • +
  • wcs – If provided, also update the wcs to the given value. [default: None, +which means keep the existing wcs]

  • +
+
+
+
+ +
+
+rot_180()[source]
+

Return a version of the image rotated 180 degrees.

+

Note: The returned image will have an undefined wcs. +If you care about the wcs, you will need to set it yourself.

+
+ +
+
+rot_ccw()[source]
+

Return a version of the image rotated 90 degrees counter-clockwise.

+

Note: The returned image will have an undefined wcs. +If you care about the wcs, you will need to set it yourself.

+
+ +
+
+rot_cw()[source]
+

Return a version of the image rotated 90 degrees clockwise.

+

Note: The returned image will have an undefined wcs. +If you care about the wcs, you will need to set it yourself.

+
+ +
+
+property scale
+

The pixel scale of the Image. Only valid if the wcs is a PixelScale.

+

If the WCS is either not set (i.e. it is None) or it is a PixelScale, then +it is permissible to change the scale with:

+
>>> image.scale = new_pixel_scale
+
+
+
+ +
+
+setCenter(*args, **kwargs)[source]
+

Set the center of the image to the given (integral) (xcen, ycen)

+

The arguments here may be either (xcen, ycen) or a PositionI instance. +Or you can provide xcen, ycen as named kwargs.

+

In terms of the rows and columns, xcen is the new x value for the central column, and ycen +is the new y value of the central row. For even-sized arrays, there is no central column +or row, so the convention we adopt in this case is to round up. For example:

+
>>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4)))
+>>> im(1,1)
+0.0
+>>> im(4,1)
+3.0
+>>> im(4,4)
+15.0
+>>> im(3,3)
+10.0
+>>> im.setCenter(0,0)
+>>> im(0,0)
+10.0
+>>> im(-2,-2)
+0.0
+>>> im(1,-2)
+3.0
+>>> im(1,1)
+15.0
+>>> im.setCenter(234,456)
+>>> im(234,456)
+10.0
+>>> im.bounds
+galsim.BoundsI(xmin=232, xmax=235, ymin=454, ymax=457)
+
+
+
+ +
+
+setOrigin(*args, **kwargs)[source]
+

Set the origin of the image to the given (integral) (x0, y0)

+

The arguments here may be either (x0, y0) or a PositionI instance. +Or you can provide x0, y0 as named kwargs.

+

In terms of the rows and columns, x0 is the new x value for the first column, +and y0 is the new y value of the first row. For example:

+
>>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4)))
+>>> im(1,1)
+0.0
+>>> im(4,1)
+3.0
+>>> im(1,4)
+12.0
+>>> im(4,4)
+15.0
+>>> im.setOrigin(0,0)
+>>> im(0,0)
+0.0
+>>> im(3,0)
+3.0
+>>> im(0,3)
+12.0
+>>> im(3,3)
+15.0
+>>> im.setOrigin(234,456)
+>>> im(234,456)
+0.0
+>>> im.bounds
+galsim.BoundsI(xmin=234, xmax=237, ymin=456, ymax=459)
+
+
+
+ +
+
+setSubImage(bounds, rhs)[source]
+

Set a portion of the full image to the values in another image

+

This is equivalent to self[bounds] = rhs

+
+ +
+
+setValue(*args, **kwargs)[source]
+

Set the pixel value at given (x,y) position

+

The arguments here may be either (x, y, value) or (pos, value) where pos is a PositionI. +Or you can provide x, y, value as named kwargs.

+

This is equivalent to self[x,y] = rhs

+
+ +
+
+setZero()[source]
+

Set all pixel values to zero.

+
+ +
+
+shift(*args, **kwargs)[source]
+

Shift the pixel coordinates by some (integral) dx,dy.

+

The arguments here may be either (dx, dy) or a PositionI instance. +Or you can provide dx, dy as named kwargs.

+

In terms of columns and rows, dx means a shift in the x value of each column in the +array, and dy means a shift in the y value of each row. In other words, the following +will return the same value for ixy. The shift function just changes the coordinates (x,y) +used for that pixel:

+
>>> ixy = im(x,y)
+>>> im.shift(3,9)
+>>> ixy = im(x+3, y+9)
+
+
+
+ +
+
+subImage(bounds)[source]
+

Return a view of a portion of the full image

+

This is equivalent to self[bounds]

+
+ +
+
+subsample(nx, ny, dtype=None)[source]
+

Subdivide the image pixels into nx x ny sub-pixels.

+

This returns a new image that is a subsampled version of the current image. +Each pixel’s flux is split (uniformly) into nx x ny smaller pixels.

+

See also bin, which is the opposite of this. Note that subsample(nx,ny) followed by +bin(nx,ny) is essentially a no op.

+

If the wcs is a Jacobian (or simpler), the output image will have its wcs set properly. +But if the wcs is more complicated, the output wcs would be fairly complicated to figure +out properly, so we leave it as None. The user should set it themselves if required.

+
+
Parameters:
+
    +
  • nx – The number of sub-pixels in the x direction for each original pixel.

  • +
  • ny – The number of sub-pixels in the y direction for each original pixel.

  • +
  • dtype – Optionally provide a dtype for the return image. [default: None, which +means to use the same dtype as the original image]

  • +
+
+
Returns:
+

a new Image

+
+
+
+ +
+
+symmetrizeNoise(noise, order=4)
+

Impose N-fold symmetry (where N=``order`` is an even integer >=4) on the noise in a square +image assuming that the noise currently in the image can be described by the +BaseCorrelatedNoise object noise. See BaseCorrelatedNoise.symmetrizeImage for more +details of how this method works.

+
+
Parameters:
+
    +
  • noise – The BaseCorrelatedNoise model to use when figuring out how much noise to add +to make the final noise have symmetry at the desired order.

  • +
  • order – Desired symmetry order. Must be an even integer larger than 2. +[default: 4]

  • +
+
+
Returns:
+

the theoretically calculated variance of the combined noise fields in the updated image.

+
+
+
+ +
+
+transpose()[source]
+

Return the tranpose of the image.

+

Note: The returned image will have an undefined wcs. +If you care about the wcs, you will need to set it yourself.

+
+ +
+
+property true_center
+

The current true center of the image as a PositionD instance.

+

Unline the nominal center returned by im.center, this value may be half-way between +two pixels if the image has an even number of rows or columns. It gives the position +(x,y) at the exact center of the image, regardless of whether this is at the center of +a pixel (integer value) or halfway between two (half-integer). For example:

+
>>> im = galsim.Image(numpy.array(range(16),dtype=float).reshape((4,4)))
+>>> im.center
+galsim.PositionI(x=3, y=3)
+>>> im.true_center
+galsim.PositionI(x=2.5, y=2.5)
+>>> im.setCenter(56,72)
+>>> im.center
+galsim.PositionI(x=56, y=72)
+>>> im.true_center
+galsim.PositionD(x=55.5, y=71.5)
+>>> im.setOrigin(0,0)
+>>> im.true_center
+galsim.PositionD(x=1.5, y=1.5)
+
+
+
+ +
+
+view(scale=None, wcs=None, origin=None, center=None, make_const=False, dtype=None, contiguous=False)[source]
+

Make a view of this image, which lets you change the scale, wcs, origin, etc. +but view the same underlying data as the original image.

+

If you do not provide either scale or wcs, the view will keep the same wcs +as the current Image object.

+
+
Parameters:
+
    +
  • scale – If provided, use this as the pixel scale for the image. [default: None]

  • +
  • wcs – If provided, use this as the wcs for the image. [default: None]

  • +
  • origin – If provided, use this as the origin position of the view. +[default: None]

  • +
  • center – If provided, use this as the center position of the view. +[default: None]

  • +
  • make_const – Make the view’s data array immutable. [default: False]

  • +
  • dtype – If provided, ensure that the output has this dtype. If the original +Image is a different dtype, then a copy will be made. [default: None]

  • +
  • contiguous – If provided, ensure that the output array is contiguous. [default: False]

  • +
+
+
+
+ +
+
+whitenNoise(noise)
+

Whiten the noise in the image assuming that the noise currently in the image can be described +by the BaseCorrelatedNoise object noise. See BaseCorrelatedNoise.whitenImage for more +details of how this method works.

+
+
Parameters:
+

noise – The BaseCorrelatedNoise model to use when figuring out how much noise to add +to make the final noise white.

+
+
Returns:
+

the theoretically calculated variance of the combined noise fields in the updated image.

+
+
+
+ +
+
+wrap(bounds, hermitian=False)[source]
+

Wrap the values in a image onto a given subimage and return the subimage.

+

This would typically be used on a k-space image where you initially draw a larger image +than you want for the FFT and then wrap it onto a smaller subset. This will cause +aliasing of course, but this is often preferable to just using the smaller image +without wrapping.

+

For complex images of FFTs, one often only stores half the image plane with the +implicit understanding that the function is Hermitian, so im(-x,-y) == im(x,y).conjugate(). +In this case, the wrapping needs to work slightly differently, so you can specify +that your image is implicitly Hermitian with the hermitian argument. Options are:

+
+
hermitian=False

(default) Normal non-Hermitian image.

+
+
hermitian=’x’

Only x>=0 values are stored with x<0 values being implicitly Hermitian. +In this case im.bounds.xmin and bounds.xmin must be 0.

+
+
hermitian=’y’

Only y>=0 values are stored with y<0 values being implicitly Hermitian. +In this case im.bounds.ymin and bounds.ymin must be 0.

+
+
+

Also, in the two Hermitian cases, the direction that is not implicitly Hermitian must be +symmetric in the image’s bounds. The wrap bounds must be almost symmetric, but missing +the most negative value. For example,:

+
>>> N = 100
+>>> im_full = galsim.ImageCD(bounds=galsim.BoundsI(0,N/2,-N/2,N/2), scale=dk)
+>>> # ... fill with im[i,j] = FT(kx=i*dk, ky=j*dk)
+>>> N2 = 64
+>>> im_wrap = im_full.wrap(galsim.BoundsI(0,N/2,-N2/2,N2/2-1, hermitian='x')
+
+
+

This sets up im_wrap to be the properly Hermitian version of the data appropriate for +passing to an FFT.

+

Note that this routine modifies the original image (and not just the subimage onto which +it is wrapped), so if you want to keep the original pristine, you should call +wrapped_image = image.copy().wrap(bounds).

+
+
Parameters:
+
    +
  • bounds – The bounds of the subimage onto which to wrap the full image.

  • +
  • hermitian – Whether the image is implicitly Hermitian and if so, whether it is the +x or y values that are not stored. [default: False]

  • +
+
+
Returns:
+

the subimage, image[bounds], after doing the wrapping.

+
+
+
+ +
+
+write(file_name=None, dir=None, hdu_list=None, clobber=True, compression='auto')
+

Write a single image to a FITS file.

+

Write the Image instance image to a FITS file, with details depending on the arguments. +This function can be called directly as galsim.fits.write(image, ...), with the image as the +first argument, or as an Image method: image.write(...).

+
+
Parameters:
+
    +
  • image – The Image to write to file. Per the description of this method, it may be +given explicitly via galsim.fits.write(image, ...) or the method may be +called directly as an image method, image.write(...). Note that if the +image has a ‘header’ attribute containing a FitsHeader, then the +FitsHeader is written to the header in the PrimaryHDU, followed by the +WCS as usual.

  • +
  • file_name – The name of the file to write to. [Either file_name or hdu_list is +required.]

  • +
  • dir – Optionally a directory name can be provided if file_name does not +already include it. [default: None]

  • +
  • hdu_list – An astropy.io.fits.HDUList. If this is provided instead of file_name, +then the Image is appended to the end of the HDUList as a new HDU. In +that case, the user is responsible for calling either +hdu_list.writeto(...) or galsim.fits.writeFile(...) afterwards. +[Either file_name or hdu_list is required.]

  • +
  • clobber – Setting clobber=True will silently overwrite existing files. +[default: True]

  • +
  • compression

    Which compression scheme to use (if any). Options are:

    +
      +
    • None or ‘none’ = no compression

    • +
    • ’rice’ = use rice compression in tiles (preserves header readability)

    • +
    • ’gzip’ = use gzip to compress the full file

    • +
    • ’bzip2’ = use bzip2 to compress the full file

    • +
    • ’gzip_tile’ = use gzip in tiles (preserves header readability)

    • +
    • ’hcompress’ = use hcompress in tiles (only valid for 2-d images)

    • +
    • ’plio’ = use plio compression in tiles (only valid for pos integer data)

    • +
    • ’auto’ = determine the compression from the extension of the file name +(requires file_name to be given):

      +
        +
      • ’.fz’ => ‘rice’

      • +
      • ’.gz’ => ‘gzip’

      • +
      • ’.bz2’ => ‘bzip2’

      • +
      • otherwise None

      • +
      +
    • +
    +

    [default: ‘auto’]

    +

  • +
+
+
+
+ +
+
+property xmax
+

Alias for self.bounds.xmax.

+
+ +
+
+property xmin
+

Alias for self.bounds.xmin.

+
+ +
+
+property ymax
+

Alias for self.bounds.ymax.

+
+ +
+
+property ymin
+

Alias for self.bounds.ymin.

+
+ +
+ +
+
+galsim._Image(array, bounds, wcs)[source]
+

Equivalent to Image(array, bounds, wcs), but without the overhead of sanity checks, +and the other options for how to provide the arguments.

+
+ +
+
+galsim.ImageF(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.float32)

+
+ +
+
+galsim.ImageD(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.float64)

+
+ +
+
+galsim.ImageI(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.int32)

+
+ +
+
+galsim.ImageS(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.int16)

+
+ +
+
+galsim.ImageUI(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.uint32)

+
+ +
+
+galsim.ImageUS(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.uint16)

+
+ +
+
+galsim.ImageCF(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.complex64)

+
+ +
+
+galsim.ImageCD(*args, **kwargs)[source]
+

Alias for galsim.Image(…, dtype=numpy.complex128)

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/index.html b/docs/_build/html/index.html new file mode 100644 index 00000000000..9a61ae6cb88 --- /dev/null +++ b/docs/_build/html/index.html @@ -0,0 +1,323 @@ + + + + + + + Indices and tables — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

GalSim: The modular galaxy image simulation toolkit

+
+ +
+
+

Indices and tables

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/install.html b/docs/_build/html/install.html new file mode 100644 index 00000000000..89659e02830 --- /dev/null +++ b/docs/_build/html/install.html @@ -0,0 +1,235 @@ + + + + + + + Installation Instructions — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation Instructions

+

GalSim is a Python module that has much of its implementation in C++ for +improved computational efficiency. +It is regularly tested on Python versions 3.7, 3.8, and 3.9 on both linux +and mac os.

+

It also seems to work on PyPy (both via conda-forge and the GitHub Actions +setup), although we don’t consider this an officially supported system. +If you use GalSim with PyPy and experience any problems, we would appreciate +hearing about them. Please open an issue describing any problems you find.

+

System requirements: GalSim currently only supports Linux and Mac OSX. +Possibly other POSIX-compliant systems, but we specifically do not +currently support Windows.

+

The usual way to install GalSim is now (starting with version 2.0) simply:

+
pip install galsim
+
+
+

which will install the latest official release of GalSim. +For complete details, see Installing With Pip.

+

Another option If you use Anaconda Python is to use conda:

+
conda install -c conda-forge galsim
+
+
+

For more information, see Installing With Conda.

+ +
+

Running tests

+

The simplest way to run our test suite by typing:

+
python setup.py test
+
+
+

This should run all the Python-layer tests with pytest and also compile and +run the C++ test suite.

+

There are a number of packages that are used by the tests, but which are not +required for GalSim installation and running. These should be installed +automatically by the above command, but you can install them manually via:

+
pip install -r test_requirements.txt
+
+
+

(As usually, you may need to add either sudo or --user.)

+

By default, the tests will run in parallel using the pytest plugins +pytest-xdist and pytest-timeout (to manage how much time each test is +allowed to run). If you want to run the Python tests in serial instead, +you can do this via:

+
python setup.py test -j1
+
+
+

You can also use this to modify how many jobs will be spawned for running the +tests.

+

Or, you can run the Python tests yourself in the tests directory by typing:

+
pytest test*.py
+
+
+

You can also run them with multiple jobs (e.g. for 4 jobs) by typing:

+
pytest -n=4 --timeout=60 test*.py
+
+
+

You need the pytest-xdist and pytest-timeout plugins for this to work.

+
+

Note

+

If your system does not have pytest installed, and you do not want +to install it, you can run all the Python tests with the script run_all_tests +in the tests directory. If this finishes without an error, then all the tests +have passed. However, note that this script runs more tests than our normal +test run using pytest, so it may take quite a while to finish. (The “all” in +the file name means run all the tests including the slow ones that we normally +skip.)

+
+
+
+

Running example scripts

+

The examples directory has a series of demo scripts:

+
demo1.py, demo2.py, ...
+
+
+

These can be considered a tutorial on getting up to speed with GalSim. Reading +through these in order will introduce you to how to use most of the features of +GalSim in Python. To run these scripts, type (e.g.):

+
python demo1.py
+
+
+

There are also a corresponding set of config files:

+
demo1.yaml, demo2.yaml, ...
+
+
+

These files can be run using the executable galsim, and will produce the +same output images as the Python scripts:

+
galsim demo1.yaml
+
+
+

They are also well commented, and can be considered a parallel tutorial for +learning the config file usage of GalSim.

+

All demo scripts are designed to be run in the GalSim/examples directory. +Some of them access files in subdirectories of the examples directory, so they +would not work correctly from other locations.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/install_conda.html b/docs/_build/html/install_conda.html new file mode 100644 index 00000000000..04bf42bfdfe --- /dev/null +++ b/docs/_build/html/install_conda.html @@ -0,0 +1,156 @@ + + + + + + + Installing With Conda — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installing With Conda

+

If you use conda (normally via the Anaconda Python distribution), then all of +the prerequisites and galsim itself are available from the conda-forge channel, +so you can use that as follows:

+
conda install -c conda-forge galsim
+
+
+

Also, if you prefer to use the defaults channel, then (at least as of this +writing), it had all the items in conda_requirements.txt, except for pybind11. +So if you have conda-forge in your list of channels, but it comes after +defaults, then that should still work and pybind11 will be the only one that +will need the conda-forge channel.

+

If you want to install from source (e.g. to work on a development branch), +but use conda for the dependencies, you can do:

+
git clone git@github.com:GalSim-developers/GalSim.git
+cd GalSim
+conda install --file conda_requirements.txt
+python setup.py install
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/install_pip.html b/docs/_build/html/install_pip.html new file mode 100644 index 00000000000..a941eacb601 --- /dev/null +++ b/docs/_build/html/install_pip.html @@ -0,0 +1,497 @@ + + + + + + + Installing With Pip — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installing With Pip

+
+

Overall summary

+

The usual way to install GalSim is now (starting with version 2.0) simply:

+
pip install galsim
+
+
+

which will install the latest official release of GalSim.

+

Note that you may need to use sudo with the above command if you are installing +into system directories. If you do not have write privileges for the directory +it is trying to install into, you can use the –user flag to install into a +local directory instead. (Normally something like $HOME/Library/Python/2.7 +or $HOME/.local, depending on your system.)

+

This might fail if certain libraries are installed in non-standard locations. +In this case, add the paths for these libraries to both the LIBRARY_PATH and +LD_LIBRARY_PATH environmental variables before running pip:

+
export LIBRARY_PATH=$LIBARY_PATH:/path/to/lib:/other/path/to/lib
+export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/lib:/other/path/to/lib
+
+
+

If you would rather install from source (e.g. to work on a development branch), +you can do:

+
git clone git@github.com:GalSim-developers/GalSim.git
+cd GalSim
+pip install -r requirements.txt
+python setup.py install
+
+
+

(again possibly with either sudo or –user).

+

Either of these installation methods should handle most of the required +dependencies for you if you do not have them already installed on your machine. +In particular, all of the python dependencies should be automatically installed +for you. See Installing Python Dependencies if you have trouble with any of these.

+

FFTW is not directly pip installable, so if the above installation fails, +you may need to install it separately. See Installing FFTW for more details +about how to do this.

+

Eigen is also not pip installable, but it is a header-only library, so it doesn’t +require any installation. Therefore, if you don’t have it it locally in a place where +setup.py can find it, a version of Eigen will be downloaded directly. +See Installing Eigen for more details.

+

Finally, while GalSim is officially a Python package, there are some users who +use the compiled C++ library directly, rather than via the Python interface. +See Installing the C++ Shared Library for information about this possibility.

+
+
+

Installing Python Dependencies

+

Normally, all of the python package dependencies will be automatically installed +by pip. The following versions are known to work with GalSim 2.1. In most cases, +other recent (especially later) versions will also work:

+
    +
  • NumPy (1.16.1)

  • +
  • Future (0.17.1)

  • +
  • Astropy (3.0.5)

  • +
  • PyBind11 (2.2.3)

  • +
  • LSSTDESC.Coord (1.0.5)

  • +
+

There are a few others modules are not technically required, but we let pip +install them along with GalSim, because they either add useful functionality +or efficiency to GalSim. These are listed in the requirements.txt file that +pip uses to determine what else to install. But if you install with +python setup.py install, then these will not be installed.

+
    +
  • Starlink (3.10.0) (Improved WCS functionality)

  • +
  • PyYaml (3.12) (Reads YAML config files)

  • +
  • Pandas (0.20) (Faster reading of ASCII input files)

  • +
+

If you want to install these yourself, the quickest way is to do:

+
pip install -r requirements.txt
+
+
+

If you want more control about which version you get or otherwise want to install +each package individually, you can do:

+
pip install numpy
+pip install future
+pip install astropy
+pip install pybind11
+pip install LSSTDESC.Coord
+
+pip install starlink-pyast
+pip install pyyaml
+pip install pandas
+
+
+

In all cases, you may need to precede the above commands with sudo or +add --user to the end as you normally do when pip installing on your system.

+
+
+

Installing FFTW

+

GalSim uses FFTW (The Fastest Fourier Transform in the West) for performing +fast Fourier transforms.

+

We require FFTW version >= 3.0. Most tests have been done with FFTW 3.3.7, +so if you have trouble with an earlier version, try upgrading to 3.3.7 or later.

+
+

Installing FFTW yourself

+

FFTW is available at the URL:

+

http://www.fftw.org/download.html

+

As of this writing, version 3.3.7 is the current latest release, for which +the following commands should work to download and install it:

+
wget http://www.fftw.org/fftw-3.3.7.tar.gz
+tar xfz fftw-3.3.7.tar.gz
+cd fftw-3.3.7
+./configure --enable-shared
+make
+sudo make install
+
+
+

If you want to install into a different directory (e.g. because you do not +have sudo privileges on your machine), then specify the alternate directory +with the –prefix flag to configure. E.g.:

+
./configure --enable-shared --prefix=$HOME
+
+
+

which will install the library into $HOME/lib and the header file into +$HOME/include. In this case, leave off the sudo from the last line. +Also, you should make sure these directories are in your LD_LIBRARY_PATH +and C_INCLUDE_PATH environment variables, respectively.

+

Alternatively, if you do not want to modify your LD_LIBRARY_PATH and/or +C_INCLUDE_PATH, you can instead set an environment variable to tell GalSim +where the files are:

+
export FFTW_DIR=/path/to/fftw/prefix
+
+
+

E.g. in the above case where prefix is $HOME, you would do:

+
export FFTW_DIR=$HOME
+
+
+

Probably, you should put this into your shell login file (e.g. .bash_profile) +so it always gets set when you log in.

+
+
+

Using an existing installation of FFTW

+

If FFTW is already installed on your system, there may be nothing to do. +If it is in a standard location like /usr/local/lib or in some other +directory in your LD_LIBRARY_PATH, then GalSim should find it without +any extra work on your part.

+

If it is in a non-standard location, and you do not want to add this path +to your LD_LIBRARY_PATH (or you are on a modern Mac that hides such system +variables from setup.py), then you can instead set the FFTW_DIR environment +variable to tell GalSim where to look when running pip or setup.py:

+
FFTW_DIR=/path/to/fftw/lib pip install galsim
+
+
+

or:

+
FFTW_DIR=/path/to/fftw/lib python setup.py install
+
+
+

For instance, if libfftw3.so is located in /opt/cray/pe/lib64, you could use +that with:

+
FFTW_DIR=/opt/cray/pe/lib64 pip install galsim
+
+
+

If you want to set up your system so that you don’t have to type that each +time you re-install galsim, you might want to set it in your .bash_profile +file or similar location. e.g.:

+
export FFTW_DIR=/opt/cray/pe/lib64
+
+
+

If you have multiple versions of FFTW installed on your system, this variable +can be used to specify which version you want GalSim to use as this will be +the first location it will check during the installation process.

+

Furthermore, setup.py will trust this location even if it cannot load the +library that it finds there. This can be useful when cross-compiling, since +the library it finds might not be loadable on the system doing the compiling.

+
+
+

Installing FFTW with conda

+

If you use conda, FFTW can be install with:

+
conda install fftw
+
+
+

This will put it into the anaconda/lib directory on your system (within your +active environment if appropriate). GalSim knows to look here, so there is +nothing additional you need to do.

+
+
+

Installing FFTW with apt-get

+

On Linux machines that use apt-get, FFTW can be installed with:

+
apt-get install libfftw3-dev
+
+
+
+
+

Installing FFTW with fink

+

If you use fink on a Mac, FFTW can be installed with:

+
fink install fftw3
+
+
+

(Make sure to use fftw3, not fftw, since fftw is version 2.)

+

This will put it into the /sw/lib directory on your system. GalSim knows to +look here, so there is nothing additional you need to do.

+
+
+

Installing FFTW with MacPorts

+

If you use MacPorts, FFTW can be installed with:

+
port install fftw-3
+
+
+

This will put it into the /opt/local/lib directory on your system. GalSim knows +to look here, so there is nothing additional you need to do.

+
+
+
+

Installing Eigen

+

GalSim uses Eigen for the C++-layer linear algebra calculations. It is a +header-only library, which means that nothing needs to be compiled to use it. +You can download the header files yourself, but if you do not, then the +installation script will download it for you automatically. So usually, +this dependency should require no work on your part.

+

However, if you have a version of Eigen already installed on your system, +you may want to use that. If the right directory is in your path for +include files (C_INCLUDE_PATH), it should find it. If not, you may specify +the right directory to use by setting the EIGEN_DIR environment variable.

+

We require Eigen version >= 3.0. The version we download automatically is +3.3.4, so that version is known to work. We have also tested with versions +3.2.8 and 3.0.4, so probably any 3.x version will work. However, if you have +trouble with another version, try upgrading to 3.3.4 or later.

+
+

Installing Eigen yourself

+

Eigen is available at the URL

+

http://eigen.tuxfamily.org/index.php

+

As of this writing, version 3.3.4 is the current latest release, for which +the following commands should work to download and install it:

+
wget http://bitbucket.org/eigen/eigen/get/3.3.4.tar.bz2
+tar xfj 3.3.4.tar.bz2
+sudo cp eigen-eigen-5a0156e40feb/Eigen /usr/local/include
+
+
+

In the final cp line, the MD5 hash (5a0156e40feb) will presumably change for +other versions, so use whatever directory tar expands into if you are using +a different version than 3.3.4.

+

If you do not have sudo privileges, you can copy to a different directory such +as $HOME/include instead and leave off the sudo from the cp command. In this +case, make sure this directory is in your C_INCLUDE_PATH environment variable.

+

Finally, you can also skip the last command above and instead set EIGEN_DIR +as an environment variable to tell GalSim where the files are:

+
export EIGEN_DIR=/some/path/to/eigen
+
+
+

This should be the directory in which the Eigen subdirectory is found. E.g.:

+
export EIGEN_DIR=$HOME/eigen-eigen-5a0156e40feb
+
+
+

Probably, you should put this into your .bash_profile file so it always gets +set when you log in.

+
+
+

Using an existing installation of Eigen

+

If Eigen is already installed on your system, there may be nothing to do. +If it is in a standard location like /usr/local/include or in some other +directory in your C_INCLUDE_PATH, then GalSim should find it without +any extra work on your part.

+

If it is in a non-standard location, and you do not want to add this path +to your C_INCLUDE_PATH, then you can instead set the EIGEN_DIR environment +variable to tell GalSim where to look:

+
export EIGEN_DIR=/some/path/to/eigen
+
+
+

For instance, if Eigen was installed into /usr/include/eigen3, then you +could use that with:

+
export EIGEN_DIR=/usr/include/eigen3
+
+
+

This command would normally be done in your .bash_profile file so it gets +executed every time you log in.

+

If you have multiple versions of Eigen installed on your system, this variable +can be used to specify which version you want GalSim to use as this will be +the first location it will check during the installation process.

+
+
+

Installing Eigen with conda

+

If you use conda, Eigen can be install with:

+
conda install eigen
+
+
+

This will put it into the anaconda/include directory on your system (within +your active environment if appropriate). GalSim knows to look here, so there +is nothing additional you need to do.

+
+
+

Installing Eigen with apt-get

+

On Linux machines that use apt-get, Eigen can be installed with:

+
apt-get install libeigen3-dev
+
+
+
+
+

Installing Eigen with fink

+

If you use fink on a Mac, Eigen can be installed with:

+
fink install eigen
+
+
+

This will put it into the /sw/include directory on your system. GalSim knows +to look here, so there is nothing additional you need to do.

+
+
+

Installing Eigen with MacPorts

+

If you use MacPorts, Eigen can be installed with:

+
port install eigen
+
+
+

This will put it into the /opt/local/include directory on your system. GalSim +knows to look here, so there is nothing additional you need to do.

+
+
+
+

Installing the C++ Shared Library

+

GalSim is first and foremost a Python package. However, it uses C++ functions behind +the scenes to implement many of the numerically intensive operations. +Some users have found it useful to link directly to the GalSim C++ library +with their own C++ code. This use case is supported, but at a lower level of +API guarantees than we make for the Python code.

+

The official public API is really only the python-layer classes and functions. +We strive to hew closely to the Semantic Versioning +specifications about maintaining backwards compatibility within major release cycles, +so user code that uses only the public API should continue to work correctly until a +major version upgrade.

+

This is NOT guaranteed for the C++ API. These are officially implementation +details in support of the Python API. However, in practice, the C++ function +signatures rarely change very much. So for the most part, you can expect your +code to continue to work for updated GalSim versions. Or if a function signature +changes slightly, it will usually be fairly simple to update to the new syntax.

+

We provide the appropriate header files to use in the installed python library +location in the include directory. These files precisely specify the +available C++-layer API for a given release.

+

The compiled library file for linking user C++ code is not installed by the default +pip or setup.py installation procedure. The relevant functions are included +in the Python module file, called _galsim.so, but this file is often not usable +for linking your own C++ code.

+

For this purpose, you will need to perform an extra step to build a shared library +that has the C++-layer functions. Run the following command:

+
python setup.py build_shared_clib
+
+
+

The built shared library will be located in build/shared_clib/. The library file +is named libgalsim.M.m.dylib on OSX or libgalsim.M.m.so on Linux, where M,m +are the major and minor version numbers for the release. You should copy this file +to some appropriate directory where your C++ code will be able to link to it.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/integ.html b/docs/_build/html/integ.html new file mode 100644 index 00000000000..beead5432da --- /dev/null +++ b/docs/_build/html/integ.html @@ -0,0 +1,430 @@ + + + + + + + Integration — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Integration

+
+
+galsim.integ.int1d(func, min, max, rel_err=1e-06, abs_err=1e-12)[source]
+

Integrate a 1-dimensional function from min to max.

+

Example usage:

+
>>> def func(x): return x**2
+>>> galsim.integ.int1d(func, 0, 1)
+0.33333333333333337
+>>> galsim.integ.int1d(func, 0, 2)
+2.666666666666667
+>>> galsim.integ.int1d(func, -1, 1)
+0.66666666666666674
+
+
+
+

Note

+

This uses an adaptive Gauss-Kronrod-Patterson method for doing the integration.

+

cf. https://www.jstor.org/stable/2004583

+

If one or both endpoints are infinite, it will automatically use an appropriate +transformation to turn it into a finite integral.

+
+
+
Parameters:
+
    +
  • func – The function to be integrated. y = func(x) should be valid.

  • +
  • min – The lower end of the integration bounds (anything < -1.e10 is treated as +negative infinity).

  • +
  • max – The upper end of the integration bounds (anything > 1.e10 is treated as positive +infinity).

  • +
  • rel_err – The desired relative error [default: 1.e-6]

  • +
  • abs_err – The desired absolute error [default: 1.e-12]

  • +
+
+
Returns:
+

the value of the integral.

+
+
+
+ +
+
+galsim.integ.hankel(func, k, nu=0, rmax=None, rel_err=1e-06, abs_err=1e-12)[source]
+

Perform an order nu Hankel transform of the given function f(r) at a specific k value.

+
+\[F(k) = \int_0^\infty f(r) J_\nu(k r) r dr\]
+
+

Note

+

For non-truncated Hankel integrals, this uses the method outlined in Ogata, 2005: +http://www.kurims.kyoto-u.ac.jp/~prims/pdf/41-4/41-4-40.pdf

+

For truncated integrals (and k=0), it uses the same adaptive Gauss-Kronrod-Patterson +method used for int1d.

+
+
+
Parameters:
+
    +
  • func – The function f(r)

  • +
  • k – (float or numpy array) The value(s) of k for which to calculate F(k).

  • +
  • nu – The order of the Bessel function to use for the transform. [default: 0]

  • +
  • rmax – An optional truncation radius at which to have f(r) drop to 0. [default: None]

  • +
  • rel_err – The desired relative accuracy [default: 1.e-6]

  • +
  • abs_err – The desired absolute accuracy [default: 1.e-12]

  • +
+
+
Returns:
+

An estimate of F(k)

+
+
+
+ +
+
+class galsim.integ.IntegrationRule[source]
+

A class that can be used to integrate something more complicated than a normal +scalar function.

+

In GalSim, we use it to do the integration of chromatic images over a bandpass. +Typically f is some kind of draw function, xs are the wavelengths, and w is the +bandpass throughput. But this class is abstracted away from all of that and can be used +for anything where the function returns something complicated, but which can be added +together to compute the quadrature.

+

Specifically the return value from f must be closed under both addition and multiplication +by a scalar (a float value).

+
+ +
+
+class galsim.integ.MidptRule[source]
+

Midpoint rule for integration.

+
+
+calculateWeights(xs, w)[source]
+

Calculate the apporpriate weights for the midpoint rule integration

+
+
Parameters:
+
    +
  • xs – Locations at which to evaluate f.

  • +
  • w – Weight function if desired [default: None]

  • +
+
+
Returns:
+

The net weights to use at each location.

+
+
+
+ +
+ +
+
+class galsim.integ.TrapzRule[source]
+

Trapezoidal rule for integration.

+
+
+calculateWeights(xs, w)[source]
+

Calculate the apporpriate weights for the trapezoidal rule integration

+
+
Parameters:
+
    +
  • xs – Locations at which to evaluate f.

  • +
  • w – Weight function if desired [default: None]

  • +
+
+
Returns:
+

The net weights to use at each location.

+
+
+
+ +
+ +
+
+class galsim.integ.QuadRule[source]
+

Quadratic rule for integration

+

This models both f and w as linear between the evaluation points, so the product is +quadratic.

+
+
+calculateWeights(xs, w)[source]
+

Calculate the apporpriate weights for the quadratic rule integration

+
+
Parameters:
+
    +
  • xs – Locations at which to evaluate f.

  • +
  • w – Weight function if desired [default: None]

  • +
+
+
Returns:
+

The net weights to use at each location.

+
+
+
+ +
+ +
+
+galsim.integ.midptRule = <galsim.integ.MidptRule object>
+

For convenience, an instance of MidptRule

+
+ +
+
+galsim.integ.trapzRule = <galsim.integ.TrapzRule object>
+

For convenience, an instance of TrapzRule

+
+ +
+
+galsim.integ.quadRule = <galsim.integ.QuadRule object>
+

For convenience, an instance of QuadRule

+
+ +
+
+class galsim.integ.ImageIntegrator[source]
+

A base class for integrators used by ChromaticObject to integrate the drawn images +over wavelengthh using a Bandpass as a weight function.

+
+
+__call__(evaluateAtWavelength, bandpass, image, drawImageKwargs, doK=False)[source]
+
+
Parameters:
+
    +
  • evaluateAtWavelength – Function that returns a monochromatic surface brightness +profile as a function of wavelength.

  • +
  • bandpassBandpass object representing the filter being imaged through.

  • +
  • imageImage used to set size and scale of output

  • +
  • drawImageKwargs – dict with other kwargs to send to ChromaticObject.drawImage +function.

  • +
  • doK – Integrate up results of ChromaticObject.drawKImage instead of +results of ChromaticObject.drawImage. [default: False]

  • +
+
+
Returns:
+

the result of integral as an Image

+
+
+
+ +
+ +
+
+class galsim.integ.SampleIntegrator(rule)[source]
+

Bases: ImageIntegrator

+

Create a chromatic surface brightness profile integrator, which will integrate over +wavelength using a Bandpass as a weight function.

+

This integrator will evaluate the integrand only at the wavelengths in bandpass.wave_list. +See ContinuousIntegrator for an integrator that evaluates the integrand at a given number of +points equally spaced apart.

+
+
Parameters:
+

rule

Which integration rule to apply to the wavelength and monochromatic surface +brightness samples. Options include:

+
    +
  • galsim.integ.midptRule: Use the midpoint integration rule

  • +
  • galsim.integ.trapzRule: Use the trapezoidal integration rule

  • +
  • galsim.integ.quadRule: Use the quadratic integration rule

  • +
+

+
+
+
+ +
+
+class galsim.integ.ContinuousIntegrator(rule, N=250, use_endpoints=True)[source]
+

Bases: ImageIntegrator

+

Create a chromatic surface brightness profile integrator, which will integrate over +wavelength using a Bandpass as a weight function.

+

This integrator will evaluate the integrand at a given number N of equally spaced +wavelengths over the interval defined by bandpass.blue_limit and bandpass.red_limit. See +SampleIntegrator for an integrator that only evaluates the integrand at the predefined set of +wavelengths in bandpass.wave_list.

+
+
Parameters:
+
    +
  • rule

    Which integration rule to apply to the wavelength and monochromatic +surface brightness samples. Options include:

    +
      +
    • galsim.integ.midptRule: Use the midpoint integration rule

    • +
    • galsim.integ.trapzRule: Use the trapezoidal integration rule

    • +
    • galsim.integ.quadRule: Use the quadratic integration rule

    • +
    +

  • +
  • N – Number of equally-wavelength-spaced monochromatic surface brightness +samples to evaluate. [default: 250]

  • +
  • use_endpoints – Whether to sample the endpoints bandpass.blue_limit and +bandpass.red_limit. This should probably be True for a rule like +numpy.trapz, which explicitly samples the integration limits. For a +rule like the midpoint rule, however, the integration limits are not +generally sampled, (only the midpoint between each integration limit and +its nearest interior point is sampled), thus use_endpoints should be +set to False in this case. [default: True]

  • +
+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/interpolant.html b/docs/_build/html/interpolant.html new file mode 100644 index 00000000000..5f44d74c0db --- /dev/null +++ b/docs/_build/html/interpolant.html @@ -0,0 +1,705 @@ + + + + + + + Interpolants — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Interpolants

+

These classes are principally used by InterpolatedImage. They define how the surface +brightness between pixel centers is defined from the values at the centers.

+
+
+class galsim.Interpolant[source]
+

A base class that defines how interpolation should be done.

+

An Interpolant is needed for an InterpolatedImage to define how interpolation should be done +an locations in between the integer pixel centers.

+
+
+static from_name(name, tol=None, gsparams=None)[source]
+

A factory function to create an Interpolant of the correct type according to +the (string) name of the Interpolant.

+

This is mostly used to simplify how config files specify the Interpolant to use.

+

Valid names are:

+
+
+
+

In addition, if you want to specify the conserve_dc option for Lanczos, you can +append either T or F to represent conserve_dc = True/False (respectively). Otherwise, +the default conserve_dc=True is used.

+
+
Parameters:
+
    +
  • name – The name of the interpolant to create.

  • +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+ +
+
+property gsparams
+

The GSParams of the Interpolant

+
+ +
+
+kval(k)[source]
+

Calculate the value of the interpolant kernel in Fourier space at one or more k values.

+
+
Parameters:
+

k – The value (as a float) or values (as a np.array) at which to compute the +amplitude of the Interpolant kernel in Fourier space.

+
+
Returns:
+

+
The k-value(s) at the k location(s). If k was an array, then this is also

an array.

+
+
+

+
+
Return type:
+

kval

+
+
+
+ +
+
+property negative_flux
+

The negative-flux fraction of the interpolation kernel.

+
+ +
+
+property positive_flux
+

The positive-flux fraction of the interpolation kernel.

+
+ +
+
+unit_integrals(max_len=None)[source]
+

Compute the unit integrals of the real-space kernel.

+

integrals[i] = int(xval(x), i-0.5, i+0.5)

+
+
Parameters:
+

max_len – The maximum length of the returned array. This is usually only relevant +for SincInterpolant, where xrange = inf.

+
+
Returns:
+

An array of unit integrals of length max_len or smaller.

+
+
Return type:
+

integrals

+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current interpolant with the given gsparams

+
+ +
+
+xval(x)[source]
+

Calculate the value of the interpolant kernel at one or more x values

+
+
Parameters:
+

x – The value (as a float) or values (as a np.array) at which to compute the +amplitude of the Interpolant kernel.

+
+
Returns:
+

+
The value(s) at the x location(s). If x was an array, then this is also

an array.

+
+
+

+
+
Return type:
+

xval

+
+
+
+ +
+ +
+
+class galsim.Delta(tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

Delta-function interpolation.

+

The interpolant for when you do not want to interpolate between samples. It is not really +intended to be used for any analytic drawing because it is infinite in the x domain at the +location of samples, and it extends to infinity in the u domain. But it could be useful for +photon-shooting, where it is trivially implemented as no displacements.

+
+
Parameters:
+
    +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+unit_integrals(max_len=None)[source]
+

Compute the unit integrals of the real-space kernel.

+

integrals[i] = int(xval(x), i-0.5, i+0.5)

+
+
Parameters:
+

max_len – The maximum length of the returned array. (ignored)

+
+
Returns:
+

An array of unit integrals of length max_len or smaller.

+
+
Return type:
+

integrals

+
+
+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+
+class galsim.Nearest(tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

Nearest-neighbor interpolation (boxcar).

+

The nearest-neighbor interpolant performs poorly as a k-space or x-space interpolant for +interpolated images. (See paper by “Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) +The objection to its use in Fourier space does not apply when shooting photons to generate +an image; in that case, the nearest-neighbor interpolant is quite efficient (but not +necessarily the best choice in terms of accuracy).

+
+
Parameters:
+
    +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+unit_integrals(max_len=None)[source]
+

Compute the unit integrals of the real-space kernel.

+

integrals[i] = int(xval(x), i-0.5, i+0.5)

+
+
Parameters:
+

max_len – The maximum length of the returned array. (ignored)

+
+
Returns:
+

An array of unit integrals of length max_len or smaller.

+
+
Return type:
+

integrals

+
+
+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+
+class galsim.Linear(tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

Linear interpolation

+

The linear interpolant is a poor choice for FFT-based operations on interpolated images, as +it rings to high frequencies. (See Bernstein & Gruen, http://arxiv.org/abs/1401.2636.) +This objection does not apply when shooting photons, in which case the linear interpolant is +quite efficient (but not necessarily the best choice in terms of accuracy).

+
+
Parameters:
+
    +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+unit_integrals(max_len=None)[source]
+

Compute the unit integrals of the real-space kernel.

+

integrals[i] = int(xval(x), i-0.5, i+0.5)

+
+
Parameters:
+

max_len – The maximum length of the returned array. This is usually only relevant +for SincInterpolant, where xrange = inf.

+
+
Returns:
+

An array of unit integrals of length max_len or smaller.

+
+
Return type:
+

integrals

+
+
+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+
+class galsim.Cubic(tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

Cubic interpolation

+

The cubic interpolant is exact to 3rd order Taylor expansion (from R. G. Keys, IEEE Trans. +Acoustics, Speech, & Signal Proc 29, p 1153, 1981). It is a reasonable choice for a four-point +interpolant for interpolated images. (See Bernstein & Gruen, http://arxiv.org/abs/1401.2636.)

+
+
Parameters:
+
    +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+unit_integrals(max_len=None)[source]
+

Compute the unit integrals of the real-space kernel.

+

integrals[i] = int(xval(x), i-0.5, i+0.5)

+
+
Parameters:
+

max_len – The maximum length of the returned array. This is usually only relevant +for SincInterpolant, where xrange = inf.

+
+
Returns:
+

An array of unit integrals of length max_len or smaller.

+
+
Return type:
+

integrals

+
+
+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+
+class galsim.Quintic(tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

Fifth order interpolation

+

The quintic interpolant is exact to 5th order in the Taylor expansion and was found by +Bernstein & Gruen (http://arxiv.org/abs/1401.2636) to give optimal results as a k-space +interpolant.

+
+
Parameters:
+
    +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+
+class galsim.SincInterpolant(tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

Sinc interpolation (inverse of nearest-neighbor).

+

The Sinc interpolant (K(x) = sin(pi x)/(pi x)) is mathematically perfect for band-limited +data, introducing no spurious frequency content beyond kmax = pi/dx for input data with pixel +scale dx. However, it is formally infinite in extent and, even with reasonable trunction, is +still quite large. It will give exact results in GSObject.kValue for InterpolatedImage +when it is used as a k-space interpolant, but is extremely slow. The usual compromise between +sinc accuracy vs. speed is the Lanczos interpolant (see its documentation for details).

+
+
Parameters:
+
    +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+unit_integrals(max_len=None)[source]
+

Compute the unit integrals of the real-space kernel.

+

integrals[i] = int(xval(x), i-0.5, i+0.5)

+
+
Parameters:
+

max_len – The maximum length of the returned array.

+
+
Returns:
+

An array of unit integrals of length max_len or smaller.

+
+
Return type:
+

integrals

+
+
+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+
+class galsim.Lanczos(n, conserve_dc=True, tol=None, gsparams=None)[source]
+

Bases: Interpolant

+

The Lanczos interpolation filter, nominally sinc(x)*sinc(x/n)

+

The Lanczos filter is an approximation to the band-limiting sinc filter with a smooth cutoff +at high x. Order n Lanczos has a range of +/- n pixels. It typically is a good compromise +between kernel size and accuracy.

+

Note that pure Lanczos, when interpolating a set of constant-valued samples, does not return +this constant. Setting conserve_dc in the constructor tweaks the function so that it +approximately conserves the value of constant (DC) input data (accurate to better than 1.e-5 +when used in two dimensions).

+
+
Parameters:
+
    +
  • n – The order of the Lanczos function

  • +
  • conserve_dc – Whether to add the first order correction to flatten out the flux response +to a constant input. [default: True, see above]

  • +
  • tol – [deprecated]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property conserve_dc
+

Whether this interpolant is modified to improve flux conservation.

+
+ +
+
+property ixrange
+

The total integral range of the interpolant. Typically 2 * xrange.

+
+ +
+
+property krange
+

The maximum extent of the interpolant in Fourier space (in 1/pixels).

+
+ +
+
+property n
+

The order of the Lanczos function.

+
+ +
+
+property xrange
+

The maximum extent of the interpolant from the origin (in pixels).

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/misc.html b/docs/_build/html/misc.html new file mode 100644 index 00000000000..0b3f2ce04a6 --- /dev/null +++ b/docs/_build/html/misc.html @@ -0,0 +1,1334 @@ + + + + + + + Miscellaneous Utilities — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Miscellaneous Utilities

+

We have a whole bunch of miscellaneous helper functions and classes in the galsim.utilities +module. Most of these are probably not particularly useful for anything other than internal +GalSim code. But we highlight a few things that might be more widely useful beyond GalSim usage.

+
+

Decorators

+
+
+class galsim.utilities.lazy_property(fget)[source]
+

This decorator will act similarly to @property, but will be efficient for multiple access +to values that require some significant calculation.

+

It works by replacing the attribute with the computed value, so after the first access, +the property (an attribute of the class) is superseded by the new attribute of the instance.

+

Note that is should only be used for non-mutable data, since the calculation will not be +repeated if anything about the instance changes.

+

Usage:

+
@lazy_property
+def slow_function_to_be_used_as_a_property(self):
+    x =  ...  # Some slow calculation.
+    return x
+
+
+

Base on an answer from http://stackoverflow.com/a/6849299

+
+ +
+
+class galsim.utilities.doc_inherit(mthd)[source]
+

This decorator will grab a doc string from a base class version of a method. +Useful if the subclass doesn’t change anything about the method API, but just has +a specialized implementation. This lets the documentation live only in one place.

+

Usage:

+
class Base:
+    def some_method(self):
+        """A nice description of the functionality
+        """
+        pass
+
+class Sub(Base):
+
+    @doc_inherit
+    def some_method(self):
+        # Don't bother with any doc string here.
+        pass
+
+
+

Based on the Docstring Inheritance Decorator at:

+

https://github.com/ActiveState/code/wiki/Python_index_1

+

Although I (MJ) modified it slightly, since the original recipe there had a bug that made it +not work properly with 2 levels of sub-classing (e.g. Pixel <- Box <- GSObject).

+
+ +
+
+class galsim.utilities.timer(f)[source]
+

A decorator that reports how long a function took to run.

+

In GalSim we decorate all of our tests with this to try to watch for long-running tests.

+
+ +
+
+

OpenMP Utilties

+
+
+galsim.utilities.get_omp_threads()[source]
+

Get the current number of OpenMP threads to be used in the C++ layer.

+
+
Returns:
+

num_threads

+
+
+
+ +
+
+galsim.utilities.set_omp_threads(num_threads, logger=None)[source]
+

Set the number of OpenMP threads to use in the C++ layer.

+
+
Parameters:
+
    +
  • num_threads – The target number of threads to use (If None or <=0, then try to use the +numer of cpus.)

  • +
  • logger – If desired, a logger object for logging any warnings here. (default: None)

  • +
+
+
Returns:
+

The number of threads OpenMP reports that it will use. Typically this +matches the input, but OpenMP reserves the right not to comply with +the requested number of threads.

+
+
+
+ +
+
+class galsim.utilities.single_threaded(*, num_threads=1)[source]
+

A context manager that turns off (or down) OpenMP threading e.g. during multiprocessing.

+

Usage:

+
with single_threaded():
+    # Code where you don't want to use any OpenMP threads in the C++ layer
+    # E.g. starting a multiprocessing pool, where you don't want each process
+    # to use multiple threads, potentially ending up with n_cpu^2 threads
+    # running at once, which would generally be bad for performance.
+
+
+
+

Note

+

This is especaily important when your compiler is gcc and you are using the +“fork” context in multiprocessing. There is a bug in gcc that can cause an +OpenMP parallel block to hang after forking. +cf. make it possible to use OMP on both sides of a fork +for more discussion about this issue.

+
+

It can also be used to set a particular number of threads other than 1, using the +optional parameter num_threads, although the original intent of this class is +to leave that as 1 (the default).

+
+
Parameters:
+

num_threads – The number of threads you want during the context [default: 1]

+
+
+
+ +
+
+

LRU Cache

+
+
+class galsim.utilities.LRU_Cache(user_function, maxsize=1024)[source]
+

Simplified Least Recently Used Cache.

+

Mostly stolen from http://code.activestate.com/recipes/577970-simplified-lru-cache/, +but added a method for dynamic resizing. The least recently used cached item is +overwritten on a cache miss.

+
+
Parameters:
+
    +
  • user_function – A python function to cache.

  • +
  • maxsize – Maximum number of inputs to cache. [Default: 1024]

  • +
+
+
+

Example:

+
>>> def slow_function(*args) # A slow-to-evaluate python function
+>>>    ...
+>>>
+>>> v1 = slow_function(*k1)  # Calling function is slow
+>>> v1 = slow_function(*k1)  # Calling again with same args is still slow
+>>> cache = galsim.utilities.LRU_Cache(slow_function)
+>>> v1 = cache(*k1)  # Returns slow_function(*k1), slowly the first time
+>>> v1 = cache(*k1)  # Returns slow_function(*k1) again, but fast this time.
+
+
+
+
+resize(maxsize)[source]
+

Resize the cache.

+

Increasing the size of the cache is non-destructive, i.e., previously cached inputs remain +in the cache. Decreasing the size of the cache will necessarily remove items from the +cache if the cache is already filled. Items are removed in least recently used order.

+
+
Parameters:
+

maxsize – The new maximum number of inputs to cache.

+
+
+
+ +
+ +
+
+

Context Manager for writing AtmosphericScreen pickles

+
+
+galsim.utilities.pickle_shared()[source]
+

A context manager to flag that one wishes to include object state from shared memory in +pickle objects.

+

Example:

+
obj = galsim_obj_with_shared_state()  # e.g., galsim.phase_screens.AtmosphericScreen
+pickle.dump(obj, file)
+
+# restart python, unloading shared state
+obj = pickle.load(file)  # fails due to missing shared state.
+
+obj = galsim_obj_with_shared_state()
+with pickle_shared():
+    pickle.dump(obj, filename)
+
+# restart python, again unloading shared state
+obj = pickle.load(file)  # loads both obj and required shared state.
+
+
+
+ +
+
+

Other Possibly Useful Classes

+
+
+class galsim.utilities.WeakMethod(f)[source]
+

Wrap a method in a weakref.

+

This is useful if you want to specialize a function if certain conditions hold. +You can check those conditions and return one of several possible implementations as +a lazy_property.

+

Using just a normal weakref doesn’t work, but this class will work.

+

From http://code.activestate.com/recipes/81253-weakmethod/

+
+ +
+
+class galsim.utilities.OrderedWeakRef[source]
+

Assign an arbitrary ordering to weakref.ref so that it can be part of a heap.

+
+ +
+
+class galsim.utilities.SimpleGenerator(obj)[source]
+

A simple class that is constructed with an arbitrary object. +Then generator() will return that object.

+

This is useful as a way to use an already existing object in a multiprocessing Proxy, +since that normally needs a factory function. So this is a factory function that +just returns an already existing object.

+
+ +
+
+

Math Calculations

+
+
+galsim.utilities.horner(x, coef, dtype=None)[source]
+

Evaluate univariate polynomial using Horner’s method.

+

I.e., take A + Bx + Cx^2 + Dx^3 and evaluate it as +A + x(B + x(C + x(D)))

+
+
Parameters:
+
    +
  • x – A numpy array of values at which to evaluate the polynomial.

  • +
  • coef – Polynomial coefficients of increasing powers of x.

  • +
  • dtype – Optionally specify the dtype of the return array. [default: None]

  • +
+
+
Returns:
+

a numpy array of the evaluated polynomial. Will be the same shape as x.

+
+
+
+ +
+
+galsim.utilities._horner(x, coef, result)[source]
+

Equivalent to horner, but x, coef, and result must be contiguous arrays.

+

In particular, result must be already allocated as an array in which to put the answer. +This is the thing that is returned from the regular horner.

+
+
Parameters:
+
    +
  • x – A numpy array of values at which to evaluate the polynomial.

  • +
  • coef – Polynomial coefficients of increasing powers of x.

  • +
  • result – Numpy array into which to write the result. Must be same shape as x.

  • +
+
+
+
+ +
+
+galsim.utilities.horner2d(x, y, coefs, dtype=None, triangle=False)[source]
+

Evaluate bivariate polynomial using nested Horner’s method.

+
+
Parameters:
+
    +
  • x – A numpy array of the x values at which to evaluate the polynomial.

  • +
  • y – A numpy array of the y values at which to evaluate the polynomial.

  • +
  • coefs – 2D array-like of coefficients in increasing powers of x and y. +The first axis corresponds to increasing the power of y, and the second to +increasing the power of x.

  • +
  • dtype – Optionally specify the dtype of the return array. [default: None]

  • +
  • triangle – If True, then the coefs are only non-zero in the upper-left triangle +of the array. [default: False]

  • +
+
+
Returns:
+

a numpy array of the evaluated polynomial. Will be the same shape as x and y.

+
+
+
+ +
+
+galsim.utilities._horner2d(x, y, coefs, result, temp, triangle=False)[source]
+

Equivalent to horner2d, but x, y, coefs, result, and temp +must be contiguous arrays.

+

In particular, result must be already allocated as an array in which to put the answer. +This is the thing that is returned from the regular horner. In addition, temp must +be allocated for the function to use as temporary work space.

+
+
Parameters:
+
    +
  • x – A numpy array of the x values at which to evaluate the polynomial.

  • +
  • y – A numpy array of the y values at which to evaluate the polynomial.

  • +
  • coefs – 2D array-like of coefficients in increasing powers of x and y. +The first axis corresponds to increasing the power of y, and the second to +increasing the power of x.

  • +
  • result – Numpy array into which to write the result. Must be same shape as x.

  • +
  • temp – Numpy array to hold temporary results. Must be the same shape as x.

  • +
  • triangle – If True, then the coefs are only non-zero in the upper-left triangle +of the array. [default: False]

  • +
+
+
+
+ +
+
+galsim.utilities.binomial(a, b, n)[source]
+

Return xy coefficients of (ax + by)^n ordered by descending powers of a.

+

Example:

+
# (x + y)^3 = 1 x^3 + 3 x^2 y + 3 x y^2 + 1 y^3
+>>>  print(binomial(1, 1, 3))
+array([ 1.,  3.,  3.,  1.])
+
+
+# (2 x + y)^3 = 8 x^3 + 12 x^2 y + 6 x y^2 + 1 y^3
+>>>  print(binomial(2, 1, 3))
+array([ 8.,  12.,  6.,  1.])
+
+
+
+
Parameters:
+
    +
  • a – First scalar in binomial to be expanded.

  • +
  • b – Second scalar in binomial to be expanded.

  • +
  • n – Exponent of expansion.

  • +
+
+
Returns:
+

Array of coefficients in expansion.

+
+
+
+ +
+
+galsim.utilities.nCr(n, r)[source]
+

Combinations. I.e., the number of ways to choose r distiguishable things from n +distinguishable things.

+
+
Parameters:
+
    +
  • from. (n The number of options to choose) –

  • +
  • choose (r The number of items to) –

  • +
+
+
Returns:
+

nCr, the (n,r) binomial coefficient.

+
+
+
+

Note

+

In Python 3, the factorial function was improved such that doing this the direct way +of calculating n! / (r! (n-r)!) seems to be the fastest algorith. In Python 2, for +largish values of n, a more complicated algorithm that avoided large integers was +faster. This function uses the direct method for both – we don’t bother to check the +version of Python to potentially select a different algorithm in the two cases.

+
+
+ +
+
+galsim.utilities.rotate_xy(x, y, theta)[source]
+

Rotates points in the xy-Cartesian plane counter-clockwise through an angle theta about +the origin of the Cartesian coordinate system.

+
+
Parameters:
+
    +
  • x – NumPy array of input x coordinates

  • +
  • y – NumPy array of input y coordinates

  • +
  • theta – Rotation angle (+ve counter clockwise) as an Angle instance

  • +
+
+
Returns:
+

the rotated coordinates (x_rot,y_rot).

+
+
+
+ +
+
+galsim.utilities.g1g2_to_e1e2(g1, g2)[source]
+

Convenience function for going from (g1, g2) -> (e1, e2).

+

Here g1 and g2 are reduced shears, and e1 and e2 are distortions - see Shear +for definitions of reduced shear and distortion in terms of axis ratios or other ways of +specifying ellipses.

+
+
Parameters:
+
    +
  • g1 – First reduced shear component

  • +
  • g2 – Second reduced shear component

  • +
+
+
Returns:
+

the corresponding distortions, e1 and e2.

+
+
+
+ +
+ +
+

Test Suite Helper Functions and Contexts

+
+
+galsim.utilities.check_pickle(obj, func=<function <lambda>>, irreprable=False, random=None)[source]
+

Check that the object is picklable.

+

Also check some related desirable properties that we want for (almost) all galsim objects:

+
    +
  1. pickle.loads(pickle.dumps(obj)) recovers something equivalent to the original.

  2. +
  3. obj != object() and object() != obj. (I.e. it doesn’t == things it shouldn’t.)

  4. +
  5. hash(obj) is the same for two equivalent objects, if it is hashable.

  6. +
  7. copy.copy(obj) returns an equivalent copy unless random=True.

  8. +
  9. copy.deepcopy(obj) returns an equivalent copy.

  10. +
  11. eval(repr(obj)) returns an equivalent copy unless random or irreprable=True.

  12. +
  13. repr(obj) makes something if irreprable=False.

  14. +
+
+
Parameters:
+
    +
  • obj – The object to test

  • +
  • func – A function of obj to use to test for equivalence. [default: lambda x: x]

  • +
  • irreprable – Whether to skip the eval(repr(obj)) test. [default: False]

  • +
  • random – Whether the obj has some intrinsic randomness. [default: False, unless +it has an rng attribute or it is a galsim.BaseDeviate]

  • +
+
+
+
+ +
+
+galsim.utilities.check_all_diff(objs, check_hash=True)[source]
+

Test that all objects test as being unequal.

+

It checks all pairs of objects in the list and asserts that obj1 != obj2.

+

If check_hash=True, then it also checks that their hashes are different.

+
+
Parameters:
+
    +
  • objs – A list of objects to test.

  • +
  • check_hash – Whether to also check the hash values.

  • +
+
+
+
+ +
+
+class galsim.utilities.CaptureLog(level=3)[source]
+

A context manager that saves logging output into a string that is accessible for +checking in unit tests.

+

After exiting the context, the attribute output will have the logging output.

+

Sample usage:

+
>>> with CaptureLog() as cl:
+...     cl.logger.info('Do some stuff')
+>>> assert cl.output == 'Do some stuff'
+
+
+
+ +
+
+class galsim.utilities.Profile(sortby='tottime', nlines=30, reverse=False, filename=None)[source]
+

A context manager that profiles a snippet of code.

+

Sample usage:

+
>>> with Profile():
+...     slow_function()
+
+
+
+
Parameters:
+
    +
  • sortby – What parameter to sort the results by. [default: tottime]

  • +
  • nlines – How many lines of output to report. [default: 30]

  • +
  • reverse – Whether to reverse the order of the output lines to put the most important +items at the bottom rather than the top. [default: False]

  • +
  • filename – If desired, a file to output the full profile information in pstats format. +[default: None]

  • +
+
+
+
+ +
+
+

Other Helper Functions

+
+
+galsim.utilities.isinteger(value)[source]
+

Check if a value is an integer type (including np.int64, long, etc.)

+

Specifically, it checks whether value == int(value).

+
+
Parameter:

value: The value to be checked whether it is an integer

+
+
+
+
Returns:
+

True if the value is an integer type, False otherwise.

+
+
+
+ +
+
+galsim.utilities.listify(arg)[source]
+

Turn argument into a list if not already iterable.

+
+
Parameters:
+

arg – An argument that may be a lit or a single item

+
+
Returns:
+

[arg] if arg was not already a list, otherwise arg.

+
+
+
+ +
+
+galsim.utilities.dol_to_lod(dol, N=None, scalar_string=True)[source]
+

Generate list of dicts from dict of lists (with broadcasting). +Specifically, generate “scalar-valued” kwargs dictionaries from a kwarg dictionary with values +that are length-N lists, or possibly length-1 lists or scalars that should be broadcasted up to +length-N lists.

+
+
Parameters:
+
    +
  • dol – A dict of lists

  • +
  • N – The length of the lists if known in advance. [default: None, which will +determine the maximum length of the lists for you]

  • +
  • scalar_string – Whether strings in the input list should be treated as scalars (and thus +broadcast to each item in the output) or not (in which case, they will +be treated as lists of characters) [default: True]

  • +
+
+
Returns:
+

A list of dicts

+
+
+
+ +
+
+galsim.utilities.functionize(f)[source]
+

Decorate a function f which accepts scalar positional or keyword arguments, to accept +arguments that can be either scalars or _functions_. If the arguments include univariate +(N-variate) functions, then the output will be a univariate (N-variate) function. While it’s +okay to mix scalar and N-variate function arguments, it is an error to mix N-variate and +M-variate function arguments.

+

Example:

+
>>> def f(x, y):      # Function of two scalars.
+...     return x + y
+>>> decorated = functionize(f)   # Function of two scalars, functions, or a mix.
+>>> result = f(2, 3)  # 5
+>>> result = f(2, lambda u: u)  # Generates a TypeError
+>>> result = decorated(2, 3)  # Scalar args returns a scalar
+>>> result = decorated(2, lambda u: u)  # Univariate argument leads to a univariate output.
+>>> print(result(5))  # 7
+>>> result = decorated(2, lambda u,v: u*v)  # Bivariate argument leads to a bivariate output.
+>>> print(result(5, 7))  # 2 + (5*7) = 37
+
+
+

We can use arguments that accept keyword arguments too:

+
>>> def f2(u, v=None):
+...    if v is None:
+...        v = 6.0
+...    return u / v
+>>> result = decorated(2, f2)
+>>> print(result(12))  # 2 + (12./6) = 4.0
+>>> print(result(12, v=4))  # 2 + (12/4) = 5
+
+
+

Note that you can also use python’s decorator syntax:

+
>>> @functionize
+>>> def f(x, y):
+...     return x + y
+
+
+
+
Parameters:
+

f – The function to be decorated.

+
+
Returns:
+

The decorated function.

+
+
+
+ +
+
+galsim.utilities.ensure_dir(target)[source]
+

Make sure the directory for the target location exists, watching for a race condition

+

In particular check if the OS reported that the directory already exists when running +makedirs, which can happen if another process creates it before this one can

+
+
Parameter:

target: The file name for which to ensure that all necessary directories exist.

+
+
+
+ +
+
+

GalSim-specific Helper Functions

+
+
+galsim.utilities.interleaveImages(im_list, N, offsets, add_flux=True, suppress_warnings=False, catch_offset_errors=True)[source]
+

Interleaves the pixel values from two or more images and into a single larger image.

+

This routine converts a list of images taken at a series of (uniform) dither offsets into a +single higher resolution image, where the value in each final pixel is the observed pixel +value from exactly one of the original images. It can be used to build a Nyquist-sampled image +from a set of images that were observed with pixels larger than the Nyquist scale.

+

In the original observed images, the integration of the surface brightness over the pixels is +equivalent to convolution by the pixel profile and then sampling at the centers of the pixels. +This procedure simulates an observation sampled at a higher resolution than the original images, +while retaining the original pixel convolution.

+

Such an image can be obtained in a fairly simple manner in simulations of surface brightness +profiles by convolving them explicitly with the native pixel response and setting a lower +sampling scale (or higher sampling rate) using the pixel_scale argument in +GSObject.drawImage routine and setting the method parameter to ‘no_pixel’.

+

However, pixel level detector effects can be included only on images drawn at the native pixel +scale, which happen to be undersampled in most cases. Nyquist-sampled images that also include +the effects of detector non-idealities can be obtained by drawing multiple undersampled images +(with the detector effects included) that are offset from each other by a fraction of a pixel.

+

This is similar to other procedures that build a higher resolution image from a set of low +resolution images, such as MultiDrizzle and IMCOM. A disadvantage of this routine compared to +ther others is that the images must be offset in equal steps in each direction. This is +difficult to acheive with real observations but can be precisely acheived in a series of +simulated images.

+

An advantage of this procedure is that the noise in the final image is not correlated as the +pixel values are each taken from just a single input image. Thus, this routine preserves the +noise properties of the pixels.

+

Here’s an example script using this routine:

+

Example:

+
>>> n = 2
+>>> gal = galsim.Gaussian(sigma=2.8)
+>>> DX = numpy.arange(0.0,1.0,1./n)
+>>> DX -= DX.mean()
+>>> im_list, offsets = [], []
+>>> for dx in DX:
+    ... offset = galsim.PositionD(dx,0.0)
+    ... offsets.append(offset)
+    ... im = galsim.Image(16,16)
+    ... gal.drawImage(image=im,offset=offset,scale=1.0)
+    ... im.applyNonlinearity(lambda x: x - 0.01*x**2) # detector effects
+    ... im_list.append(im)
+>>> img = galsim.utilities.interleaveImages(im_list=im_list,N=(n,1),offsets=offsets)
+
+
+
+
Parameters:
+
    +
  • im_list – A list containing the galsim.Image instances to be interleaved.

  • +
  • N – Number of images to interleave in either directions. It can be of +type int if equal number of images are interleaved in both +directions or a list or tuple of two integers, containing the number +of images in x and y directions respectively.

  • +
  • offsets – A list containing the offsets as galsim.PositionD instances +corresponding to every image in im_list. The offsets must be +spaced equally and must span an entire pixel area. The offset +values must be symmetric around zero, hence taking positive and +negative values, with upper and lower limits of +0.5 and -0.5 +(limit values excluded).

  • +
  • add_flux – Should the routine add the fluxes of all the images (True) or +average them (False)? [default: True]

  • +
  • suppress_warnings – Suppresses the warnings about the pixel scale of the output, if +True. [default: False]

  • +
  • catch_offset_errors – Checks for the consistency of offsets with N and raises +errors if inconsistencies found (True). Recommended, but could slow +down the routine a little. [default: True]

  • +
+
+
Returns:
+

the interleaved Image

+
+
+
+ +
+
+galsim.utilities.deInterleaveImage(image, N, conserve_flux=False, suppress_warnings=False)[source]
+

The routine to do the opposite of what interleaveImages routine does. It generates a +(uniform) dither sequence of low resolution images from a high resolution image.

+

Many pixel level detector effects, such as interpixel capacitance, persistence, charge +diffusion etc. can be included only on images drawn at the native pixel scale, which happen to +be undersampled in most cases. Nyquist-sampled images that also include the effects of detector +non-idealities can be obtained by drawing multiple undersampled images (with the detector +effects included) that are offset from each other by a fraction of a pixel. If the offsets are +uniformly spaced, then images can be combined using interleaveImages into a Nyquist-sampled +image.

+

Drawing multiple low resolution images of a light profile can be a lot slower than drawing a +high resolution image of the same profile, even if the total number of pixels is the same. A +uniformly offset dither sequence can be extracted from a well-resolved image that is drawn by +convolving the surface brightness profile explicitly with the native pixel response and setting +a lower sampling scale (or higher sampling rate) using the pixel_scale argument in +GSObject.drawImage routine and setting the method parameter to ‘no_pixel’.

+

Here is an example script using this routine:

+

Example:

+
>>> n = 2
+>>> gal = galsim.Gaussian(sigma=2.8)
+>>> gal_pix = galsim.Convolve([gal,galsim.Pixel(scale=1.0)])
+>>> img = gal_pix.drawImage(gal_pix,scale=1.0/n,method='no_pixel')
+>>> im_list, offsets = galsim.utilities.deInterleaveImage(img,N=n)
+>>> for im in im_list:
+>>>     im.applyNonlinearity(lambda x: x-0.01*x**2) #detector effects
+>>> img_new = galsim.utilities.interleaveImages(im_list,N=n,offsets)
+
+
+
+
Parameters:
+
    +
  • image – Input image from which lower resolution images are extracted.

  • +
  • N – Number of images extracted in either directions. It can be of type +‘int’ if equal number of images are extracted in both directions or a +list or tuple of two integers, containing the number of images in x +and y directions respectively.

  • +
  • conserve_flux – Should the routine output images that have, on average, same total +pixel values as the input image (True) or should the pixel values +summed over all the images equal the sum of pixel values of the input +image (False)? [default: False]

  • +
  • suppress_warnings – Suppresses the warnings about the pixel scale of the output, if True. +[default: False]

  • +
+
+
Returns:
+

a list of images (Image) and offsets (PositionD) to reconstruct the input image using +interleaveImages.

+
+
+
+ +
+
+galsim.utilities.thin_tabulated_values(x, f, rel_err=0.0001, trim_zeros=True, preserve_range=True, fast_search=True, interpolant='linear')[source]
+

Remove items from a set of tabulated f(x) values so that the error in the integral is still +accurate to a given relative accuracy.

+

The input x and f values can be lists, NumPy arrays, or really anything that can be +converted to a NumPy array. The new lists will be output as numpy arrays.

+
+
Parameters:
+
    +
  • x – The x values in the f(x) tabulation.

  • +
  • f – The f values in the f(x) tabulation.

  • +
  • rel_err – The maximum relative error to allow in the integral from the removal. +[default: 1.e-4]

  • +
  • trim_zeros – Remove redundant leading and trailing points where f=0? (The last +leading point with f=0 and the first trailing point with f=0 will be +retained). Note that if both trim_leading_zeros and preserve_range are +True, then the only the range of x after zero trimming is preserved. +[default: True]

  • +
  • preserve_range – Should the original range of x be preserved? (True) Or should the ends +be trimmed to include only the region where the integral is +significant? (False) [default: True]

  • +
  • fast_search – If set to True, then the underlying algorithm will use a relatively fast +O(N) algorithm to select points to include in the thinned approximation. +If set to False, then a slower O(N^2) algorithm will be used. We have +found that the slower algorithm tends to yield a thinned representation +that retains fewer samples while still meeting the relative error +requirement. [default: True]

  • +
  • interpolant – The interpolant to assume for the tabulated values. [default: ‘linear’]

  • +
+
+
Returns:
+

a tuple of lists (x_new, y_new) with the thinned tabulation.

+
+
+
+ +
+
+galsim.utilities.old_thin_tabulated_values(x, f, rel_err=0.0001, preserve_range=False)[source]
+

Remove items from a set of tabulated f(x) values so that the error in the integral is still +accurate to a given relative accuracy.

+

The input x and f values can be lists, NumPy arrays, or really anything that can be +converted to a NumPy array. The new lists will be output as python lists.

+
+

Note

+

In Issue #739, Josh wrote thin_tabulated_values as a replacement for this function, +which had been buggy – not actually hitting its target relative accuracy. So on the +same issue, Mike fixed this algorithm to at least work correctly.

+

However, we recommend using the above algorithm, since it keeps fewer sample locations +for a given rel_err than the old algorithm.

+

On the other hand, the old algorithm (this one) may be quite a bit faster, so we retain +it here in case there is a use case where it is more appropriate.

+
+
+
Parameters:
+
    +
  • x – The x values in the f(x) tabulation.

  • +
  • f – The f values in the f(x) tabulation.

  • +
  • rel_err – The maximum relative error to allow in the integral from the removal. +[default: 1.e-4]

  • +
  • preserve_range – Should the original range of x be preserved? (True) Or should the ends +be trimmed to include only the region where the integral is +significant? (False) [default: False]

  • +
+
+
Returns:
+

a tuple of lists (x_new, y_new) with the thinned tabulation.

+
+
+
+ +
+
+galsim.utilities.parse_pos_args(args, kwargs, name1, name2, integer=False, others=[])[source]
+

Parse the args and kwargs of a function call to be some kind of position.

+

We allow four options:

+
+

f(x,y) +f(galsim.PositionD(x,y)) or f(galsim.PositionI(x,y)) +f( (x,y) ) (or any indexable thing) +f(name1=x, name2=y)

+
+

If the inputs must be integers, set integer=True. +If there are other args/kwargs to parse after these, then their names should be +be given as the parameter others, which are passed back in a tuple after the position.

+
+
Parameters:
+
    +
  • args – The args of the original function.

  • +
  • kwargs – The kwargs of the original function.

  • +
  • name1 – The allowed kwarg for the first coordinate.

  • +
  • name2 – The allowed kwarg for the second coordinate.

  • +
  • integer – Whether to return a PositionI rather than a PositionD. [default: False]

  • +
  • others – If given, other values to also parse and return from the kwargs. [default: []]

  • +
+
+
Returns:
+

a Position instance, possibly also with other values if others is given.

+
+
+
+ +
+
+galsim.utilities.rand_arr(shape, deviate)[source]
+

Function to make a 2d array of random deviates (of any sort).

+
+
Parameters:
+
    +
  • shape – A list of length 2, indicating the desired 2d array dimensions

  • +
  • deviate – Any GalSim deviate (see random.py) such as UniformDeviate, GaussianDeviate, +etc. to be used to generate random numbers

  • +
+
+
Returns:
+

a NumPy array of the desired dimensions with random numbers generated using the +supplied deviate.

+
+
+
+ +
+
+galsim.utilities.convert_interpolant(interpolant)[source]
+

Convert a given interpolant to an Interpolant if it is given as a string.

+
+
Parameter:

interpolant: Either an Interpolant or a string to convert.

+
+
+
+
Returns:
+

an Interpolant

+
+
+
+ +
+
+galsim.utilities.structure_function(image)[source]
+

Estimate the angularly-averaged structure function of a 2D random field.

+

The angularly-averaged structure function D(r) of the 2D field phi is defined as:

+
+\[D(|r|) = \langle |phi(x) - phi(x+r)|^2 \rangle\]
+

where the x and r on the RHS are 2D vectors, but the \(|r|\) on the LHS is just a scalar +length.

+

The image must have its scale attribute defined. It will be used in the calculations to +set the scale of the radial distances.

+
+
Parameters:
+

imageImage containing random field realization.

+
+
Returns:
+

A python callable mapping a separation length r to the estimate of the structure +function D(r).

+
+
+
+ +
+
+galsim.utilities.combine_wave_list(*args)[source]
+

Combine wave_list attributes of all objects in obj_list while respecting blue_limit and +red_limit attributes. Should work with any combination of SED, Bandpass, and +ChromaticObject instances.

+
+
Parameters:
+

obj_list – List of SED, Bandpass, or ChromaticObject instances.

+
+
Returns:
+

wave_list, blue_limit, red_limit

+
+
+
+ +
+
+galsim.utilities.math_eval(str, other_modules=())[source]
+

Evaluate a string that may include numpy, np, or math commands.

+
+
Parameters:
+
    +
  • str – The string to evaluate

  • +
  • numpy (other_modules. Other modules in addition to) – Should be given as a list of strings. [default: None]

  • +
  • np – Should be given as a list of strings. [default: None]

  • +
  • well. (math to import as) – Should be given as a list of strings. [default: None]

  • +
+
+
Returns:
+

Whatever the string evaluates to.

+
+
+
+ +
+
+galsim.utilities.unweighted_moments(image, origin=None)[source]
+

Computes unweighted 0th, 1st, and 2nd moments in image coordinates. Respects image bounds, +but ignores any scale or wcs.

+
+
Parameters:
+
    +
  • imageImage from which to compute moments

  • +
  • origin – Optional origin in image coordinates used to compute Mx and My +[default: galsim.PositionD(0, 0)].

  • +
+
+
Returns:
+

Dict with entries for [M0, Mx, My, Mxx, Myy, Mxy]

+
+
+
+ +
+
+galsim.utilities.unweighted_shape(arg)[source]
+

Computes unweighted second moment size and ellipticity given either an image or a dict of +unweighted moments.

+
+
The size is:

rsqr = Mxx+Myy

+
+
The ellipticities are:

e1 = (Mxx-Myy) / rsqr +e2 = 2*Mxy / rsqr

+
+
+
+
Parameters:
+

arg – Either a galsim.Image or the output of unweighted_moments(image).

+
+
Returns:
+

Dict with entries for [rsqr, e1, e2]

+
+
+
+ +
+
+galsim.utilities.rand_with_replacement(n, n_choices, rng, weight=None, _n_rng_calls=False)[source]
+

Select some number of random choices from a list, with replacement, using a supplied RNG.

+

n random choices with replacement are made assuming that those choices should range from 0 +to n_choices-1, so they can be used as indices in a list/array. If weight is supplied, +then it should be an array of length n_choices that ranges from 0-1, and can be used to +make weighted choices from the list.

+
+
Parameters:
+
    +
  • n – Number of random selections to make.

  • +
  • n_choices – Number of entries from which to choose.

  • +
  • rng – RNG to use. Must a galsim.BaseDeviate instance.

  • +
  • weight – Optional list of weight factors to use for weighting the selection of +random indices.

  • +
+
+
Returns:
+

a NumPy array of length n containing the integer-valued indices that were selected.

+
+
+
+ +
+
+galsim.utilities.check_share_file(filename, subdir)[source]
+

Find SED or Bandpass file, possibly adding share_dir/subdir.

+
+
Parameters:
+
+
+
Returns:
+

True, correct_filename if the file was found +False, ‘’ if not

+
+
+
+ +
+
+galsim.utilities.find_out_of_bounds_position(x, y, bounds, grid=False)[source]
+

Given arrays of x and y values that are known to contain at least one +position that is out-of-bounds of the given bounds instance, return one +such PositionD.

+
+
Parameters:
+
    +
  • x – Array of x values

  • +
  • y – Array of y values

  • +
  • boundsBounds instance

  • +
  • grid – Bool indicating whether to check the outer product of x and y +(grid=True), or each sequential pair of x and y (grid=False). +If the latter, then x and y should have the same shape.

  • +
+
+
Returns:
+

a PositionD from x and y that is out-of-bounds of bounds.

+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/nfwhalo.html b/docs/_build/html/nfwhalo.html new file mode 100644 index 00000000000..e824cd60fa5 --- /dev/null +++ b/docs/_build/html/nfwhalo.html @@ -0,0 +1,434 @@ + + + + + + + NFW Halo Shears — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

NFW Halo Shears

+

This is the “lensing engine” for calculating shears around an NFW halo.

+
+
+class galsim.NFWHalo(mass, conc, redshift, halo_pos=galsim.PositionD(x=0.0, y=0.0), omega_m=None, omega_lam=None, cosmo=None)[source]
+

Class describing NFW halos.

+

This class computes the lensing fields shear and convergence of a spherically symmetric NFW +halo of given mass, concentration, redshift, assuming a particular cosmology. +No mass-concentration relation is employed.

+

Based on Matthias Bartelmann’s libastro.

+

The cosmology to use can be set either by providing a Cosmology instance as cosmo, +or by providing omega_m and/or omega_lam. +If only one of the latter is provided, the other is taken to be one minus that. +If no cosmology parameters are set, a default Cosmology is constructed.

+
+
Parameters:
+
    +
  • mass – Mass defined using a spherical overdensity of 200 times the critical density +of the universe, in units of M_solar/h.

  • +
  • conc – Concentration parameter, i.e., ratio of virial radius to NFW scale radius.

  • +
  • redshift – Redshift of the halo.

  • +
  • halo_posPosition of halo center (in arcsec). [default: PositionD(0,0)]

  • +
  • omega_m – Omega_matter to pass to Cosmology constructor. [default: None]

  • +
  • omega_lam – Omega_lambda to pass to Cosmology constructor. [default: None]

  • +
  • cosmo – A Cosmology instance. [default: None]

  • +
+
+
+
+
+_getShear(pos_x, pos_y, z_s, reduced=True)[source]
+

Equivalent to getShear, but without some sanity checks and the positions must be +given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • z_s – Source redshift(s).

  • +
  • reduced – Whether returned shear(s) should be reduced shears. [default: True]

  • +
+
+
Returns:
+

the (possibly reduced) shears as a tuple (g1,g2) (either scalars or numpy arrays)

+
+
+
+ +
+
+_getConvergence(pos_x, pos_y, z_s)[source]
+

Equivalent to getConvergence, but without some sanity checks and the positions must be +given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • z_s – Source redshift(s).

  • +
+
+
Returns:
+

the convergence as either a scalar or a numpy array

+
+
+
+ +
+
+_getMagnification(pos_x, pos_y, z_s)[source]
+

Equivalent to getMagnification, but without some sanity checks and the positions must +be given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • z_s – Source redshift(s).

  • +
+
+
Returns:
+

the magnification as either a scalar or a numpy array

+
+
+
+ +
+
+_getLensing(pos_x, pos_y, z_s)[source]
+

Equivalent to getLensing, but without some sanity checks and the positions must +be given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • z_s – Source redshift(s).

  • +
+
+
Returns:
+

the reduced shears and magnifications as a tuple (g1,g2,mu) (each being +either a scalar or a numpy array)

+
+
+
+ +
+
+getConvergence(pos, z_s, units=coord.arcsec)[source]
+

Calculate convergence of halo at specified positions.

+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • z_s – Source redshift(s).

  • +
  • unit – Angular units of coordinates. [default: galsim.arcsec]

  • +
+
+
Returns:
+

the convergence, kappa

+
+
+

If the input pos is given a single position, kappa is the convergence value. +If the input pos is given a list/array of positions, kappa is a NumPy array.

+
+ +
+
+getLensing(pos, z_s, units=coord.arcsec)[source]
+

Calculate lensing shear and magnification of halo at specified positions.

+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • z_s – Source redshift(s).

  • +
  • units – Angular units of coordinates. [default: galsim.arcsec]

  • +
+
+
Returns:
+

the reduced shears and magnifications as a tuple (g1,g2,mu)

+
+
+

If the input pos is given a single position, the return values are the shear and +magnification values at that position. +If the input pos is given a list/array of positions, they are NumPy arrays.

+
+ +
+
+getMagnification(pos, z_s, units=coord.arcsec)[source]
+

Calculate magnification of halo at specified positions.

+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • z_s – Source redshift(s).

  • +
  • units – Angular units of coordinates. [default: galsim.arcsec]

  • +
+
+
Returns:
+

the magnification mu

+
+
+

If the input pos is given a single position, mu is the magnification value. +If the input pos is given a list/array of positions, mu is a NumPy array.

+
+ +
+
+getShear(pos, z_s, units=coord.arcsec, reduced=True)[source]
+

Calculate (reduced) shear of halo at specified positions.

+
+
Parameters:
+
    +
  • po

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • z_s – Source redshift(s).

  • +
  • units – Angular units of coordinates. [default: galsim.arcsec]

  • +
  • reduced – Whether returned shear(s) should be reduced shears. [default: True]

  • +
+
+
Returns:
+

the (possibly reduced) shears as a tuple (g1,g2)

+
+
+

If the input pos is given a single position, (g1,g2) are the two shear components. +If the input pos is given a list/array of positions, they are NumPy arrays.

+
+ +
+ +
+
+class galsim.Cosmology(omega_m=0.3, omega_lam=0.7)[source]
+

Basic cosmology calculations.

+

Cosmology calculates expansion function E(a) and angular diameter distances Da(z) for a +LambdaCDM universe. Radiation is assumed to be zero and Dark Energy constant with w = -1 (no +quintessence), but curvature is arbitrary.

+

Based on Matthias Bartelmann’s libastro.

+
+
Parameters:
+
    +
  • omega_m – Present day energy density of matter relative to critical density. +[default: 0.3]

  • +
  • omega_lam – Present day density of Dark Energy relative to critical density. +[default: 0.7]

  • +
+
+
+
+
+Da(z, z_ref=0)[source]
+

Compute angular diameter distance between two redshifts in units of c/H0.

+

In order to get the distance in Mpc/h, multiply by c/H0~3000.

+
+
Parameters:
+
    +
  • z – Redshift.

  • +
  • z_ref – Reference redshift, with z_ref <= z. [default: 0]

  • +
+
+
+
+ +
+
+E(a)[source]
+

Evaluates expansion function.

+
+
Parameters:
+

a – Scale factor.

+
+
+
+ +
+
+a(z)[source]
+

Compute scale factor.

+
+
Parameters:
+

z – Redshift

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/noise.html b/docs/_build/html/noise.html new file mode 100644 index 00000000000..2e05dc7f48b --- /dev/null +++ b/docs/_build/html/noise.html @@ -0,0 +1,567 @@ + + + + + + + Noise Generators — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Noise Generators

+

GalSim has a number of different noise models one can use for adding noise to an Image:

+
    +
  • GaussianNoise adds Gaussian noise with a specified \(\sigma\) (or variance) to each pixel.

  • +
  • PoissonNoise treats each pixel value as the expectation value for the number of incident +photons in the pixel, and implements a Poisson process drawing a realization of the observed +number of photons in each pixel.

  • +
  • CCDNoise combines the two above noise types to implement Poisson photon noise plus Gaussian +read noise.

  • +
  • VariableGaussianNoise is like GaussianNoise, but allows for a different variance in each pixel.

  • +
  • DeviateNoise adds noise according to any of the various Random Deviates implemented in GalSim.

  • +
+

These are all subclasses of the base class BaseNoise, which mostly just defines the common +API for all of these classes.

+
+
+class galsim.BaseNoise(rng=None)[source]
+

Base class for all noise classes.

+

This class should not be constructed directly. Rather, you should instantiate one of the +subclasses:

+ +

which define what kind of noise you want to implement. This base class mostly just serves as +a way to check if an object is a valid noise object with:

+
>>> isinstance(noise, galsim.BaseNoise)
+
+
+
+
+__mul__(variance_ratio)[source]
+

Multiply the variance of the noise by variance_ratio.

+
+
Parameters:
+

variance_ratio – The factor by which to scale the variance of the correlation +function profile.

+
+
Returns:
+

a new Noise object whose variance has been scaled by the given amount.

+
+
+
+ +
+
+__div__(variance_ratio)[source]
+

Equivalent to self * (1/variance_ratio)

+
+ +
+
+applyTo(image)[source]
+

Add noise to an input Image.

+

e.g.:

+
>>> noise.applyTo(image)
+
+
+

On output the Image instance image will have been given additional noise according +to the current noise model.

+

Note: This is equivalent to the alternate syntax:

+
>>> image.addNoise(noise)
+
+
+

which may be more convenient or clearer.

+
+ +
+
+getVariance()[source]
+

Get variance in current noise model.

+
+ +
+
+property rng
+

The BaseDeviate of this noise object.

+
+ +
+
+withScaledVariance(variance_ratio)[source]
+

Return a new noise object with the variance scaled up by the specified factor.

+

This is equivalent to noise * variance_ratio.

+
+
Parameters:
+

variance_ratio – The factor by which to scale the variance of the correlation +function profile.

+
+
Returns:
+

a new Noise object whose variance has been scaled by the given amount.

+
+
+
+ +
+
+withVariance(variance)[source]
+

Return a new noise object (of the same type as the current one) with the specified +variance.

+
+
Parameters:
+

variance – The desired variance in the noise.

+
+
Returns:
+

a new Noise object with the given variance.

+
+
+
+ +
+ +
+
+class galsim.GaussianNoise(rng=None, sigma=1.0)[source]
+

Bases: BaseNoise

+

Class implementing simple Gaussian noise.

+

This is a simple noise model where each pixel in the image gets Gaussian noise with a +given sigma.

+

Example:

+

The following will add Gaussian noise to every element of an image:

+
>>> gaussian_noise = galsim.GaussianNoise(rng, sigma=1.0)
+>>> image.addNoise(gaussian_noise)
+
+
+
+
Parameters:
+
    +
  • rng – A BaseDeviate instance to use for generating the random numbers.

  • +
  • sigma – The rms noise on each pixel. [default: 1.]

  • +
+
+
Attributes:
+
    +
  • rng – The internal random number generator (read-only)

  • +
  • sigma – The value of the constructor parameter sigma (read-only)

  • +
+
+
+
+
+copy(rng=None)[source]
+

Returns a copy of the Gaussian noise model.

+

By default, the copy will share the BaseDeviate random number generator with the parent +instance. However, you can provide a new rng to use in the copy if you want with:

+
>>> noise_copy = noise.copy(rng=new_rng)
+
+
+
+ +
+
+property sigma
+

The input sigma value.

+
+ +
+ +
+
+class galsim.PoissonNoise(rng=None, sky_level=0.0)[source]
+

Bases: BaseNoise

+

Class implementing Poisson noise according to the flux (and sky level) present in the image.

+

The PoissonNoise class encapsulates a simple version of the noise model of a normal CCD image +where each pixel has Poisson noise corresponding to the number of electrons in each pixel +(including an optional extra sky level).

+

Note that if the image to which you are adding noise already has a sky level on it, +then you should not provide the sky level here as well. The sky level here corresponds +to a level that is taken to be already subtracted from the image, but that originally +contributed to the addition of Poisson noise.

+

Example:

+

The following will add Poisson noise to every element of an image:

+
>>> poisson_noise = galsim.PoissonNoise(rng, sky_level=0.)
+>>> image.addNoise(poisson_noise)
+
+
+
+
Parameters:
+
    +
  • rng – A BaseDeviate instance to use for generating the random numbers.

  • +
  • sky_level – The sky level in electrons per pixel that was originally in the input image, +but which is taken to have already been subtracted off. [default: 0.]

  • +
+
+
Attributes:
+
    +
  • rng – The internal random number generator (read-only)

  • +
  • sky_level – The value of the constructor parameter sky_level (read-only)

  • +
+
+
+
+
+copy(rng=None)[source]
+

Returns a copy of the Poisson noise model.

+

By default, the copy will share the BaseDeviate random number generator with the parent +instance. However, you can provide a new rng to use in the copy if you want with:

+
>>> noise_copy = noise.copy(rng=new_rng)
+
+
+
+ +
+
+property sky_level
+

The input sky_level.

+
+ +
+ +
+
+class galsim.CCDNoise(rng=None, sky_level=0.0, gain=1.0, read_noise=0.0)[source]
+

Bases: BaseNoise

+

Class implementing a basic CCD noise model.

+

The CCDNoise class encapsulates the noise model of a normal CCD image. The noise has two +components: first, Poisson noise corresponding to the number of electrons in each pixel +(including an optional extra sky level); second, Gaussian read noise.

+

Note that if the image to which you are adding noise already has a sky level on it, +then you should not provide the sky level here as well. The sky level here corresponds +to a level is taken to be already subtracted from the image, but which was present +for the Poisson noise.

+

The units here are slightly confusing. We try to match the most common way that each of +these quantities is usually reported. Note: ADU stands for Analog/Digital Units; they are the +units of the numbers in the final image. Some places use the term “counts” for this.

+
    +
  • sky_level is normally measured from the image itself, so it is normally quoted in ADU/pixel.

  • +
  • gain is a property of the detector and is normally measured in the laboratory. The units +are normally e-/ADU. This is backwards what might be more intuitive, ADU/e-, but that’s +how astronomers use the term gain, so we follow suit here.

  • +
  • read_noise is also a property of the detector and is usually quoted in e-/pixel.

  • +
+

If you are manually applying the quantum efficiency of the detector (e-/photon), then this +would normally be applied before the noise. However, it is also fine to fold in the quantum +efficiency into the gain to give units photons/ADU. Either way is acceptable. Just make sure +you give the read noise in photons as well in this case.

+

Example:

+

The following will add CCD noise to every element of an image:

+
>>> ccd_noise = galsim.CCDNoise(rng, sky_level=0., gain=1., read_noise=0.)
+>>> image.addNoise(ccd_noise)
+
+
+
+
Parameters:
+
    +
  • rng – A BaseDeviate instance to use for generating the random numbers.

  • +
  • sky_level – The sky level in ADU per pixel that was originally in the input image, +but which is taken to have already been subtracted off. [default: 0.]

  • +
  • gain – The gain for each pixel in electrons per ADU; setting gain<=0 will shut +off the Poisson noise, and the Gaussian rms will take the value +read_noise as being in units of ADU rather than electrons. [default: 1.]

  • +
  • read_noise – The read noise on each pixel in electrons (gain > 0.) or ADU (gain <= 0.). +Setting read_noise=0. will shut off the Gaussian noise. [default: 0.]

  • +
+
+
Attributes:
+
    +
  • rng – The internal random number generator (read-only)

  • +
  • sky_level – The value of the constructor parameter sky_level (read-only)

  • +
  • gain – The value of the constructor parameter gain (read-only)

  • +
  • read_noise – The value of the constructor parameter read_noise (read-only)

  • +
+
+
+
+
+copy(rng=None)[source]
+

Returns a copy of the CCD noise model.

+

By default, the copy will share the BaseDeviate random number generator with the parent +instance. However, you can provide a new rng to use in the copy if you want with:

+
>>> noise_copy = noise.copy(rng=new_rng)
+
+
+
+ +
+
+property gain
+

The input gain.

+
+ +
+
+property read_noise
+

The input read_noise.

+
+ +
+
+property sky_level
+

The input sky_level.

+
+ +
+ +
+
+class galsim.VariableGaussianNoise(rng, var_image)[source]
+

Bases: BaseNoise

+

Class implementing Gaussian noise that has a different variance in each pixel.

+

The following will add variable Gaussian noise to every element of an image:

+
>>> variable_noise = galsim.VariableGaussianNoise(rng, var_image)
+>>> image.addNoise(variable_noise)
+
+
+
+
Parameters:
+
    +
  • rng – A BaseDeviate instance to use for generating the random numbers.

  • +
  • var_image – The variance of the noise to apply to each pixel. This image must be the +same shape as the image for which you eventually call addNoise().

  • +
+
+
Attributes:
+
    +
  • rng – The internal random number generator (read-only)

  • +
  • var_image – The value of the constructor parameter var_image (read-only)

  • +
+
+
+
+
+applyTo(image)[source]
+

Add noise to an input Image.

+

e.g.:

+
>>> noise.applyTo(image)
+
+
+

On output the Image instance image will have been given additional noise according +to the current noise model.

+

Note: This is equivalent to the alternate syntax:

+
>>> image.addNoise(noise)
+
+
+

which may be more convenient or clearer.

+
+ +
+
+copy(rng=None)[source]
+

Returns a copy of the variable Gaussian noise model.

+

By default, the copy will share the BaseDeviate random number generator with the parent +instance. However, you can provide a new rng to use in the copy if you want with:

+
>>> noise_copy = noise.copy(rng=new_rng)
+
+
+
+ +
+
+property var_image
+

The input var_image.

+
+ +
+ +
+
+class galsim.DeviateNoise(dev)[source]
+

Bases: BaseNoise

+

Class implementing noise with an arbitrary BaseDeviate object.

+

The DeviateNoise class provides a way to treat an arbitrary deviate as the noise model for +each pixel in an image.

+

The following will add noise according to a given random deviate to every element of an image:

+
>>> dev_noise = galsim.DeviateNoise(dev)
+>>> image.addNoise(dev_noise)
+
+
+
+
Parameters:
+

dev – A BaseDeviate subclass to use as the noise deviate for each pixel.

+
+
Attributes:
+

rng – The internal random number generator (read-only)

+
+
+
+
+copy(rng=None)[source]
+

Returns a copy of the DeviateNoise instance.

+

By default, the copy will share the BaseDeviate random number generator with the parent +instance. However, you can provide a new rng to use in the copy if you want with:

+
>>> noise_copy = noise.copy(rng=new_rng)
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/objects.inv b/docs/_build/html/objects.inv new file mode 100644 index 00000000000..2b3e9bcd44f Binary files /dev/null and b/docs/_build/html/objects.inv differ diff --git a/docs/_build/html/older.html b/docs/_build/html/older.html new file mode 100644 index 00000000000..9e2b44b825d --- /dev/null +++ b/docs/_build/html/older.html @@ -0,0 +1,1127 @@ + + + + + + + v2.5 — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Below is a summary of the major changes with each new tagged version of GalSim. +Each version may also include various other minor changes and bug fixes not +listed here for brevity. See the CHANGELOG files associated with each +version for a more complete list. Issue numbers related to each change are +given in parentheses.

+
+

v2.5

+

API Changes

+ +

Config Updates

+
    +
  • Fixed a bug in Scattered type, when world_pos is specified in the stamp field. (#1190)

  • +
  • Added a new initial_image input type. (#1237)

  • +
  • Added skip_failures option in stamp fields. (#1238)

  • +
  • Let input items depend on other input items. (#1239)

  • +
  • Allow profiling output to reach the logger when running with -v0. (#1245)

  • +
  • Added Eval type for GSObjects. (#1250)

  • +
+

New Features

+
    +
  • Updated Roman telescope data to Phase C (aka Cycle 9) specifications (#1017)

  • +
  • Added ShapeData.applyWCS method to convert HSM shapes to sky coordinates. (#1221)

  • +
  • Added DoubleZernike class and related functionality. (#1221)

  • +
  • Added some test-related utility functions to galsim.utilities. (#1240)

  • +
  • Added utilities.merge_sorted function. (#1243)

  • +
  • Added EmissionLine class to represent emission line SEDs. (#1247, #1249)

  • +
  • Updated data in The Roman Space Telescope Module module to Phase C (Cycle 9) information. (#1017, #1251)

  • +
  • Added interpolant option to SED and Bandpass classes. (#1257)

  • +
  • Added interpolant option to galsim.trapz. (#1257)

  • +
  • Added clip_neg option to DistDeviate class. (#1257)

  • +
  • Implemented algorithm for ChromaticSum to let it be used as a photon_op. (#1259)

  • +
  • Added PhotonArray.copyFrom method. (#1259)

  • +
+

Performance Improvements

+
    +
  • Added some support for GPU offloading. (#1212, #1217, #1218, #1222, #1224, #1230)

  • +
  • Don’t add a WavelengthSampler to photon_ops if it is already there. (#1229, #1236)

  • +
  • Work around an OMP bug that disables multiprocessing on some systems. (#1241)

  • +
  • Sped up the combine_wave_list function, using the new merge_sorted function. (#1243)

  • +
  • No longer keep a separate wave_list array in ChromaticObject. (#1245)

  • +
  • Delayed the calculation of the sed attributes of ChromaticObject until needed. (#1245)

  • +
  • Reduce long-term memory usage of Silicon class. (#1246)

  • +
  • Improved the behavior of SEDs when using spline interpolant. (#1187, #1257)

  • +
  • No longer pickle the SED of chromatic objects when the SED is a derived value. (#1257)

  • +
+

Bug Fixes

+
    +
  • Fixed a bug that could lead to overflow in extremely large images. (#1017)

  • +
  • Fixed a slight error in the Moffat maxk calculation. (#1208, #1210)

  • +
  • Fixed a bug that prevented Eval types from generating lists in config files in some contexts. +(#1220, #1223)

  • +
  • Fixed the absorption depth calculation in the Silicon class to allow wavelengths that are +outside the given range of the absorption lookup table. (#1227)

  • +
  • Changed the SED class to correctly broadcast over waves when the SED is constant. (#1228, #1235)

  • +
  • Fixed some errors when drawing ChromaticTransformation objects with photon shooting. (#1229)

  • +
  • Fixed the flux drawn by ChromaticConvolution with photon shooting when poisson_flux=True. (#1229)

  • +
  • Fixed a slight inaccuracy in the FFT phase shifts for single-precision images. (#1231, #1234)

  • +
  • Fixed a bug that prevented a convolution of two PhaseScreenPSF objects from being drawn with +photon shooting. (#1242)

  • +
  • Fixed a bug in the SED class normalization when using astropy.units for flux_type. (#1254, #1256)

  • +
  • Fixed a bug in SiliconSensor if the image is outside the range where tree rings are defined. +(#1258)

  • +
+
+
+

v2.4

+

API Changes

+
    +
  • Removed CppEllipse, AstronomicalConstants.h in C++ layer. (#1129)

  • +
  • Removed AttributeDict. (#1129)

  • +
  • Some changes to the C++ Image constructors to include a maxptr value. (#1149)

  • +
  • Changed SincInterpolant.ixrange to be consistent with the value of xrange. (#1154)

  • +
  • Changed galsim.scene namespace name to galsim.galaxy_sample. (#1174)

  • +
+

Config Updates

+
    +
  • Added Correlated noise type. (#731, #1174)

  • +
  • Added galaxy_sample input type and SampleGalaxy GSObject type. (#795, #1174)

  • +
  • Added COSMOSValue and SampleValue value types. (#954, #1174)

  • +
  • Allowed template file names to be evaluated using the “$” shorthand notation. (#1138)

  • +
  • Added RegisterTemplate. (#1143)

  • +
  • Fixed some errors in PhotonDCR usage in the config layer. (#1148)

  • +
  • Added option to specify the dtype for images built by config. (#1160)

  • +
  • Fixed inconsistent behavior of image.world_pos in image type=Single. (#1160)

  • +
  • Let a flux item for an object with an SED normalize the SED. (#1160)

  • +
  • Fixed some edge cases where the created image could not have the requested wcs. (#1160)

  • +
  • Added option to initialize input objects in an InputLoader. (#1162, #1163)

  • +
  • Fixed error in returned variance for CCDNoise builder. (#1166, #1167)

  • +
  • Changed the way the internal random number sequence works. (#1169)

  • +
  • Made it possible to delete items in a config list. (#1183)

  • +
  • Fixed error in how input fields check when they are current. (#1184)

  • +
  • Fixed drawImage to work correctly for method=fft when using photon_ops. (#1193)

  • +
  • Fixed the proxies used by config Input items to allow access to attributes. (#1195)

  • +
  • Add –log_format option to galsim executable. (#1201)

  • +
  • Allow input objects with has_nobj=True to return an approximate number of objects. (#1202)

  • +
  • Add options to InputLoader to make inputs with AtmosphericScreens work properly. (#1206)

  • +
  • Only use proxies for input objects if not yet in a multiprocessing context. (#1206)

  • +
  • Made it possible to delete items in a config list. (#1183)

  • +
  • Allowed input objects to return an approximate number of objects for the initial pass. (#1202)

  • +
  • Added options to InputLoader to make inputs with AtmosphericScreens work properly. (#1206)

  • +
  • Only use proxies for input objects if not yet in a multiprocessing context. (#1206)

  • +
+

New Features

+ +

Performance Improvements

+
    +
  • Made Silicon sensor use ~half as many points for the pixels. (#1118, #1137)

  • +
  • Use single precision for Silicon pixel boundaries. (#1140)

  • +
  • Moved some of the logic related to the Silicon sensor to the python layer. (#1141)

  • +
  • Let BaseDeviate.generate use multiple threads in C++ layer. (#1177)

  • +
  • Fixed a slow-down in multiprocessing especially when running very many (>10) processes. (#1213)

  • +
+

Bug Fixes

+
    +
  • Fixed some cases where HSM would fail to converge. (#1132, #1149)

  • +
  • Fixed error in InterpolatedImage.withGSParams not updating stepk and maxk. (#1154)

  • +
  • Fixed error in ChromaticSum photon shooting when n_photons is given. (#1156, #1157)

  • +
  • Fixed some rounding errors that could happen with integer-typed images. (#1160)

  • +
  • Fixed an assert error that would trigger if hsm was run on images with negative stride. (#1185)

  • +
  • Fix the flux scaling of drawReal for objects with non-diagonal jacobian. (#1197, #1198)

  • +
  • Fixed the pip installation to include the galsim/share directory, which was missing.

  • +
  • Fixed error in default nobj calculation for extra_object output when not doing the +normal BuildFile processing.

  • +
  • Fixed error in how input fields check when they are current. (#1184)

  • +
  • Fixed an assert error that would trigger if hsm was run on images with negative stride. (#1185)

  • +
  • Fixed drawImage to work correctly for method=fft when using photon_ops. (#1193)

  • +
  • Fixed the proxies used by config Input items to allow access to attributes. (#1195)

  • +
  • Fixed the flux scaling of drawReal for objects with non-diagonal jacobian. (#1197, #1198)

  • +
  • Fixed a potential segmentation fault when using photon_ops with FFT drawing. (#1216)

  • +
  • Fixed the Silicon class to handle invalid wavelengths gracefully. (#1227)

  • +
  • Fixed the config template processing to handle recursive templates. (#1233)

  • +
  • Fixed the modules field in config files to allow sub-modules without the parent module. (#1233)

  • +
+
+
+

v2.3

+

Dependency Changes

+
    +
  • Removed future as a dependency. (#1082)

  • +
  • Download eigen automatically if not found on your system. (#1086)

  • +
+

API Changes

+
    +
  • Deprecated the rng parameter of WavelengthSampler and FRatioAngles. (#540)

  • +
  • Deprecated withOrigin method for non-local WCS types. (#1073)

  • +
  • Updated numerical details of the Kolmogorov class. (#1084)

  • +
  • Changed galsim.wfirst module to galsim.roman. (#1088)

  • +
  • Changed default ii_pad_factor for PhaseScreenPSF, OpticalPSF to 1.5. (#1089)

  • +
  • Deprecated the high_accuracy and approximate_struts parameters for the +galsim.roman.getPSF function. (#1089)

  • +
  • Changed surface_ops parameter to GSObject.drawImage to photon_ops. (#1093)

  • +
  • Added logger option to some config functions and methods. (#1095)

  • +
  • Deprecated galsim.integ.trapz and galsim.integ.midpt. (#1098)

  • +
  • Changed the convention for the f array passed to the LookupTable2D +constructor to be the transpose of what it was. (#1103)

  • +
  • Changed the behavior of PhaseScreenPSF, OpticalPSF, and +ChromaticOpticalPSF by adding the kwarg fft_sign. (#1104)

  • +
  • Changed _InterpolatedImage to not recenter the image to (0,0) as InterpolatedImage does. (#1151)

  • +
+

Config Updates

+
    +
  • Added ability to draw chromatic objects with config files. (#510)

  • +
  • Added demo12.yaml and demo13.yaml to the demo suite. (#510, #1121)

  • +
  • Fixed a issues with using a Current item before it was parsed. (#1083)

  • +
  • Added value-type-specific type names (e.g. Random_float, etc.) (#1083)

  • +
  • Fixed a subtle issue in Eval string processing. (#1083)

  • +
  • Added photon_ops and sensor as options in the stamp processing. (#1093)

  • +
  • Removed the _nobjects_only mechanism from input objects. (#1095)

  • +
  • Allowed Eval fields to use any modules that are listed in the top-level +modules field. (#1121)

  • +
  • Added Roman config types: RomanSCA, RomanBandpass, and RomanPSF. (#1121)

  • +
+

New Features

+ +

Performance Improvements

+
    +
  • Implemented Transformation._drawReal and Transformation._drawKImage in python. (#934)

  • +
  • Sped up the draw routines for InterpolatedImage. (#935)

  • +
  • Improved the quality and speed of Roman PSFs. (#1089)

  • +
  • Sped up GSFitsWCS calculations for SIP and PV distorted WCSs. (#1092)

  • +
  • Various speed improvements in config processing. (#1095, #1098)

  • +
  • Sped up SED.calculateFlux and some other SED and Bandpass calculations. (#1098)

  • +
  • Sped up the Hankel transforms in several classes. (#1099)

  • +
  • Improved the accuracy of stepk for Kolmogorov profiles. (#1110)

  • +
  • Sped up Zernike arithmetic. (#1124)

  • +
  • Removed some overhead in some “leading underscore” methods. (#1126)

  • +
+

Bug Fixes

+
    +
  • Fixed horner and horner2d when inputs are complex. (#1054)

  • +
  • Fixed VonKarman integration to be more reliable. (#1058)

  • +
  • Fixed minor bug in repr of OpticalPSF class. (#1061)

  • +
  • Fixed bug in RandomKnots when multiplied by an SED. (#1064)

  • +
  • Fixed bug in galsim.fits.writeMulti not writing headers. (#1091)

  • +
  • Fixed some problems with the shared library build. (#1128)

  • +
  • Fixed a rare problem with SED.sampleWavelength sometimes generating invalid values. (#1131)

  • +
  • Fixed a bug where InterpolatedImage.drawReal could possibly cause seg faults

  • +
  • Fixed an error in our handling of the Roman Cycle 7 aberrations file. (#1142)

  • +
  • Fixed an error when drawing an InterpolatedImage completely off the target image. (#1164)

  • +
+
+
+

v2.2

+

Deprecated Features

+
    +
  • Deprecated galsim.correlatednoise._BaseCorrelatedNoise. (#160)

  • +
  • Deprecated RandomWalk in favor of RandomKnots. (#977)

  • +
  • Deprecated the tol parameter of the various Interpolant classes. (#1038)

  • +
+

API Changes

+
    +
  • Removed functionality to store/reload WFIRST PSFs, and to get multiple WFIRST PSFs (#919)

  • +
  • Changed the function signature of StampBuilder.addNoise. (#1048)

  • +
+

Changes to Shared Files

+ +

Config Updates

+
    +
  • Added some more customization hooks in the StampBuilder class. (#1048)

  • +
  • Added quick_skip, obj_rng=False, rng_index_key options. (#1048)

  • +
+

Documentation Updates

+
    +
  • Switched docs to Sphinx. (#160)

  • +
+

New Features

+
    +
  • Added FitsHeader.extend method. Also, read_header option to galsim.fits.read. (#877)

  • +
  • Updated lots of WFIRST module to use Cycle 7 specifications. (#919)

  • +
  • Extended WFIRST aberrations to 22 Zernike coefficients and vary them across FOV. (#919)

  • +
  • Improved efficiency of drawing RandomKnots objects when transformed. (#977)

  • +
  • Added WFIRST fermi persistence model. (#992)

  • +
  • Added r0_500 argument to VonKarman. (#1005)

  • +
  • Improved ability of AtmosphericScreen to use shared memory in multiprocessing context. (#1006)

  • +
  • Use OpenMP when appropriate in SiliconSensor.accumulate (#1008)

  • +
  • Added array versions of BaseWCS.toWorld and BaseWCS.toImage. (#1026)

  • +
  • Exposed some methods of Interpolant classes that had only been in the C++ layer. (#1038)

  • +
  • Added Zernike polynomial +, -, and * operators. (#1047)

  • +
  • Added Zernike polynomial properties .laplacian and .hessian. (#1047)

  • +
  • Added center option to the GSObject.drawImage method. (#1053)

  • +
  • Added a new context, galsim.utilities.pickle_shared, which can be used to include shared +data in the pickle file. (#1057)

  • +
  • Added ability to shear a position. (Backported from 2.3 series.) (#1090)

  • +
+

Bug Fixes

+
    +
  • Fixed a couple places where negative fluxes were not working correctly. (#472)

  • +
  • Fixed FITS I/O to write out comments of header items properly. (#877)

  • +
  • Fixed error in the serialization of RandomKnots instances. (#977)

  • +
  • Fixed error in PhaseScreenPSF when aberrations has len=1. (#1006, #1029)

  • +
  • Fixed error in BaseWCS.makeSkyImage when crossing ra=0 line for some WCS classes. (#1030)

  • +
  • Fixed slight error in the realized flux of some profiles when using photon shooting. (#1036)

  • +
  • Fixed error in Sersic class when n is very, very close to 0.5. (#1041)

  • +
  • Fixed a compiler error for clang on linux systems.

  • +
  • Fixed integration in VonKarman for some problematic r0 values. (#1058)

  • +
  • Fixed a bug in RandomKnots when multiplied by an SED. (#1064)

  • +
  • Fixed a bug in photon shooting which could cause seg faults. (#1079)

  • +
+
+
+

v2.1

+

Deprecated Features

+
    +
  • Deprecated PhaseScreenPSF attributes img and finalized. (#990)

  • +
  • Deprecated GSParams items allowed_flux_variation, small_fraction_of_flux, +and range_division_for_extreama. (#993)

  • +
+

New Features

+
    +
  • Added RandomWalk profile option. (#821)

  • +
  • Added spline as LookupTable2D interpolant. (#982)

  • +
  • Added ability to use an Interpolant in LookupTable and LookupTable2D. (#982)

  • +
  • Added option for faster grid interpolation of LookupTable2D. (#982)

  • +
  • Added offset and flux_ratio options to WCS.toWorld and toImage. (#993)

  • +
+

Bug Fixes

+
    +
  • Corrected the diffusion functional form in SiliconSensor. (#981)

  • +
  • Fixed a bug in the PhaseScreenPSF withGSParams function. (#990)

  • +
  • Fixed a seg fault bug when PoissonDeviate is given mean=0. (#996)

  • +
  • Fixed the galsim executable to work correctly when installed by SCons.

  • +
  • Fixed Convolve and Sum sometimes making unnecessary copies.

  • +
  • Fixed error when using non-int integer types as seed of BaseDeviate (#1009)

  • +
  • Fixed error in use of non-integer grid_spacing in PowerSpectrum (#1020)

  • +
  • Fixed FitsHeader to not unnecessarily read data of fits file. (#1024)

  • +
  • Switched to yaml.safe_load to avoid PyYAML v5.0 warnings (#1025)

  • +
  • Fixed cases where numpy objected to subtracting floats from ints. (#1025)

  • +
+
+
+

v2.0

+

Installation Changes

+
    +
  • Now installable via pip or setup.py install. (#809)

  • +
+

Dependency Changes

+
    +
  • Officially no longer support Python 2.6 or 3.4. (#755)

  • +
  • No longer support pre-astropy versions of pyfits or astropy <v1.0 (#755)

  • +
  • No longer support pre-2016 version of the COSMOS catalog. (#755)

  • +
  • Added dependency on LSSTDESC.Coord. (#809)

  • +
  • Removed dependency on boost. (#809)

  • +
  • Removed dependency on TMV. (#809)

  • +
  • Added dependency on pybind11 for setup.py installations. (#809)

  • +
  • Added dependency on Eigen for setup.py installations. (#809)

  • +
+

API Changes

+
    +
  • Changed the default maximum_fft_size to 8192 from 4096. (#755)

  • +
  • Changed the order of arguments of galsim.wfirst.allDetectorEffects. (#755)

  • +
  • Changed how CelestialCoord.project and deproject work. (#809)

  • +
  • Changed name of InclinedExponential.disk_half_light_radius. (#809)

  • +
  • Removed galsim_yaml and galsim_json scripts. (#809)

  • +
  • Removed lsst module, which was broken. (#964)

  • +
  • Changed how gsparams work for objects that wrap other objects. (#968)

  • +
+

Deprecated Features

+
    +
  • Removed all features deprecated in 1.x versions.

  • +
+

New Features

+
    +
  • Changed errors to raise a GalSimError or a subclass thereof. (#755)

  • +
  • Changed the type of warnings raised by GalSim to GalSimWarning. (#755)

  • +
  • Added the withGSParams() method for all GSObjects. (#968)

  • +
+
+
+

v1.6

+

API Changes

+
    +
  • Delayed AtmosphericScreen instantiation until its first use. (#864)

  • +
  • Simplified return values of NFWHalo and PowerSpectrum methods. (#855)

  • +
  • Simplified return value of LookupTable, SED and Bandpass access. (#955)

  • +
+

Bug Fixes

+
    +
  • Fixed error in amplitude of phase screens created by AtmosphericScreen (#864)

  • +
  • Fixed a bug in the DES MEDS writer setting the cutout row/col wrong. (#928)

  • +
  • Fixed some small bugs in complicated uses of config processing. (#928)

  • +
  • Fixed memory leak when drawing PhaseScreenPSFs using photon-shooting (#942)

  • +
  • Fixed a few minor bugs in the Silicon code. (#963)

  • +
  • Fixed a bug in the SED.thin() rel_err value. (#963)

  • +
+

Deprecated Features

+
    +
  • Deprecated passing Image arguments to kappaKaiserSquires function. (#855)

  • +
  • Deprecated the interpolant argument for PowerSpectrum methods getShear, +getConvergence, getMagnification, and getLensing. (#855)

  • +
  • Deprecated PowerSpectrum.subsampleGrid. (#855)

  • +
+

New Features

+
    +
  • Added Zernike submodule. (#832, #951)

  • +
  • Updated PhaseScreen to accept None as a valid time argument. (#864)

  • +
  • Added SecondKick profile GSObject. (#864)

  • +
  • Updated PhaseScreenPSFs to use SecondKick with geometric_shooting. (#864)

  • +
  • Added VonKarman profile GSObject. (#940)

  • +
  • Added PhotonDCR surface op. (#955)

  • +
  • Added astropy units as allowed values of wave_type in Bandpass. (#955)

  • +
  • Added SiliconSensor.calculate_pixel_area. (#963)

  • +
  • Added transpose option in SiliconSensor. (#963)

  • +
+
+
+

v1.5

+

API Changes

+
    +
  • Simplified the return value of galsim.config.ReadConfig. (#580)

  • +
  • Changed return type of RealGalaxyCatalog.getGal and getPSF. (#640)

  • +
  • Reorganized files in share/galsim directory. (#640)

  • +
  • Changed SED objects to have real dimensions. (#789)

  • +
  • Changed drawKImage to return a single ImageCD. (#799)

  • +
  • Changed InterpolatedKImage to take an ImageCD. (#799)

  • +
  • Dynamic PhaseScreenPSFs require an explicit start time and time step. (#824)

  • +
  • OpticalScreen now requires diam argument. (#824)

  • +
  • Switched galsim.Image(image) to make a copy rather than a view. (#873)

  • +
  • Changed the behavior of RealGalaxyCatalog.preload (#884)

  • +
+

Dependency Changes

+
    +
  • Added astropy as a required dependency for chromatic functionality. (#789)

  • +
  • Switched scons tests test runner from nosetests to pytest. (#892)

  • +
+

Bug Fixes

+
    +
  • Fixed parity mistake in configuration of WFIRST focal plane. (#675)

  • +
  • Fixed an error in the magnification calculated by NFWHalo.getLensing(). (#580)

  • +
  • Fixed bug when whitening noise in images based on COSMOS training datasets +using the config functionality. (#792)

  • +
  • Fixed bug in image.subImage that could cause seg faults in some cases. (#848)

  • +
  • Fixed bug in GSFitsWCS that made toImage sometimes fail to converge. (#880)

  • +
  • Fixed bug that could cause Kolmogorov to go into an endless loop. (#952)

  • +
+

Deprecated Features

+
    +
  • Deprecated simReal function. (#787)

  • +
  • Deprecated Chromatic class. (#789)

  • +
  • Deprecated .copy() methods for immutable classes, including GSObject, +ChromaticObject, SED, and Bandpass. (#789)

  • +
  • Deprecated wmult parameter of drawImage. (#799)

  • +
  • Deprecated Image.at method. (#799)

  • +
  • Deprecated gain parameter of drawKImage. (#799)

  • +
  • Deprecated ability to create multiple PhaseScreenPSFs with single call +to makePSF. (#824)

  • +
  • Deprecated the use of np.trapz and galsim.integ.mipdt as valid +integration rules for use by ImageIntegrators. (#887)

  • +
  • Changed the Angle.rad method to a property. (#904)

  • +
  • Deprecated the functions HMS_Angle and DMS_Angle. (#904)

  • +
  • Deprecated the function ShapeletSize and FitShapelet. (#904)

  • +
  • Deprecated using Interpolant base class as a factory function. (#904)

  • +
  • Deprecated use of the SBProfile attribute of GSObject. (#904)

  • +
  • Deprecated making a GSObject directly. (#904)

  • +
  • Deprecated use of the image attribute of Image. (#904)

  • +
  • PhotonArray.addTo(image) now takes a regular galsim.Image argument. (#904)

  • +
  • Deprecated the various PhotonArray.get* functions. (#904)

  • +
  • Deprecated calculateFlux(bandpass=None). (#905)

  • +
  • Deprecated the various get* methods that are equivalent to a property. +e.g. obj.getFlux() -> obj.flux, etc. (#904)

  • +
  • Deprecated ChromaticObject.obj. (#904)

  • +
  • Changed the objlist attribute of ChromaticSum and ChromaticConvolution to +obj_list. (#904)

  • +
  • Deprecated OpticalScreen.coef_array. (#904)

  • +
  • Changed a number of GSObject methods to properties. (#904)

    +
    +
      +
    • obj.stepK() -> obj.stepk

    • +
    • obj.maxK() -> obj.maxk

    • +
    • obj.nyquistScale() -> obj.nyquist_scale

    • +
    • obj.centroid() -> obj.centroid

    • +
    • obj.getPositiveFlux() -> obj.positive_flux

    • +
    • obj.getNegativeFlux() -> obj.negative_flux

    • +
    • obj.maxSB() -> obj.max_sb

    • +
    • obj.isAxisymmetric() -> obj.is_axisymmetric

    • +
    • obj.isAnalyticX() -> obj.is_analytic_x

    • +
    • obj.isAnalyticK() -> obj.is_analytic_k

    • +
    • obj.hasHardEdges() -> obj.has_hard_edges

    • +
    +
    +
  • +
  • Renamed ChromaticObject.centroid(bandpass) to calculateCentroid. (#904)

  • +
  • Changed a few Image methods to properties. (#904)

    +
    +
      +
    • image.center() -> image.center

    • +
    • image.trueCenter() -> image.true_center

    • +
    • image.origin() -> image.origin

    • +
    +
    +
  • +
+

New Features

+
    +
  • Added DeltaFunction. (#533)

  • +
  • Added ChromaticRealGalaxy. (#640)

  • +
  • Added CovarianceSpectrum. (#640)

  • +
  • Added HST bandpasses covering AEGIS and CANDELS surveys (#640)

  • +
  • Added drawKImage method for ChromaticObject and CorrelatedNoise (#640)

  • +
  • Updated WFIRST WCS some other basic numbers to Cycle 7 design. (#675)

  • +
  • Added support for unsigned int Images. (#715)

  • +
  • Added a new Sensor class hierarchy, including SiliconSensor. (#722)

  • +
  • Added save_photons option to drawImage. (#722)

  • +
  • Added image.bin and image.subsample methods. (#722)

  • +
  • Added annular Zernike option for optical aberration coefficients. (#771)

  • +
  • Added ability to use numpy, np, or math in all places where we evaluate +user input. (#776)

  • +
  • Added keywords exptime and area to drawImage(). (#789)

  • +
  • Added ability to use astropy.units for units of SEDs. (#789).

  • +
  • Added InclinedExponential and InclinedSersic. (#782, #811)

  • +
  • Added ability to select from a RealGalaxyCatalog or COSMOSCatalog using +the ‘weight’ entries to account for selection effects. (#787)

  • +
  • Added complex Image dtypes (aka ImageCD and ImageCF). (#799, #873)

  • +
  • Added maxSB() method to GSObjects. (#799)

  • +
  • Added im[x,y] = value and value = im[x,y] syntax. (#799)

  • +
  • Added ability to do FFTs directly on images. (#799)

  • +
  • Added galsim.RandomWalk. (#819)

  • +
  • Added generate function to BaseDeviate and sed.sampleWavelength. (#822)

  • +
  • Added function assignPhotonAngles (#823)

  • +
  • Added geometric optics approximation for photon-shooting PhaseScreenPSFs. +(#824)

  • +
  • Added gradient method to LookupTable2D. (#824)

  • +
  • Added surface_ops option to drawImage function. (#827)

  • +
  • Added ii_pad_factor kwarg to PhaseScreenPSF and OpticalPSF. (#835)

  • +
  • Added galsim.fft module. (#840)

  • +
  • Added a hook to the WCS classes to allow them to vary with color. (#865)

  • +
  • Added optional variance parameter to PowerSpectrum.buildGrid. (#865)

  • +
  • Added CelestialCoord.get_xyz() and CeletialCoord.from_xyz(). (#865)

  • +
  • Added an optional center argument for Angle.wrap(). (#865)

  • +
  • Added recenter option to drawKImage. (#873)

  • +
  • Added option to use circular weight function in HSM moments. (#917)

  • +
+

New config features

+
    +
  • Changed galsim.config.CalculateNoiseVar to CalculateNoiseVariance. (#820)

  • +
  • Setting config[‘rng’] is no longer required when manually running commands +like galsim.config.BuildGSObject. (#820)

  • +
  • Allow PoissonNoise and CCDNoise without any sky level. (#820)

  • +
  • Let ‘None’ in the config file mean None. (#820)

  • +
  • Remove default value for ‘max_extra_noise’ for photon shooting. (#820)

  • +
  • Added –except_abort option to galsim executable. (#820)

  • +
  • Added optional probability parameter ‘p’ for Random bool values. (#820)

  • +
  • Added ability to specify world_pos in celestial coordinates. (#865)

  • +
  • Added the ability to have multiple rngs. (#865)

  • +
  • Added ngrid, center, variance, index options to power_spectrum input field. +(#865)

  • +
  • Added skip option in stamp field. (#865)

  • +
  • Added ‘:field’ syntax for templates. (#865)

  • +
+
+
+

v1.4

+

API Changes

+
    +
  • Changed the galsim.Bandpass and galsim.SED classes to require formerly +optional keywords wave_type and flux_type. (#745)

  • +
+

Dependency Changes

+
    +
  • Added future module as a dependency. (#534)

  • +
  • Changed PyYAML to a non-optional dependency. (#768)

  • +
+

Bug Fixes

+
    +
  • Improved ability of galsim.fits.read to handle invalid FITS headers. (#602)

  • +
  • Fixed bug in des module, building meds file with wcs from input images. (#654)

  • +
  • Fixed a bug in the way Images are instantiated for certain combinations of +ChromaticObjects and image-setup keyword arguments (#683)

  • +
  • Added ability to manipulate the width of the moment-measuring weight function +for the KSB shear estimation method of the galsim.hsm package. (#686)

  • +
  • Fixed an error in the CCDNoise.getVariance() function. (#713)

  • +
  • Fixed an assert failure in InterpolatedImage if image is all zeros. (#720)

  • +
  • Updated ups table file so that setup command is setup galsim. (#724)

  • +
  • Improved algorithm for thinning SEDs and Bandpasses. (#739)

  • +
  • Fixed a bug in how DistDeviate handles nearly flat pdfs. (#741)

  • +
  • Fixed a bug in chromatic parametric COSMOS galaxy models. (#745)

  • +
  • Fixed a bug in the Sum and Convolution constructors when list has only a +single element. (#763)

  • +
  • Fixed a bug related to boost-v1.60 python shared_ptr registration. (#764)

  • +
  • Changed an assert in the HSM module to an exception. (#784)

  • +
+

Deprecated Features

+
    +
  • Deprecated the gal.type=Ring option in the config files. (#698)

  • +
+

New Features

+
    +
  • Added OutputCatalog class. (#301, #691)

  • +
  • Added methods calculateHLR, calculateMomentRadius, and calculateFWHM. (#308)

  • +
  • Added LookupTable2D. (#465)

  • +
  • Added support for Python 3. (#534)

  • +
  • Added AtmosphericScreen, OpticalScreen, and PhaseScreenList. (#549)

  • +
  • Added PhaseScreenPSF. (#549)

  • +
  • Added Atmosphere function. (#549)

  • +
  • Rewrote OpticalPSF using new PhaseScreen framework. (#549)

  • +
  • Extended OpticalPSF to handle arbitrary Zernike order. (#549)

  • +
  • Added a simple, linear model for persistence. (#554)

  • +
  • Added BoundsI.numpyShape(). (#654)

  • +
  • Enabled FITS files with unsigned integer to read as ImageI or ImageS. (#654)

  • +
  • Made COSMOSCatalog write an index parameter. (#654, #694)

  • +
  • Added ability to specify lambda and r0 separately for Kolmogorov. (#657)

  • +
  • Enabled initializing an InterpolatedImage from a user-specified HDU. (#660)

  • +
  • Changed galsim.fits.writeMulti to allow hdus in “image” list. (#691)

  • +
  • Added wcs argument to Image.resize(). (#691)

  • +
  • Added BaseDeviate.discard(n) and BaseDeviate.raw(). (#691)

  • +
  • Added sersic_prec option to COSMOSCatalog.makeGalaxy(). (#691)

  • +
  • Enabled image quality cuts in the COSMOSCatalog class. (#693)

  • +
  • Added convergence_threshold in HSMParams. (#709)

  • +
  • Improved the readability of Image and BaseDeviate reprs. (#723)

  • +
  • Sped up Bandpass, SED, and LookupTable classes. (#735)

  • +
  • Added the FourierSqrt operator. (#748)

  • +
  • Made Bandpass.thin() and truncate() preserve the zeropoint. (#711)

  • +
  • Added version information to the compiled C++ library. (#750)

  • +
+

Updates to galsim executable

+
    +
  • Dropped default verbosity from 2 to 1. (#691)

  • +
  • Added galsim -n njobs -j jobnum to split run into multiple jobs. (#691)

  • +
  • Added galsim -p to perform profiling on the run. (#691)

  • +
+

New config features

+
    +
  • Added ability to write truth catalogs using output.truth field. (#301, #691)

  • +
  • Improved the extensibility of the config parsing. (#691, #774)

  • +
  • Added the ‘template’ option. (#691)

  • +
  • Made ‘$’ and ‘@’ shorthand for Eval and Current. (#691)

  • +
  • Allowed gsobjects to be referenced from Current types. (#691)

  • +
  • Added x,f specification for a RandomDistribution. (#691)

  • +
  • Added a new ‘stamp’ top level field. (#691)

  • +
  • Added new stamp type=Ring to effect ring tests. (#698)

  • +
+
+
+

v1.3

+

Installation Changes

+
    +
  • Require functionality in TMV 0.72. (#616)

  • +
+

API Changes

+
    +
  • Changed the name of the bounds.addBorder() method to withBorder. (#218)

  • +
  • Removed (from the python layer) Interpolant2d and InterpolantXY. (#218)

  • +
  • Removed the MultipleImage way of constructing an SBInterpolatedImage. (#218, #642)

  • +
  • Made the default tolerance for all Interpolants equal to 1.e-4.. (#218)

  • +
  • Deprecated the __rdiv__ operator from Bandpass and SED. (#218)

  • +
  • Made all returned matrices consistently use numpy.array, rather than +numpy.matrix. (#218)

  • +
  • Made the classes PositionI, PositionD, GSParams, and HSMParams immutable. +(#218, #643)

  • +
  • Deprecated CorrelatedNoise.calculateCovarianceMatrix. (#630)

  • +
  • Officially deprecated the methods and functions that had been described as +having been removed or changed to a different name. (#643)

  • +
  • Added function to interleave a set of dithered images into a single +higher-resolution image. (#666)

  • +
+

New Features

+
    +
  • Made all GalSim objects picklable unless they use fundamentally unpicklable +things such as lambda expressions, improved str and repr representations, +made __eq__, __ne__, and __hash__ work better. (#218)

  • +
  • Added ability to set the zeropoint of a bandpass to a numeric value on +construction. (#218)

  • +
  • Added ability to set the redshift of an SED on construction. (#218)

  • +
  • Updated CorrelatedNoise to work with images that have a non-trivial WCS. +(#501)

  • +
  • Added new methods of the image class to simulate detector effects (#555, #558).

  • +
  • Enabled constructing a FitsHeader object from a dict, and allow +FitsHeader to be default constructed with no keys. (#590)

  • +
  • Added a module, galsim.wfirst, that includes information about the planned +WFIRST mission, along with helper routines for constructing appropriate PSFs, +bandpasses, WCS, etc. (#590)

  • +
  • Added native support for the TAN-SIP WCS type using GSFitsWCS. (#590)

  • +
  • Added a helper program, galsim_download_cosmos, that downloads the COSMOS +RealGalaxy catalog. (#590)

  • +
  • Added new class with methods for making realistic galaxy samples using COSMOS: +the COSMOSCatalog class and its method makeObj(). (#590 / #635).

  • +
  • Added information about PSF to the data returned by EstimateShear(). (#612)

  • +
  • Added Spergel(2010) profile GSObject (#616).

  • +
  • Added an option to the ChromaticObject class that allows for faster image +rendering via interpolation between stored images. (#618)

  • +
  • Added new ChromaticAiry and ChromaticOpticalPSF classes for representing +chromatic optical PSFs. (#618)

  • +
  • Enable initializing a DES_PSFEx object using a pyfits HDU directly instead +of a filename. (#626)

  • +
  • Added TopHat class implementing a circular tophat profile. (#639)

  • +
  • Added ability of Noise objects to take a new random number generator (a +BaseDeviate instance) when being copied. (#643)

  • +
  • Added InterpolatedKImage GSObject for constructing a surface brightness +profile out of samples of its Fourier transform. (#642)

  • +
  • Enabled constructing a FitsHeader object from a list of (key, value) pairs, +which preserves the order of the items in the header. (#672)

  • +
+

Bug Fixes and Improvements

+
    +
  • Fixed a bug in the normalization of SEDs that use wave_type=’A’. (#218)

  • +
  • Switched the sign of the angle returned by CelestialCoord.angleBetween. +(#590)

  • +
  • Fixed a bug in UncorrelatedNoise where the variance was set incorrectly. +(#630)

  • +
  • Changed the implementation of drawing Box and Pixel profiles in real space +(i.e. without being convolved by anything) to actually draw the surface +brightness at the center of each pixel. (#639)

  • +
  • Fixed a bug where InterpolatedImage and Box profiles were not correctly +rendered when transformed by something that includes a flip. (#645)

  • +
  • Fixed a bug in rendering profiles that involve two separate shifts. (#645)

  • +
  • Fixed a bug if drawImage was given odd nx, ny parameters, the drawn profile +was not correctly centered in the image. (#645)

  • +
  • Added intermediate results cache to ChromaticObject.drawImage and +ChromaticConvolution.drawImage to speed up the rendering of groups +of similar (same SED, same Bandpass, same PSF) chromatic profiles. (#670)

  • +
+

Updates to config options

+
    +
  • Added COSMOSGalaxy type, with corresponding cosmos_catalog input type. (#590)

  • +
  • Added Spergel type. (#616)

  • +
  • Added lam, diam, scale_units options to Airy and OpticalPSF types. (#618)

  • +
  • Added TopHat type. (#639)

  • +
+
+
+

v1.2

+

New Features

+
    +
  • Changed name of noise whitening routine from noise.applyWhiteningTo(image) +to image.whitenNoise(noise). (#529)

  • +
  • Added image.symmetrizeNoise. (#529)

  • +
  • Added magnitudes as a method to set the flux of SED objects. (#547)

  • +
  • Added SED.calculateDCRMomentShifts, SED.calculateChromaticSeeingRatio. (#547)

  • +
  • Added image.applyNonlinearity and image.addReciprocityFaiure. (#552)

  • +
  • Renamed alias_threshold to folding_threshold. (#562)

  • +
  • Extended to the rotate, shear, and transform methods of ChromaticObject +the ability to take functions of wavelength for the arguments. (#581)

  • +
  • Added cdmodel module to describe charge deflection in CCD pixels. (#524)

  • +
  • Added pupil_plane_im option to OpticalPSF. (#601)

  • +
  • Added nx, ny, and bounds keywords to drawImage() and drawKImage() +methods. (#603)

  • +
+

Bug Fixes and Improvements

+
    +
  • Improved efficiency of noise generation by correlated noise models. (#563)

  • +
  • Modified BoundsI and PositionI initialization to ensure that integer elements +in NumPy arrays with dtype==int are handled without error. (#486)

  • +
  • Changed the default seed used for Deviate objects when no seed is given to +use /dev/urandom if it is available. (#537)

  • +
  • Changed SED and Bandpass methods to preserve type when returning a new object +when possible. (#547)

  • +
  • Made file_name argument to CorrelatedNoise.getCOSMOSNoise() be able +to have a default value in the repo. (#548)

  • +
  • Fixed the dtype= kwarg of Image constructor on some platforms. (#571)

  • +
  • Added workaround for bug in pyfits 3.0 in galsim.fits.read. (#572)

  • +
  • Fixed a bug in the Image constructor when passed a NumPy array with the +opposite byteorder as the system native one. (#594)

  • +
  • Fixed bug that prevented calling LookupTables on non-square 2d arrays. (#599)

  • +
  • Updated the code to account for a planned change in NumPy 1.9. (#604)

  • +
  • Fixed a bug where the dtype of an Image could change when resizing. (#604)

  • +
  • Defined a hidden __version__ attribute according to PEP 8 standards. (#610)

  • +
+

Updates to config options

+
    +
  • Moved noise whitening option from being an attribute of the RealGalaxy class, +to being a part of the description of the noise. (#529)

  • +
  • Added RandomPoisson, RandomBinomial, RandomWeibull, RandomGamma, and +RandomChi2 random number generators. (#537)

  • +
+
+
+

v1.1

+

Non-backward-compatible API changes

+
    +
  • Changed Pixel to take a single scale parameter. (#364)

  • +
  • Added new Box class. (#364)

  • +
  • Changed Angle.wrap() to return the wrapped angle. (#364)

  • +
  • Changed Bounds methods addBorder, shift, and expand to return new +Bounds objects. (#364)

  • +
  • Merged the GSParams parameters shoot_relerr and shoot_abserr into the +parameters integration_relerr and integration_abserr. (#535)

  • +
+

Other changes to the API

+
    +
  • Changed the name of the dx parameter in various places to scale. (#364)

  • +
  • Combined the old Image, ImageView and ConstImageView arrays of class +names into a single python layer Image class. (#364)

  • +
  • Changed the methods createSheared, createRotated, etc. to more succinct +names shear, rotate, etc. (#511)

  • +
  • Changed the setFlux and scaleFlux methods to return new objects. (#511)

  • +
  • Changed the Shapelet.fitImage method to FitShapelet (#511)

  • +
  • Changed the nyquistDx method to nyquistScale. (#511)

  • +
  • Moved as many classes as possible toward an immutable design. (#511)

  • +
  • Combined the draw and drawShoot methods into a single drawImage method +with more options about how the profile should be rendered. (#535)

  • +
  • Changed the name of drawK to drawKImage. (#535)

  • +
+

New Features

+
    +
  • Added new set of WCS classes. (#364)

  • +
  • Added CelestialCoord class to represent (ra,dec) coordinates. (#364)

  • +
  • Added Bandpass, SED, and ChromaticObject classes. (#467)

  • +
  • Added aberrations parameter of OpticalPSF. (#409)

  • +
  • Added max_size parameter to OpticalPSF. (#478)

  • +
  • Added text_file parameter to FitsHeader and FitsWCS. (#508)

  • +
  • Modified addNoiseSNR() method to return the added variance. (#526)

  • +
  • Added dtype option to drawImage and drawKImage. (#526)

  • +
+

Bug fixes and improvements

+
    +
  • Sped up the gzip and bzip2 I/O. (#344)

  • +
  • Fixed some bugs in the treatment of correlated noise. (#526, #528)

  • +
+

Updates to config options

+
    +
  • Added more options for image.wcs field. (#364)

  • +
  • Changed the name of sky_pos to world_pos. (#364)

  • +
  • Removed pix top layer in config structure. Add draw_method=no_pixel to +do what pix : None used to do. (#364)

  • +
  • Added draw_method=real_space to try to use real-space convolution. (#364)

  • +
  • Added ability to index Sequence types by any running index. (#364, #536)

  • +
  • Added Sum type for value types for which it makes sense. (#457)

  • +
  • Allowed modification of config parameters from the command line. (#479)

  • +
  • Added image.retry_failures. (#482)

  • +
  • Added output.retry_io item to retry failed write commands. (#482)

  • +
  • Changed the default sequence indexing for most things to be ‘obj_num_in_file’ +rather than ‘obj_num’. (#487)

  • +
  • Added draw_method=sb. (#535)

  • +
  • Changed the output.psf.real_space option to output.psf.draw_method +and allow all of the options that exist for image.draw_method. (#535)

  • +
  • Added an index item for Ring objects. (#536)

  • +
+
+
+

v1.0

+

Notable bug fixes and improvements

+
    +
  • Fixed bug in the rendering of shifted images. (#424)

  • +
  • Improved the fidelity of the Lanczos conserve_dc=True option. (#442)

  • +
  • Switched default interpolant for RealGalaxy to Quintic, since it was +found to be more accurate in general. (#442)

  • +
  • Fixed a bug in InterpolatedImage calculateStepK function. (#454)

  • +
  • Fixed a bug in Image class resize function. (#461)

  • +
  • Sped up OpticalPSF and RealGalaxy significantly. (#466, #474)

  • +
  • Fixed a bug in the fourier rendering of truncated Sersic profiles. (#470)

  • +
  • Fixed some bugs in the config machinery when files have varying numbers +of objects. (#487)

  • +
  • Support astropy.io.fits in lieu of stand-alone pyfits module. (#488)

  • +
  • Fixed a bug in config where ‘safe’ objects were not being correctly +invalidated when a new input item should have invalidated them.

  • +
  • Fixed a bug in the drawing of a Pixel all by itself. (#497)

  • +
+

New features

+
    +
  • Added galsim executable (instead of galsim_yaml, galsim_json). (#460)

  • +
  • Updated the allowed range for Sersic n to 0.3 – 6.2. (#325)

  • +
  • Made RealGalaxy objects keep track of their (correlated) noise. (#430)

  • +
  • Changed noise padding options for RealGalaxy and InterpolatedImage. (#430)

  • +
  • Added VariableGaussianNoise class. (#430)

  • +
  • Added offset parameter to both draw and drawShoot. (#439)

  • +
  • Changed the name of InputCatalog to just Catalog. (#449)

  • +
  • Added Dict class. (#449)

  • +
  • Added MEDS file output to des module. (#376)

  • +
  • Removed des module from default imports of GalSim. Now need to import +galsim.des explicitly or load with galsim -m des … (#460)

  • +
+

Updates to config options

+
    +
  • Added RealGalaxyOriginal galaxy type. (#389)

  • +
  • Added whiten option for RealGalaxy objects. (#430)

  • +
  • Added Current type. (#430)

  • +
  • Added offset option in image field. (#449)

  • +
  • Added the ability to have multiple input catalogs, dicts, etc. (#449)

  • +
  • Added signal_to_noise option for PSFs when there is no galaxy. (#459)

  • +
  • Added output.skip and output.noclobber options. (#474)

  • +
+
+
+

v0.5

+

New features

+
    +
  • Added Shapelet class. (#350)

  • +
  • Added ability to truncate Sersic profiles. (#388)

  • +
  • Added trefoil and struts to OpticalPSF. (#302, #390)

  • +
  • Updates to lensing engine:

    +
      +
    • Added document describing how lensing engine code works. (#248)

    • +
    • Added ability to draw (gamma,kappa) from same power spectrum. (#304)

    • +
    • Added kmin_factor and kmax_factor parameters to buildGrid. (#377)

    • +
    • Added PowerSpectrumEstimator class in pse module. (#382)

    • +
    +
  • +
  • Added GSParams (#343, #426) and HSMParams (#365) classes.

  • +
  • Added des module and example scripts. (#350)

  • +
  • Added applyWhiteningTo method to CorrelatedNoise class. (#352)

  • +
  • Changed the default centering convention for even-sized images to be in the +actual center, rather than 1/2 pixel off-center. (#380)

  • +
  • Enabled InputCatalog to read FITS catalogs. (#350)

  • +
  • Added FitsHeader class and config option. (#350)

  • +
  • Added the ability to read/write to a specific HDU. (#350)

  • +
  • Add new function galsim.fits.writeFile. (#417)

  • +
  • Added LINKFLAGS SCons option. (#380)

  • +
+

Updates to config

+
    +
  • Added index_convention option. (#380)

  • +
  • Changed the name of the center item for the Scattered image type to +image_pos, and added a new sky_pos item. (#380)

  • +
+

Bug fixes

+
    +
  • Fix some errors related to writing to an HDUList. (#417)

  • +
  • Fixed ringing when Sersic objectss were drawn with FFTs. (#426)

  • +
  • Fixed bugs in obj.drawK() function. (#407)

  • +
  • Fixed bugs with InterpolatedImage objects. (#389, #432)

  • +
  • Fixed bug in draw routine for shifted objects. (#380)

  • +
  • Fixed bug in the generation of correlated noise fields. (#352)

  • +
+
+
+

v0.4

+
    +
  • Added ability to pad images for InterpolatedImage or RealGalaxy with either +correlated or uncorrelated noise. (#238)

  • +
  • Added python-level LookupTable class which wraps the existing C++ Table +class. (#305)

  • +
  • Lensing engine updates: (#305)

    +
      +
    • Added the option of drawing shears from a tabulated P(k)

    • +
    • Added ability to handle conversions between different angular units.

    • +
    • Fixed an important bug in the normalization of the generated shears.

    • +
    +
  • +
  • Added a DistDeviate class. (#306)

  • +
  • Added galsim.correlatednoise.get_COSMOS_CorrFunc(…). (#345)

  • +
  • Added im.addNoiseSNR(). (#349)

  • +
  • Made a new Noise hierarchy for CCDNoise (no longer a BaseDeviate), +GaussianNoise, PoissonNoise, DeviateNoise. (#349)

  • +
+
+
+

v0.3

+
    +
  • Fixed several bugs in the Sersic class that had been causing ringing. +(#319, #330)

  • +
  • Added support for reading and writing compressed fits files. (#299)

  • +
  • Added InterpolatedImage class to wrap existing C++ level SBInterpolatedImage. +(#333)

  • +
  • Added a new class structure for representing 2D correlation functions, used +to describe correlated noise in images. (#297).

  • +
  • Add FormattedStr option for string values in config files. (#315)

  • +
  • Added obj.drawK() to the python layer. (#319)

  • +
  • Fixed several sources of memory leaks. (#327)

  • +
  • Updated the moments and PSF correction code to use the Image class and TMV; +to handle weight and bad pixel maps for the input Images; and to run ~2-3 +times faster. (#331, #332)

  • +
  • Fixed bug in config RandomCircle when using inner_radius option.

  • +
+
+
+

v0.2

+
    +
  • Significant revamping and commenting of demos, including both python and +config versions (#243, #285, #289).

  • +
  • Added python-level int1d function to wrap C++-level integrator, which +allowed us to remove our dependency on scipy. (#288)

  • +
  • Significant expansion in config functionality, using YAML/JSON format +config files (#291, #295).

  • +
  • Fixed some bugs in Image handling (including bugs related to duplicate +numpy.int32 types), and made Image handling generally more robust (#293, #294).

  • +
  • Fixed bug in wrapping of angles (now not automatic – use wrap() explicitly).

  • +
+
+
+

v0.1

+

Initial version of GalSim with most of the basic functionality.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/overview.html b/docs/_build/html/overview.html new file mode 100644 index 00000000000..6da76f7f649 --- /dev/null +++ b/docs/_build/html/overview.html @@ -0,0 +1,328 @@ + + + + + + + Overview — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Overview

+https://github.com/GalSim-developers/GalSim/workflows/GalSim%20CI/badge.svg?branch=main +https://codecov.io/gh/GalSim-developers/GalSim/branch/master/graph/badge.svg?branch=main +https://img.shields.io/badge/astro--ph.IM-1407.7676-B31B1B.svg +https://img.shields.io/badge/ADS-Rowe%20et%20al%2C%202015-blue.svg +

GalSim is open-source software for simulating images of astronomical objects +(stars, galaxies) in a variety of ways. The bulk of the calculations are +carried out in C++, and the user interface is in Python. In addition, the code +can operate directly on “config” files, for those users who prefer not to work +in Python. The impetus for the software package was a weak lensing community +data challenge, called GREAT3:

+
+
+

However, the code has numerous additional capabilities beyond those needed for +the challenge, and has been useful for a number of projects that needed to +simulate high-fidelity galaxy images with accurate sizes and shears. At the +end of this file, there is a list of the code capabilities and plans for future +development. For details of algorithms and code validation, please see

+
+
+

The GalSim version numbering tries to follow Semantic Versioning +This means that releases are numbered as M.m.r, where M is a major version number, +m is the minor version, and r is the revision (or patch or bugfix) number.

+

The public API is preserved within a given major version number. So code that works +with version 2.2.3 (say) should continue to work for all subsequent 2.x.x versions. +Minor versions indicate new features being added to the API. Revision versions +don’t add any new features, but fix bugs in the previous release.

+
+

Basic Installation

+

Normally, to install GalSim, you should just need to run:

+
pip install galsim
+
+
+

Depending on your setup, you may need to add either sudo to the start +or –user to the end of this command as you normally do when pip installing +packages.

+

See Installation Instructions for full details including one dependency (FFTW) that is not +pip installable, so you may need to install before running this command.

+

You can also use conda via conda-forge:

+
conda install -c conda-forge galsim
+
+
+
+
+

Source Distribution

+

To get the latest version of the code, you can grab the tarball (or zip file) from

+

https://github.com/GalSim-developers/GalSim/releases/

+

Also, feel free to fork the repository:

+

https://github.com/GalSim-developers/GalSim/fork

+

Or clone the repository with either of the following:

+
git clone git@github.com:GalSim-developers/GalSim.git
+git clone https://github.com/GalSim-developers/GalSim.git
+
+
+

The code is also distributed via Fink, Macports, and Homebrew for Mac users. +See Installation Instructions (in INSTALL.rst) for more information.

+

The code is licensed under a BSD-style license. See the file LICENSE for more +details.

+
+
+

Keeping up-to-date with GalSim

+

There is a GalSim mailing list, organized through the Google Group +galsim-announce. Members of the group will receive news and updates about the +GalSim code, including notifications of major version releases, new features +and bugfixes.

+

You do not need a Google Account to subscribe to the group, simply send any +email to:

+
galsim-announce+subscribe@googlegroups.com
+
+
+

If you receive a confirmation request (check junk mail filters!) simply reply +directly to that email, with anything, to confirm. You may also click the link +in the confirmation request, but you may be asked for a Google Account login.

+

To unsubscribe, simply send any email to:

+
galsim-announce+unsubscribe@googlegroups.com
+
+
+

You should receive notification that your unsubscription was successful.

+
+
+

How to communicate with the GalSim developers

+

Currently, the lead developers for GalSim are:

+
+
    +
  • Mike Jarvis (mikejarvis17 at gmail)

  • +
  • Rachel Mandelbaum (rmandelb at andrew dot cmu dot edu)

  • +
  • Josh Meyers (jmeyers314 at gmail)

  • +
+
+

However, many others have contributed to GalSim over the years as well, for +which we are very grateful.

+

If you have a question about how to use GalSim, a good place to ask it is at +StackOverflow. Some of the GalSim developers +have alerts set up to be automatically notified about questions with the +‘galsim’ tag, so there is a good chance that your question will be answered.

+

If you have any trouble installing or using the code, or find a bug, or have a +suggestion for a new feature, please open up an Issue on our GitHub +repository. We also accept +pull requests if you have something you’d like to contribute to the code base.

+

If none of these communication avenues seem appropriate, you can also contact +us directly at the above email addresses.

+
+
+

Demonstration scripts

+

There are a number of scripts in examples/ that demonstrate how the code can +be used. These are called demo1.pydemo13.py. You can run them by +typing (e.g.) python demo1.py while sitting in examples/, All demo scripts +are designed to be run in the examples/ directory. Some of them access +files in subdirectories of the examples/ directory, so they would not work +correctly from other locations.

+

A completely parallel sequence of configuration files, called demo1.yaml … +demo13.yaml, demonstrates how to make the same set of simulations using +config files that are parsed by the executable bin/galsim.

+

Two other scripts in the examples/ directory that may be of interest, but +are not part of the GalSim tutorial series, are make_coadd.py, which +demonstrates the use of the FourierSqrt transformation to optimally coadd +images, and psf_wf_movie.py, which demonstrates the realistic atmospheric +PSF code by making a movie of a time-variable PSF and wavefront.

+

As the project develops through further versions, and adds further +capabilities to the software, more demo scripts may be added to examples/ +to illustrate what GalSim can do.

+
+
+

Summary of current capabilities

+

Currently, GalSim has the following capabilities:

+
    +
  • Can generate PSFs from a variety of simple parametric models such as Moffat, +Kolmogorov, and Airy, as well as an optical PSF model that includes Zernike +aberrations to arbitrary order, and an optional central obscuration and +struts.

  • +
  • Can simulate galaxies from a variety of simple parametric models as well as +from real HST data. For information about downloading a suite of COSMOS +images, see

    +

    https://github.com/GalSim-developers/GalSim/wiki/RealGalaxy%20Data

    +
  • +
  • Can simulate atmospheric PSFs from realistic turbulent phase screens.

  • +
  • Can make the images either via i) Fourier transform, ii) real-space +convolution (real-space being occasionally faster than Fourier), or +iii) photon-shooting. The exception is that objects that include a +deconvolution (such as RealGalaxy objects) must be carried out using Fourier +methods only.

  • +
  • Can handle wavelength-dependent profiles and integrate over filter +bandpasses appropriately, including handling wavlengths properly when +photon shooting.

  • +
  • Can apply shear, magnification, dilation, or rotation to a galaxy profile +including lensing-based models from a power spectrum or NFW halo profile.

  • +
  • Can draw galaxy images into arbitrary locations within a larger image.

  • +
  • Can add noise using a variety of noise models, including correlated noise.

  • +
  • Can whiten or apply N-fold symmetry to existing correlated noise that is +already in an image.

  • +
  • Can read in input values from a catalog, a dictionary file (such as a JSON +or YAML file), or a fits header.

  • +
  • Can write images in a variety of formats: regular FITS files, FITS data +cubes, or multi-extension FITS files. It can also compress the output files +using various compressions including gzip, bzip2, and rice.

  • +
  • Can carry out nearly any simulation that a user might want using two parallel +methods: directly using Python code, or by specifying the simulation +properties in an input configuration script. See the demo scripts in +the examples/ directory for examples of each.

  • +
  • Supports a variety of possible WCS options from a simple pixel scale factor +of arcsec/pixel to affine transforms to arbitrary functions of (x,y), +including a variety of common FITS WCS specifications.

  • +
  • Can include a range of simple detector effects such as nonlinearity, +brighter-fatter effect, etc.

  • +
  • Has a module that is particularly meant to simulate images for the Roman +Space Telescope.

  • +
+
+

Planned future development

+

We plan to add the following additional capabilities in future versions of +GalSim:

+
    +
  • Simulating more sophisticated detector defects and image artifacts. E.g. +vignetting, fringing, cosmic rays, saturation, bleeding, … (cf. Issues +#553, #828)

  • +
  • Proper modeling of extinction due to dust. (cf. Issues #541, #550)

  • +
  • More kinds of realistic galaxies. (cf. Issues #669, #795, #808)

  • +
  • Various speed improvements. (cf. Issues #205, #566, #875)

  • +
+

There are many others as well. Please see

+

https://github.com/GalSim-developers/GalSim/issues

+

for a list of the current open issues. And feel free to add an issue if there +is something useful that you think should be possible, but is not currently +implemented.

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/phase_psf.html b/docs/_build/html/phase_psf.html new file mode 100644 index 00000000000..ac09aaa9c86 --- /dev/null +++ b/docs/_build/html/phase_psf.html @@ -0,0 +1,1582 @@ + + + + + + + Phase-screen PSFs — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Phase-screen PSFs

+

We have available a more complicated kind of PSF model that tries to correctly model the +wavefront as it passed through various “screens” such as the atmosphere or optics. +It has a number of ancillary helper functions and classes associated with it to +define things like the aperture and the effect of the various screens.

+

For PSFs drawn using real-space or Fourier methods, these utilities essentially evaluate the +Fourier optics diffraction equation:

+
+\[PSF(x,y) = \int \left| {\cal F}\left(A(u, v) e^{i \phi(u, v, x, y, t)} \right) \right|^2 dt\]
+

where \(x\), \(y\) are focal plane coordinates and \(u\), \(v\) are +pupil plane coordinates, \(A\) is the aperture, \(\phi\) is the phase of the +incident wavefront, and \(\cal F\) is the Fourier transform operator.

+

For method='phot', two possible strategies are available.

+
    +
  1. The first strategy is to draw the PSF using Fourier methods into an InterpolatedImage, +and then shoot photons from that profile. This strategy has good accuracy, but can be +computationally expensive, particularly for atmospheric PSFs that need to be built up in +small increments to simulate a finite exposure time.

  2. +
  3. The second strategy, which can be significantly faster, especially for atmospheric PSFs, +is to use the geometric optics approximation. This approximation has good accuracy for +atmospheric PSFs, so we make it the default for PhaseScreenPSF. The accuracy is somewhat +less good for purely optical PSFs though, so the default behavior for OpticalPSF is to use +the first strategy. The geometric_shooting keyword can be used in both cases to +override the default.

  4. +
+

The main classes of note are:

+
+
Aperture

Class representing the illuminated region of pupil.

+
+
AtmosphericScreen

Class implementing phase(u, v, x, y, t) for von Karman type turbulence, with possibly evolving +“non-frozen-flow” phases.

+
+
OpticalScreen

Class implementing optical aberrations using Zernike polynomial expansions in the wavefront.

+
+
UserScreen

Class implementing a user-defined phase screen.

+
+
PhaseScreenList

Python sequence type to hold multiple phase screens, for instance to simulate turbulence at +different altitudes, or self-consistently model atmospheric and optical phase aberrations. +A key method is PhaseScreenList.makePSF, which will take the list of phase screens, add +them together linearly (Fraunhofer approximation), and evaluate the above diffraction equation +to yield a PhaseScreenPSF object.

+
+
PhaseScreenPSF

A GSObject holding the evaluated PSF from a set of phase screens.

+
+
OpticalPSF

A GSObject for optical PSFs with potentially complicated pupils and Zernike aberrations.

+
+

Note

+

OpticalPSF is technically a kind of PhaseScreenPSF, but if you only want the +optical model, you generally don’t need to bother with building any of the screens +manually. The OpticalPSF class constructor will handle this for you.

+
+
+
SecondKick

A GSObject describing the high-k turbulence portion of an atmospheric PSF convolved by +an Airy PSF. When using photon shooting with a PhaseScreenPSF, small scale (high-k) +features are not well approximated by the geometric optics approximation, since this is +where Fourier effects such as diffraction become important. The SecondKick class can +compensate by simulating the appropriate behavior at high k values.

+
+
Atmosphere

Convenience function to quickly assemble multiple AtmosphericScreen instances into a +PhaseScreenList.

+
+
+
+
+class galsim.Aperture(diam, lam=None, circular_pupil=True, obscuration=0.0, nstruts=0, strut_thick=0.05, strut_angle=coord.Angle(0.0, coord.radians), oversampling=1.0, pad_factor=1.0, screen_list=None, pupil_plane_im=None, pupil_angle=coord.Angle(0.0, coord.radians), pupil_plane_scale=None, pupil_plane_size=None, gsparams=None)[source]
+

Class representing a telescope aperture embedded in a larger pupil plane array – for use +with the PhaseScreenPSF class to create PSFs via Fourier or geometric optics.

+

The pupil plane array is completely specified by its size, sampling interval, and pattern of +illuminated pixels. Pupil plane arrays can be specified either geometrically or using an image +to indicate the illuminated pixels. In both cases, various options exist to control the pupil +plane size and sampling interval.

+

Geometric pupil specification:

+

The first way to specify the details of the telescope aperture is through a series of keywords +indicating the diameter, size of the central obscuration, and the nature of the struts +holding up the secondary mirror (or prime focus cage, etc.). The struts are assumed to be +rectangular obscurations extending from the outer edge of the pupil to the outer edge of the +obscuration disk (or to the pupil center if obscuration = 0.). You can specify how many +struts there are (evenly spaced in angle), how thick they are as a fraction of the pupil +diameter, and what angle they start at relative to the positive y direction.

+

The size (in meters) and sampling interval (in meters) of the pupil plane array representing the +aperture can be set directly using the the pupil_plane_size and pupil_plane_scale +keywords. However, in most situations, it’s probably more convenient to let GalSim set these +automatically based on the pupil geometry and the nature of the (potentially time-varying) +phase aberrations from which a PSF is being derived.

+

The pupil plane array physical size is by default set to twice the pupil diameter producing a +Nyquist sampled PSF image. While this would always be sufficient if using sinc interpolation +over the PSF image for subsequent operations, GalSim by default uses the much faster (though +approximate) quintic interpolant, which means that in some cases – in particular, for +significantly aberrated optical PSFs without atmospheric aberrations – it may be useful to +further increase the size of the pupil plane array, thereby increasing the sampling rate of the +resulting PSF image. This can be done by increasing the oversampling keyword.

+

A caveat to the above occurs when using geometric_shooting=True to draw using +photon-shooting. In this case, we only need an array just large enough to avoid clipping the +pupil, which we can get by setting oversampling=0.5.

+

The pupil plane array physical sampling interval (which is directly related to the resulting PSF +image physical size) is set by default to the same interval as would be used to avoid +significant aliasing (image folding) for an obscured Airy profile with matching diameter and +obscuration and for the value of folding_threshold in the optionally specified gsparams +argument. If the phase aberrations are significant, however, the PSF image size computed this +way may still not be sufficiently large to avoid aliasing. To further increase the pupil plane +sampling rate (and hence the PSF image size), you can increase the value of the pad_factor +keyword.

+

An additional way to set the pupil sampling interval for a particular set of phase screens +(i.e., for a particular PhaseScreenList) is to provide the screens in the screen_list +argument. Each screen in the list computes its own preferred sampling rate and the +PhaseScreenList appropriately aggregates these. This last option also requires that a +wavelength lam be specified, and is particularly helpful for creating PSFs derived from +turbulent atmospheric screens.

+

Finally, when specifying the pupil geometrically, Aperture may choose to make a small adjustment +to pupil_plane_scale in order to produce an array with a good size for FFTs. If your +application depends on knowing the size and scale used with the Fourier optics framework, you +can obtain these from the aper.pupil_plane_size and aper.pupil_plane_scale attributes.

+

Pupil image specification:

+

The second way to specify the pupil plane configuration is by passing in an image of it. This +can be useful, for example, if the struts are not evenly spaced or are not radially directed, as +is assumed by the simple model for struts described above. In this case, an exception is raised +if keywords related to struts are also given. On the other hand, the obscuration keyword is +still used to ensure that the PSF images are not aliased, though it is ignored during the actual +construction of the pupil plane illumination pattern. Note that for complicated pupil +configurations, it may be desireable to increase pad_factor for more fidelity at the expense +of slower running time. Finally, the pupil_plane_im that is passed in can be rotated during +internal calculations by specifying a pupil_angle keyword.

+

If you choose to pass in a pupil plane image, it must be a square array in which the image of +the pupil is centered. The areas that are illuminated should have some value >0, and the other +areas should have a value of precisely zero. Based on what the Aperture class determines is a +good PSF sampling interval, the image of the pupil plane that is passed in might be zero-padded +during internal calculations. (The pupil plane array size and scale values can be accessed via +the aper.pupil_plane_size and aper.pupil_plane_scale attributes.) The pixel scale of +the pupil plane can be specified in one of three ways. In descending order of priority, these +are:

+
+
    +
  1. The pupil_plane_scale keyword argument (units are meters).

  2. +
  3. The pupil_plane_im.scale attribute (units are meters).

  4. +
  5. If (1) and (2) are both None, then the scale will be inferred by assuming that the +illuminated pixel farthest from the image center is at a physical distance of self.diam/2.

  6. +
+
+

The pupil_plane_size and lam keywords are both ignored when constructing an Aperture +from an image.

+
+
Parameters:
+
    +
  • diam – Aperture diameter in meters.

  • +
  • lam – Wavelength in nanometers. [default: None]

  • +
  • circular_pupil – Adopt a circular pupil? [default: True]

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture +linear dimension. [0., 1.). [default: 0.0]

  • +
  • nstruts – Number of radial support struts to add to the central obscuration. +[default: 0]

  • +
  • strut_thick – Thickness of support struts as a fraction of aperture diameter. +[default: 0.05]

  • +
  • strut_angleAngle made between the vertical and the strut starting closest to it, +defined to be positive in the counter-clockwise direction; must be an +Angle instance. [default: 0. * galsim.degrees]

  • +
  • oversampling – Optional oversampling factor in the image plane for the PSF +eventually constructed using this Aperture. Setting +oversampling < 1 will produce aliasing in the PSF (not good). +[default: 1.0]

  • +
  • pad_factor – Additional multiple by which to extend the PSF image to avoid +folding. [default: 1.0]

  • +
  • screen_list – An optional PhaseScreenList object. If present, then get a good +pupil sampling interval using this object. [default: None]

  • +
  • pupil_plane_im – The GalSim.Image, NumPy array, or name of file containing the pupil +plane image, to be used instead of generating one based on the +obscuration and strut parameters. [default: None]

  • +
  • pupil_angle – If pupil_plane_im is not None, rotation angle for the pupil plane +(positive in the counter-clockwise direction). Must be an Angle +instance. [default: 0. * galsim.degrees]

  • +
  • pupil_plane_scale – Sampling interval in meters to use for the pupil plane array. In +most cases, it’s a good idea to leave this as None, in which case +GalSim will attempt to find a good value automatically. The +exception is when specifying the pupil arrangement via an image, in +which case this keyword can be used to indicate the sampling of that +image. See also pad_factor for adjusting the pupil sampling scale. +[default: None]

  • +
  • pupil_plane_size – Size in meters to use for the pupil plane array. In most cases, it’s +a good idea to leave this as None, in which case GalSim will attempt +to find a good value automatically. See also oversampling for +adjusting the pupil size. [default: None]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property diam
+

Aperture diameter in meters

+
+ +
+
+property gsparams
+

The GSParams of this object.

+
+ +
+
+property illuminated
+

A boolean array indicating which positions in the pupil plane are exposed to the sky.

+
+ +
+
+property npix
+

The number of pixels in each direction of the pupil-plane image.

+
+ +
+
+property obscuration
+

Fraction linear obscuration of pupil.

+
+ +
+
+property pupil_plane_scale
+

The scale_size of the pupil-plane image.

+
+ +
+
+property pupil_plane_size
+

The size of the pupil-plane image.

+
+ +
+
+samplePupil(photons, rng)[source]
+

Set the pupil_u and pupil_v values in the PhotonArray by sampling the current aperture.

+
+ +
+
+property u
+

Pupil horizontal coordinate array in meters.

+
+ +
+
+property v
+

Pupil vertical coordinate array in meters.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current aperture with the given gsparams

+
+ +
+ +
+
+class galsim.AtmosphericScreen(screen_size, screen_scale=None, altitude=0.0, r0_500=0.2, L0=25.0, vx=0.0, vy=0.0, alpha=1.0, time_step=None, rng=None, suppress_warning=False, mp_context=None)[source]
+

An atmospheric phase screen that can drift in the wind and evolves (“boils”) over time. The +initial phases and fractional phase updates are drawn from a von Karman power spectrum, which is +defined by a Fried parameter that effectively sets the amplitude of the turbulence, and an outer +scale beyond which the turbulence power flattens.

+

AtmosphericScreen delays the actual instantiation of the phase screen array in memory until it +is used for either drawing a PSF or querying the wavefront or wavefront gradient. This is to +facilitate automatic truncation of the screen power spectrum depending on the use case. For +example, when drawing a PhaseScreenPSF using Fourier methods, the entire power spectrum should +generally be used. On the other hand, when drawing using photon-shooting and the geometric +approximation, it’s better to truncate the high-k modes of the power spectrum here so +that they can be handled instead by a SecondKick object (which also happens automatically; see +the PhaseScreenPSF docstring). (See Peterson et al. 2015 for more details about the second +kick). Querying the wavefront or wavefront gradient will instantiate the screen using the full +power spectrum.

+

This class will normally attempt to sanity check that the screen has been appropriately +instantiated depending on the use case, i.e., depending on whether it’s being used to draw with +Fourier optics or geometric optics. If you want to turn this warning off, however, you can +use the suppress_warning keyword argument.

+

If you wish to override the automatic truncation determination, then you can directly +instantiate the phase screen array using the AtmosphericScreen.instantiate method.

+

Note that once a screen has been instantiated with a particular set of truncation parameters, it +cannot be re-instantiated with another set of parameters.

+

Shared memory:

+

Instantiated AtmosphericScreen objects can consume a significant amount of memory. For example, +an atmosphere with 6 screens, each extending 819.2 m and with resolution of 10 cm will consume +3 GB of memory. In contexts where both a realistic atmospheric PSF and high throughput via +multiprocessing are required, allocating this 3 GB of memory once in a shared memory space +accessible to each subprocess (as opposed to once per subprocess) is highly desireable. We +provide a few functions here to enable such usage:

+
+
    +
  • The mp_context keyword argument to AtmosphericScreen. +This is used to indicate which multiprocessing process launching context will be used. +This is important for setting up the shared memory correctly.

  • +
  • The initWorker and initWorkerArgs functions. +These should be used in a call to multiprocessing.Pool to correctly inform the worker +process where to find AtmosphericScreen shared memory.

  • +
+
+

A template example might look something like:

+
import galsim
+import multiprocessing as mp
+
+def work(i, atm):
+    args, moreArgs = fn(i)
+    psf = atm.makePSF(*args)
+    return psf.drawImage(*moreArgs)
+
+ctx = mp.get_context("spawn")  # "spawn" is generally the safest context available
+
+atm = galsim.Atmosphere(..., mp_context=ctx)  # internally calls AtmosphericScreen ctor
+nProc = 4  # Note, can set this to None to get a good default
+
+# Note: Especially if you are using "fork" context, then you want to make sure to run
+#       your Pool in a single_threaded context.  Even if not, it's probably a good idea
+#       so each process isn't spawning lots of OpenMP (or other) threads.
+with galsim.utilities.single_threaded():
+    with ctx.Pool(
+        nProc,
+        initializer=galsim.phase_screens.initWorker,
+        initargs=galsim.phase_screens.initWorkerArgs()
+    ) as pool:
+        results = []
+        # First submit
+        for i in range(10):
+            results.append(pool.apply_async(work, (i, atm)))
+        # Then wait to finish
+        for r in results:
+            r.wait()
+# Turn future objects into actual returned images.
+results = [r.get() for r in results]
+
+
+

It is also possible to manually instantiate each of the AtmosphericScreen objects in a +PhaseScreenList in parallel using a process pool. This requires knowing what k-scale to +truncate the screen at:

+
atm = galsim.Atmosphere(..., mp_context=ctx)
+with galsim.utilities.single_threaded():
+    with ctx.Pool(
+        nProc,
+        initializer=galsim.phase_screens.initWorker,
+        initargs=galsim.phase_screens.initWorkerArgs()
+    ) as pool:
+        dummyPSF = atm.makePSF(...)
+        kmax = dummyPSF.screen_kmax
+        atm.instantiate(pool=pool, kmax=kmax)
+
+
+

Finally, the above multiprocessing shared memory tricks are only currently supported for +non-time-evolving screens (alpha=1).

+

Pickling:

+

The shared memory portion of an atmospheric screen is not included by default in the pickle of +an AtmosphericScreen instance. This means that while it is possible to pickle/unpickle an +AtmosphericScreen as normal within a single launch of a potentially-multiprocess program, (as +long as the shared memory is persistent), a different path is needed to say, create an +AtmosphericScreen pickle, quit python, restart python, and unpickle the screen. The same +holds for any object that wraps an AtmosphericScreen as an attribute as well, which could +include, e.g., PhaseScreenList or PhaseScreenPSF.

+

To get around this limitation, the context manager galsim.utilities.pickle_shared can be +used. For example:

+
screen = galsim.AtmosphericScreen(...)
+with galsim.utilities.pickle_shared():
+    pickle.dump(screen, open('myScreen.pkl', 'wb'))
+
+
+

will pickle both the screen object and any required shared memory used in its definition. +Unpickling then proceeds exactly as normal:

+
screen = pickle.load(open('myScreen.pkl', 'rb'))
+
+
+
+
Parameters:
+
    +
  • screen_size – Physical extent of square phase screen in meters. This should be large +enough to accommodate the desired field-of-view of the telescope as +well as the meta-pupil defined by the wind speed and exposure time. +Note that the screen will have periodic boundary conditions, so while +the code will still run with a small screen, this may introduce +artifacts into PSFs or PSF correlation functions. Also note that +screen_size may be tweaked by the initializer to ensure screen_size +is a multiple of screen_scale.

  • +
  • screen_scale – Physical pixel scale of phase screen in meters. An order unity multiple +of the Fried parameter is usually sufficiently small, but users should +test the effects of varying this parameter to ensure robust results. +[default: r0_500]

  • +
  • altitude – Altitude of phase screen in km. This is with respect to the telescope, +not sea-level. [default: 0.0]

  • +
  • r0_500 – Fried parameter setting the amplitude of turbulence; contributes to +“size” of the resulting atmospheric PSF. Specified at wavelength 500 +nm, in units of meters. [default: 0.2]

  • +
  • L0 – Outer scale in meters. The turbulence power spectrum will smoothly +approach a constant at scales larger than L0. Set to None or +np.inf for a power spectrum without an outer scale. [default: 25.0]

  • +
  • vx – x-component wind velocity in meters/second. [default: 0.]

  • +
  • vy – y-component wind velocity in meters/second. [default: 0.]

  • +
  • alpha – Square root of fraction of phase that is “remembered” between time_steps +(i.e., alpha**2 is the fraction remembered). The fraction +sqrt(1-alpha**2) is then the amount of turbulence freshly generated in +each step. Setting alpha=1.0 results in a frozen-flow atmosphere. +Note that computing PSFs from frozen-flow atmospheres may be +significantly faster than computing PSFs with non-frozen-flow +atmospheres. If alpha != 1.0, then it is required that a +time_step is also specified. [default: 1.0]

  • +
  • time_step – Time interval between phase boiling updates. Note that this is distinct +from the time interval used to integrate the PSF over time, which is set +by the time_step keyword argument to PhaseScreenPSF or +PhaseScreenList.makePSF. If time_step is not None, then +it is required that alpha is set to something other than 1.0. +[default: None]

  • +
  • rng – Random number generator as a BaseDeviate. If None, then use +the clock time or system entropy to seed a new generator. +[default: None]

  • +
  • suppress_warning – Turn off instantiation sanity checking. (See above) [default: False]

  • +
  • enable (mp_context GalSim uses shared memory for phase screen allocation to better) – multiprocessing. Use this keyword to set the launch context for +multiprocessing. Usually it will be sufficient to leave this at its +default. [default: None]

  • +
+
+
+

Relevant SPIE paper: +“Remembrance of phases past: An autoregressive method for generating realistic atmospheres in +simulations” +Srikar Srinath, Univ. of California, Santa Cruz; +Lisa A. Poyneer, Lawrence Livermore National Lab.; +Alexander R. Rudy, UCSC; S. Mark Ammons, LLNL +Published in Proceedings Volume 9148: Adaptive Optics Systems IV +September 2014

+
+
+property altitude
+

The altitude of the screen in km.

+
+ +
+
+instantiate(kmin=0.0, kmax=inf, check=None)[source]
+
+
Parameters:
+
    +
  • kmin – Minimum k-mode to include when generating phase screens. Generally this will +only be used when testing the geometric approximation for atmospheric PSFs. +[default: 0]

  • +
  • kmax – Maximum k-mode to include when generating phase screens. This may be used in +conjunction with SecondKick to complete the geometric approximation for +atmospheric PSFs. [default: np.inf]

  • +
  • check – Sanity check indicator. If equal to ‘FFT’, then check that phase screen +Fourier modes are not being truncated, which is appropriate for full Fourier +optics. If equal to ‘phot’, then check that phase screen Fourier modes are +being truncated, which is appropriate for the geometric optics approximation. +If None, then don’t perform a check. Also, don’t perform a check if +self.suppress_warning is True.

  • +
+
+
+
+ +
+
+property kmax
+

The maximum k value being used.

+
+ +
+
+property kmin
+

The minimum k value being used.

+
+ +
+
+wavefront(u, v, t=None, theta=(coord.Angle(0.0, coord.radians), coord.Angle(0.0, coord.radians)))[source]
+

Compute wavefront due to atmospheric phase screen.

+

Wavefront here indicates the distance by which the physical wavefront lags or leads the +ideal plane wave.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Times (in seconds) at which to evaluate wavefront. Can be None, a scalar or +an iterable. If None, then the internal time of the phase screens will be +used for all u, v. If scalar, then the size will be broadcast up to match +that of u and v. If iterable, then the shape must match the shapes of u and +v. [default: None]

  • +
  • theta – Field angle at which to evaluate wavefront, as a 2-tuple of galsim.Angle +instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] Only a single +theta is permitted.

  • +
+
+
Returns:
+

Array of wavefront lag or lead in nanometers.

+
+
+
+ +
+
+wavefront_gradient(u, v, t=None, theta=(coord.Angle(0.0, coord.radians), coord.Angle(0.0, coord.radians)))[source]
+

Compute gradient of wavefront due to atmospheric phase screen.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Times (in seconds) at which to evaluate wavefront gradient. Can be None, a +scalar or an iterable. If None, then the internal time of the phase screens +will be used for all u, v. If scalar, then the size will be broadcast up to +match that of u and v. If iterable, then the shape must match the shapes of +u and v. [default: None]

  • +
  • theta – Field angle at which to evaluate wavefront, as a 2-tuple of galsim.Angle +instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] Only a single +theta is permitted.

  • +
+
+
Returns:
+

Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m.

+
+
+
+ +
+ +
+
+class galsim.OpticalScreen(diam, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, coma1=0.0, coma2=0.0, trefoil1=0.0, trefoil2=0.0, spher=0.0, aberrations=None, annular_zernike=False, obscuration=0.0, lam_0=500.0)[source]
+

Class to describe optical aberrations in terms of Zernike polynomial coefficients.

+

Input aberration coefficients are assumed to be supplied in units of wavelength, and correspond +to the Zernike polynomials in the Noll convention defined in +Noll, J. Opt. Soc. Am. 66, 207-211(1976). For a brief summary of the polynomials, refer to +http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials.

+
+
Parameters:
+
    +
  • diam – Diameter of pupil in meters.

  • +
  • tip – Tip aberration in units of reference wavelength. [default: 0]

  • +
  • tilt – Tilt aberration in units of reference wavelength. [default: 0]

  • +
  • defocus – Defocus in units of reference wavelength. [default: 0]

  • +
  • astig1 – Astigmatism (like e2) in units of reference wavelength. +[default: 0]

  • +
  • astig2 – Astigmatism (like e1) in units of reference wavelength. +[default: 0]

  • +
  • coma1 – Coma along y in units of reference wavelength. [default: 0]

  • +
  • coma2 – Coma along x in units of reference wavelength. [default: 0]

  • +
  • trefoil1 – Trefoil (one of the arrows along y) in units of reference wavelength. +[default: 0]

  • +
  • trefoil2 – Trefoil (one of the arrows along x) in units of reference wavelength. +[default: 0]

  • +
  • spher – Spherical aberration in units of reference wavelength. +[default: 0]

  • +
  • aberrations – Optional keyword, to pass in a list, tuple, or NumPy array of +aberrations in units of reference wavelength (ordered according to +the Noll convention), rather than passing in individual values for each +individual aberration. Note that aberrations[1] is piston (and not +aberrations[0], which is unused.) This list can be arbitrarily long to +handle Zernike polynomial aberrations of arbitrary order.

  • +
  • annular_zernike – Boolean indicating that aberrations specify the amplitudes of annular +Zernike polynomials instead of circular Zernike polynomials. +[default: False]

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture linear +dimension. [0., 1.). Note it is the user’s responsibility to ensure +consistency of OpticalScreen obscuration and Aperture obscuration. +[default: 0.0]

  • +
  • lam_0 – Reference wavelength in nanometers at which Zernike aberrations are +being specified. [default: 500]

  • +
+
+
+
+
+wavefront(u, v, t=None, theta=None)[source]
+

Compute wavefront due to optical phase screen.

+

Wavefront here indicates the distance by which the physical wavefront lags or leads the +ideal plane wave.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Ignored for OpticalScreen.

  • +
  • theta – Ignored for OpticalScreen.

  • +
+
+
Returns:
+

Array of wavefront lag or lead in nanometers.

+
+
+
+ +
+
+wavefront_gradient(u, v, t=None, theta=None)[source]
+

Compute gradient of wavefront due to optical phase screen.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Ignored for OpticalScreen.

  • +
  • theta – Ignored for OpticalScreen.

  • +
+
+
Returns:
+

Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m.

+
+
+
+ +
+ +
+
+class galsim.UserScreen(table, diam=None, obscuration=0.0)[source]
+

Create a (static) user-defined phase screen.

+
+
Parameters:
+
    +
  • table – LookupTable2D instance representing the wavefront as a function on the +entrance pupil. Units are (meters, meters) -> nanometers.

  • +
  • diam – Diameter of entrance pupil in meters. If None, then use the length of the +larger side of the LookupTable2D rectangle in table. This keyword is only +used to compute a value for stepk, and thus has no effect on the +wavefront() or wavefront_gradient() methods.

  • +
  • obscuration – Optional fractional circular obscuration of pupil. Like diam, only used +for computing a value for stepk.

  • +
+
+
+
+
+wavefront(u, v, t=None, theta=None)[source]
+

Evaluate wavefront from lookup table.

+

Wavefront here indicates the distance by which the physical wavefront lags or leads the +ideal plane wave.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Ignored for UserScreen.

  • +
  • theta – Ignored for UserScreen.

  • +
+
+
Returns:
+

Array of wavefront lag or lead in nanometers.

+
+
+
+ +
+
+wavefront_gradient(u, v, t=None, theta=None)[source]
+

Evaluate gradient of wavefront from lookup table.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Ignored for UserScreen.

  • +
  • theta – Ignored for UserScreen.

  • +
+
+
Returns:
+

Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m.

+
+
+
+ +
+ +
+
+class galsim.PhaseScreenList(*layers)[source]
+

List of phase screens that can be turned into a PSF. Screens can be either atmospheric +layers or optical phase screens. Generally, one would assemble a PhaseScreenList object using +the function Atmosphere. Layers can be added, removed, appended, etc. just like items can +be manipulated in a python list. For example:

+
# Create an atmosphere with three layers.
+>>> screens = galsim.PhaseScreenList([galsim.AtmosphericScreen(...),
+                                      galsim.AtmosphericScreen(...),
+                                      galsim.AtmosphericScreen(...)])
+# Add another layer
+>>> screens.append(galsim.AtmosphericScreen(...))
+# Remove the second layer
+>>> del screens[1]
+# Switch the first and second layer.  Silly, but works...
+>>> screens[0], screens[1] = screens[1], screens[0]
+
+
+
+
Parameters:
+

layers – Sequence of phase screens.

+
+
+
+
+instantiate(pool=None, _bar=None, **kwargs)[source]
+

Instantiate the screens in this PhaseScreenList.

+
+
Parameters:
+
    +
  • pool – A multiprocessing.Pool object to use to instantiate screens in parallel.

  • +
  • **kwargs – Keyword arguments to forward to screen.instantiate().

  • +
+
+
+
+ +
+
+makePSF(lam, **kwargs)[source]
+

Create a PSF from the current PhaseScreenList.

+
+
Parameters:
+
    +
  • lam – Wavelength in nanometers at which to compute PSF.

  • +
  • t0 – Time at which to start exposure in seconds. [default: 0.0]

  • +
  • exptime – Time in seconds over which to accumulate evolving instantaneous +PSF. [default: 0.0]

  • +
  • time_step – Time interval in seconds with which to sample phase screens when +drawing using real-space or Fourier methods, or when using +photon-shooting without the geometric optics approximation. Note +that the default value of 0.025 is fairly arbitrary. For careful +studies, we recommend checking that results are stable when +decreasing time_step. Also note that when drawing using +photon-shooting with the geometric optics approximation this +keyword is ignored, as the phase screen can be sampled +continuously in this case instead of at discrete intervals. +[default: 0.025]

  • +
  • flux – Flux of output PSF. [default: 1.0]

  • +
  • theta – Field angle of PSF as a 2-tuple of Angle instances. +[default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)]

  • +
  • interpolant – Either an Interpolant instance or a string indicating which +interpolant should be used. Options are ‘nearest’, ‘sinc’, +‘linear’, ‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the +integer order to use. [default: galsim.Quintic()]

  • +
  • scale_unit – Units to use for the sky coordinates of the output profile. +[default: galsim.arcsec]

  • +
  • ii_pad_factor – Zero-padding factor by which to extend the image of the PSF when +creating the InterpolatedImage. See the +InterpolatedImage docstring for more details. [default: 1.5]

  • +
  • suppress_warning – If pad_factor is too small, the code will emit a warning +telling you its best guess about how high you might want to raise +it. However, you can suppress this warning by using +suppress_warning=True. [default: False]

  • +
  • geometric_shooting – If True, then when drawing using photon shooting, use geometric +optics approximation where the photon angles are derived from the +phase screen gradient. If False, then first draw using Fourier +optics and then shoot from the derived InterpolatedImage. +[default: True]

  • +
  • aperAperture to use to compute PSF(s). [default: None]

  • +
  • second_kick – An optional second kick to also convolve by when using geometric +photon-shooting. (This can technically be any GSObject, though +usually it should probably be a SecondKick object). If None, then a +good second kick will be chosen automatically based on +screen_list. If False, then a second kick won’t be applied. +[default: None]

  • +
  • kcrit – Critical Fourier scale (in units of 1/r0) at which to separate low-k +and high-k turbulence. The default value was chosen based on +comparisons between Fourier optics and geometric optics with a +second kick correction. While most values of kcrit smaller than the +default produce similar results, we caution the user to compare the +affected geometric PSFs against Fourier optics PSFs carefully before +changing this value. [default: 0.2]

  • +
  • fft_sign – The sign (+/-) to use in the exponent of the Fourier kernel when +evaluating the Fourier optics PSF. As of version 2.3, GalSim uses a +plus sign by default, which we believe to be consistent with, for +example, how Zemax computes a Fourier optics PSF on DECam. Before +version 2.3, the default was a negative sign. Input should be +either the string ‘+’ or the string ‘-’. [default: ‘+’]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+

The following are optional keywords to use to setup the aperture if aper is not +provided.

+
+
Parameters:
+
    +
  • diam – Aperture diameter in meters.

  • +
  • circular_pupil – Adopt a circular pupil? [default: True]

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture +linear dimension. [0., 1.). [default: 0.0]

  • +
  • nstruts – Number of radial support struts to add to the central +obscuration. [default: 0]

  • +
  • strut_thick – Thickness of support struts as a fraction of aperture diameter. +[default: 0.05]

  • +
  • strut_angleAngle made between the vertical and the strut starting closest to +it, defined to be positive in the counter-clockwise direction; +must be an Angle instance. [default: 0. * galsim.degrees]

  • +
  • oversampling – Optional oversampling factor in the image plane for the PSF +eventually constructed using this Aperture. Setting +oversampling < 1 will produce aliasing in the PSF (not good). +[default: 1.0]

  • +
  • pad_factor – Additional multiple by which to extend the PSF image to avoid +folding. [default: 1.0]

  • +
  • pupil_plane_im – The GalSim.Image, NumPy array, or name of file containing the +pupil plane image, to be used instead of generating one based on +the obscuration and strut parameters. [default: None]

  • +
  • pupil_angle – If pupil_plane_im is not None, rotation angle for the pupil +plane (positive in the counter-clockwise direction). Must be an +Angle instance. [default: 0. * galsim.degrees]

  • +
  • pupil_plane_scale – Sampling interval in meters to use for the pupil plane array. In +most cases, it’s a good idea to leave this as None, in which case +GalSim will attempt to find a good value automatically. The +exception is when specifying the pupil arrangement via an image, +in which case this keyword can be used to indicate the sampling +of that image. See also pad_factor for adjusting the pupil +sampling scale. [default: None]

  • +
  • pupil_plane_size – Size in meters to use for the pupil plane array. In most cases, +it’s a good idea to leave this as None, in which case GalSim will +attempt to find a good value automatically. See also +oversampling for adjusting the pupil size. [default: None]

  • +
+
+
+
+ +
+
+wavefront(u, v, t, theta=(coord.Angle(0.0, coord.radians), coord.Angle(0.0, coord.radians)))[source]
+

Compute cumulative wavefront due to all phase screens in PhaseScreenList.

+

Wavefront here indicates the distance by which the physical wavefront lags or leads the +ideal plane wave (pre-optics) or spherical wave (post-optics).

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Times (in seconds) at which to evaluate wavefront. Can be None, a scalar or an +iterable. If None, then the internal time of the phase screens will be used +for all u, v. If scalar, then the size will be broadcast up to match that of +u and v. If iterable, then the shape must match the shapes of u and v.

  • +
  • theta – Field angle at which to evaluate wavefront, as a 2-tuple of galsim.Angle +instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] +Only a single theta is permitted.

  • +
+
+
Returns:
+

Array of wavefront lag or lead in nanometers.

+
+
+
+ +
+
+wavefront_gradient(u, v, t, theta=(coord.Angle(0.0, coord.radians), coord.Angle(0.0, coord.radians)))[source]
+

Compute cumulative wavefront gradient due to all phase screens in PhaseScreenList.

+
+
Parameters:
+
    +
  • u – Horizontal pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • v – Vertical pupil coordinate (in meters) at which to evaluate wavefront. Can +be a scalar or an iterable. The shapes of u and v must match.

  • +
  • t – Times (in seconds) at which to evaluate wavefront gradient. Can be None, a +scalar or an iterable. If None, then the internal time of the phase screens +will be used for all u, v. If scalar, then the size will be broadcast up to +match that of u and v. If iterable, then the shape must match the shapes of +u and v.

  • +
  • theta – Field angle at which to evaluate wavefront, as a 2-tuple of galsim.Angle +instances. [default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)] +Only a single theta is permitted.

  • +
+
+
Returns:
+

Arrays dWdu and dWdv of wavefront lag or lead gradient in nm/m.

+
+
+
+ +
+ +
+
+class galsim.PhaseScreenPSF(screen_list, lam, t0=0.0, exptime=0.0, time_step=0.025, flux=1.0, theta=(coord.Angle(0.0, coord.radians), coord.Angle(0.0, coord.radians)), interpolant=None, scale_unit=coord.arcsec, ii_pad_factor=None, suppress_warning=False, geometric_shooting=True, aper=None, second_kick=None, kcrit=0.2, fft_sign='+', gsparams=None, _force_stepk=0.0, _force_maxk=0.0, _bar=None, **kwargs)[source]
+

Bases: GSObject

+

A PSF surface brightness profile constructed by integrating over time the instantaneous PSF +derived from a set of phase screens and an aperture.

+

There are two equivalent ways to construct a PhaseScreenPSF given a PhaseScreenList:

+
>>> psf = screen_list.makePSF(...)
+>>> psf = PhaseScreenPSF(screen_list, ...)
+
+
+

Computing a PSF from a phase screen also requires an Aperture be specified. This can be done +either directly via the aper keyword, or by setting a number of keywords that will be passed +to the Aperture constructor. The aper keyword always takes precedence.

+

There are effectively three ways to draw a PhaseScreenPSF (or GSObject that includes a +PhaseScreenPSF):

+
    +
  1. Fourier optics

    +
    +

    This is the default, and is performed for all drawImage methods except method=’phot’. This +is generally the most accurate option. For a PhaseScreenList that includes an +AtmosphericScreen, however, this can be prohibitively slow. For OpticalPSF, though, +this can sometimes be a good option.

    +
    +
  2. +
  3. Photon-shooting from an image produced using Fourier optics.

    +
    +

    This is done if geometric_shooting=False when creating the PhaseScreenPSF, and method=’phot’ +when calling drawImage. This actually performs the same calculations as the Fourier optics +option above, but then proceeds by shooting photons from that result. This can sometimes be +a good option for OpticalPSFs, especially if the same OpticalPSF can be reused for may +objects, since the Fourier part of the process would only be performed once in this case.

    +
    +
  4. +
  5. Photon-shooting using the “geometric approximation”.

    +
    +

    This is done if geometric_shooting=True when creating the PhaseScreenPSF, and method=’phot’ +when calling drawImage. In this case, a completely different algorithm is used make an +image. Photons are uniformly generated in the Aperture pupil, and then the phase gradient +at that location is used to deflect each photon in the image plane. This method, which +corresponds to geometric optics, is broadly accurate for phase screens that vary slowly +across the aperture, and is usually several orders of magnitude or more faster than Fourier +optics (depending on the flux of the object, of course, but much faster even for rather +bright flux levels).

    +

    One short-coming of this method is that it neglects interference effects, i.e. diffraction. +For PhaseScreenList that include at least one AtmosphericScreen, a correction, dubbed +the “second kick”, will automatically be applied to handle both the quickly varying modes +of the screens and the diffraction pattern of the Aperture. For PhaseScreenLists without +an AtmosphericScreen, the correction is simply an Airy function. Note that this +correction can be overridden using the second_kick keyword argument, and also tuned to some +extent using the kcrit keyword argument.

    +
    +
  6. +
+

Note also that calling drawImage on a PhaseScreenPSF that uses a PhaseScreenList with any +uninstantiated AtmosphericScreen will perform that instantiation, and that the details of the +instantiation depend on the drawing method used, and also the kcrit keyword argument to +PhaseScreenPSF. See the AtmosphericScreen docstring for more details.

+
+
Parameters:
+
    +
  • screen_listPhaseScreenList object from which to create PSF.

  • +
  • lam – Wavelength in nanometers at which to compute PSF.

  • +
  • t0 – Time at which to start exposure in seconds. [default: 0.0]

  • +
  • exptime – Time in seconds over which to accumulate evolving instantaneous PSF. +[default: 0.0]

  • +
  • time_step – Time interval in seconds with which to sample phase screens when +drawing using real-space or Fourier methods, or when using +photon-shooting without the geometric optics approximation. Note +that the default value of 0.025 is fairly arbitrary. For careful +studies, we recommend checking that results are stable when +decreasing time_step. Also note that when drawing using +photon-shooting with the geometric optics approximation this +keyword is ignored, as the phase screen can be sampled +continuously in this case instead of at discrete intervals. +[default: 0.025]

  • +
  • flux – Flux of output PSF [default: 1.0]

  • +
  • theta – Field angle of PSF as a 2-tuple of Angle instances. +[default: (0.0*galsim.arcmin, 0.0*galsim.arcmin)]

  • +
  • interpolant – Either an Interpolant instance or a string indicating which +interpolant should be used. Options are ‘nearest’, ‘sinc’, ‘linear’, +‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the integer order +to use. [default: galsim.Quintic()]

  • +
  • scale_unit – Units to use for the sky coordinates of the output profile. +[default: galsim.arcsec]

  • +
  • ii_pad_factor – Zero-padding factor by which to extend the image of the PSF when +creating the InterpolatedImage. See the InterpolatedImage +docstring for more details. [default: 1.5]

  • +
  • suppress_warning – If pad_factor is too small, the code will emit a warning telling +you its best guess about how high you might want to raise it. +However, you can suppress this warning by using +suppress_warning=True. [default: False]

  • +
  • geometric_shooting – If True, then when drawing using photon shooting, use geometric +optics approximation where the photon angles are derived from the +phase screen gradient. If False, then first draw using Fourier +optics and then shoot from the derived InterpolatedImage. +[default: True]

  • +
  • aperAperture to use to compute PSF(s). [default: None]

  • +
  • second_kick – An optional second kick to also convolve by when using geometric +photon-shooting. (This can technically be any GSObject, though +usually it should probably be a SecondKick object). If None, then a +good second kick will be chosen automatically based on +screen_list. If False, then a second kick won’t be applied. +[default: None]

  • +
  • kcrit – Critical Fourier scale (in units of 1/r0) at which to separate low-k +and high-k turbulence. The default value was chosen based on +comparisons between Fourier optics and geometric optics with a second +kick correction. While most values of kcrit smaller than the default +produce similar results, we caution the user to compare the affected +geometric PSFs against Fourier optics PSFs carefully before changing +this value. [default: 0.2]

  • +
  • fft_sign – The sign (+/-) to use in the exponent of the Fourier kernel when +evaluating the Fourier optics PSF. As of version 2.3, GalSim uses a +plus sign by default, which we believe to be consistent with, for +example, how Zemax computes a Fourier optics PSF on DECam. Before +version 2.3, the default was a negative sign. Input should be either +the string ‘+’ or the string ‘-’. [default: ‘+’]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+

The following are optional keywords to use to setup the aperture if aper is not provided:

+
+
Parameters:
+
    +
  • diam – Aperture diameter in meters. [default: None]

  • +
  • circular_pupil – Adopt a circular pupil? [default: True]

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture +linear dimension. [0., 1.). [default: 0.0]

  • +
  • nstruts – Number of radial support struts to add to the central obscuration. +[default: 0]

  • +
  • strut_thick – Thickness of support struts as a fraction of aperture diameter. +[default: 0.05]

  • +
  • strut_angleAngle made between the vertical and the strut starting closest to it, +defined to be positive in the counter-clockwise direction; must be an +Angle instance. [default: 0. * galsim.degrees]

  • +
  • oversampling – Optional oversampling factor in the image plane for the PSF +eventually constructed using this Aperture. Setting +oversampling < 1 will produce aliasing in the PSF (not good). +[default: 1.0]

  • +
  • pad_factor – Additional multiple by which to extend the PSF image to avoid +folding. [default: 1.0]

  • +
  • pupil_plane_im – The GalSim.Image, NumPy array, or name of file containing the pupil +plane image, to be used instead of generating one based on the +obscuration and strut parameters. [default: None]

  • +
  • pupil_angle – If pupil_plane_im is not None, rotation angle for the pupil plane +(positive in the counter-clockwise direction). Must be an Angle +instance. [default: 0. * galsim.degrees]

  • +
  • pupil_plane_scale – Sampling interval in meters to use for the pupil plane array. In +most cases, it’s a good idea to leave this as None, in which case +GalSim will attempt to find a good value automatically. The +exception is when specifying the pupil arrangement via an image, in +which case this keyword can be used to indicate the sampling of that +image. See also pad_factor for adjusting the pupil sampling +scale. [default: None]

  • +
  • pupil_plane_size – Size in meters to use for the pupil plane array. In most cases, it’s +a good idea to leave this as None, in which case GalSim will attempt +to find a good value automatically. See also oversampling for +adjusting the pupil size. [default: None]

  • +
+
+
+
+
+property fft_sign
+

The sign (+/-) to use in the exponent of the Fourier kernel when evaluating the Fourier +optics PSF.

+
+ +
+
+property flux
+

The flux of the profile.

+
+ +
+
+property kcrit
+

The critical Fourier scale being used for this object.

+
+ +
+
+property screen_list
+

The PhaseScreenList being used for this object.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+

You may either provide a GSParams instance:

+
>>> gsparams = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+>>> obj = obj.withGSParams(gsparams)
+
+
+

or one or more named parameters as keyword arguments:

+
>>> obj = obj.withGSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+
+
+
+

Note

+

The latter style will leave all non-named parameters at their current +values. It only updates the named parameters to the given values.

+
+
+ +
+ +
+
+class galsim.SecondKick(lam, r0, diam, obscuration=0, kcrit=0.2, flux=1, scale_unit=coord.arcsec, gsparams=None)[source]
+

Bases: GSObject

+

Class describing the expectation value of the high-k turbulence portion of an atmospheric +PSF convolved by an Airy PSF.

+

The power spectrum of atmospheric phase fluctuations is assumed to follow the von Karman +prescription, but possibly modified by the addition of a critical scale below which the power +is zero. (See the VonKarman docstring for more details).

+

As an expectation value, this profile is formally only exact in the infinite-exposure limit. +However, at least for large apertures, we have found that this expectation value is approached +rapidly, and can be applied for even fairly small exposure times.

+

The intended use for this profile is as a correction to applying the geometric approximation to +PhaseScreenPSF objects when drawing using geometric photon shooting. In this case, the +PhaseScreenPSF will simulate the effects of the low frequency turbulence modes, which can be +treated purely using refraction, while the SecondKick handles the high frequency modes.

+

The geometric approximation is only valid for length scales larger than some critical scale +where the effects of interference are unimportant. For smaller length scales, interference +(diffraction) must be handled using an optical paradigm that acknowledges the wave nature of +light, such as Fourier optics.

+

Fourier optics calculations are many orders of magnitude slower than geometric optics +calculations for typical flux levels, however, so we implement a scale-splitting algorithm first +described in Peterson et al. (2015) for the LSST PhoSim package. Essentially, phase +fluctuations below a critical mode in Fourier space, labeled kcrit, are handled by the fast +geometric optics calculations present in PhaseScreenPSF. Fluctuations for Fourier modes above +kcrit are then calculated analytically by SecondKick. Because very many oscillations of +these high-k modes both fit within a given telescope aperture and pass by the aperture during a +moderate length exposure time, we can use the same analytic expectation value calculation for +the high-k component of all PSFs across a field of view, thus incurring the somewhat expensive +calculation for Fourier optics only once.

+

There are two limiting cases for this profile that may helpful for readers trying to understand +how this class works. When kcrit = 0, then all turbulent modes are included, and this surface +brightness profile becomes identical to the convolution of an Airy profile and a Von Karman +profile. In contrast, when kcrit = inf, then none of the turbulent modes are included, and this +surface brightness profile is just an Airy profile. In other words, the full effect of an +Airy profile, and additionally some portion (which depends on kcrit) of a VonKarman profile +are modeled.

+

For more details, we refer the reader to the original implementation described in

+
+

Peterson et al. 2015 ApJSS vol. 218

+
+
+
Parameters:
+
    +
  • lam – Wavelength, either as an astropy Quantity or a float in nanometers.

  • +
  • r0 – Fried parameter, either as an astropy Quantity or a float in meters.

  • +
  • diam – Aperture diameter, either as an astropy Quantity or a float in nanmeters.

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture +linear dimension. [0., 1.). [default: 0.0]

  • +
  • kcrit – Critical Fourier mode (in units of 1/r0) below which the turbulence +power spectrum will be truncated. [default: 0.2]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • scale_unit – Units assumed when drawing this profile or evaluating xValue, kValue, +etc. Should be a galsim.AngleUnit or a string that can be used to +construct one (e.g., ‘arcsec’, ‘radians’, etc.). [default: galsim.arcsec]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property diam
+

The input diam value.

+
+ +
+
+property kcrit
+

The input kcrit value.

+
+ +
+
+property lam
+

The input lam value.

+
+ +
+
+property obscuration
+

The input obscuration value.

+
+ +
+
+property r0
+

The input r0 value.

+
+ +
+
+property scale_unit
+

The input scale_unit value.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+galsim.Atmosphere(screen_size, rng=None, _bar=None, **kwargs)[source]
+

Create an atmosphere as a list of turbulent phase screens at different altitudes. The +atmosphere model can then be used to simulate atmospheric PSFs.

+

Simulating an atmospheric PSF is typically accomplished by first representing the 3-dimensional +turbulence in the atmosphere as a series of discrete 2-dimensional phase screens. These screens +may blow around in the wind, and may or may not also evolve in time. This function allows one +to quickly assemble a list of atmospheric phase screens into a galsim.PhaseScreenList object, +which can then be used to evaluate PSFs through various columns of atmosphere at different field +angles.

+

The atmospheric screens currently available represent turbulence following a von Karman power +spectrum. Specifically, the phase power spectrum in each screen can be written

+
+\[\psi(\nu) = 0.023 r_0^{-5/3} \left(\nu^2 + \frac{1}{L_0^2}\right)^{11/6}\]
+

where \(\psi(\nu)\) is the power spectral density at spatial frequency \(\nu\), +\(r_0\) is the Fried parameter (which has dimensions of length) and sets the amplitude of +the turbulence, and \(L_0\) is the outer scale (also dimensions of length) beyond which the +power asymptotically flattens.

+

Typical values for \(r_0\) are ~0.1 to 0.2 meters, which corresponds roughly to PSF FWHMs +of ~0.5 to 1.0 arcsec for optical wavelengths. Note that \(r_0\) is a function of +wavelength, scaling like \(r_0 \sim \lambda^{6/5}\). To reduce confusion, the input +parameter here is named r0_500 and refers explicitly to the Fried parameter at a wavelength +of 500 nm. The outer scale is typically in the 10s of meters and does not vary with wavelength.

+

To create multiple layers, simply specify keyword arguments as length-N lists instead of scalars +(works for all arguments except rng). If, for any of these keyword arguments, you want to +use the same value for each layer, then you can just specify the argument as a scalar and the +function will automatically broadcast it into a list with length equal to the longest found +keyword argument list. Note that it is an error to specify keywords with lists of different +lengths (unless only one of them has length > 1).

+

The one exception to the above is the keyword r0_500. The effective Fried parameter for a +set of atmospheric layers is:

+
r0_500_effective = (sum(r**(-5./3) for r in r0_500s))**(-3./5)
+
+
+

Providing r0_500 as a scalar or single-element list will result in broadcasting such that +the effective Fried parameter for the whole set of layers equals the input argument. You can +weight the contribution of each layer with the r0_weights keyword.

+

As an example, the following code approximately creates the atmosphere used by Jee+Tyson(2011) +for their study of atmospheric PSFs for LSST. Note this code takes about ~2 minutes to run on +a fast laptop, and will consume about (8192**2 pixels) * (8 bytes) * (6 screens) ~ 3 GB of +RAM in its final state, and more at intermediate states.:

+
>>> altitude = [0, 2.58, 5.16, 7.73, 12.89, 15.46]  # km
+>>> r0_500 = 0.16  # m
+>>> weights = [0.652, 0.172, 0.055, 0.025, 0.074, 0.022]
+>>> speed = np.random.uniform(0, 20, size=6)  # m/s
+>>> direction = [np.random.uniform(0, 360)*galsim.degrees for i in range(6)]
+>>> npix = 8192
+>>> screen_scale = r0_500
+>>> atm = galsim.Atmosphere(r0_500=r0_500, r0_weights=weights,
+                            screen_size=screen_scale*npix,
+                            altitude=altitude, L0=25.0, speed=speed,
+                            direction=direction, screen_scale=screen_scale)
+
+
+

Once the atmosphere is constructed, a 15-sec exposure length, 5ms time step, monochromatic PSF +at 700nm (using an 8.4 meter aperture, 0.6 fractional obscuration and otherwise default +settings) takes about 7 minutes to draw on a fast laptop.:

+
>>> psf = atm.makePSF(lam=700.0, exptime=15.0, time_step=0.005, diam=8.4, obscuration=0.6)
+>>> img1 = psf.drawImage()  # ~7 min
+
+
+

The same psf, if drawn using photon-shooting on the same laptop, will generate photons at a rate +of about 1 million per second.:

+
>>> img2 = psf.drawImage(nx=32, ny=32, scale=0.2, method='phot', n_photons=1e6)  # ~1 sec.
+
+
+

Note that the Fourier-based calculation compute time will scale linearly with exposure time, +while the photon-shooting calculation compute time will scale linearly with the number of +photons being shot.

+

Many factors will affect the timing of results, of course, including aperture diameter, gsparams +settings, pad_factor and oversampling options to makePSF, time_step and exposure time, frozen +vs. non-frozen atmospheric layers, and so on. We recommend that users try varying these +settings to find a balance of speed and fidelity.

+
+
Parameters:
+
    +
  • r0_500 – Fried parameter setting the amplitude of turbulence; contributes to “size” +of the resulting atmospheric PSF. Specified at wavelength 500 nm, in units +of meters. [default: 0.2]

  • +
  • r0_weights – Weights for splitting up the contribution of r0_500 between different +layers. Note that this keyword is only allowed if r0_500 is either a +scalar or a single-element list. [default: None]

  • +
  • screen_size – Physical extent of square phase screen in meters. This should be large +enough to accommodate the desired field-of-view of the telescope as well as +the meta-pupil defined by the wind speed and exposure time. Note that +the screen will have periodic boundary conditions, so the code will run +with a smaller sized screen, though this may introduce artifacts into PSFs +or PSF correlation functions. Note that screen_size may be tweaked by the +initializer to ensure screen_size is a multiple of screen_scale.

  • +
  • screen_scale – Physical pixel scale of phase screen in meters. A fraction of the Fried +parameter is usually sufficiently small, but users should test the effects +of this parameter to ensure robust results. +[default: same as each screen’s r0_500]

  • +
  • altitude – Altitude of phase screen in km. This is with respect to the telescope, not +sea-level. [default: 0.0]

  • +
  • L0 – Outer scale in meters. The turbulence power spectrum will smoothly +approach a constant at scales larger than L0. Set to None or np.inf +for a power spectrum without an outer scale. [default: 25.0]

  • +
  • speed – Wind speed in meters/second. [default: 0.0]

  • +
  • direction – Wind direction as galsim.Angle [default: 0.0 * galsim.degrees]

  • +
  • alpha – Square root of fraction of phase that is “remembered” between time_steps +(i.e., alpha**2 is the fraction remembered). The fraction sqrt(1-alpha**2) +is then the amount of turbulence freshly generated in each step. Setting +alpha=1.0 results in a frozen-flow atmosphere. Note that computing PSFs +from frozen-flow atmospheres may be significantly faster than computing +PSFs with non-frozen-flow atmospheres. [default: 1.0]

  • +
  • time_step – Time interval between phase boiling updates. Note that this is distinct +from the time interval used when integrating the PSF over time, which is +set by the time_step keyword argument to PhaseScreenPSF or +PhaseScreenList.makePSF. If time_step is not None, then it is +required that alpha is set to something other than 1.0. [default: None]

  • +
  • rng – Random number generator as a BaseDeviate. If None, then use the +clock time or system entropy to seed a new generator. [default: None]

  • +
+
+
+
+ +
+
+galsim.phase_screens.initWorkerArgs()[source]
+

Function used to generate worker arguments to pass to multiprocessing.Pool initializer.

+

See AtmosphericScreen docstring for more information.

+
+ +
+
+galsim.phase_screens.initWorker(share)[source]
+

Worker initialization function to pass to multiprocessing.Pool initializer.

+

See AtmosphericScreen docstring for more information.

+
+ +
+
+galsim.phase_screens.reset_shared_screens()[source]
+

Reset the global dict that contains screens being shared across multiprocessing processes.

+

This is almost never necessary. However, if you first use one multiprocessing context with +initWorker and initWorkerArgs, and then switch to a different context for another +multiprocessing action, the dict we use for storing the shared memory will not be usable +in the new context. In this case, you should reset the global dict before starting +the second multiprocessing action by running:

+
galsim.phase_screens.reset_shared_screens()
+
+
+

If you only ever use one multiprocessing context in your program, you should never need +to call this.

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/photon.html b/docs/_build/html/photon.html new file mode 100644 index 00000000000..d35ba416fae --- /dev/null +++ b/docs/_build/html/photon.html @@ -0,0 +1,256 @@ + + + + + + + Photon Shooting — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Photon Shooting

+

Photon shooting was used successfully to generate the simulated images for the GREAT08 and GREAT10 +weak lensing challenges. The objects were convolutions of elliptical Sersic-profile galaxies with +Moffat-profile PSFs. GalSim extends this technique to enable photon shooting for nearly all of its +possible objects, except for deconvolutions.

+

When we “shoot” a GSObject or ChromaticObject, +\(N_\gamma\) photons are created with fluxes \(f_i\) and +positions \(x_i\). The total photon flux within any region has an expectation value of the +integrated surface brightness of the object in that region, and the total photon flux in any +two regions are uncorrelated. The actual realized flux in each region is distributed according +to Poisson statistics of the number of photons that actually fall in the region.

+

We allow for non-uniform \(f_i\) values primarily so that we can represent negative values of +surface brightness. This is necessary to realize interpolation with kernels that have negative +regions (as will any interpolant that approximates band-limited behavior), and to correctly render +interpolated images that have negative pixel values, such as might arise from using empirical, +noisy galaxy images.

+

The basic way to activate photon shooting is to use method='phot' when calling the +GSObject.drawImage or ChromaticObject.drawImage method. +This will switch over to photon shooting, and the resulting +image will have photon shot noise included from the finite number of photons being shot.

+
+

Note

+

This method necessarily accounts for integration over the pixel by summing the photons that +are incident in each. This means that if your surface brightness profile already +includes the pixel convolution, then you will get the wrong answer. Such profiles should +normally use method='no_pixel'. This kind of profile is often the result of PSF estimation +codes, so some care is required if you intend to use photon shooting with PSFs that come from +measurements of real data.

+
+

There are a number of other parameters that are relevant only when photon shooting that let you +customize the behavior to some extent:

+
+
+
n_photons

The total number of photons to shoot is normally calculated from the object’s +flux. This flux is taken to be given in photons/cm^2/s, so for most simple +profiles, this times area * exptime (both of which default to 1) will equal +the number of photons shot. (See the discussion in Rowe et al, 2015, for why +this might be modified for InterpolatedImage and related profiles.) However, +you can manually set a different number of photons with n_photons.

+
+
rng

Since photon shooting is a stochastic process, it needs a random number generator. +This should be a BaseDeviate instance. If none is provided, one will be +created automatically.

+
+
max_extra_noise

This allows you to gain some speed by shooting fewer photons with \(f_i > 1\) +at the expense of increasing the noise in each pixel above the natural Poisson +value. This parameter specifies how much extra noise you are willing to tolerate. +It is only relevant if you are not setting n_photons, so the number of photons +is being automatically calculated. The max_extra_noise parameter specifies +how much extra noise per pixel is allowed because of this approximation. A +typical value might be max_extra_noise = sky_level / 100 where sky_level +is the flux per pixel due to the sky.

+
+
poisson_flux

Normally the total flux of the shot photons will itself be a Poisson random +value with GSObject.flux as the expectation value. However, you can disable +this effect by setting poisson_flux=False to have it shoot exactly the +flux of the GSObject.

+
+
sensor

The default behavior is for the photons to simply accumulate in the pixel where +they land. However, more sophisticated behavior is possible by providing a +Sensor object, which can implement e.g. the brighter-fatter effect, charge +diffusion, and other effects present in real sensors. See Sensor Models +for more information about the current options.

+
+
photon_ops

Prior to accumulating on the sensor, one might want to apply one or more +Photon Operators to the photons. These operators can be used to apply +a variety of effects to the photons: changing their fluxes or positions, +assigning wavelengths or incidence angles, etc. The photon_ops argument +should be a list of any such operators you want to apply.

+
+
maxN

For very bright objects, one might want to limit the number of photons that are +shot before being accumulated. Normally all the photons are generated first +and stored in a PhotonArray. Then the Photon Operators (if any) are +applied. And finally the photons are accumulated onto the image pixels. +If you set maxN, then this process will be done in batches of at most this +many photons at a time.

+
+
save_photons

This provides the ability to return the PhotonArray that was accumulated +in case you want to do anything else with it.

+
+
+
+

If you prefer even more fine-grained control over photon shooting, you can use the following +methods:

+
+
+
GSObject.drawPhot

This is the actual driver function that GSObject.drawImage calls after +performing some basic sanity checks and image setup. If you are trying to +optimize your code for low flux objects, you might find it useful to do the +image setup yourself and then call this directly.

+
+
GSObject.shoot

This is the method that actually shoots the photons for a GSObject. It +does not apply any photon operators or accumulate onto the Image.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/photon_array.html b/docs/_build/html/photon_array.html new file mode 100644 index 00000000000..b4079d40b19 --- /dev/null +++ b/docs/_build/html/photon_array.html @@ -0,0 +1,597 @@ + + + + + + + Photon Arrays — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Photon Arrays

+
+
+class galsim.PhotonArray(N, x=None, y=None, flux=None, dxdz=None, dydz=None, wavelength=None, pupil_u=None, pupil_v=None, time=None)[source]
+

The PhotonArray class encapsulates the concept of a collection of photons incident on +a detector.

+

A PhotonArray object is not typically constructed directly by the user. Rather, it is +typically constructed as the return value of the GSObject.shoot method. +At this point, the photons only have x,y,flux values.

+

Then there are a number of Photon Operators, which perform various modifications to the +photons such as giving them wavelengths (WavelengthSampler or inclination angles +(FRatioAngles) or move them around according to the effect of differential chromatic +refraction (DCR; PhotonDCR).

+

One could also add functionality to remove some photons due to fringing or vignetting, +but these are not yet implemented.

+

A PhotonArray instance has the following attributes, each of which is a numpy array:

+
+
Attributes:
+
    +
  • x – The incidence x position of the photons in image coordinates (pixels), +typically measured at the top of the detector.

  • +
  • y – The incidence y position of the photons in image coordinates (pixels), +typically measured at the top of the detector.

  • +
  • flux – The flux of the photons in units of photons. Typically, these are all 1, +but see the note below for reasons some photons might have flux != 1.

  • +
  • dxdz – The tangent of the inclination angles in the x direction. Note that we define +the +z direction as towards towards the dielectric medium of the detector and +-z as towards vacuum; consequently, a photon with increasing x in time has +positive dxdz.

  • +
  • dydz – The tangent of the inclination angles in the y direction. Note that we define +the +z direction as towards towards the dielectric medium of the detector and +-z as towards vacuum; consequently, a photon with increasing y in time has +positive dydz.

  • +
  • wavelength The wavelength of the photons (in nm)

  • +
  • pupil_u – Horizontal location of photon as it intersected the entrance pupil plane +(meters).

  • +
  • pupil_v – Vertical location of photon as it intersected the entrance pupil plane +(meters).

  • +
  • time – Time stamp for photon impacting the pupil plane (seconds).

  • +
+
+
+

Unlike most GalSim objects (but like Image), PhotonArrays are mutable. It is permissible +to write values to the above attributes with code like:

+
>>> photon_array.x += numpy.random.random(1000) * 0.01
+>>> photon_array.flux *= 20.
+>>> photon_array.wavelength = sed.sampleWavelength(photonarray.size(), bandpass)
+etc.
+
+
+

All of these will update the existing numpy arrays being used by the photon_array instance.

+
+

Note

+

Normal photons have flux=1, but we allow for “fat” photons that combine the effect of +several photons at once for efficiency. Also, some profiles need to use negative flux +photons to properly implement photon shooting (e.g. InterpolatedImage, which uses negative +flux photons to get the interpolation correct). Finally, when we “remove” photons, for +better efficiency, we actually just set the flux to 0 rather than recreate new numpy arrays.

+
+

The initialization constructs a PhotonArray to hold N photons, but does not set the values of +anything yet. The constructor allocates space for the x,y,flux arrays, since those are always +needed. The other arrays are only allocated on demand if the user accesses these attributes.

+
+
Parameters:
+
    +
  • N – The number of photons to store in this PhotonArray. This value cannot be +changed.

  • +
  • x – Optionally, the initial x values. [default: None]

  • +
  • y – Optionally, the initial y values. [default: None]

  • +
  • flux – Optionally, the initial flux values. [default: None]

  • +
  • dxdz – Optionally, the initial dxdz values. [default: None]

  • +
  • dydz – Optionally, the initial dydz values. [default: None]

  • +
  • wavelength – Optionally, the initial wavelength values (in nm). [default: None]

  • +
  • pupil_u – Optionally, the initial pupil_u values. [default: None]

  • +
  • pupil_v – Optionally, the initial pupil_v values. [default: None]

  • +
  • time – Optionally, the initial time values. [default: None]

  • +
+
+
+
+
+addTo(image)[source]
+

Add flux of photons to an image by binning into pixels.

+

Photons in this PhotonArray are binned into the pixels of the input +Image and their flux summed into the pixels. The Image is assumed to represent +surface brightness, so photons’ fluxes are divided by image pixel area. +Photons past the edges of the image are discarded.

+
+
Parameters:
+

image – The Image to which the photons’ flux will be added.

+
+
Returns:
+

the total flux of photons the landed inside the image bounds.

+
+
+
+ +
+
+allocateAngles()[source]
+

Allocate memory for the incidence angles, dxdz and dydz.

+
+ +
+
+allocatePupil()[source]
+

Allocate the memory for the pupil coordinates, pupil_u and pupil_v.

+
+ +
+
+allocateTimes()[source]
+

Allocate the memory for the time stamps, time.

+
+ +
+
+allocateWavelengths()[source]
+

Allocate the memory for the wavelength array.

+
+ +
+
+assignAt(istart, rhs)[source]
+

Assign the contents of another PhotonArray to this one starting at istart.

+
+
Parameters:
+
    +
  • istart – The first index at which to insert new values.

  • +
  • rhs – The other PhotonArray from which to get the values.

  • +
+
+
+
+ +
+
+classmethod concatenate(photon_arrays)[source]
+

Create a single PhotonArray from a list of multiple PhotonArrays.

+

The size of the created PhotonArray will be the sum of the sizes of the given arrays, +and the values will be concatenations of the values in each.

+
+

Note

+

The optional value arrays (e.g. dxdz, wavelength, etc.) must be given in +all the given photon_arrays or in none of them. This is not checked.

+
+
+
Parameters:
+

photon_arrays – A list of PhotonArray objects to be concatenated.

+
+
+
+ +
+
+convolve(rhs, rng=None)[source]
+

Convolve this PhotonArray with another.

+

..note:

+
If both self and rhs have wavelengths, angles, pupil coordinates or times assigned,
+then the values from the first array (i.e. self) take precedence.
+
+
+
+ +
+
+copyFrom(rhs, target_indices=slice(None, None, None), source_indices=slice(None, None, None), do_xy=True, do_flux=True, do_other=True)[source]
+

Copy some contents of another PhotonArray to some elements of this one.

+

Specifically each element of rhs[source_indices] is mapped to self[target_indices]. +The values s1 and s2 may be slices, list of indices, or anything else that is a valid +key for a numpy array.

+
+
Parameters:
+
    +
  • rhs – The PhotonArray from which to get values.

  • +
  • target_indices – The indices at which to assign values in the current PhotonArray (self). +[default: slice(None)]

  • +
  • source_indices – The indices from which to get values from the PhotonArray, rhs. +[default: slice(None)]

  • +
  • do_xy – Whether to include copying the x and y arrays. [default: True]

  • +
  • do_flux – Whether to include copying the flux array. [default: True]

  • +
  • do_other – Whether to include copying the other arrays (angles, wavelength, +pupil positions, time). [default: True]

  • +
+
+
+
+ +
+
+property dxdz
+

dx/dz.

+
+
Type:
+

The tangent of the inclination angles in the x direction

+
+
+
+ +
+
+property dydz
+

dy/dz.

+
+
Type:
+

The tangent of the inclination angles in the y direction

+
+
+
+ +
+
+property flux
+

The flux of the photons.

+
+ +
+
+classmethod fromArrays(x, y, flux, dxdz=None, dydz=None, wavelength=None, pupil_u=None, pupil_v=None, time=None, is_corr=False)[source]
+

Create a PhotonArray from pre-allocated numpy arrays without any copying.

+

The normal PhotonArray constructor always allocates new arrays and copies any provided +initial values into those new arrays. This class method, by constrast, constructs a +PhotonArray that references existing numpy arrays, so that any PhotonOps or photon shooting +of GSObjects applied to the resulting PhotonArray will also be reflected in the original +arrays.

+

Note that the input arrays must all be the same length, have dtype float64 and be +c_contiguous.

+
+
Parameters:
+
    +
  • x – X values.

  • +
  • y – X values.

  • +
  • flux – Flux values.

  • +
  • dxdz – Optionally, the initial dxdz values. [default: None]

  • +
  • dydz – Optionally, the initial dydz values. [default: None]

  • +
  • wavelength – Optionally, the initial wavelength values (in nm). [default: None]

  • +
  • pupil_u – Optionally, the initial pupil_u values (in m). [default: None]

  • +
  • pupil_v – Optionally, the initial pupil_v values (in m). [default: None]

  • +
  • time – Optionally, the initial time values (in s). [default: None]

  • +
  • is_corr – Whether or not the photons are correlated. [default: False]

  • +
+
+
+
+ +
+
+getTotalFlux()[source]
+

Return the total flux of all the photons.

+
+ +
+
+hasAllocatedAngles()[source]
+

Returns whether the arrays for the incidence angles dxdz and dydz have been +allocated.

+
+ +
+
+hasAllocatedPupil()[source]
+

Returns whether the arrays for the pupil coordinates pupil_u and pupil_v have been +allocated.

+
+ +
+
+hasAllocatedTimes()[source]
+

Returns whether the array for the time stamps time has been allocated.

+
+ +
+
+hasAllocatedWavelengths()[source]
+

Returns whether the wavelength array has been allocated.

+
+ +
+
+isCorrelated()[source]
+

Returns whether the photons are correlated

+
+ +
+
+classmethod makeFromImage(image, max_flux=1.0, rng=None)[source]
+

Turn an existing Image into a PhotonArray that would accumulate into this image.

+

The flux in each non-zero pixel will be turned into 1 or more photons with random positions +within the pixel bounds. The max_flux parameter (which defaults to 1) sets an upper +limit for the absolute value of the flux of any photon. Pixels with abs values > maxFlux +will spawn multiple photons.

+
+
Parameters:
+
    +
  • image – The image to turn into a PhotonArray

  • +
  • max_flux – The maximum flux value to use for any output photon [default: 1]

  • +
  • rng – A BaseDeviate to use for the random number generation [default: None]

  • +
+
+
Returns:
+

a PhotonArray

+
+
+
+ +
+
+property pupil_u
+

Horizontal location of photon as it intersected the entrance pupil plane.

+
+ +
+
+property pupil_v
+

Vertical location of photon as it intersected the entrance pupil plane.

+
+ +
+
+classmethod read(file_name)[source]
+

Create a PhotonArray, reading the photon data from a FITS file.

+

The file being read in is not arbitrary. It is expected to be a file that was written +out with the PhotonArray.write method.:

+
>>> photons.write('photons.fits')
+>>> photons2 = galsim.PhotonArray.read('photons.fits')
+
+
+
+
Parameters:
+

file_name – The file name of the input FITS file.

+
+
+
+ +
+
+scaleFlux(scale)[source]
+

Rescale the photon fluxes by the given factor.

+
+
Parameter:

scale: The factor by which to scale the fluxes.

+
+
+
+ +
+
+scaleXY(scale)[source]
+

Scale the photon positions (x and y) by the given factor.

+
+
Parameter:

scale: The factor by which to scale the positions.

+
+
+
+ +
+
+setCorrelated(is_corr=True)[source]
+

Set whether the photons are correlated

+
+ +
+
+setTotalFlux(flux)[source]
+

Rescale the photon fluxes to achieve the given total flux.

+
+
Parameter:

flux: The target flux

+
+
+
+ +
+
+size()[source]
+

Return the size of the photon array. Equivalent to len(self).

+
+ +
+
+property time
+

Time stamp of when photon encounters the pupil plane.

+
+ +
+
+property wavelength
+

The wavelength of the photons (in nm).

+
+ +
+
+write(file_name)[source]
+

Write a PhotonArray to a FITS file.

+

The output file will be a FITS binary table with a row for each photon in the PhotonArray. +Columns will include ‘id’ (sequential from 1 to nphotons), ‘x’, ‘y’, and ‘flux’. +Additionally, the columns ‘dxdz’, ‘dydz’, and ‘wavelength’ will be included if they are +set for this PhotonArray object.

+

The file can be read back in with the classmethod PhotonArray.read:

+
>>> photons.write('photons.fits')
+>>> photons2 = galsim.PhotonArray.read('photons.fits')
+
+
+
+
Parameters:
+

file_name – The file name of the output FITS file.

+
+
+
+ +
+
+property x
+

The incidence x position in image coordinates (pixels), typically at the top of +the detector.

+
+ +
+
+property y
+

The incidence y position in image coordinates (pixels), typically at the top of +the detector.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/photon_ops.html b/docs/_build/html/photon_ops.html new file mode 100644 index 00000000000..8afab7114fd --- /dev/null +++ b/docs/_build/html/photon_ops.html @@ -0,0 +1,546 @@ + + + + + + + Photon Operators — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Photon Operators

+
+
+class galsim.PhotonOp[source]
+

A base class for photon operators, which just defines the interface.

+

Photon operators are designed to apply some physical effect to a bundle of photons. They +may adjust the fluxes in some way, or the positions, maybe in a wavelength-dependent way, etc. +They are typically applied via a photon_ops argument to the GSObject.drawImage method. +The order typically matters, so the operators are applied in the order they appear in the list.

+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Apply the photon operator to a PhotonArray.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.WavelengthSampler(sed, bandpass=None, rng=None, npoints=None)[source]
+

A photon operator that uses sed.sampleWavelength to set the wavelengths array of a +PhotonArray.

+
+
Parameters:
+
    +
  • sed – The SED to use for the objects spectral energy distribution.

  • +
  • bandpass – A Bandpass object representing a filter, or None to sample over the full +SED wavelength range.

  • +
  • rng – If provided, a random number generator that is any kind of BaseDeviate +object. If rng is None, one will be automatically created, using the +time as a seed. [default: None]

  • +
  • npoints – Number of points DistDeviate should use for its internal interpolation +tables. [default: None, which uses the DistDeviate default]

  • +
+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Assign wavelengths to the photons sampled from the SED * Bandpass.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.FRatioAngles(fratio, obscuration=0.0, rng=None)[source]
+

A photon operator that assigns photon directions based on the f/ratio and obscuration.

+

Assigns arrival directions at the focal plane for photons, drawing from a uniform +brightness distribution between the obscuration angle and the edge of the pupil defined +by the f/ratio of the telescope. The angles are expressed in terms of slopes dx/dz +and dy/dz.

+
+
Parameters:
+
    +
  • fratio – The f/ratio of the telescope (e.g. 1.2 for LSST)

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture +linear dimension. [0., 1.). [default: 0.0]

  • +
  • rng – A random number generator to use or None, in which case an rng +will be automatically constructed for you. [default: None]

  • +
+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Assign directions to the photons in photon_array.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.PhotonDCR(base_wavelength, scale_unit=coord.arcsec, **kwargs)[source]
+

A photon operator that applies the effect of differential chromatic refraction (DCR) +and optionally the chromatic dilation due to atmospheric seeing.

+

Due to DCR, blue photons land closer to the zenith than red photons. Kolmogorov turbulence +also predicts that blue photons get spread out more by the atmosphere than red photons, +specifically FWHM is proportional to \(\lambda^{-0.2}\). Both of these effects can be +implemented by wavelength-dependent shifts of the photons.

+

Since DCR depends on the zenith angle and the parallactic angle (which is the position angle of +the zenith measured from North through East) of the object being drawn, these must be specified +via keywords. There are four ways to specify these values:

+
    +
  1. explicitly provide zenith_angle as a keyword of type Angle, and parallactic_angle +will be assumed to be 0 by default.

  2. +
  3. explicitly provide both zenith_angle and parallactic_angle as keywords of type +Angle.

  4. +
  5. provide the coordinates of the object obj_coord and the coordinates of the zenith +zenith_coord as keywords of type CelestialCoord.

  6. +
  7. provide the coordinates of the object obj_coord as a CelestialCoord, the hour angle +of the object HA as an Angle, and the latitude of the observer latitude as an +Angle.

  8. +
+

DCR also depends on temperature, pressure and water vapor pressure of the atmosphere. The +default values for these are expected to be appropriate for LSST at Cerro Pachon, Chile, but +they are broadly reasonable for most observatories.

+

This photon op is intended to match the functionality of ChromaticAtmosphere, but acting +on the photon array rather than as a ChromaticObject. The photons will need to have +wavelengths defined in order to work.

+
+

Warning

+

The alpha parameter is only appropriate for stars. This photon op will act on +all of the photons, so applying a chromatic dilation according to the chromatic +seeing is the wrong thing to do when the surface brightness being rendered is +not a pure PSF. As such, the default is alpha=0, not -0.2, which would be +appropriate for Kolmogorov turbulence.

+
+
+
Parameters:
+
    +
  • base_wavelength – Wavelength (in nm) represented by the fiducial photon positions

  • +
  • scale_unit – Units used for the positions of the photons. [default: galsim.arcsec]

  • +
  • alpha – Power law index for wavelength-dependent seeing. This should only +be used if doing a star-only simulation. It is not correct when +drawing galaxies. [default: 0.]

  • +
  • zenith_angleAngle from object to zenith, expressed as an Angle. [default: 0]

  • +
  • parallactic_angle – Parallactic angle, i.e. the position angle of the zenith, measured +from North through East. [default: 0]

  • +
  • obj_coord – Celestial coordinates of the object being drawn as a CelestialCoord. +[default: None]

  • +
  • zenith_coord – Celestial coordinates of the zenith as a CelestialCoord. +[default: None]

  • +
  • HA – Hour angle of the object as an Angle. [default: None]

  • +
  • latitude – Latitude of the observer as an Angle. [default: None]

  • +
  • pressure – Air pressure, either as an astropy Quantity or a float in units of +kiloPascals. [default: 69.328 kPa]

  • +
  • temperature – Temperature, either as an astropy Quantity or a float in units of +Kelvin. [default: 293.15 K]

  • +
  • H2O_pressure – Water vapor pressure, either as an astropy Quantity or a float in units +of kiloPascals. [default: 1.067 kPa]

  • +
+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Apply the DCR effect to the photons

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.FocusDepth(depth)[source]
+

A photon operator that focuses/defocuses photons by changing the height of the focal +surface with respect to the conical beam.

+
+
Parameters:
+

depth – The z-distance by which to displace the focal surface, in units of pixels. A +positive (negative) number here indicates an extra- (intra-) focal sensor height. +I.e., depth > 0 means the sensor surface intersects the beam after it has +converged, and depth < 0 means the sensor surface intersects the beam before it +has converged.

+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Adjust a photon bundle to account for the change in focal depth.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.Refraction(index_ratio)[source]
+

A photon operator that refracts photons (manipulating their dxdz and dydz values) at an +interface, commonly the interface between vacuum and silicon at the surface of a CCD.

+

Assumes that the surface normal is along the z-axis. If the refraction would result in total +internal reflection, then those photon’s dxdz and dydz values are set to NaN, and flux values +set to 0.0.

+
+
Parameters:
+

index_ratio – The ratio of the refractive index on the far side of the interface to the +near side. Can be given as a number or a callable function. In the +latter case, the function should accept a numpy array of vacuum wavelengths +as input and return a numpy array of refractive index ratios.

+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Refract photons

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.PupilImageSampler(diam, **kwargs)[source]
+

A photon operator that samples the pupil-plane positions given a pupil-plane image. +Samples are drawn discretely from pupil plane image pixels marked as illuminated.

+
+
Parameters:
+
    +
  • diam – Aperture diameter in meters.

  • +
  • lam – Wavelength in nanometers. [default: None]

  • +
  • circular_pupil – Adopt a circular pupil? [default: True]

  • +
  • obscuration – Linear dimension of central obscuration as fraction of aperture +linear dimension. [0., 1.). [default: 0.0]

  • +
  • nstruts – Number of radial support struts to add to the central obscuration. +[default: 0]

  • +
  • strut_thick – Thickness of support struts as a fraction of aperture diameter. +[default: 0.05]

  • +
  • strut_angleAngle made between the vertical and the strut starting closest to it, +defined to be positive in the counter-clockwise direction; must be an +Angle instance. [default: 0. * galsim.degrees]

  • +
  • oversampling – Optional oversampling factor in the image plane for the PSF +eventually constructed using this Aperture. Setting +oversampling < 1 will produce aliasing in the PSF (not good). +[default: 1.0]

  • +
  • pad_factor – Additional multiple by which to extend the PSF image to avoid +folding. [default: 1.0]

  • +
  • pupil_plane_im – The GalSim.Image, NumPy array, or name of file containing the pupil +plane image, to be used instead of generating one based on the +obscuration and strut parameters. [default: None]

  • +
  • pupil_angle – If pupil_plane_im is not None, rotation angle for the pupil plane +(positive in the counter-clockwise direction). Must be an Angle +instance. [default: 0. * galsim.degrees]

  • +
  • pupil_plane_scale – Sampling interval in meters to use for the pupil plane array. In +most cases, it’s a good idea to leave this as None, in which case +GalSim will attempt to find a good value automatically. The +exception is when specifying the pupil arrangement via an image, in +which case this keyword can be used to indicate the sampling of that +image. See also pad_factor for adjusting the pupil sampling scale. +[default: None]

  • +
  • pupil_plane_size – Size in meters to use for the pupil plane array. In most cases, it’s +a good idea to leave this as None, in which case GalSim will attempt +to find a good value automatically. See also oversampling for +adjusting the pupil size. [default: None]

  • +
+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Sample the pupil plane u,v positions for each photon.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.PupilAnnulusSampler(R_outer, R_inner=0.0)[source]
+

A photon operator that uniformly samples an annular entrance pupil.

+
+
Parameters:
+
    +
  • R_outer – Annulus outer radius in meters.

  • +
  • R_inner – Annulus inner radius in meters. [default: 0.0]

  • +
+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Sample the pupil plane u,v positions for each photon.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+
+class galsim.TimeSampler(t0=0.0, exptime=0.0)[source]
+

A photon operator that uniformly samples photon time stamps within some interval.

+
+
Parameters:
+
    +
  • t0 – The nominal start time of the observation in seconds. [default: 0]

  • +
  • exptime – The exposure time in seconds. [default: 0]

  • +
+
+
+
+
+applyTo(photon_array, local_wcs=None, rng=None)[source]
+

Add time stamps to photons.

+
+
Parameters:
+
    +
  • photon_array – A PhotonArray to apply the operator to.

  • +
  • local_wcs – A LocalWCS instance defining the local WCS for the current photon +bundle in case the operator needs this information. [default: None]

  • +
  • rng – A random number generator to use if needed. [default: None]

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/pos.html b/docs/_build/html/pos.html new file mode 100644 index 00000000000..31a063c8b1b --- /dev/null +++ b/docs/_build/html/pos.html @@ -0,0 +1,240 @@ + + + + + + + Positions — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Positions

+
+
+class galsim.Position[source]
+

A class for representing 2D positions on the plane.

+

Position is a base class for two slightly different kinds of positions: +PositionD describes positions with floating point values in x and y. +PositionI described positions with integer values in x and y.

+

In the C++ layer, these are templates, but of course no such thing exists in Python, +so the trailing D or I indicate the type.

+

Initialization:

+

For the float-valued position class, example initializations include:

+
>>> pos = galsim.PositionD(x=0.5, y=-0.5)
+>>> pos = galsim.PositionD(0.5, -0.5)
+>>> pos = galsim.PositionD( (0.5, -0.5) )
+
+
+

And for the integer-valued position class, example initializations include:

+
>>> pos = galsim.PositionI(x=45, y=13)
+>>> pos = galsim.PositionI(45, 13)
+>>> pos = galsim.PositionD( (45, 15) )
+
+
+
+
Attributes:
+
    +
  • x – The x component of the position

  • +
  • y – The y component of the position

  • +
+
+
+

Arithmetic:

+

Most arithmetic that makes sense for a position is allowed:

+
>>> pos1 + pos2
+>>> pos1 - pos2
+>>> pos * x
+>>> pos / x
+>>> -pos
+>>> pos1 += pos2
+>>> pos1 -= pos2
+>>> pos *= x
+>>> pos -= x
+
+
+

Note though that the types generally need to match. For example, you cannot multiply +a PositionI by a float add a PositionD to a PositionI in place.

+

Position instances can be sheared by a galsim.Shear:

+
>>> pos = galsim.PositionD(x=0.5, y=-0.5)
+>>> shear = galsim.Shear(g1=0.1, g2=-0.1)
+>>> sheared_pos = pos.shear(shear)
+
+
+

Note that this operation will always return a PositionD even if +an integer position is being sheared.

+
+
+shear(shear)[source]
+

Shear the position.

+

See the doc string of galsim.Shear.getMatrix for more details.

+
+
Parameters:
+

shear – a galsim.Shear instance

+
+
Returns:
+

a galsim.PositionD instance.

+
+
+
+ +
+ +
+
+class galsim.PositionI(*args, **kwargs)[source]
+

Bases: Position

+

A Position that takes only integer values.

+

Typically used for coordinate positions on an image.

+

See the Position doc string for more details.

+
+ +
+
+class galsim.PositionD(*args, **kwargs)[source]
+

Bases: Position

+

A Position that takes floating point values.

+

See the Position doc string for more details.

+
+
+round()[source]
+

Return the rounded-off PositionI version of this position.

+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/powerspectrum.html b/docs/_build/html/powerspectrum.html new file mode 100644 index 00000000000..c723e01ea4b --- /dev/null +++ b/docs/_build/html/powerspectrum.html @@ -0,0 +1,901 @@ + + + + + + + Power Spectrum Shears — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Power Spectrum Shears

+

This is the “lensing engine” for calculating shears according to a Gaussian process with +specified E- and/or B-mode power spectra.

+
+
+class galsim.PowerSpectrum(e_power_function=None, b_power_function=None, delta2=False, units=coord.arcsec)[source]
+

Class to represent a lensing shear field according to some power spectrum \(P(k)\).

+

General considerations:

+

A PowerSpectrum represents some (flat-sky) shear power spectrum, either for gridded points or at +arbitary positions. This class is originally initialized with a power spectrum from which we +would like to generate g1 and g2 (and, optionally, convergence kappa) values. It generates +shears on a grid, and if necessary, when getShear (or another “get” method) is called, it +will interpolate to the requested positions. For detail on how these processes are carried +out, please see the document in the GalSim repository, devel/modules/lensing_engine.pdf.

+

This class generates the shears according to the input power spectrum using a DFT approach, +which means that we implicitly assume our discrete representation of \(P(k)\) on a grid is +one complete cell in an infinite periodic series. We are making assumptions about what +\(P(k)\) is doing outside of our minimum and maximum k range, and those must be kept in +mind when comparing with theoretical expectations. Specifically, since the power spectrum is +realized on only a finite grid it has been been effectively bandpass filtered between a +minimum and maximum k value in each of the k1, k2 directions. See the buildGrid method for +more information.

+

As a result, the shear generation currently does not include sample variance due to coverage of +a finite patch. We explicitly enforce \(P(0)=0\), which is true for the full sky in a +reasonable cosmological model, but it ignores the fact that our little patch of sky might +reasonably live in some special region with respect to shear correlations. Our \(P(0)=0\) +is essentially setting the integrated power below our minimum k value to zero. The +implications of the discrete representation, and the \(P(0)=0\) choice, are discussed in +more detail in devel/modules/lensing_engine.pdf.

+

The effective shear correlation function for the gridded points will be modified both because of +the DFT approach to representing shears according to a power spectrum, and because of the power +cutoff below and above the minimum k values. The latter effect can be particularly important on +large scales, so the buildGrid method has some keywords that can be used to reduce the +impact of the minimum k set by the grid extent. The calculateXi() method can be used to +calculate the expected shear correlation functions given the minimum and maximum k for some grid +(but ignoring the discrete vs. continuous Fourier transform effects), for comparison with some +ideal theoretical correlation function given an infinite k range.

+

When interpolating the shears to non-gridded points, the shear correlation function and power +spectrum are modified; see the getShear and other “get” method docstrings for more details.

+

The power spectra to be used:

+

When creating a PowerSpectrum instance, you must specify at least one of the E or B mode power +spectra, which is normally given as a function \(P(k)\). The typical thing is to just use a lambda +function in Python (i.e., a function that is not associated with a name); for example, to define +\(P(k)=k^2\), one would use lambda k : k**2. But the power spectra can also be more complicated +user-defined functions that take a single argument k and return the power at that k +value, or they can be instances of the LookupTable class for power spectra that are known at +particular k values but for which there is not a simple analytic form.

+

Cosmologists often express the power spectra in terms of an expansion in spherical harmonics +(ell), i.e., the \(C_\ell\) values. In the flat-sky limit, we can replace \(\ell\) +with \(k\) and \(C_\ell\) with \(P(k)\). Thus, \(k\) and \(P(k)\) have +dimensions of inverse angle and angle^2, respectively. It is quite common for people to plot +\(\ell(\ell+1) C_\ell/2\pi\), a dimensionless quantity; the analogous flat-sky +quantity is \(\Delta^2 = k^2 P(k)/2\pi\).

+

By default, the PowerSpectrum object assumes it is getting \(P(k)\), but it is possible to +instead give it \(\Delta^2\) by setting the optional keyword delta2 = True in the +constructor.

+

The power functions must return a list/array that is the same size as what they are given, e.g., +in the case of no power or constant power, a function that just returns a float would not be +permitted; it would have to return an array of floats all with the same value.

+

It is important to note that the power spectra used to initialize the PowerSpectrum object +should use the same units for k and \(P(k)\), i.e., if k is in inverse radians then +\(P(k)\) should be in radians^2 (as is natural for outputs from a cosmological shear power +spectrum calculator). However, when we actually draw images, there is a natural scale that +defines the pitch of the image, which is typically taken to be arcsec. This definition of a +specific length scale means that by default we assume all quantities to the PowerSpectrum are +in arcsec, and those are the units used for internal calculations, but the units keyword +can be used to specify different input units for \(P(k)\) (again, within the constraint +that k and \(P(k)\) must be consistent). If the delta2 keyword is set to specify that +the input is actually the dimensionless power \(\Delta^2\), then the input units are +taken to apply only to the k values.

+
+
Parameters:
+
    +
  • e_power_function – A function or other callable that accepts a NumPy array of abs(k) +values, and returns the E-mode power spectrum P_E(abs(k)) in an array of +the same shape. The function should return the power spectrum desired +in the E (gradient) mode of the image. +It may also be a string that can be converted to a function using +eval('lambda k : '+e_power_function), a LookupTable, or +file_name from which to read in a LookupTable. If a file_name +is given, the resulting LookupTable uses the defaults for the +LookupTable class, namely spline interpolation in \(P(k)\). +Users who wish to deviate from those defaults (for example, to +interpolate in log(P) and log(k), as might be more natural for +power-law functions) should instead read in the file to create a +LookupTable using the necessary non-default settings. [default: None, +which means no E-mode power.]

  • +
  • b_power_function – A function or other callable that accepts a NumPy array of abs(k) +values, and returns the B-mode power spectrum P_B(abs(k)) in an array of +the same shape. The function should return the power spectrum desired +in the B (curl) mode of the image. See description of +e_power_function for input format options. [default: None, which +means no B-mode power.]

  • +
  • delta2 – Is the power actually given as dimensionless \(\Delta^2\), which +requires us to multiply by \(2\pi / k^2\) to get the shear power +\(P(k)\) in units of angle^2? [default: False]

  • +
  • units – The angular units used for the power spectrum (i.e. the units of +k^-1 and sqrt(P)). This should be either an AngleUnit instance +(e.g. galsim.radians) or a string (e.g. ‘radians’). [default: arcsec]

  • +
+
+
+
+
+_getShear(pos_x, pos_y, reduced=True, periodic=False)[source]
+

Equivalent to getShear, but without some sanity checks and the positions must be +given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • reduced – Whether returned shear(s) should be reduced shears. [default: True]

  • +
  • periodic – Whether the interpolation should treat the positions as being defined +with respect to a periodic grid. [default: False]

  • +
+
+
Returns:
+

the (possibly reduced) shears as a tuple (g1,g2) (either scalars or numpy arrays)

+
+
+
+ +
+
+_getConvergence(pos_x, pos_y, periodic=False)[source]
+

Equivalent to getConvergence, but without some sanity checks and the positions must be +given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • periodic – Whether the interpolation should treat the positions as being defined +with respect to a periodic grid. [default: False]

  • +
+
+
Returns:
+

the convergence, kappa (either a scalar or a numpy array)

+
+
+
+ +
+
+_getMagnification(pos_x, pos_y, periodic=False)[source]
+

Equivalent to getMagnification, but without some sanity checks and the positions must +be given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • periodic – Whether the interpolation should treat the positions as being defined +with respect to a periodic grid. [default: False]

  • +
+
+
Returns:
+

the magnification, mu (either a scalar or a numpy array)

+
+
+
+ +
+
+_getLensing(pos_x, pos_y, periodic=False)[source]
+

Equivalent to getLensing, but without some sanity checks and the positions must +be given as pos_x, pos_y in arcsec.

+
+
Parameters:
+
    +
  • pos_x – x position in arcsec (either a scalar or a numpy array)

  • +
  • pos_y – y position in arcsec (either a scalar or a numpy array)

  • +
  • periodic – Whether the interpolation should treat the positions as being defined +with respect to a periodic grid. [default: False]

  • +
+
+
Returns:
+

the reduced shear and magnification as a tuple (g1,g2,mu) (either scalars or +numpy arrays)

+
+
+
+ +
+
+buildGrid(grid_spacing, ngrid, rng=None, interpolant=None, center=galsim.PositionD(x=0.0, y=0.0), units=coord.arcsec, get_convergence=False, kmax_factor=1, kmin_factor=1, bandlimit='hard', variance=None)[source]
+

Generate a realization of the current power spectrum on the specified grid.

+

Basic functionality:

+

This function will generate a Gaussian random realization of the specified E and B mode +shear power spectra at a grid of positions, specified by the input parameters +grid_spacing (distance between grid points) and ngrid (number of grid points in +each direction.) Units for grid_spacing and center can be specified using the +units keyword; the default is arcsec, which is how all values are stored internally. +It automatically computes and stores grids for the shears and convergence. However, since +many users are primarily concerned with shape distortion due to shear, the default is to +return only the shear components; the get_convergence keyword can be used to also +return the convergence.

+

The quantities that are returned are the theoretical shears and convergences, usually +denoted gamma and kappa, respectively. Users who wish to obtain the more +observationally-relevant reduced shear and magnification (that describe real lensing +distortions) can either use the getShear, getMagnification, or getLensing methods +after buildGrid, or can use the convenience function galsim.lensing_ps.theoryToObserved +to convert from theoretical to observed quantities.

+

Caveats of the DFT approach:

+

Note that the shears generated using this method correspond to the PowerSpectrum multiplied +by a sharp bandpass filter, set by the dimensions of the grid.

+

The filter sets \(P(k) = 0\) for:

+
abs(k1), abs(k2) < kmin / 2
+
+
+

and:

+
abs(k1), abs(k2) > kmax + kmin / 2
+
+
+

where:

+
kmin = 2. * pi / (ngrid * grid_spacing)
+kmax = pi / grid_spacing
+
+
+

and where we have adopted the convention that grid points at a given k represent the +interval between (k - dk/2) and (k + dk/2) (noting that the grid spacing dk in k space +is equivalent to kmin).

+

It is worth remembering that this bandpass filter will not look like a circular annulus +in 2D k space, but is rather more like a thick-sided picture frame, having a small +square central cutout of dimensions kmin by kmin. These properties are visible in +the shears generated by this method.

+

If you care about these effects and want to ameliorate their effect, there are two +optional kwargs you can provide: kmin_factor and kmax_factor, both of which are 1 +by default. These should be integers >= 1 that specify some factor smaller or larger +(for kmin and kmax respectively) you want the code to use for the underlying grid in +fourier space. The final shear grid is returned using the specified ngrid and +grid_spacing parameters. But the intermediate grid in Fourier space will be larger +by the specified factors.

+

Note: These are really just for convenience, since you could easily get the same effect +by providing different values of ngrid and grid_spacing and then take a subset of them. +The kmin_factor and kmax_factor just handle the scalings appropriately for you.

+

Use of kmin_factor and kmax_factor should depend on the desired application. For +accurate representation of power spectra, one should not change these values from their +defaults of 1. Changing them from one means the E- and B-mode power spectra that are input +will be valid for the larger intermediate grids that get generated in Fourier space, but not +necessarily for the smaller ones that get returned to the user. However, for accurate +representation of cosmological shear correlation functions, use of kmin_factor larger +than one can be helpful in getting the shear correlations closer to the ideal theoretical +ones (see devel/module/lensing_engine.pdf for details).

+

Aliasing:

+

If the user provides a power spectrum that does not include a cutoff at kmax, then our +method of generating shears will result in aliasing that will show up in both E- and +B-modes. Thus the buildGrid method accepts an optional keyword argument called +bandlimit that can tell the PowerSpectrum object to cut off power above kmax +automatically, where the relevant kmax is larger than the grid Nyquist frequency by a factor +of kmax_factor. The allowed values for bandlimit are None (i.e., do nothing), +hard (set power to zero above the band limit), or soft (use an arctan-based +softening function to make the power go gradually to zero above the band limit). By +default, bandlimit=hard. Use of this keyword does nothing to the internal +representation of the power spectrum, so if the user calls the buildGrid method again, +they will need to set bandlimit again (and if their grid setup is different in a way +that changes kmax, then that’s fine).

+

Interpolation:

+

If the grid is being created for the purpose of later interpolating to random positions, the +following findings should be kept in mind: since the interpolant modifies the effective +shear correlation function on scales comparable to <~3x the grid spacing, the grid spacing +should be chosen to be at least 3 times smaller than the minimum scales on which the user +wishes to reproduce the shear correlation function accurately. Ideally, the grid should be +somewhat larger than the region in which shears at random points are needed, so that edge +effects in the interpolation will not be important. For this purpose, there should be >~5 +grid points outside of the region in which interpolation will take place. Ignoring this +edge effect and using the grid for interpolation out to its edges can suppress shear +correlations on all scales by an amount that depends on the grid size; for a 100x100 grid, +the suppression is ~2-3%. Note that the above numbers came from tests that use a +cosmological shear power spectrum; precise figures for this suppression can also depend on +the shear correlation function itself.

+

Sign conventions and other info:

+

Note also that the convention for axis orientation differs from that for the GREAT10 +challenge, so when using codes that deal with GREAT10 challenge outputs, the sign of our g2 +shear component must be flipped.

+

For more information on the effects of finite grid representation of the power spectrum +see devel/modules/lensing_engine.pdf.

+

Examples:

+
    +
  1. Get shears on a grid of points separated by 1 arcsec:

    +
    >>> my_ps = galsim.PowerSpectrum(lambda k : k**2)
    +>>> g1, g2 = my_ps.buildGrid(grid_spacing = 1., ngrid = 100)
    +
    +
    +

    The returned g1, g2 are 2-d NumPy arrays of values, corresponding to the values of +g1 and g2 at the locations of the grid points.

    +

    For a given value of grid_spacing and ngrid, we could get the x and y values on +the grid using:

    +
    >>> import numpy as np
    +>>> min = (-ngrid/2 + 0.5) * grid_spacing
    +>>> max = (ngrid/2 - 0.5) * grid_spacing
    +>>> x, y = np.meshgrid(np.arange(min,max+grid_spacing,grid_spacing),
    +...                    np.arange(min,max+grid_spacing,grid_spacing))
    +
    +
    +

    where the center of the grid is taken to be (0,0).

    +
  2. +
  3. Rebuild the grid using a particular rng and set the location of the center of the grid +to be something other than the default (0,0):

    +
    >>> g1, g2 = my_ps.buildGrid(grid_spacing = 8., ngrid = 65,
    +...                          rng = galsim.BaseDeviate(1413231),
    +...                          center = galsim.PositionD(256.5, 256.5) )
    +
    +
    +
  4. +
  5. Make a PowerSpectrum from a tabulated \(P(k)\) that gets interpolated to find the +power at all necessary values of k, then generate shears and convergences on a grid, and +convert to reduced shear and magnification so they can be used to transform galaxy +images. E.g., assuming that k and P_k are NumPy arrays containing k and \(P(k)\):

    +
    >>> tab_pk = galsim.LookupTable(k, P_k)
    +>>> my_ps = galsim.PowerSpectrum(tab_pk)
    +>>> g1, g2, kappa = my_ps.buildGrid(grid_spacing = 1., ngrid = 100,
    +...                                 get_convergence = True)
    +>>> g1_r, g2_r, mu = galsim.lensing_ps.theoryToObserved(g1, g2, kappa)
    +
    +
    +
  6. +
+
+
Parameters:
+
    +
  • grid_spacing – Spacing for an evenly spaced grid of points, by default in arcsec +for consistency with the natural length scale of images created +using the GSObject.drawImage method. Other units can be +specified using the units keyword.

  • +
  • ngrid – Number of grid points in each dimension. [Must be an integer]

  • +
  • rng – A BaseDeviate object for drawing the random numbers. +[default: None]

  • +
  • interpolantInterpolant that will be used for interpolating the gridded shears +by methods like getShear, getConvergence, etc. if they are +later called. [default: galsim.Lanczos(5)]

  • +
  • center – If setting up a new grid, define what position you want to consider +the center of that grid. Units must be consistent with those for +grid_spacing. [default: galsim.PositionD(0,0)]

  • +
  • units – The angular units used for the positions. [default: arcsec]

  • +
  • get_convergence – Return the convergence in addition to the shear? Regardless of the +value of get_convergence, the convergence will still be computed +and stored for future use. [default: False]

  • +
  • kmin_factor

    Factor by which the grid spacing in fourier space is smaller than +the default. i.e.:

    +
    kmin = 2. * pi / (ngrid * grid_spacing) / kmin_factor
    +
    +
    +

    [default: 1; must be an integer]

    +

  • +
  • kmax_factor

    Factor by which the overall grid in fourier space is larger than +the default. i.e.:

    +
    kmax = pi / grid_spacing * kmax_factor
    +
    +
    +

    [default: 1; must be an integer]

    +

  • +
  • bandlimit – Keyword determining how to handle power \(P(k)\) above the +limiting k value, kmax. The options None, ‘hard’, and ‘soft’ +correspond to doing nothing (i.e., allow P(>kmax) to be aliased to +lower k values), cutting off all power above kmax, and applying a +softening filter to gradually cut off power above kmax. Use of +this keyword does not modify the internally-stored power spectrum, +just the shears generated for this particular call to buildGrid. +[default: “hard”]

  • +
  • variance – Optionally renormalize the variance of the output shears to a +given value. This is useful if you know the functional form of +the power spectrum you want, but not the normalization. This lets +you set the normalization separately. The resulting shears should +have var(g1) + var(g2) ~= variance. If only e_power_function is +given, then this is also the variance of kappa. Otherwise, the +variance of kappa may be smaller than the specified variance. +[default: None]

  • +
+
+
Returns:
+

the tuple (g1,g2[,kappa]), where each is a 2-d NumPy array and kappa is included +iff get_convergence is set to True.

+
+
+
+ +
+
+calculateXi(grid_spacing, ngrid, kmax_factor=1, kmin_factor=1, n_theta=100, units=coord.arcsec, bandlimit='hard')[source]
+

Calculate shear correlation functions for the current power spectrum on the specified +grid.

+

This function will calculate the theoretical shear correlation functions, \(\xi_+\) +and \(\xi_-\), for this power spectrum and the grid configuration specified using +keyword arguments, taking into account the minimum and maximum k range implied by the grid +parameters, kmin_factor and kmax_factor. Most theoretical correlation function +calculators assume an infinite k range, so this utility can be used to check how close the +chosen grid parameters (and the implied minimum and maximum k) come to the “ideal” result. +This is particularly useful on large scales, since in practice the finite grid extent +limits the minimum k value and therefore can suppress shear correlations on large scales. +Note that the actual shear correlation function in the generated shears will still differ +from the one calculated here due to differences between the discrete and continuous Fourier +transform.

+

The quantities that are returned are three NumPy arrays: separation theta (in the adopted +units), \(\xi_+\), and \(\xi_-\). These are defined in terms of the E- and B-mode +shear power spectrum as in the document devel/modules/lensing_engine.pdf, equations 2 +and 3. The values that are returned are for a particular theta value, not an average over +a range of theta values in some bin of finite width.

+

This method has been tested with cosmological shear power spectra; users should check for +sanity of outputs if attempting to use power spectra that have very different scalings with +k.

+
+
Parameters:
+
    +
  • grid_spacing – Spacing for an evenly spaced grid of points, by default in arcsec +for consistency with the natural length scale of images created +using the GSObject.drawImage method. Other units can be specified +using the units keyword.

  • +
  • ngrid – Number of grid points in each dimension. [Must be an integer]

  • +
  • units – The angular units used for the positions. [default = arcsec]

  • +
  • kmin_factor

    (Optional) Factor by which the grid spacing in fourier space is +smaller than the default. i.e.:

    +
    kmin = 2. * pi / (ngrid * grid_spacing) / kmin_factor
    +
    +
    +

    [default kmin_factor = 1; must be an integer]

    +

  • +
  • kmax_factor

    (Optional) Factor by which the overall grid in fourier space is +larger than the default. i.e.:

    +
    kmax = pi / grid_spacing * kmax_factor
    +
    +
    +

    [default kmax_factor = 1; must be an integer]

    +

  • +
  • n_theta – (Optional) Number of logarithmically spaced bins in angular +separation. [default n_theta=100]

  • +
  • bandlimit – (Optional) Keyword determining how to handle power \(P(k)\) above +the limiting k value, kmax. The options None, ‘hard’, and ‘soft’ +correspond to doing nothing (i.e., allow P(>kmax) to be aliased to +lower k values), cutting off all power above kmax, and applying a +softening filter to gradually cut off power above kmax. Use of this +keyword does not modify the internally-stored power spectrum, just +the result generated by this particular call to calculateXi. +[default bandlimit="hard"]

  • +
+
+
Returns:
+

the tuple (theta, xi_p, xi_m), 1-d NumPy arrays for the angular separation theta +and the two shear correlation functions.

+
+
+
+ +
+
+getConvergence(pos, units=coord.arcsec, periodic=False)[source]
+

This function can interpolate between grid positions to find the convergence values for a +given list of input positions (or just a single position). Before calling this function, +you must call buildGrid first to define the grid of convergences on which to interpolate. +The docstring for buildGrid provides some guidance on appropriate grid configurations to +use when building a grid that is to be later interpolated to random positions.

+

Note that the interpolation (specified when calling buildGrid) modifies the effective +2-point functions of these quantities. See docstring for getShear docstring for caveats +about interpolation. The user is advised to be very careful about deviating from the +default Lanczos-5 interpolant.

+

The usage of getConvergence() is the same as for getShear, except that it returns only a +single quantity (convergence value or array of convergence values) rather than two +quantities. See documentation for getShear for some examples.

+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list or array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • units – The angular units used for the positions. [default: arcsec]

  • +
  • periodic – Whether the interpolation should treat the positions as being defined +with respect to a periodic grid, which will wrap them around if they +are outside the bounds of the original grid on which shears and +convergences were defined. If not, then convergences are set to zero +for positions outside the original grid. [default: False]

  • +
+
+
Returns:
+

the convergence, kappa (either a scalar or a numpy array)

+
+
+

If the input pos is given a single position, kappa is the convergence value. +If the input pos is given a list/array of positions, kappa is a NumPy array.

+
+ +
+
+getLensing(pos, units=coord.arcsec, periodic=False)[source]
+

This function can interpolate between grid positions to find the lensing observable +quantities (reduced shears g1 and g2, and magnification mu) for a given list of input +positions (or just a single position). Before calling this function, you must call +buildGrid first to define the grid of shears and convergences on which to interpolate. +The docstring for buildGrid provides some guidance on appropriate grid configurations to +use when building a grid that is to be later interpolated to random positions.

+

Note that the interpolation (specified when calling buildGrid) modifies the effective +2-point functions of these quantities. See docstring for getShear docstring for caveats +about interpolation. The user is advised to be very careful about deviating from the +default Lanczos-5 interpolant.

+

The usage of getLensing is the same as for getShear, except that it returns three +quantities (two reduced shear components and magnification) rather than two. See +documentation for getShear for some examples.

+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • units – The angular units used for the positions. [default: arcsec]

  • +
  • periodic – Whether the interpolation should treat the positions as being +defined with respect to a periodic grid, which will wrap them around +if they are outside the bounds of the original grid on which shears +and convergences were defined. If not, then shear is set to zero +and magnification is set to 1 for positions outside the original +grid. [default: False]

  • +
+
+
Returns:
+

shear and magnification as a tuple (g1,g2,mu).

+
+
+

If the input pos is given a single position, the return values are the shear and +magnification values at that position. +If the input pos is given a list/array of positions, they are NumPy arrays.

+
+ +
+
+getMagnification(pos, units=coord.arcsec, periodic=False)[source]
+

This function can interpolate between grid positions to find the lensing magnification (mu) +values for a given list of input positions (or just a single position). Before calling this +function, you must call buildGrid first to define the grid of shears and convergences on +which to interpolate. The docstring for buildGrid provides some guidance on appropriate +grid configurations to use when building a grid that is to be later interpolated to random +positions.

+

Note that the interpolation (specified when calling buildGrid) modifies the effective +2-point functions of these quantities. See docstring for getShear docstring for caveats +about interpolation. The user is advised to be very careful about deviating from the +default Lanczos-5 interpolant.

+

The usage of getMagnification is the same as for getShear, except that it returns only +a single quantity (a magnification value or array of magnification values) rather than a +pair of quantities. See documentation for getShear for some examples.

+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • units – The angular units used for the positions. [default: arcsec]

  • +
  • periodic – Whether the interpolation should treat the positions as being +defined with respect to a periodic grid, which will wrap them around +if they are outside the bounds of the original grid on which shears +and convergences were defined. If not, then magnification is set to +1 for positions outside the original grid. [default: False]

  • +
+
+
Returns:
+

the magnification, mu (either a scalar or a numpy array)

+
+
+

If the input pos is given a single position, mu is the magnification value. +If the input pos is given a list/array of positions, mu is a NumPy array.

+
+ +
+
+getShear(pos, units=coord.arcsec, reduced=True, periodic=False)[source]
+

This function can interpolate between grid positions to find the shear values for a given +list of input positions (or just a single position). Before calling this function, you must +call buildGrid first to define the grid of shears and convergences on which to +interpolate. The docstring for buildGrid provides some guidance on appropriate grid +configurations to use when building a grid that is to be later interpolated to random +positions.

+

By default, this method returns the reduced shear, which is defined in terms of shear and +convergence as reduced shear g=gamma/(1-kappa); the reduced keyword can be set to +False in order to return the non-reduced shear.

+

Note that the interpolation (specified when calling buildGrid) modifies the effective +shear power spectrum and correlation function somewhat, though the effects can be limited +by careful choice of grid parameters (see buildGrid() docstring for details). Assuming +those guidelines are followed, then the shear correlation function modifications due to use +of the quintic, Lanczos-3, and Lanczos-5 interpolants are below 5% on all scales from the +grid spacing to the total grid extent, typically below 2%. The linear, cubic, and nearest +interpolants perform significantly more poorly, with modifications of the correlation +functions that can reach tens of percent on the scales where the recommended interpolants +perform well. Thus, the default interpolant is Lanczos-5, and users should think carefully +about the acceptability of significant modification of the shear correlation function before +changing to use linear, cubic, or nearest.

+

Users who wish to ensure that the shear power spectrum is preserved post-interpolation +should consider using the periodic interpolation option, which assumes the shear field +is periodic (i.e., the sky is tiled with many copies of the given shear field). Those who +care about the correlation function should not use this option, and for this reason it’s +not the default.

+

Examples:

+
    +
  1. Get the shear for a particular point:

    +
    >>> g1, g2 = my_ps.getShear(pos = galsim.PositionD(12, 412))
    +
    +
    +

    This time the returned values are just floats and correspond to the shear for the +provided position.

    +
  2. +
  3. You can also provide a position as a tuple to save the explicit PositionD construction:

    +
    >>> g1, g2 = my_ps.getShear(pos = (12, 412))
    +
    +
    +
  4. +
  5. Get the shears for a bunch of points at once:

    +
    >>> xlist = [ 141, 313,  12, 241, 342 ]
    +>>> ylist = [  75, 199, 306, 225, 489 ]
    +>>> poslist = [ galsim.PositionD(xlist[i],ylist[i]) for i in range(len(xlist)) ]
    +>>> g1, g2 = my_ps.getShear( poslist )
    +>>> g1, g2 = my_ps.getShear( (xlist, ylist) )
    +
    +
    +

    Both calls do the same thing. The returned g1, g2 this time are numpy arrays of g1, g2 +values. The arrays are the same length as the number of input positions.

    +
  6. +
+
+
Parameters:
+
    +
  • pos

    Position(s) of the source(s), assumed to be post-lensing! +Valid ways to input this:

    +
      +
    • single Position instance

    • +
    • tuple of floats: (x,y)

    • +
    • list/array of Position instances

    • +
    • tuple of lists/arrays: ( xlist, ylist )

    • +
    +

  • +
  • units – The angular units used for the positions. [default: arcsec]

  • +
  • reduced – Whether returned shear(s) should be reduced shears. [default: True]

  • +
  • periodi – Whether the interpolation should treat the positions as being defined +with respect to a periodic grid, which will wrap them around if they +are outside the bounds of the original grid on which shears were +defined. If not, then shears are set to zero for positions outside the +original grid. [default: False]

  • +
+
+
Returns:
+

the shear as a tuple, (g1,g2)

+
+
+

If the input pos is given a single position, (g1,g2) are the two shear components. +If the input pos is given a list/array of positions, they are NumPy arrays.

+
+ +
+
+nRandCallsForBuildGrid()[source]
+

Return the number of times the rng() was called the last time buildGrid was called.

+

This can be useful for keeping rngs in sync if the connection between them is broken +(e.g. when calling the function through a Proxy object).

+
+ +
+ +
+
+class galsim.lensing_ps.PowerSpectrumRealizer(ngrid, pixel_size, p_E, p_B)[source]
+

Class for generating realizations of power spectra with any area and pixel size.

+

This class is not one that end-users should expect to interact with. It is designed to quickly +generate many realizations of the same shear power spectra on a square grid. The initializer +sets up the grids in k-space and computes the power on them. It also computes spin weighting +terms. You can alter any of the setup properties later. It currently only works for square +grids (at least, much of the internals would be incorrect for non-square grids), so while it +nominally contains arrays that could be allowed to be non-square, the constructor itself +enforces squareness.

+
+
Parameters:
+
    +
  • ngrid – The size of the grid in one dimension.

  • +
  • pixel_size – The size of the pixel sides, in units consistent with the units expected +by the power spectrum functions.

  • +
  • p_E – Equivalent to e_power_function in the documentation for the +PowerSpectrum class.

  • +
  • p_B – Equivalent to b_power_function in the documentation for the +PowerSpectrum class.

  • +
+
+
+
+
+__call__(gd, variance=None)[source]
+

Generate a realization of the current power spectrum.

+
+
Parameters:
+
    +
  • gd – A Gaussian deviate to use when generating the shear fields.

  • +
  • variance – Optionally renormalize the variance of the output shears to a +given value. This is useful if you know the functional form of +the power spectrum you want, but not the normalization. This lets +you set the normalization separately. The resulting shears should +have var(g1) + var(g2) ~= variance. If only e_power_function is +given, then this is also the variance of kappa. Otherwise, the +variance of kappa may be smaller than the specified variance. +[default: None]

  • +
+
+
+

@return a tuple of NumPy arrays (g1,g2,kappa) for the shear and convergence.

+
+ +
+ +
+
+galsim.lensing_ps.theoryToObserved(gamma1, gamma2, kappa)[source]
+

Helper function to convert theoretical lensing quantities to observed ones.

+

This helper function is used internally by the methods PowerSpectrum.getShear, +PowerSpectrum.getMagnification, and PowerSpectrum.getLensing to convert from theoretical +quantities (shear and convergence) to observable ones (reduced shear and magnification). +Users of PowerSpectrum.buildGrid outputs can also apply this method directly to the outputs +in order to get the values of reduced shear and magnification on the output grid.

+
+
Parameters:
+
    +
  • gamma1 – The first shear component, which must be the NON-reduced shear. This and +all other inputs may be supplied either as individual floating point +numbers or lists/arrays of floats.

  • +
  • gamma2 – The second (x) shear component, which must be the NON-reduced shear.

  • +
  • kappa – The convergence.

  • +
+
+
Returns:
+

the reduced shear and magnification as a tuple (g1, g2, mu)

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/pse.html b/docs/_build/html/pse.html new file mode 100644 index 00000000000..0fe80031349 --- /dev/null +++ b/docs/_build/html/pse.html @@ -0,0 +1,233 @@ + + + + + + + Power Spectrum Estimation — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Power Spectrum Estimation

+

The galsim.pse module was developed largely by Joe Zuntz and tweaked by assorted GalSim +developers. This development and testing of this module took place in a separate (private) +repository before the code was moved into the GalSim repository, but there are some demonstrations +of the performance of this code in devel/modules/lensing_engine.pdf.

+
+
+class galsim.pse.PowerSpectrumEstimator(N=100, sky_size_deg=10.0, nbin=15)[source]
+

Class for estimating the shear power spectrum from gridded shears.

+

This class stores all the data used in power spectrum estimation that is fixed with the geometry +of the problem - the binning and spin weighting factors.

+

The only public method is estimate(), which is called with 2D g1 and g2 arrays on a +square grid. It assumes the flat sky approximation (where ell and k are +interchangeable), and rebins the observed ell modes into a user-defined number of logarithimic +bins in ell. Given that the grid parameters are precomputed and stored when the +PowerSpectrumEstimator is initialized, computation of the PS for multiple sets of shears +corresponding to the same grid setup can proceed more rapidly than if everything had to be +recomputed each time.

+

Below is an example of how to use this code (relying on GalSim to provide the arrays of g1 and +g2, though that is by no means required, and assuming that the user is sitting in the examples/ +directory):

+
>>> grid_size = 10.  # Define the total grid extent, in degrees
+>>> ngrid = 100      # Define the number of grid points in each dimension: (ngrid x ngrid)
+>>> n_ell = 15       # Choose the number of logarithmic bins in ell or k for outputs
+>>>
+>>> # Define a lookup-table for the power spectrum as a function of k based on the outputs
+>>> # of iCosmo (see demo11.py for more description of how this was generated).
+>>> my_tab = galsim.LookupTable(file='data/cosmo-fid.zmed1.00.out')
+>>>
+>>> # Generate a galsim.PowerSpectrum with this P(k), noting the units.
+>>> my_ps = galsim.PowerSpectrum(my_tab, units=galsim.radians)
+>>>
+>>> # Build a grid of shear values with the desired parameters.
+>>> g1, g2 = my_ps.buildGrid(grid_spacing=grid_size/ngrid, ngrid=ngrid,
+...                          units=galsim.degrees)
+>>>
+>>> # Initialize a PowerSpectrumEstimator with the chosen grid geometry and number of ell
+>>> # bins. Note that these values are actually the default, so we didn't technically have
+>>> # to specifythem.
+>>> my_pse = galsim.pse.PowerSpectrumEstimator(ngrid, grid_size, n_ell)
+>>>
+>>> # Estimate the power based on this set of g1, g2.  If we get another set of shears for
+>>> # the same grid geometry, we can reuse the same PowerSpectrumEstimator object.
+>>> ell, P_e, P_b, P_eb = my_pse.estimate(g1, g2)
+
+
+

The output NumPy arrays ell, P_e, P_b, and P_eb contain the effective ell +value, the E-mode auto-power spectrum, the B-mode auto-power spectrum, and the EB cross-power +spectrum. The units are inverse radians for ell, and radians^2 for the output power spectra.

+

Some important notes:

+
    +
  1. Power spectrum estimation requires a weight function which decides how the averaging is done +across ell within each bin. By default that weighting is flat in ell using an analytic +calculation of the area in ell space, but this is easy to change with the _bin_power +function. (Note this area averaged bin weighting is only approximate for the higher +frequency bins in which the lower ell edge is greater than pi * ngrid / grid_size, +due to the annular ell region being cut off by the square grid edges beyond this value.) +A keyword allows for weighting by the power itself.

  2. +
  3. This is the power spectrum of the gridded data, not the underlying field - we do not +account for the effects of the finite grid (basically, ignoring all the reasons why power +spectrum estimation is hard - see devel/modules/lensing_engine.pdf in the GalSim repository). +Users must account for the contribution of noise in g1, g2 and any masking.

  4. +
  5. The binning is currently fixed as uniform in log(ell).

  6. +
  7. The code for this class uses the notation of the GREAT10 handbook (Kitching et al. 2011, +http://dx.doi.org/10.1214/11-AOAS484), equations 17-21.

  8. +
+

galsim.pse.PowerSpectrumEstimator.__init__

+
+
+estimate(g1, g2, weight_EE=False, weight_BB=False, theory_func=None)[source]
+

Compute the EE, BB, and EB power spectra of two 2D arrays g1 and g2.

+

For example usage, see the docstring for the PowerSpectrumEstimator class.

+
+
Parameters:
+
    +
  • g1 – The shear component g1 as a square 2D NumPy array.

  • +
  • g2 – The shear component g2 as a square 2D NumPy array.

  • +
  • weight_EE – If True, then the E auto-power spectrum is re-computed weighting by +the power within each logarithmically-spaced ell bin. [default: False]

  • +
  • weight_BB – If True, then the B auto-power spectrum is re-computed weighting by +the power within each logarithmically-spaced ell bin. [default: False]

  • +
  • theory_func – An optional callable function that can be used to get an idealized +value of power at each point on the grid, and then see what results +it gives for our chosen ell binning. [default: None]

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/psf.html b/docs/_build/html/psf.html new file mode 100644 index 00000000000..513fb6d2401 --- /dev/null +++ b/docs/_build/html/psf.html @@ -0,0 +1,853 @@ + + + + + + + Point-spread functions — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Point-spread functions

+

There are a number of profiles that are designed to be used for point-spread functions (PSFs). +There is nothing in GalSim restricting these classes to be used only for PSFs or stars, +but that was the use case they were designed for.

+
+

Airy Profile

+
+
+class galsim.Airy(lam_over_diam=None, lam=None, diam=None, obscuration=0.0, flux=1.0, scale_unit=None, gsparams=None)[source]
+

Bases: GSObject

+

A class describing the surface brightness profile for an Airy disk (perfect +diffraction-limited PSF for a circular aperture), with an optional central obscuration.

+

For more information, refer to

+

http://en.wikipedia.org/wiki/Airy_disc

+

The Airy profile is defined in terms of the diffraction angle, which is a function of the +ratio lambda / D, where lambda is the wavelength of the light (say in the middle of the +bandpass you are using) and D is the diameter of the telescope.

+

The natural units for this value is radians, which is not normally a convenient unit to use for +other GSObject dimensions. Assuming that the other sky coordinates you are using are all in +arcsec (e.g. the pixel scale when you draw the image, the size of the galaxy, etc.), then you +should convert this to arcsec as well:

+
>>> lam = 700  # nm
+>>> diam = 4.0    # meters
+>>> lam_over_diam = (lam * 1.e-9) / diam  # radians
+>>> lam_over_diam *= 206265  # Convert to arcsec
+>>> airy = galsim.Airy(lam_over_diam)
+
+
+

To make this process a bit simpler, we recommend instead providing the wavelength and diameter +separately using the parameters lam (in nm) and diam (in m). GalSim will then convert +this to any of the normal kinds of angular units using the scale_unit parameter:

+
>>> airy = galsim.Airy(lam=lam, diam=diam, scale_unit=galsim.arcsec)
+
+
+

When drawing images, the scale_unit should match the unit used for the pixel scale or the WCS. +e.g. in this case, a pixel scale of 0.2 arcsec/pixel would be specified as pixel_scale=0.2.

+
+
Parameters:
+
    +
  • lam_over_diam – The parameter that governs the scale size of the profile. +See above for details about calculating it.

  • +
  • lam – Lambda (wavelength) either as an astropy Quantity, or as a float in units +of nanometers. Must be supplied with diam, and in this case, image +scales (scale) should be specified in units of scale_unit.

  • +
  • diam – Telescope diameter either as an astropy Quantity, or as a float in units of +meters. Must be supplied with lam, and in this case, image scales +(scale) should be specified in units of scale_unit.

  • +
  • obscuration – The linear dimension of a central obscuration as a fraction of the +pupil dimension. [default: 0]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • scale_unit – Units to use for the sky coordinates when calculating lam/diam if these +are supplied separately. Note that the results of using properties like +fwhm will be returned in units of scale_unit as well. Should +be either a galsim.AngleUnit or a string that can be used to construct +one (e.g., ‘arcsec’, ‘radians’, etc.). [default: galsim.arcsec]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property fwhm
+

The FWHM of this Airy profile (only supported for obscuration = 0.).

+
+ +
+
+property half_light_radius
+

The half light radius of this Airy profile (only supported for obscuration = 0.).

+
+ +
+
+property lam_over_diam
+

The input lambda/diam value.

+
+ +
+
+property obscuration
+

The input obscuration.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Moffat Profile

+
+
+class galsim.Moffat(beta, scale_radius=None, half_light_radius=None, fwhm=None, trunc=0.0, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a Moffat surface brightness profile.

+

The Moffat surface brightness profile is

+
+\[I(R) \sim \left(1 + (r/r_0)^2\right)^{-\beta}\]
+

where \(r_0\) is scale_radius.

+

The GalSim representation of a Moffat profile also includes an optional truncation beyond a +given radius.

+

For more information, refer to

+
+
+

A Moffat can be initialized using one (and only one) of three possible size parameters: +scale_radius, fwhm, or half_light_radius. Exactly one of these three is required.

+
+
Parameters:
+
    +
  • beta – The beta parameter of the profile.

  • +
  • scale_radius – The scale radius of the profile. Typically given in arcsec. +[One of scale_radius, fwhm, or half_light_radius is +required.]

  • +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of scale_radius, fwhm, or half_light_radius is +required.]

  • +
  • fwhm – The full-width-half-max of the profile. Typically given in arcsec. +[One of scale_radius, fwhm, or half_light_radius is +required.]

  • +
  • trunc – An optional truncation radius at which the profile is made to drop to +zero, in the same units as the size parameter. +[default: 0, indicating no truncation]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property beta
+

The beta parameter of this Moffat profile.

+
+ +
+
+property half_light_radius
+

The half-light radius of this Moffat profile.

+
+ +
+
+property scale_radius
+

The scale radius of this Moffat profile.

+
+ +
+
+property trunc
+

The truncation radius (if any) of this Moffat profile.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Kolmogorov Profile

+
+
+class galsim.Kolmogorov(lam_over_r0=None, fwhm=None, half_light_radius=None, lam=None, r0=None, r0_500=None, flux=1.0, scale_unit=None, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a Kolmogorov surface brightness profile, which represents a long +exposure atmospheric PSF.

+

The Kolmogorov profile is defined in Fourier space. Its transfer function is:

+
+\[T(k) \sim \exp(-D(k)/2)\]
+

where

+
+\[D(k) = 6.8839 \left(\frac{\lambda k}{2\pi r_0} \right)^{5/3},\]
+

\(\lambda\) is the wavelength of the light (say in the middle of the bandpass you are +using), and \(r_0\) is the Fried parameter. Typical values for the Fried parameter are on +the order of 0.1 m for most observatories and up to 0.2 m for excellent sites. The values are +usually quoted at \(\lambda\) = 500nm and \(r_0\) depends on wavelength as +\(r_0 \sim \lambda^{6/5}\).

+

For more information, refer to

+
+
+

The Kolmogorov profile is normally defined in terms of the ratio \(\lambda / r_0\). +The natural units for this ratio is radians, which is not normally a convenient unit to use for +other GSObject dimensions. Assuming that the other sky coordinates you are using are all in +arcsec (e.g. the pixel scale when you draw the image, the size of the galaxy, etc.), then you +should convert this to arcsec as well:

+
>>> lam = 700  # nm
+>>> r0 = 0.15 * (lam/500)**1.2  # meters
+>>> lam_over_r0 = (lam * 1.e-9) / r0  # radians
+>>> lam_over_r0 *= 206265  # Convert to arcsec
+>>> psf = galsim.Kolmogorov(lam_over_r0)
+
+
+

To make this process a bit simpler, we recommend instead providing the wavelength and Fried +parameter separately using the parameters lam (in nm) and either r0 or r0_500 +(in m). GalSim will then convert this to any of the normal kinds of angular units using the +scale_unit parameter:

+
>>> psf = galsim.Kolmogorov(lam=lam, r0=r0, scale_unit=galsim.arcsec)
+
+
+

or:

+
>>> psf = galsim.Kolmogorov(lam=lam, r0_500=0.15, scale_unit=galsim.arcsec)
+
+
+

When drawing images, the scale_unit should match the unit used for the pixel scale or the WCS. +e.g. in this case, a pixel scale of 0.2 arcsec/pixel would be specified as pixel_scale=0.2.

+

A Kolmogorov object may also be initialized using fwhm or half_light_radius. Exactly +one of these four ways to define the size is required.

+

The FWHM of the Kolmogorov PSF is \(\sim 0.976 \lambda/r_0\) arcsec. +(e.g., Racine 1996, PASP 699, 108).

+
+
Parameters:
+
    +
  • lam_over_r0 – The parameter that governs the scale size of the profile. +See above for details about calculating it. [One of lam_over_r0, +fwhm, half_light_radius, or lam (along with either r0 or +r0_500) is required.]

  • +
  • fwhm – The full-width-half-max of the profile. Typically given in arcsec. +[One of lam_over_r0, fwhm, half_light_radius, or lam +(along with either r0 or r0_500) is required.]

  • +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of lam_over_r0, fwhm, half_light_radius, or lam +(along with either r0 or r0_500) is required.]

  • +
  • lam – Lambda (wavelength) either as an astropy Quantity or a float in units of +nanometers. Must be supplied with either r0 or r0_500, and in +this case, image scales (scale) should be specified in units of +scale_unit.

  • +
  • r0 – The Fried parameter, either as an astropy Quantity or a float in units +of meters. Must be supplied with lam, and in this case, image +scales (scale) should be specified in units of scale_unit.

  • +
  • r0_500 – The Fried parameter at wavelength of 500 nm, either as an astropy +Quantity or a float in units of meters. The Fried parameter at the +given wavelength, lam, will be computed using the standard relation +r0 = r0_500 * (lam/500)**1.2.

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • scale_unit – Units to use for the sky coordinates when calculating lam/r0 if these +are supplied separately. Note that the results of using properties +like fwhm will be returned in units of scale_unit as well. Should +be either a galsim.AngleUnit or a string that can be used to construct +one (e.g., ‘arcsec’, ‘radians’, etc.). [default: galsim.arcsec]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+

In addition to the usual GSObject methods, Kolmogorov has the following access properties:

+
+
Attributes:
+
    +
  • lam_over_r0 – The input ratio lambda / r0

  • +
  • fwhm – The full-width half-max size

  • +
  • half_light_radius – The half-light radius

  • +
+
+
+
+
+property fwhm
+

The FWHM of this Kolmogorov profile.

+
+ +
+
+property half_light_radius
+

The half light radius of this Kolmogorov profile.

+
+ +
+
+property lam_over_r0
+

The input lambda / r0 for this Kolmogorov profile.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Von Karman Profile

+
+
+class galsim.VonKarman(lam, r0=None, r0_500=None, L0=25.0, flux=1, scale_unit=coord.arcsec, force_stepk=0.0, do_delta=False, suppress_warning=False, gsparams=None)[source]
+

Bases: GSObject

+

Class describing a von Karman surface brightness profile, which represents a long exposure +atmospheric PSF. The difference between the von Karman profile and the related Kolmogorov +profile is that the von Karman profile includes a parameter for the outer scale of atmospheric +turbulence, which is a physical scale beyond which fluctuations in the refractive index stop +growing, typically between 10 and 100 meters. Quantitatively, the von Karman phase fluctuation +power spectrum at spatial frequency f is proportional to

+
+\[P(f) = r_0^{-5/3} \left(f^2 + L_0^{-2}\right)^{-11/6}\]
+

where \(r_0\) is the Fried parameter which sets the overall turbulence amplitude and +\(L_0\) is the outer scale in meters. +The Kolmogorov power spectrum proportional to \(r_0^{-5/3} f^{-11/3}\) is recovered +as \(L_0 \rightarrow \infty\).

+

For more information, we recommend the following references:

+
+

Martinez et al. 2010 A&A vol. 516 +Conan 2008 JOSA vol. 25

+
+
+

Note

+

If one blindly follows the math for converting the von Karman power spectrum into a PSF, one +finds that the PSF contains a delta-function at the origin with fractional flux of:

+
+\[F_\mathrm{delta} = e^{-0.086 (r_0/L_0)^{-5/3}}\]
+

In almost all cases of interest this evaluates to something tiny, often on the order of +\(10^{-100}\) or smaller. By default, GalSim will ignore this delta function entirely +since it usually doesn’t make any difference, but can complicate some calculations like +drawing using method=’real_space’ or by formally requiring huge Fourier transforms for +drawing using method=’fft’. If for some reason you want to keep the delta function +though, then you can pass the do_delta=True argument to the VonKarman initializer.

+
+
+
Parameters:
+
    +
  • lam – Wavelength, either as an astropy Quantity or a float in nanometers.

  • +
  • r0 – Fried parameter at specified wavelength lam, either as an astropy +Quantity or a float in meters. Exactly one of r0 and r0_500 should be +specified.

  • +
  • r0_500 – Fried parameter at 500 nm, either as an astropy Quantity or a float in +meters. Exactly one of r0 and r0_500 should be specified.

  • +
  • L0 – Outer scale, either as an astropy Quantity or a float in meters. +[default: 25.0 m]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • scale_unit – Units assumed when drawing this profile or evaluating xValue, kValue, +etc. Should be a galsim.AngleUnit or a string that can be used to +construct one (e.g., ‘arcsec’, ‘radians’, etc.). +[default: galsim.arcsec]

  • +
  • force_stepk – By default, VonKarman will derive a value of stepk from a computed +real-space surface brightness profile and gsparams settings. Although +this profile is cached for future instantiations of identical VonKarman +objects, it is relatively slow to compute for the first instance and +can dominate the compute time when drawing many VonKaman’s with +different parameters using method ‘fft’, ‘sb’, or ‘no_pixel’, a +situation that may occur, e.g., in a fitting context. This keyword +enables a user to bypass the real-space profile computation by directly +specifying a stepk value. Note that if the .half_light_radius +property is queried, or the object is drawn using method ‘phot’ or +‘real_space’, then the real-space profile calculation is performed (if +not cached) at that point. [default: 0.0, which means do not force a +value of stepk]

  • +
  • do_delta – Include delta-function at origin? (not recommended; see above). +[default: False]

  • +
  • suppress_warning – For some combinations of r0 and L0, it may become impossible to satisfy +the gsparams.maxk_threshold criterion (see above). In that case, the +code will emit a warning alerting the user that they may have entered a +non-physical regime. However, this warning can be suppressed with this +keyword. [default: False]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property L0
+

The input L0 value.

+
+ +
+
+property delta_amplitude
+

The amplitude of the delta function at the center.

+
+ +
+
+property do_delta
+

Whether to include the delta function at the center.

+
+ +
+
+property force_stepk
+

Forced value of stepk or 0.0.

+
+ +
+
+property half_light_radius
+

The half-light radius.

+
+ +
+
+property lam
+

The input lam value.

+
+ +
+
+property r0
+

The input r0 value.

+
+ +
+
+property r0_500
+

The input r0_500 value.

+
+ +
+
+property scale_unit
+

The input scale_units.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Optical PSF

+
+
+class galsim.OpticalPSF(lam_over_diam=None, lam=None, diam=None, tip=0.0, tilt=0.0, defocus=0.0, astig1=0.0, astig2=0.0, coma1=0.0, coma2=0.0, trefoil1=0.0, trefoil2=0.0, spher=0.0, aberrations=None, annular_zernike=False, aper=None, circular_pupil=True, obscuration=0.0, interpolant=None, oversampling=1.5, pad_factor=1.5, ii_pad_factor=None, flux=1.0, nstruts=0, strut_thick=0.05, strut_angle=coord.Angle(0.0, coord.radians), pupil_plane_im=None, pupil_plane_scale=None, pupil_plane_size=None, pupil_angle=coord.Angle(0.0, coord.radians), scale_unit=coord.arcsec, fft_sign='+', gsparams=None, _force_stepk=0.0, _force_maxk=0.0, suppress_warning=False, geometric_shooting=False)[source]
+

Bases: GSObject

+

A class describing aberrated PSFs due to telescope optics. Its underlying implementation +uses an InterpolatedImage to characterize the profile.

+

The diffraction effects are characterized by the diffraction angle, which is a function of the +ratio lambda / D, where lambda is the wavelength of the light and D is the diameter of the +telescope. The natural unit for this value is radians, which is not normally a convenient +unit to use for other GSObject dimensions. Assuming that the other sky coordinates you are +using are all in arcsec (e.g. the pixel scale when you draw the image, the size of the galaxy, +etc.), then you should convert this to arcsec as well:

+
>>> lam = 700  # nm
+>>> diam = 4.0    # meters
+>>> lam_over_diam = (lam * 1.e-9) / diam  # radians
+>>> lam_over_diam *= 206265  # Convert to arcsec
+>>> psf = galsim.OpticalPSF(lam_over_diam, ...)
+
+
+

To make this process a bit simpler, we recommend instead providing the wavelength and diameter +separately using the parameters lam (in nm) and diam (in m). GalSim will then convert +this to any of the normal kinds of angular units using the scale_unit parameter:

+
>>> psf = galsim.OpticalPSF(lam=lam, diam=diam, scale_unit=galsim.arcsec, ...)
+
+
+

When drawing images, the scale_unit should match the unit used for the pixel scale or the WCS. +e.g. in this case, a pixel scale of 0.2 arcsec/pixel would be specified as pixel_scale=0.2.

+

Input aberration coefficients are assumed to be supplied in units of wavelength, and correspond +to the Zernike polynomials in the Noll convention defined in +Noll, J. Opt. Soc. Am. 66, 207-211(1976). For a brief summary of the polynomials, refer to +http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials. By default, the +aberration coefficients indicate the amplitudes of _circular_ Zernike polynomials, which are +orthogonal over a circle. If you would like the aberration coefficients to instead be +interpretted as the amplitudes of _annular_ Zernike polynomials, which are orthogonal over an +annulus (see Mahajan, J. Opt. Soc. Am. 71, 1 (1981)), set the annular_zernike keyword +argument to True.

+

There are two ways to specify the geometry of the pupil plane, i.e., the obscuration disk size +and the areas that will be illuminated outside of it. The first way is to use keywords that +specify the size of the obscuration, and the nature of the support struts holding up the +secondary mirror (or prime focus cage, etc.). These are taken to be rectangular obscurations +extending from the outer edge of the pupil to the outer edge of the obscuration disk (or the +pupil center if obscuration = 0.). You can specify how many struts there are (evenly spaced +in angle), how thick they are as a fraction of the pupil diameter, and what angle they start at +relative to the positive y direction.

+

The second way to specify the pupil plane configuration is by passing in an image of it. This +can be useful for example if the struts are not evenly spaced or are not radially directed, as +is assumed by the simple model for struts described above. In this case, keywords related to +struts are ignored; moreover, the obscuration keyword is used to ensure that the images are +properly sampled (so it is still needed), but the keyword is then ignored when using the +supplied image of the pupil plane. Note that for complicated pupil configurations, it may be +desireable to increase pad_factor for more fidelity at the expense of slower running time. +The pupil_plane_im that is passed in can be rotated during internal calculations by +specifying a pupil_angle keyword.

+

If you choose to pass in a pupil plane image, it must be a square array in which the image of +the pupil is centered. The areas that are illuminated should have some value >0, and the other +areas should have a value of precisely zero. Based on what the OpticalPSF class thinks is the +required sampling to make the PSF image, the image that is passed in of the pupil plane might be +zero-padded during internal calculations. The pixel scale of the pupil plane can be specified +in one of three ways. In descending order of priority, these are:

+
    +
  1. The pupil_plane_scale keyword argument (units are meters).

  2. +
  3. The pupil_plane_im.scale attribute (units are meters).

  4. +
  5. If (1) and (2) are both None, then the scale will be inferred by assuming that the +illuminated pixel farthest from the image center is at a physical distance of self.diam/2.

  6. +
+

Note that if the scale is specified by either (1) or (2) above (which always includes specifying +the pupil_plane_im as a filename, since the default scale then will be 1.0), then the +lam_over_diam keyword must not be used, but rather the lam and diam keywords are required +separately. Finally, to ensure accuracy of calculations using a pupil plane image, we recommend +sampling it as finely as possible.

+

As described above, either specify the lam/diam ratio directly in arbitrary units:

+
>>> optical_psf = galsim.OpticalPSF(lam_over_diam=lam_over_diam, defocus=0., ...)
+
+
+

or, use separate keywords for the telescope diameter and wavelength in meters and nanometers, +respectively:

+
>>> optical_psf = galsim.OpticalPSF(lam=lam, diam=diam, defocus=0., ...)
+
+
+

Either of these options initializes optical_psf as an OpticalPSF instance.

+
+
Parameters:
+
    +
  • lam_over_diam – Lambda / telescope diameter in the physical units adopted for scale +(user responsible for consistency). Either lam_over_diam, or +lam and diam, must be supplied.

  • +
  • lam – Lambda (wavelength), either as an astropy Quantity or a float in units +of nanometers. Must be supplied with diam, and in this case, image +scales (scale) should be specified in units of scale_unit.

  • +
  • diam – Telescope diameter, either as an astropy Quantity or a float in units of +meters. Must be supplied with lam, and in this case, image scales +(scale) should be specified in units of scale_unit.

  • +
  • tip – Tip in units of incident light wavelength. [default: 0]

  • +
  • tilt – Tilt in units of incident light wavelength. [default: 0]

  • +
  • defocus – Defocus in units of incident light wavelength. [default: 0]

  • +
  • astig1 – Astigmatism (like e2) in units of incident light wavelength. +[default: 0]

  • +
  • astig2 – Astigmatism (like e1) in units of incident light wavelength. +[default: 0]

  • +
  • coma1 – Coma along y in units of incident light wavelength. [default: 0]

  • +
  • coma2 – Coma along x in units of incident light wavelength. [default: 0]

  • +
  • trefoil1 – Trefoil (one of the arrows along y) in units of incident light +wavelength. [default: 0]

  • +
  • trefoil2 – Trefoil (one of the arrows along x) in units of incident light +wavelength. [default: 0]

  • +
  • spher – Spherical aberration in units of incident light wavelength. +[default: 0]

  • +
  • aberrations – Optional keyword, to pass in a list, tuple, or NumPy array of +aberrations in units of reference wavelength (ordered according to +the Noll convention), rather than passing in individual values for each +individual aberration. Note that aberrations[1] is piston (and not +aberrations[0], which is unused.) This list can be arbitrarily long to +handle Zernike polynomial aberrations of arbitrary order.

  • +
  • annular_zernike – Boolean indicating that aberrations specify the amplitudes of annular +Zernike polynomials instead of circular Zernike polynomials. +[default: False]

  • +
  • aperAperture object to use when creating PSF. [default: None]

  • +
  • circular_pupil – Adopt a circular pupil? [default: True]

  • +
  • obscuration – Linear dimension of central obscuration as fraction of pupil linear +dimension, [0., 1.). This should be specified even if you are providing +a pupil_plane_im, since we need an initial value of obscuration to +use to figure out the necessary image sampling. [default: 0]

  • +
  • interpolant – Either an Interpolant instance or a string indicating which interpolant +should be used. Options are ‘nearest’, ‘sinc’, ‘linear’, ‘cubic’, +‘quintic’, or ‘lanczosN’ where N should be the integer order to use. +[default: galsim.Quintic()]

  • +
  • oversampling – Optional oversampling factor for the InterpolatedImage. Setting +oversampling < 1 will produce aliasing in the PSF (not good). +Usually oversampling should be somewhat larger than 1. 1.5 is +usually a safe choice. [default: 1.5]

  • +
  • pad_factor – Additional multiple by which to zero-pad the PSF image to avoid folding +compared to what would be employed for a simple Airy. Note that +pad_factor may need to be increased for stronger aberrations, i.e. +those larger than order unity. [default: 1.5]

  • +
  • ii_pad_factor – Zero-padding factor by which to extend the image of the PSF when +creating the InterpolatedImage. See the InterpolatedImage +docstring for more details. [default: 1.5]

  • +
  • suppress_warning – If pad_factor is too small, the code will emit a warning telling you +its best guess about how high you might want to raise it. However, +you can suppress this warning by using suppress_warning=True. +[default: False]

  • +
  • geometric_shooting – If True, then when drawing using photon shooting, use geometric +optics approximation where the photon angles are derived from the +phase screen gradient. If False, then first draw using Fourier +optics and then shoot from the derived InterpolatedImage. +[default: False]

  • +
  • flux – Total flux of the profile. [default: 1.]

  • +
  • nstruts – Number of radial support struts to add to the central obscuration. +[default: 0]

  • +
  • strut_thick – Thickness of support struts as a fraction of pupil diameter. +[default: 0.05]

  • +
  • strut_angleAngle made between the vertical and the strut starting closest to it, +defined to be positive in the counter-clockwise direction; must be an +Angle instance. [default: 0. * galsim.degrees]

  • +
  • pupil_plane_im – The GalSim.Image, NumPy array, or name of file containing the pupil +plane image, to be used instead of generating one based on the +obscuration and strut parameters. [default: None]

  • +
  • pupil_angle – If pupil_plane_im is not None, rotation angle for the pupil plane +(positive in the counter-clockwise direction). Must be an Angle +instance. [default: 0. * galsim.degrees]

  • +
  • pupil_plane_scale – Sampling interval in meters to use for the pupil plane array. In +most cases, it’s a good idea to leave this as None, in which case +GalSim will attempt to find a good value automatically. The +exception is when specifying the pupil arrangement via an image, in +which case this keyword can be used to indicate the sampling of that +image. See also pad_factor for adjusting the pupil sampling scale. +[default: None]

  • +
  • pupil_plane_size – Size in meters to use for the pupil plane array. In most cases, it’s +a good idea to leave this as None, in which case GalSim will attempt +to find a good value automatically. See also oversampling for +adjusting the pupil size. [default: None]

  • +
  • scale_unit – Units to use for the sky coordinates when calculating lam/diam if these +are supplied separately. Should be either a galsim.AngleUnit or a +string that can be used to construct one (e.g., ‘arcsec’, ‘radians’, +etc.). [default: galsim.arcsec]

  • +
  • fft_sign – The sign (+/-) to use in the exponent of the Fourier kernel when +evaluating the Fourier optics PSF. As of version 2.3, GalSim uses a +plus sign by default, which we believe to be consistent with, for +example, how Zemax computes a Fourier optics PSF on DECam. Before +version 2.3, the default was a negative sign. Input should be either +the string ‘+’ or the string ‘-’. [default: ‘+’]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/random.html b/docs/_build/html/random.html new file mode 100644 index 00000000000..0d28ccf8159 --- /dev/null +++ b/docs/_build/html/random.html @@ -0,0 +1,178 @@ + + + + + + + Noise and Random Values — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Noise and Random Values

+

An important part of generating realistic images is to add appropriate levels of noise. +When simulating objects, you may also want the actual objects being drawn to be random in some +way. GalSim has a number of classes to help inject these kinds of randomness into your +simulations.

+
    +
  • Random Deviates describes how to generate pseudo-random numbers according to a variety of +different probability distribution functions.

  • +
  • Noise Generators describes how to add noise to images according a a few different noise models.

  • +
  • Correlated Noise describes both how to add correlated noise to images and also ways to remove +(or reduce) existing correlations from an image.

  • +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/real_gal.html b/docs/_build/html/real_gal.html new file mode 100644 index 00000000000..d171ec0163d --- /dev/null +++ b/docs/_build/html/real_gal.html @@ -0,0 +1,885 @@ + + + + + + + “Real” Galaxies — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

“Real” Galaxies

+
+

Individual Real Galaxies

+

The RealGalaxy class uses images of galaxies from real astrophysical data (e.g. the Hubble Space +Telescope), along with a PSF model of the optical properties of the telescope that took these +images, to simulate new galaxy images with a different (must be larger) telescope PSF. A +description of the simulation method can be found in Section 5 of Mandelbaum et al. (2012; MNRAS, +540, 1518), although note that the details of the implementation in Section 7 of that work are not +relevant to the more recent software used here.

+

The RealGalaxyCatalog class stores all required information about a real galaxy simulation +training sample and accompanying PSF model. +This modelling requires external data for the galaxy images and PSF models, which is read into a +RealGalaxyCatalog object from FITS files. An example catalog of 100 real galaxies is in the +repository itself at

+
+

GalSim/examples/data/real_galaxy_catalog_23.5_example.fits

+
+

For access to larger catalogs of objects, see Downloading the COSMOS Catalog below.

+
+
+class galsim.RealGalaxy(real_galaxy_catalog, index=None, id=None, random=False, rng=None, x_interpolant=None, k_interpolant=None, flux=None, flux_rescale=None, pad_factor=4, noise_pad_size=0, area_norm=1.0, gsparams=None, logger=None)[source]
+

Bases: GSObject

+

A class describing real galaxies from some training dataset. Its underlying implementation +uses a Convolution instance of an InterpolatedImage (for the observed galaxy) with a +Deconvolution of another InterpolatedImage (for the PSF).

+

This class uses a catalog describing galaxies in some training data (for more details, see the +RealGalaxyCatalog documentation) to read in data about realistic galaxies that can be used for +simulations based on those galaxies. Also included in the class is additional information that +might be needed to make or interpret the simulations, e.g., the noise properties of the training +data. Users who wish to draw RealGalaxies that have well-defined flux scalings in various +passbands, and/or parametric representations, should use the COSMOSGalaxy class.

+

Because RealGalaxy involves a Deconvolution, method = 'phot' is unavailable for the +GSObject.drawImage function, and it is essential that users convolve each RealGalaxy with a +PSF that is at least as large as the original HST PSF (stored as an attribute) before rendering +any images. This is necessary to eliminate noise that was amplified due to deconvolution of the +HST PSF.

+

Example:

+
>>> real_galaxy = galsim.RealGalaxy(real_galaxy_catalog, index=None, id=None, random=False,
+...                                 rng=None, x_interpolant=None, k_interpolant=None,
+...                                 flux=None, pad_factor=4, noise_pad_size=0,
+...                                 gsparams=None)
+
+
+

This initializes real_galaxy with three InterpolatedImage objects (one for the deconvolved +galaxy, and saved versions of the original HST image and PSF). Note that there are multiple +keywords for choosing a galaxy; exactly one must be set.

+

Note that tests suggest that for optimal balance between accuracy and speed, k_interpolant +and pad_factor should be kept at their default values. The user should be aware that +significant inaccuracy can result from using other combinations of these parameters; more +details can be found in http://arxiv.org/abs/1401.2636, especially table 1, and in comment +https://github.com/GalSim-developers/GalSim/issues/389#issuecomment-26166621 and the following +comments.

+

If you don’t set a flux, the flux of the returned object will be the flux of the original +HST data, scaled to correspond to a 1 second HST exposure (though see the area_norm +parameter below, and also caveats related to using the flux parameter). If you want a flux +appropriate for a longer exposure, or for a telescope with a different collecting area than HST, +you can either renormalize the object with the flux_rescale parameter, or by using the +exptime and area parameters to GSObject.drawImage.

+

Note that RealGalaxy objects use arcsec for the units of their linear dimension. If you +are using a different unit for other things (the PSF, WCS, etc.), then you should dilate +the resulting object with gal.dilate(galsim.arcsec / scale_unit).

+
+
Parameters:
+
    +
  • real_galaxy_catalog – A RealGalaxyCatalog object with basic information about where to +find the data, etc.

  • +
  • index – Index of the desired galaxy in the catalog. [One of index, +id, or random is required.]

  • +
  • id – Object ID for the desired galaxy in the catalog. [One of index, +id, or random is required.]

  • +
  • random – If True, then select a random galaxy from the catalog. If the +catalog has a ‘weight’ associated with it to allow for correction of +selection effects in which galaxies were included, the ‘weight’ +factor is used to remove those selection effects rather than +selecting a completely random object. +[One of index, id, or random is required.]

  • +
  • rng – A random number generator to use for selecting a random galaxy +(may be any kind of BaseDeviate or None) and to use in generating +any noise field when padding. [default: None]

  • +
  • x_interpolant – Either an Interpolant instance or a string indicating which +real-space interpolant should be used. Options are ‘nearest’, +‘sinc’, ‘linear’, ‘cubic’, ‘quintic’, or ‘lanczosN’ where N should +be the integer order to use. [default: galsim.Quintic()]

  • +
  • k_interpolant – Either an Interpolant instance or a string indicating which +k-space interpolant should be used. Options are ‘nearest’, ‘sinc’, +‘linear’, ‘cubic’, ‘quintic’, or ‘lanczosN’ where N should be the +integer order to use. We strongly recommend leaving this parameter +at its default value; see text above for details. +[default: galsim.Quintic()]

  • +
  • flux – Total flux, if None then original flux in image is adopted without +change. Note that, technically, this parameter sets the flux of the +postage stamp image and not the flux of the contained galaxy. +These two values will be strongly correlated when the signal-to- +noise ratio of the galaxy is large, but may be considerably +different if the flux of the galaxy is small with respect to the +noise variations in the postage stamp. To avoid complications with +faint galaxies, consider using the flux_rescale parameter. +[default: None]

  • +
  • flux_rescale – Flux rescaling factor; if None, then no rescaling is done. Either +flux or flux_rescale may be set, but not both. +[default: None]

  • +
  • pad_factor – Factor by which to pad the Image when creating the +InterpolatedImage. We strongly recommend leaving this parameter +at its default value; see text above for details. [default: 4]

  • +
  • noise_pad_size – If provided, the image will be padded out to this size (in arcsec) +with the noise specified in the real galaxy catalog. This is +important if you are planning to whiten the resulting image. You +should make sure that the padded image is larger than the postage +stamp onto which you are drawing this object. +[default: None]

  • +
  • area_norm – Area in cm^2 by which to normalize the flux of the returned object. +When area_norm=1 (the default), drawing with GSObject.drawImage +keywords exptime=1 and area=1 will simulate an image with the +appropriate number of counts for a 1 second exposure with the +original telescope/camera (e.g., with HST when using the COSMOS +catalog). +If you would rather explicitly specify the collecting area of the +telescope when using GSObject.drawImage with a RealGalaxy, +then you should set area_norm equal to the collecting area of the +source catalog telescope when creating the RealGalaxy (e.g., +area_norm=45238.93416 for HST). [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • logger – A logger object for output of progress statements if the user wants +them. [default: None]

  • +
+
+
+
+
+classmethod makeFromImage(image, PSF, xi, **kwargs)[source]
+

Create a RealGalaxy directly from image, PSF, and noise description.

+
+
Parameters:
+
    +
  • imageImage of the galaxy you want to simulate.

  • +
  • PSFGSObject representing the PSF of the galaxy image. Note that this PSF +should include the response of the pixel convolution.

  • +
  • xiBaseCorrelatedNoise object characterizing the noise correlations in the input +image.

  • +
+
+
+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given GSParams.

+

You may either provide a GSParams instance:

+
>>> gsparams = galsim.GSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+>>> obj = obj.withGSParams(gsparams)
+
+
+

or one or more named parameters as keyword arguments:

+
>>> obj = obj.withGSParams(folding_threshold=1.e-4, maxk_threshold=1.e-4)
+
+
+
+

Note

+

The latter style will leave all non-named parameters at their current +values. It only updates the named parameters to the given values.

+
+
+ +
+ +
+
+class galsim.RealGalaxyCatalog(file_name=None, sample=None, dir=None, preload=False, logger=None)[source]
+

Class containing a catalog with information about real galaxy training data.

+

The RealGalaxyCatalog class reads in and stores information about a specific training sample of +realistic galaxies. We assume that all files containing the images (galaxies and PSFs) live in +one directory; they could be individual files, or multiple HDUs of the same file. Currently +there is no functionality that lets this be a FITS data cube, because we assume that the object +postage stamps will in general need to be different sizes depending on the galaxy size.

+

Note that when simulating galaxies based on HST but using either realistic or parametric galaxy +models, the COSMOSCatalog class may be more useful. It allows the imposition of selection +criteria and other subtleties that are more difficult to impose with RealGalaxyCatalog.

+

While you could create your own catalog to use with this class, the typical use cases would +be to use one of the catalogs that we have created and distributed. There are three such +catalogs currently, which can be use with one of the following initializations:

+
    +
  1. A small example catalog is distributed with the GalSim distribution. This catalog only +has 100 galaxies, so it is not terribly useful as a representative galaxy population. +But for simplistic use cases, it might be sufficient. We use it for our unit tests and +in some of the demo scripts (demo6, demo10, and demo11). To use this catalog, you would +initialize with:

    +
    >>> rgc = galsim.RealGalaxyCatalog('real_galaxy_catalog_23.5_example.fits',
    +                                   dir='path/to/GalSim/examples/data')
    +
    +
    +
  2. +
  3. There are two larger catalogs based on HST observations of the COSMOS field with around +26,000 and 56,000 galaxies each with a limiting magnitude of F814W=23.5. (The former is +a subset of the latter.) For information about how to download these catalogs, see the +RealGalaxy Data Download Page on the GalSim Wiki:

    +

    https://github.com/GalSim-developers/GalSim/wiki/RealGalaxy%20Data

    +

    Be warned that the catalogs are quite large. The larger one is around 11 GB after unpacking +the tarball. To use one of these catalogs, you would initialize with:

    +
    >>> rgc = galsim.RealGalaxyCatalog('real_galaxy_catalog_23.5.fits',
    +                                   dir='path/to/download/directory')
    +
    +
    +
  4. +
  5. There is a catalog containing a random subsample of the HST COSMOS images with a limiting +magnitude of F814W=25.2. More information about downloading these catalogs can be found on +the RealGalaxy Data Download page linked above.

  6. +
  7. Finally, we provide a program that will download the large COSMOS sample for you and +put it in the $PREFIX/share/galsim directory of your installation path. The program is:

    +
    galsim_download_cosmos
    +
    +
    +

    which gets installed in the $PREFIX/bin directory when you install GalSim. If you use +this program to download the COSMOS catalog, then you can use it with:

    +
    >>> rgc = galsim.RealGalaxyCatalog()
    +
    +
    +

    GalSim knows the location of the installation share directory, so it will automatically +look for it there.

    +
  8. +
+
+
Parameters:
+
    +
  • file_name – The file containing the catalog. [default: None, which will look for the +F814W<25.2 COSMOS catalog in $PREFIX/share/galsim. It will raise an +exception if the catalog is not there telling you to run +galsim_download_cosmos.]

  • +
  • sample – A keyword argument that can be used to specify the sample to use, i.e., +“23.5” or “25.2”. At most one of file_name and sample should be +specified. +[default: None, which results in the same default as file_name=None.]

  • +
  • dir – The directory containing the catalog, image, and noise files, or symlinks to +them. [default: None]

  • +
  • preload – Whether to preload the header information. If preload=True, the bulk of +the I/O time is in the constructor. If preload=False, there is +approximately the same total I/O time (assuming you eventually use most of +the image files referenced in the catalog), but it is spread over the +various calls to getGalImage and getPSFImage. [default: False]

  • +
  • logger – An optional logger object to log progress. [default: None]

  • +
+
+
+
+
+getBandpass()[source]
+

Returns a Bandpass object for the catalog.

+
+ +
+
+getGalImage(i)[source]
+

Returns the galaxy at index i as an Image object.

+
+ +
+
+getIndexForID(id)[source]
+

Internal function to find which index number corresponds to the value ID in the ident +field.

+
+ +
+
+getNoise(i, rng=None, gsparams=None)[source]
+

Returns the noise correlation function at index i as a BaseCorrelatedNoise object.

+
+ +
+
+getNoiseProperties(i)[source]
+

Returns the components needed to make the noise correlation function at index i. +Specifically, the noise image (or None), the pixel_scale, and the noise variance, +as a tuple (im, scale, var).

+
+ +
+
+getPSF(i, x_interpolant=None, k_interpolant=None, gsparams=None)[source]
+

Returns the PSF at index i as a GSObject.

+
+ +
+
+getPSFImage(i)[source]
+

Returns the PSF at index i as an Image object.

+
+ +
+
+preload()[source]
+

Preload the files into memory.

+

There are memory implications to this, so we don’t do this by default. However, it can be +a big speedup if memory isn’t an issue.

+
+ +
+ +
+
+

Realistic Scene

+

The COSMOSCatalog class is also based on the above RealGalaxyCatalog, and has functionality +for defining a “sky scene”, i.e., a galaxy sample with reasonable properties that can then be +placed throughout a large image. It can simulate either RealGalaxy objects using the HST images +or parametric models based on those images.

+
+

Note

+

Currently, this only includes routines for making a COSMOS-based galaxy sample, but it could be +expanded to include star samples as well.

+
+
+
+class galsim.COSMOSCatalog(file_name=None, sample=None, dir=None, **kwargs)[source]
+

A class representing a random subsample of galaxies from the COSMOS sample with F814W<25.2 +(default), or alternatively the entire sample with F814W<23.5.

+

Depending on the keyword arguments, particularly use_real, the catalog may be able to +generate real galaxies, parametric galaxies, or both. To use this with either type of +galaxies, you need to get the COSMOS datasets in the format that GalSim recognizes; see

+

https://github.com/GalSim-developers/GalSim/wiki/RealGalaxy-Data

+

option (1) for more information. Note that if you want to make real galaxies you need to +download and store the full tarball with all galaxy images, whereas if you want to make +parametric galaxies you only need the catalog real_galaxy_catalog_25.2_fits.fits (and the +selection file real_galaxy_catalog_25.2_selection.fits if you want to place cuts on the +postage stamp quality) and can delete the galaxy and PSF image files.

+

Finally, we provide a program that will download the large COSMOS sample for you and +put it in the $PREFIX/share/galsim directory of your installation path. The program is:

+
galsim_download_cosmos
+
+
+

which gets installed in the $PREFIX/bin directory when you install GalSim. If you use +this program to download the COSMOS catalog, then you can use it with:

+
cat = galsim.COSMOSCatalog()
+
+
+

GalSim knows the location of the installation share directory, so it will automatically +look for it there.

+

In addition to the option of specifying catalog names, this class also accepts a keyword +argument sample that can be used to switch between the samples with limiting magnitudes of +23.5 and 25.2.

+

After getting the catalogs, there is a method makeGalaxy() that can make a GSObject +corresponding to any chosen galaxy in the catalog (whether real or parametric). See +GalaxySample.makeGalaxy for more information. As an interesting application and example of +the usage of these routines, consider the following code:

+
>>> im_size = 64
+>>> pix_scale = 0.05
+>>> bp_file = os.path.join(galsim.meta_data.share_dir, 'wfc_F814W.dat.gz')
+>>> bandpass = galsim.Bandpass(bp_file, wave_type='ang').thin().withZeropoint(25.94)
+>>> cosmos_cat = galsim.COSMOSCatalog()
+>>> psf = galsim.OpticalPSF(diam=2.4, lam=1000.) # bigger than HST F814W PSF.
+>>> indices = np.arange(10)
+>>> real_gal_list = cosmos_cat.makeGalaxy(indices, gal_type='real',
+...                                       noise_pad_size=im_size*pix_scale)
+>>> param_gal_list = cosmos_cat.makeGalaxy(indices, gal_type='parametric', chromatic=True)
+>>> for ind in indices:
+>>>     real_gal = galsim.Convolve(real_gal_list[ind], psf)
+>>>     param_gal = galsim.Convolve(param_gal_list[ind], psf)
+>>>     im_real = galsim.Image(im_size, im_size)
+>>>     im_param = galsim.Image(im_size, im_size)
+>>>     real_gal.drawImage(image=im_real, scale=pix_scale)
+>>>     param_gal.drawImage(bandpass, image=im_param, scale=pix_scale)
+>>>     im_real.write('im_real_'+str(ind)+'.fits')
+>>>     im_param.write('im_param_'+str(ind)+'.fits')
+
+
+

This code snippet will draw images of the first 10 entries in the COSMOS catalog, at slightly +lower resolution than in COSMOS, with a real image and its parametric representation for each of +those objects.

+

When using the ‘real’ rather than ‘parametric’ option, please read the documentation for the +RealGalaxy class for additional caveats about the available drawing methods and +the need to convolve with a suitable PSF.

+
+
Parameters:
+
    +
  • file_name – The file containing the catalog. [default: None, which will look for the +F814W<25.2 COSMOS catalog in $PREFIX/share/galsim. It will raise an +exception if the catalog is not there telling you to run +galsim_download_cosmos.]

  • +
  • sample – A keyword argument that can be used to specify the sample to use, i.e., +“23.5” or “25.2”. At most one of file_name and sample should be +specified. [default: None, which results in the same default as +file_name=None.]

  • +
  • dir – The directory with the catalog file and, if making realistic galaxies, +the image and noise files (or symlinks to them). [default: None, which +will look in $PREFIX/share/galsim.]

  • +
  • preload – Keyword that is only used for real galaxies, not parametric ones, to +choose whether to preload the header information. If preload=True, +the bulk of the I/O time is in the constructor. If preload=False, +there is approximately the same total I/O time (assuming you eventually +use most of the image files referenced in the catalog), but it is spread +over the calls to makeGalaxy(). [default: False]

  • +
  • use_real – Enable the use of realistic galaxies? [default: True] +If this parameter is False, then makeGalaxy(gal_type='real') will +not be allowed, and there will be a (modest) decrease in RAM and time +spent on I/O when initializing the COSMOSCatalog. If the real +catalog is not available for some reason, it will still be possible to +make parametric images.

  • +
  • exclusion_level

    Level of additional cuts to make on the galaxies based on the quality +of postage stamp definition and/or parametric fit quality [beyond the +minimal cuts imposed when making the catalog - see Mandelbaum et +al. (2012, MNRAS, 420, 1518) for details]. Options:

    +
      +
    • ”none”: No cuts.

    • +
    • ”bad_stamp”: Apply cuts to eliminate galaxies that have failures in +postage stamp definition. These cuts may also eliminate a small +subset of the good postage stamps as well.

    • +
    • ”bad_fits”: Apply cuts to eliminate galaxies that have failures in the +parametric fits. These cuts may also eliminate a small +subset of the good parametric fits as well.

    • +
    • ”marginal”: Apply the above cuts, plus ones that eliminate some more +marginal cases.

    • +
    +

    [default: “marginal”]

    +

  • +
  • min_hlr – Exclude galaxies whose fitted half-light radius is smaller than this +value (in arcsec). [default: 0, meaning no limit]

  • +
  • max_hlr – Exclude galaxies whose fitted half-light radius is larger than this +value (in arcsec). [default: 0, meaning no limit]

  • +
  • min_flux – Exclude galaxies whose fitted flux is smaller than this value. +[default: 0, meaning no limit]

  • +
  • max_flux – Exclude galaxies whose fitted flux is larger than this value. +[default: 0, meaning no limit]

  • +
  • exptime

    The exposure time (in seconds) to assume when creating galaxies. +.. note:

    +
    The processed COSMOS ACS/HST science images have units of
    +counts/second; i.e. they have an effective exposure time of 1
    +second in terms of their flux levels. The default value
    +corresponds to a 1 second exposure on HST, which will match
    +these processed images.
    +
    +
    +

    [default: 1]

    +

  • +
  • area – The effective collecting area (in cm^2) to assume when creating +galaxies. [default: None, which means to use the original HST +collecting area = pi/4 * 240**2 * (1.-0.33**2)]

  • +
+
+
+

After construction, the following attributes are available:

+
+
Attributes:
+

nobjects – The number of objects in the catalog

+
+
+
+ +
+
+class galsim.GalaxySample(file_name, dir=None, preload=False, orig_exptime=1.0, orig_area=1.0, use_real=True, exclusion_level='marginal', min_hlr=0, max_hlr=0.0, min_flux=0.0, max_flux=0.0, cut_ratio=0.8, sn_limit=10.0, min_mask_dist=11, exptime=None, area=None, _use_sample=None)[source]
+

A class representing a random subsample of galaxies from an arbitrary deep survey.

+

Depending on the keyword arguments, particularly use_real, a GalaxySample may be able to +generate real galaxies, parametric galaxies, or both.

+

The original version of this functionality is now in the subclass COSMOSCatalog, which +specializes to HST observations of the COSMOS field. GalaxySample is a generalization of +that, which works for arbitrary data sets, although the user is responsible for building +the appropriate input files to use with it.

+

Unlike COSMOSCatalog, which has easy options for picking out one of the two galaxy subsets +that are available for download using galsim_download_cosmos, for this class you need to +manually specify the file name.

+
>>> sample = galsim.GalaxySample(file_name)
+
+
+

Other than this difference, the functionality of this class is the same as COSMOSCatalog. +See the documentation of that function for more detail.

+
+
Parameters:
+
    +
  • file_name – The file containing the catalog.

  • +
  • dir – The directory with the catalog file and, if making realistic galaxies, +the image and noise files (or symlinks to them). [default: None, which +will look in $PREFIX/share/galsim.]

  • +
  • preload – Keyword that is only used for real galaxies, not parametric ones, to +choose whether to preload the header information. If preload=True, +the bulk of the I/O time is in the constructor. If preload=False, +there is approximately the same total I/O time (assuming you eventually +use most of the image files referenced in the catalog), but it is spread +over the calls to makeGalaxy(). [default: False]

  • +
  • orig_exptime – The exposure time (in seconds) of the original observations. +[default: 1]

  • +
  • orig_area – The effective collecting area (in cm^2) of the original observations. +[default: 1]

  • +
  • use_real – Enable the use of realistic galaxies? [default: True] +If this parameter is False, then makeGalaxy(gal_type='real') will +not be allowed, and there will be a (modest) decrease in RAM and time +spent on I/O when initializing the COSMOSCatalog. If the real +catalog is not available for some reason, it will still be possible to +make parametric images.

  • +
  • exclusion_level

    Level of additional cuts to make on the galaxies based on the quality +of postage stamp definition and/or parametric fit quality [beyond the +minimal cuts imposed when making the catalog - see Mandelbaum et +al. (2012, MNRAS, 420, 1518) for details]. Options:

    +
      +
    • ”none”: No cuts.

    • +
    • ”bad_stamp”: Apply cuts to eliminate galaxies that have failures in +postage stamp definition. These cuts may also eliminate a small +subset of the good postage stamps as well.

    • +
    • ”bad_fits”: Apply cuts to eliminate galaxies that have failures in the +parametric fits. These cuts may also eliminate a small +subset of the good parametric fits as well.

    • +
    • ”marginal”: Apply the above cuts, plus ones that eliminate some more +marginal cases.

    • +
    +

    Use of “bad_stamp” or “marginal” requires a CATALOG_selection.fits +file (where CATALOG is file_name without the “.fits” extension). +[default: “none”]

    +

  • +
  • min_hlr – Exclude galaxies whose fitted half-light radius is smaller than this +value (in arcsec). [default: 0, meaning no limit]

  • +
  • max_hlr – Exclude galaxies whose fitted half-light radius is larger than this +value (in arcsec). [default: 0, meaning no limit]

  • +
  • min_flux – Exclude galaxies whose fitted flux is smaller than this value. +[default: 0, meaning no limit]

  • +
  • max_flux – Exclude galaxies whose fitted flux is larger than this value. +[default: 0, meaning no limit]

  • +
  • cut_ratio – For the “bad_stamp” exclusions, cut out any stamps with average +adjacent pixels larger than this fraction of the peak pixel count. +[default: 0.8]

  • +
  • sn_limit – For the “bad_stamp” exclusions, cut out any stamps with estimated +S/N for an elliptical Gaussian less than this limit. [default: 10.]

  • +
  • min_mask_dist – For the “bad_stamp” exclusions, remove any stamps that have some +masked pixels closer to the center than this minimum distance +(in pixels). [default: 10]

  • +
  • exptime – The exposure time (in seconds) to assume when creating galaxies. +[default: None, which means to use orig_exptime]

  • +
  • area – The effective collecting area (in cm^2) to assume when creating +galaxies. [default: None, which means to use orig_area]

  • +
+
+
+

After construction, the following attributes are available:

+
+
Attributes:
+

nobjects – The number of objects in the sample

+
+
+
+
+canMakeReal()[source]
+

Is it permissible to call makeGalaxy with gal_type=’real’?

+
+ +
+
+getParametricRecord(index)[source]
+

Get the parametric record for a given index

+
+ +
+
+getRealParams(index)[source]
+

Get the parameters needed to make a RealGalaxy for a given index.

+
+ +
+
+getValue(key, index)[source]
+

Get the value in the parametric catalog at the given key and index

+
+ +
+
+makeGalaxy(index=None, gal_type=None, chromatic=False, noise_pad_size=5, deep=False, sersic_prec=0.05, rng=None, n_random=None, gsparams=None)[source]
+

Routine to construct one or more GSObject instances corresponding to the catalog entry +with a particular index or indices.

+

The flux of the galaxy corresponds to a 1 second exposure time with the Hubble Space +Telescope. Users who wish to simulate F814W images with a different telescope and an +exposure time longer than 1 second should multiply by that exposure time, and by the square +of the ratio of the effective diameter of their telescope compared to that of HST. +(Effective diameter may differ from the actual diameter if there is significant +obscuration.) See demo11.py for an example that explicitly takes this normalization into +account.

+

Due to the adopted flux normalization, drawing into an image with the COSMOS bandpass, +zeropoint of 25.94, and pixel scale should give the right pixel values to mimic the actual +COSMOS science images. The COSMOS science images that we use are normalized to a count rate +of 1 second, which is why there is no need to rescale to account for the COSMOS exposure +time.

+

There is an option to make chromatic objects (chromatic=True); however, it is important +to bear in mind that we do not actually have spatially-resolved color information for these +galaxies, so this keyword can only be True if we are using parametric galaxies. Even then, +we simply do the most arbitrary thing possible, which is to assign bulges an elliptical +SED, disks a disk-like SED, and Sersic galaxies with intermediate values of n some +intermediate SED. We assume that the photometric redshift is the correct redshift for +these galaxies (which is a good assumption for COSMOS 30-band photo-z for these bright +galaxies). For the given SED and redshift, we then normalize to give the right (observed) +flux in F814W. Note that for a mock “deep” sample, the redshift distributions of the +galaxies would be modified, which is not included here.

+

For this chromatic option, it is still the case that the output flux normalization is +appropriate for the HST effective telescope diameter and a 1 second exposure time, so users +who are simulating another scenario should account for this.

+

Note that the returned objects use arcsec for the units of their linear dimension. If you +are using a different unit for other things (the PSF, WCS, etc.), then you should dilate +the resulting object with gal.dilate(galsim.arcsec / scale_unit).

+
+
Parameters:
+
    +
  • index – Index of the desired galaxy in the catalog for which a GSObject +should be constructed. You may also provide a list or array of +indices, in which case a list of objects is returned. If None, +then a random galaxy (or more: see n_random kwarg) is chosen, +correcting for catalog-level selection effects if weights are +available. [default: None]

  • +
  • gal_type – Either ‘real’ or ‘parametric’. This determines which kind of +galaxy model is made. [If catalog was loaded with use_real=False, +then this defaults to ‘parametric’, and in fact ‘real’ is +not allowed. If catalog was loaded with use_real=True, then +this defaults to ‘real’.]

  • +
  • chromatic – Make this a chromatic object, or not? [default: False]

  • +
  • noise_pad_size – For realistic galaxies, the size of region to pad with noise, +in arcsec. [default: 5, an arbitrary, but not completely +ridiculous choice.]

  • +
  • deep – Modify fluxes and sizes of galaxies from the F814W<23.5 sample in +order to roughly simulate an F814W<25 sample but with higher S/N, as +in GREAT3? [default: False] Note that this keyword will be ignored +(except for issuing a warning) if the input catalog already +represents the F814W<25.2 sample.

  • +
  • sersic_prec – The desired precision on the Sersic index n in parametric galaxies. +GalSim is significantly faster if it gets a smallish number of +Sersic values, so it can cache some of the calculations and use +them again the next time it gets a galaxy with the same index. +If sersic_prec is 0.0, then use the exact value of index n from +the catalog. But if it is >0, then round the index to that +precision. [default: 0.05]

  • +
  • rng – A random number generator to use for selecting a random galaxy +(may be any kind of BaseDeviate or None) and to use in generating +any noise field when padding. [default: None]

  • +
  • n_random – The number of random galaxies to build, if ‘index’ is None. +[default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
Returns:
+

Either a GSObject or a ChromaticObject depending on the value of chromatic, +or a list of them if index is an iterable.

+
+
+
+ +
+
+selectRandomIndex(n_random=1, rng=None, _n_rng_calls=False)[source]
+

Routine to select random indices out of the catalog. This routine does a weighted random +selection with replacement (i.e., there is no guarantee of uniqueness of the selected +indices). Weighting uses the weight factors available in the catalog, if any; these weights +are typically meant to remove any selection effects in the catalog creation process.

+
+
Parameters:
+
    +
  • n_random – Number of random indices to return. [default: 1]

  • +
  • rng – A random number generator to use for selecting a random galaxy +(may be any kind of BaseDeviate or None). [default: None]

  • +
+
+
Returns:
+

A single index if n_random==1 or a NumPy array containing the randomly-selected +indices if n_random>1.

+
+
+
+ +
+ +
+
+

Downloading the COSMOS Catalog

+

A set of ~56 000 real galaxy images with I<23.5, or another set of ~87 000 with I<25.2, with +original PSFs, can be downloaded from Zenodo:

+

https://zenodo.org/record/3242143

+

The tar ball for the I<23.5 sample (COSMOS_23.5_training_sample.tar.gz) is roughly 4 GB and +contains catalogs and images with a README. The tar ball for the I<25.2 sample +(COSMOS_25.2_training_sample.tar.gz) is of similar size and format.

+

GalSim also comes with a script galsim_download_cosmos that downloads the I<23.5 sample. +It works with both samples, with the I<25.2 sample being the default but with keyword arguments +to choose between the two:

+
usage: galsim_download_cosmos [-h] [-v {0,1,2,3}] [-f] [-q] [-u] [--save]
+                              [-d DIR] [-s {23.5,25.2}] [--nolink]
+
+This program will download the COSMOS RealGalaxy catalog and images and place
+them in the GalSim share directory so they can be used as the default files
+for the RealGalaxyCatalog class. See https://github.com/GalSim-
+developers/GalSim/wiki/RealGalaxy%20Data for more details about the files
+being downloaded.
+
+optional arguments:
+  -h, --help            show this help message and exit
+  -v {0,1,2,3}, --verbosity {0,1,2,3}
+                        Integer verbosity level: min=0, max=3 [default=2]
+  -f, --force           Force overwriting the current file if one exists
+  -q, --quiet           Don't ask about re-downloading an existing file.
+                        (implied by verbosity=0)
+  -u, --unpack          Re-unpack the tar file if not downloading
+  --save                Save the tarball after unpacking.
+  -d DIR, --dir DIR     Install into an alternate directory and link from the
+                        share/galsim directory
+  -s {23.5,25.2}, --sample {23.5,25.2}
+                        Flux limit for sample to download; either 23.5 or 25.2
+  --nolink              Don't link to the alternate directory from
+                        share/galsim
+
+Note: The unpacked files total almost 6 GB in size!
+
+
+
+

Note

+

The galsim_download_cosmos program will put the downloaded files into a subdirectory +of the galsim.meta_data.share_dir directory. (cf. Shared Data) +This is normally convenient for access, since classes such as RealGalaxyCatalog and +COSMOSCatalog will look in this directory automatically for you. However, if you +reinstall GalSim, everything in this directory will be removed and overwritten. +Therefore, we normally recommend using the -d DIR option to place the downloaded +files into another location. E.g.:

+
galsim_download_cosmos -d ~/share
+
+
+

It will still be required to rerun this after reinstalling GalSim, but it will notice that +you already have the files downloaded and merely update the symbolic link.

+
+

Instructions for how to download a copy of the GREAT3 data are found at

+

https://github.com/barnabytprowe/great3-public#how-to-get-the-data

+
+
+

HSC Postage Stamp Data

+

The HST postage stamp data from

+

http://adsabs.harvard.edu/abs/2017arXiv171000885M

+

which includes studies of the impact of blending on shear estimation in HSC, was released as part +of the HSC survey’s second incremental data release. The sample is larger than the above, goes to +the depth of COSMOS, and does not have nearby objects masked.

+

The links to download it and the instructions on its use are at

+

https://hsc-release.mtk.nao.ac.jp/doc/index.php/weak-lensing-simulation-catalog-pdr1/

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/roman.html b/docs/_build/html/roman.html new file mode 100644 index 00000000000..b3a69114d91 --- /dev/null +++ b/docs/_build/html/roman.html @@ -0,0 +1,890 @@ + + + + + + + The Roman Space Telescope Module — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The Roman Space Telescope Module

+

The galsim.roman module contains information and functionality that can be used to simulate +images for the Roman Space Telescope. Some of the functionality is specific to Roman per se, but +some of the routines are more generically simulating aspects of the HgCdTe detectors, which will +be used on Roman. These routines might therefore be useful for simulating observations from +other telescopes that will use these detectors.

+

The demo script demo13.py illustrates the use of most of this functionality.

+
+

Note

+

To use this module, you must separately import galsim.roman. These functions are +not automatically imported when you import galsim.

+
+
+

Module-level Attributes

+

There are a number of attributes of the galsim.roman module, which define some numerical +parameters related to the Roman geometry. Some of these parameters relate to the entire +wide-field imager. Others, especially the return values of the functions to get the +PSF and WCS, are specific to each SCA (Sensor Chip Assembly, the equivalent of a chip for an optical +CCD) and therefore are indexed based on the SCA. All SCA-related arrays are 1-indexed, i.e., the +entry with index 0 is None and the entries from 1 to n_sca are the relevant ones. This is +consistent with diagrams and so on provided by the Roman project, which are 1-indexed.

+

The NIR detectors that will be used for Roman have a different photon detection process from CCDs. +In particular, the photon detection process begins with charge generation. However, instead of +being read out along columns (as for CCDs), they are read directly from each pixel. Moreover, the +actual quantity that is measured is technically not charge, but rather voltage. The charge is +inferred based on the capacitance. To use a common language with that for CCDs, we will often refer +to quantities measured in units of e-/pixel, but for some detector non-idealities, it is important +to keep in mind that it is voltage that is sensed.

+
+
gain

The gain for all SCAs (sensor chip assemblies) is expected to be the roughly the same, +and we currently have no information about how different they will be, so this is a +single value rather than a list of values. Once the actual detectors exist and have been +characterized, it might be updated to be a dict with entries for each SCA.

+
+
pixel_scale

The pixel scale in units of arcsec/pixel. This value is approximate and does not +include effects like distortion, which are included in the WCS.

+
+
diameter

The telescope diameter in meters.

+
+
obscuration

The linear obscuration of the telescope, expressed as a fraction of the diameter.

+
+
collecting_area
+
The actual collecting area after accounting for obscuration, struts, etc. in

units of cm^2.

+
+
+
+
exptime

The typical exposure time in units of seconds. The number that is stored is for a +single dither. Each location within the survey will be observed with a total of 5-7 +dithers across 2 epochs.

+
+
n_dithers

The number of dithers per filter (typically 5-7, so this is currently 6 as a +reasonable effective average).

+
+
dark_current

The dark current in units of e-/pix/s.

+
+
nonlinearity_beta

The coefficient of the (counts)^2 term in the detector nonlinearity +function. This will not ordinarily be accessed directly by users; instead, +it will be accessed by the convenience function in this module that defines +the nonlinearity function as counts_out = counts_in + beta*counts_in^2. +Alternatively users can use the galsim.roman.applyNonlinearity routine, +which already knows about the expected form of the nonlinearity in the +detectors.

+
+
reciprocity_alpha

The normalization factor that determines the effect of reciprocity failure +of the detectors for a given exposure time. Alternatively, users can use +the galsim.roman.addReciprocityFailure routine, which knows about this +normalization factor already, and allows users to choose an exposure time or +use the default Roman exposure time.

+
+
read_noise

A total of 8.5e-. This comes from 20 e- per correlated double sampling (CDS) and a +5 e- floor, so the CDS read noise dominates. The source of CDS read noise is the +noise introduced when subtracting a single pair of reads; this can be reduced by +averaging over multiple reads. Also, this read_noise value might be reduced +based on improved behavior of newer detectors which have lower CDS noise.

+
+
thermal_backgrounds

The thermal backgrounds (in units of e-/pix/s) are based on a temperature +of 282 K, but this plan might change in future. The thermal backgrounds +depend on the band, so this is not a single number; instead, it’s a +dictionary that is accessed by the name of the optical band, e.g., +galsim.roman.thermal_backgrounds['F184'] (where the names of the +bandpasses can be obtained using the getBandpasses routine described +below).

+
+
pupil_plane_file

There is actually a separate file for each SCA giving the pupil plane mask +for the Roman telescope as seen from the center of each SCA. When building +the PSF with galsim.roman.getPSF, it will use the correct one for the given +SCA. However, for backwards compatibility, if anyone needs a generic image +of the pupil plane, this file is for SCA 2, near the center of the WFC field.

+
+
pupil_plane_scale

The pixel scale in meters per pixel for the image in pupil_plane_file.

+
+
stray_light_fraction

The fraction of the sky background that is allowed to contribute as stray +light. Currently this is required to be <10% of the background due to +zodiacal light, so its value is set to 0.1 (assuming a worst-case). This +could be used to get a total background including stray light.

+
+
ipc_kernel

The 3x3 kernel to be used in simulations of interpixel capacitance (IPC), using +galsim.roman.applyIPC.

+
+
persistence_coefficients

The retention fraction of the previous eight exposures in a simple, +linear model for persistence.

+
+
persistence_fermi_params

The parameters in the fermi persistence model.

+
+
n_sca

The number of SCAs in the focal plane.

+
+
n_pix_tot

Each SCA has n_pix_tot x n_pix_tot pixels.

+
+
n_pix

The number of pixels that are actively used. The 4 outer rows and columns will be +attached internally to capacitors rather than to detector pixels, and used to monitor +bias voltage drifts. Thus, images seen by users will be n_pix x n_pix.

+
+
jitter_rms
+
The worst-case RMS jitter per axis for Roman in the current design (reality

will likely be much better than this). Units: arcsec.

+
+
+
+
charge_diffusion

The per-axis sigma to use for a Gaussian representing charge diffusion for +Roman. Units: pixels.

+
+
+

For example, to get the gain value, use galsim.roman.gain. Most numbers related to the nature of +the detectors are subject to change as further lab tests are done.

+
+
+

Roman Functions

+

This module also contains the following routines:

+
+
galsim.roman.getBandpasses

A utility to get a dictionary containing galsim.Bandpass objects for each of +the Roman imaging bandpasses, which by default have AB zeropoints given using +the GalSim zeropoint convention (see getBandpasses docstring for more details).

+
+
galsim.roman.getSkyLevel

A utility to find the expected sky level due to zodiacal light at a given +position, in a given band.

+
+
galsim.roman.applyNonlinearity

A routine to apply detector nonlinearity of the type expected for Roman.

+
+
galsim.roman.addReciprocityFailure

A routine to include the effects of reciprocity failure in images at +the level expected for Roman.

+
+
galsim.roman.applyIPC

A routine to incorporate the effects of interpixel capacitance in Roman images.

+
+
galsim.roman.applyPersistence

A routine to incorporate the effects of persistence - the residual images +from earlier exposures after resetting.

+
+
galsim.roman.allDetectorEffects

A routine to add all sources of noise and all (implemented) detector +effects to an image containing astronomical objects plus background. In +principle, users can simply use this routine instead of separately using +the various routines like galsim.roman.applyNonlinearity.

+
+
galsim.roman.getPSF

A routine to get a chromatic representation of the PSF in a single SCA.

+
+
galsim.roman.getWCS

A routine to get the WCS for each SCA in the focal plane, for a given target RA, dec, +and orientation angle.

+
+
galsim.roman.findSCA

A routine that can take the WCS from getWCS and some sky position, and indicate in +which SCA that position can be found, optionally including half of the gaps between +SCAs (to identify positions that are in the focal plane array but in the gap between SCAs).

+
+
galsim.roman.allowedPos

A routine to check whether Roman is allowed to look at a given position on a +given date, given the constraints on orientation with respect to the sun.

+
+
galsim.roman.bestPA

A routine to calculate the best observatory orientation angle for Roman when looking +at a given position on a given date.

+
+
+

Another routine that may be necessary is galsim.utilities.interleaveImages. +The Roman PSFs at native Roman pixel scale are undersampled. A Nyquist-sampled PSF image can be +obtained by a two-step process:

+
+
    +
  1. Call the galsim.roman.getPSF routine and convolve the PSF with the Roman pixel response +to get the effective PSF.

  2. +
  3. Draw the effective PSF onto an Image using drawImage routine, with a pixel scale lesser +than the native pixel scale (using the ‘method=no_pixel’ option).

  4. +
+
+

However, if pixel-level effects such as nonlinearity and interpixel capacitance must be applied to +the PSF images, then they must drawn at the native pixel scale. A Nyquist-sampled PSF image can be +obtained in such cases by generating multiple images with offsets (a dither sequence) and then +combining them using galsim.utilities.interleaveImages.

+
+
+galsim.roman.getBandpasses(AB_zeropoint=True, default_thin_trunc=True, include_all_bands=False, **kwargs)[source]
+

Utility to get a dictionary containing the Roman ST bandpasses used for imaging.

+

This routine reads in a file containing a list of wavelengths and throughput for all Roman +bandpasses, and uses the information in the file to create a dictionary. This file is in units +of effective area (m^2), which includes the nominal mirror size and obscuration in each +bandpass. We divide these by the nominal roman.collecting_area, so the bandpass objects +include both filter transmission losses and the obscuration differences relevant for +each bandpass. I.e. you should always use roman.collecting_area for the collecting area +in any flux calculation, and the bandpass will account for the differences from this.

+

In principle it should be possible to replace the version of the file with another one, provided +that the format obeys the following rules:

+
    +
  • There is a column called ‘Wave’, containing the wavelengths in microns.

  • +
  • The other columns are labeled by the name of the bandpass.

  • +
+

The bandpasses can be either truncated or thinned before setting the zero points, by passing in +the keyword arguments that need to get propagated through to the Bandpass.thin() and/or +Bandpass.truncate() routines. Or, if the user wishes to thin and truncate using the defaults +for those two routines, they can use default_thin_trunc=True. This option is the default, +because the stored ‘official’ versions of the bandpasses cover a wide wavelength range. So even +if thinning is not desired, truncation is recommended.

+

By default, the routine will set an AB zeropoint (unless AB_zeropoint=False). The +zeropoint in GalSim is defined such that the flux is 1 photon/cm^2/sec through the +bandpass. This differs from an instrumental bandpass, which is typically defined such that the +flux is 1 photon/sec for that instrument. The difference between the two can be calculated as +follows:

+
# Shift zeropoint based on effective collecting area in cm^2.
+delta_zp = 2.5 * np.log10(galsim.roman.collecting_area)
+
+
+

delta_zp will be a positive number that should be added to the GalSim zeropoints to compare +with externally calculated instrumental zeropoints. When using the GalSim zeropoints for +normalization of fluxes, the area kwarg to drawImage can be used to get the right +normalization (giving it the quantity galsim.roman.collecting_area).

+

This routine also loads information about sky backgrounds in each filter, to be used by the +galsim.roman.getSkyLevel() routine. The sky background information is saved as an attribute in +each Bandpass object.

+

There are some subtle points related to the filter edges, which seem to depend on the field +angle at some level. This is more important for the grism than for the imaging, so currently +this effect is not included in the Roman bandpasses in GalSim.

+

The bandpass throughput file is translated from a spreadsheet Roman_effarea_20201130.xlsx at +https://roman.gsfc.nasa.gov/science/WFI_technical.html.

+

Example:

+
>>> roman_bandpasses = galsim.roman.getBandpasses()
+>>> f184_bp = roman_bandpasses['F184']
+
+
+
+
Parameters:
+
    +
  • AB_zeropoint – Should the routine set an AB zeropoint before returning the bandpass? +If False, then it is up to the user to set a zero point. [default: +True]

  • +
  • default_thin_trunc – Use the default thinning and truncation options? Users who wish to +use no thinning and truncation of bandpasses, or who want control over +the level of thinning and truncation, should have this be False. +[default: True]

  • +
  • include_all_bands – Should the routine include the non-imaging bands (e.g., grisms)? +This does not implement any dispersion physics by itself. +There is currently no estimate for the thermal background for these +bands and they are set to zero arbitrarily. +[default: False]

  • +
  • **kwargs – Other kwargs are passed to either Bandpass.thin or +Bandpass.truncate as appropriate.

  • +
+
+
+

@returns A dictionary containing bandpasses for all Roman imaging filters.

+
+ +
+
+galsim.roman.getSkyLevel(bandpass, world_pos=None, exptime=None, epoch=2025, date=None)[source]
+

Get the expected sky level for a Roman ST observation due to zodiacal light for this bandpass +and position.

+

This routine requires Bandpass objects that were loaded by galsim.roman.getBandpasses(). That +routine will have stored tables containing the sky background as a function of position on the +sky for that bandpass. This routine then interpolates between the values in those tables to +arbitrary positions on the sky.

+

The numbers that are stored in the Bandpass object bandpass are background level in units of +e-/s/arcsec^2. Multiplying by the exposure time gives a result in e-/arcsec^2. The +result can either be multiplied by the approximate pixel area to get e-/pix, or the result can +be used with wcs.makeSkyImage() to make an image of the sky that properly includes the actual +pixel area as a function of position on the detector.

+

The source of the tables that are being interpolated is Chris Hirata’s publicly-available Roman +exposure time calculator (ETC):

+
+
+

Using the throughput files loaded into the bandpass object, the calculation nominally +returns photons/s/arcsec^2, but the input bandpasses used internally by the ETC +code include the quantum efficiency, to effectively convert to e-/s/arcsec^2. Note that in +general results will depend on the adopted model for zodiacal light, and these are uncertain at +the ~10% level. One must also better sample the integration in the zodiacal light calculation +to match the output tables used by GalSim here.

+

Positions should be specified with the world_pos keyword, which must be a CelestialCoord +object. If no world_pos is supplied, then the routine will use a default position that +looks sensibly away from the sun.

+
+
Parameters:
+
    +
  • bandpass – A Bandpass object.

  • +
  • world_pos – A position, given as a CelestialCoord object. If None, then the routine +will use an ecliptic longitude of 90 degrees with respect to the sun +position (as a fair compromise between 0 and 180), and an ecliptic latitude +of 30 degrees with respect to the sun position (decently out of the plane +of the Earth-sun orbit). [default: None]

  • +
  • exptime – Exposure time in seconds. If None, use the default Roman exposure time. +[default: None]

  • +
  • epoch – The epoch to be used for estimating the obliquity of the ecliptic when +converting world_pos to ecliptic coordinates. This keyword is only used +if date is None, otherwise date is used to determine the epoch. +[default: 2025]

  • +
  • date – The date of the observation, provided as a python datetime object. If None, +then the conversion to ecliptic coordinates assumes the sun is at ecliptic +coordinates of (0,0), as it is at the vernal equinox. [default: None]

  • +
+
+
Returns:
+

the expected sky level in e-/arcsec^2.

+
+
+
+ +
+
+galsim.roman.getPSF(SCA, bandpass, SCA_pos=None, pupil_bin=4, wcs=None, n_waves=None, extra_aberrations=None, wavelength=None, gsparams=None, logger=None, high_accuracy=None, approximate_struts=None)[source]
+

Get a single PSF for Roman ST observations.

+

The user must provide the SCA and bandpass; the latter is used when setting up the pupil +plane configuration and when interpolating chromatic information, if requested.

+

This routine carries out linear interpolation of the aberrations within a given SCA, based on +the Roman Cycle 9 specification of the aberrations as a function of focal plane +position, more specifically from the WebbPSF data files from webbpsf-data-1.2.1.tar.gz +downloaded from https://webbpsf.readthedocs.io/en/latest/installation.html#data-install. +The abberation file is webbpsf-data/WFI/wim_zernikes_cycle9.csv.

+

The mask images for the Roman pupil plane are available in the same WebbPSF data files. +There are separate files for each SCA, since the view of the spider pattern varies somewhat +across the field of view of the wide field camera. Users usually don’t need +to worry about any of this, as GalSim will select the correct pupil image automatically based +on the SCA and bandpass provided.

+

The full pupil plane images are 4096 x 4096, which use a lot of memory and are somewhat slow +to use, so we normally bin them by a factor of 4 (resulting in 1024 x 1024 images). This +provides enough detail for most purposes and is much faster to render than using the full pupil +plane images. This bin factor is a settable parameter, called pupil_bin. If you want the +more accurate, slower calculation using the full images, you can set it to 1. In the other +direction, using pupil_bin=8 (resulting in a 512 x 512 image) still provides fairly reasonable +results and is even faster to render. It is not generally recommended to use higher binning +than that, as the diffraction spikes will become noticeably degraded.

+
+

Note

+

This function will cache the aperture calculation, so repeated calls with the same +SCA and bandpass should be much faster after the first call, as the pupil plane will +already be loaded. If you need to clear the cache for memory reasons, you may call:

+
galsim.roman.roman_psfs._make_aperture.clear()
+
+
+

to recover any memory currently being used for this cache. Of course, subsequent calls to +getPSF will need to rebuild the aperture at that point.

+
+

The PSF that is returned by default will be oriented with respect to the SCA coordinates, +not world coordinates as is typical in GalSim. The pupil plane has a fixed orientation +with respect to the focal plane, so the PSF rotates with the telescope. To obtain a +PSF in world coordinates, which can be convolved with galaxies (that are normally described +in world coordinates), you may pass in a wcs parameter to this function. This will +project the PSF into world coordinates according to that WCS before returning it. Otherwise, +the return value is equivalent to using wcs=galim.PixelScale(galsim.roman.pixel_scale).

+

The calculation takes advantage of the fact that the diffraction limit and aberrations have a +simple, understood wavelength-dependence. (The Roman abberation data for Cycle 9 does in fact +provide aberrations as a function of wavelength, but the deviation from the expected chromatic +dependence is sub-percent so we neglect it here.) For reference, the script used to parse the +Zernikes given on the webpage and create the files in the GalSim repository can be found in +devel/external/parse_roman_zernikes_1217.py. The resulting chromatic object can be used to +draw into any of the Roman bandpasses, though the pupil plane configuration will only be +correct for those bands in the same range (i.e., long- or short-wavelength bands).

+

For applications that require very high accuracy in the modeling of the PSF, with very limited +aliasing, you may want to lower the folding_threshold in the gsparams. Otherwise very bright +stars will show some reflections in the spider pattern and possibly some boxiness at the +outskirts of the PSF. Using gsparams = GSParams(folding_threshold=2.e-3) generally +provides good results even for very bright (e.g. mag=10) stars. In these cases, you probably +also want to reduce pupil_bin somewhat from the default value of 4.

+

By default, no additional aberrations are included above the basic design. However, users can +provide an optional keyword extra_aberrations that will be included on top of those that are +part of the design. This should be in the same format as for the ChromaticOpticalPSF class, +with units of waves at the fiducial wavelength, 1293 nm. Currently, only aberrations up to order +22 (Noll convention) are simulated. For Roman, the tolerance for additional +aberrations was a total of 90 nanometers RMS as of mid-2015, distributed largely among coma, +astigmatism, trefoil, and spherical aberrations (NOT defocus). This information might serve as +a guide for reasonable extra_aberrations inputs. The reference for that number is +an earlier Cycle 5 document:

+

http://roman.gsfc.nasa.gov/science/sdt_public/wps/references/instrument/README_AFTA_C5_WFC_Zernike_and_Field_Data.pdf

+

However, the default (non-extra) aberrations are from Cycle 7 material linked earlier in this +docstring.

+

Jitter and charge diffusion are, by default, not included. Users who wish to include these can +find some guidelines for typical length scales of the Gaussians that can represent these +effects, and convolve the ChromaticOpticalPSF with appropriate achromatic Gaussians.

+

The PSFs are always defined assuming the user will specify length scales in arcsec.

+

Users may find they do not have to call getPSF for all objects in their simulations; for a +given SCA and position within the SCA, and a given pupil plane configuration and wavelength +information, it should be possible to reuse the PSFs.

+
+
Parameters:
+
    +
  • SCA – Single value specifying the SCA for which the PSF should be +loaded.

  • +
  • bandpass – Single string specifying the bandpass to use when defining the +pupil plane configuration and/or interpolation of chromatic PSFs. +You may also pass a string ‘long’ or ‘short’ for this argument, in +which case, the correct pupil plane configuration will be used for +long- or short-wavelength bands (F184 is long, all else is short). +In this case, no interpolation can be used, since it is defined +using the extent of the chosen bandpass. If wavelength is given, +then bandpass may be None, which will use the short-wavelength pupil +plane image.

  • +
  • SCA_pos – Single galsim.PositionD indicating the position within the SCA +for which the PSF should be created. If None, the exact center of +the SCA is chosen. [default: None]

  • +
  • pupil_bin – The binning to apply to the pupil plane image. (See discussion above.) +[default: 4]

  • +
  • wcs – The WCS to use to project the PSF into world coordinates. +[default: galsim.PixelScale(galsim.roman.pixel_scale)]

  • +
  • n_waves – Number of wavelengths to use for setting up interpolation of the +chromatic PSF objects, which can lead to much faster image +rendering. If None, then no interpolation is used. Note that +users who want to interpolate can always set up the interpolation +later on even if they do not do so when calling getPSF. +[default: None]

  • +
  • extra_aberrations – Array of extra aberrations to include in the PSF model, on top of +those that are part of the Roman design. These should be +provided in units of waves at the fiducial wavelength of 1293 nm, +as an array of length 23 with entries 4 through 22 corresponding +to defocus through the 22nd Zernike in the Noll convention. +[default: None]

  • +
  • wavelength – An option to get an achromatic PSF for a single wavelength, for +users who do not care about chromaticity of the PSF. If None, +then the fully chromatic PSF is returned. Alternatively the user +should supply either (a) a wavelength in nanometers, and they +will get achromatic OpticalPSF objects for that wavelength, or +(b) a bandpass object, in which case they will get achromatic +OpticalPSF objects defined at the effective wavelength of that +bandpass. [default: False]

  • +
  • gsparams – An optional GSParams argument. See the docstring for GSParams +for details. [default: None]

  • +
+
+
Returns:
+

A single PSF object (either a ChromaticOpticalPSF or an OpticalPSF depending on the +inputs).

+
+
+
+ +
+
+galsim.roman.getWCS(world_pos, PA=None, date=None, SCAs=None, PA_is_FPA=False)[source]
+

This routine returns a dict containing a WCS for each of the Roman SCAs (Sensor Chip Array, the +equivalent of a chip in an optical CCD). The Roman SCAs are labeled 1-18, so these numbers are +used as the keys in the dict. Alternatively the user can request a subset of the SCAs using the +SCAs option. The basic instrument parameters used to create the WCS correspond to those in +Cycle 6, which includes some significant updates from Cycle 5, including a 90 degree rotation of +the focal plane axes relative to the payload axes, and two rows of SCAs are swapped.

+

The user must specify a position for observation, at which the center of the focal plane array +will point. This must be supplied as a CelestialCoord world_pos. In general, only certain +positions are observable on certain dates, and for a given position there is an optimal position +angle for the observatory (with the solar panels pointed as directly towards the sun as +possible). Users who are knowledgable about these details may choose to supply a position angle +as PA, either for the observatory or for the focal plane (using PA_is_FPA to indicate +this). But otherwise, the routine will simply choose the optimal position angle for a given +date.

+

To fully understand all possible inputs and outputs to this routine, users may wish to consult +the diagram on the GalSim wiki, +https://github.com/GalSim-developers/GalSim/wiki/GalSim-Roman-module-diagrams

+
+
Parameters:
+
    +
  • world_pos – A galsim.CelestialCoord indicating the position to observe at the center +of the focal plane array (FPA). Note that if the given position is not +observable on the given date, then the routine will raise an exception.

  • +
  • PA – A galsim.Angle representing the position angle of the observatory +Y +axis, unless PA_is_FPA=True, in which case it’s the position angle of +the FPA. For users to do not care about this, then leaving this as None +will result in the routine using the supplied date and world_pos to +select the optimal orientation for the observatory. Note that if a user +supplies a PA value, the routine does not check whether this orientation +is actually allowed. [default: None]

  • +
  • date – The date of the observation, as a python datetime object. If None, then the +vernal equinox in 2025 will be used. [default: None]

  • +
  • PA_is_FPA – If True, then the position angle that was provided was the PA of the focal +plane array, not the observatory. [default: False]

  • +
  • SCAs – A single number or iterable giving the SCAs for which the WCS should be +obtained. If None, then the WCS is calculated for all SCAs. +[default: None]

  • +
+
+
Returns:
+

A dict of WCS objects for each SCA.

+
+
+
+ +
+
+galsim.roman.findSCA(wcs_dict, world_pos, include_border=False)[source]
+

This is a subroutine to take a dict of WCS (one per SCA) from galsim.roman.getWCS() and query +which SCA a particular real-world coordinate would be located on. The position (world_pos) +should be specified as a galsim.CelestialCoord. If the position is not located on any of the +SCAs, the result will be None. Note that if wcs_dict does not include all SCAs in it, then +it’s possible the position might lie on one of the SCAs that was not included.

+

Depending on what the user wants to do with the results, they may wish to use the +include_border keyword. This keyword determines whether or not to include an additional +border corresponding to half of the gaps between SCAs. For example, if a user is drawing a +single image they may wish to only know whether a given position falls onto an SCA, and if so, +which one (ignoring everything in the gaps). In contrast, a user who plans to make a sequence +of dithered images might find it most useful to know whether the position is either on an SCA or +close enough that in a small dither sequence it might appear on the SCA at some point. Use of +include_border switches between these scenarios.

+
+
Parameters:
+
    +
  • wcs_dict – The dict of WCS’s output from galsim.roman.getWCS().

  • +
  • world_pos – A galsim.CelestialCoord indicating the sky position of interest.

  • +
  • include_border – If True, then include the half-border around SCA to cover the gap +between each sensor. [default: False]

  • +
+
+
Returns:
+

an integer value of the SCA on which the position falls, or None if the position is not +on any SCA.

+
+
+
+ +
+
+galsim.roman.allowedPos(world_pos, date)[source]
+

This routine can be used to check whether Roman would be allowed to look at a particular +position (world_pos) on a given date. This is determined by the angle of this position +relative to the Sun.

+

In general, Roman can point at angles relative to the Sun in the range 90+/-36 degrees. +Obviously, pointing too close to the Sun would result in overly high sky backgrounds. It is +less obvious why Roman cannot look at a spot directly opposite from the Sun (180 degrees on the +sky). The reason is that the observatory is aligned such that if the observer is looking at +some sky position, the solar panels are oriented at 90 degrees from that position. So it’s +always optimal for the observatory to be pointing at an angle of 90 degrees relative to the +Sun. It is also permitted to look within 36 degrees of that optimal position.

+
+
Parameters:
+
    +
  • world_pos – A galsim.CelestialCoord indicating the position at which the observer +wishes to look.

  • +
  • date – A python datetime object indicating the desired date of observation.

  • +
+
+
Returns:
+

True or False, indicating whether it is permitted to look at this position on this date.

+
+
+
+ +
+
+galsim.roman.bestPA(world_pos, date)[source]
+

This routine determines the best position angle for the observatory for a given observation date +and position on the sky.

+

The best/optimal position angle is determined by the fact that the solar panels are at 90 +degrees to the position being observed, and it is best to have those facing the Sun as directly +as possible. Note that if a given world_pos is not actually observable on the given +date, then this routine will return None.

+
+
Parameters:
+
    +
  • world_pos – A galsim.CelestialCoord indicating the position at which the observer +wishes to look.

  • +
  • date – A python datetime object indicating the desired date of observation.

  • +
+
+
Returns:
+

the best position angle for the observatory, as a galsim.Angle, or None if the position +is not observable.

+
+
+
+ +
+
+galsim.roman.convertCenter(world_pos, SCA, PA=None, date=None, PA_is_FPA=False, tol=coord.Angle(2.42406840554768e-06, coord.radians))[source]
+

This is a simple helper routine that takes an input position world_pos that is meant to +correspond to the position of the center of an SCA, and tells where the center of the focal +plane array should be. The goal is to provide a position that can be used as an input to +getWCS(), which wants the center of the focal plane array.

+

The results of the calculation are deterministic if given a fixed position angle (PA). If it’s +not given one, it will try to determine the best one for this location and date, like getWCS() +does.

+

Because of distortions varying across the focal plane, this routine has to iteratively correct +its initial result based on empirical tests. The tol kwarg can be used to adjust how +careful it will be, but it always does at least one iteration.

+

To fully understand all possible inputs and outputs to this routine, users may wish to consult +the diagram on the GalSim wiki, +https://github.com/GalSim-developers/GalSim/wiki/GalSim-Roman-module-diagrams

+
+
Parameters:
+
    +
  • world_pos – A galsim.CelestialCoord indicating the position to observe at the center of the +given SCA. Note that if the given position is not observable on +the given date, then the routine will raise an exception.

  • +
  • SCA – A single number giving the SCA for which the center should be located at +world_pos.

  • +
  • PA – galsim.Angle representing the position angle of the observatory +Y axis, unless +PA_is_FPA=True, in which case it’s the position angle of the FPA. For +users to do not care about this, then leaving this as None will result in the +routine using the supplied date and world_pos to select the optimal +orientation for the observatory. Note that if a user supplies a PA value, +the routine does not check whether this orientation is actually allowed. +[default: None]

  • +
  • date – The date of the observation, as a python datetime object. If None, then the +vernal equinox in 2025 will be used. [default: None]

  • +
  • PA_is_FPA – If True, then the position angle that was provided was the PA of the focal +plane array, not the observatory. [default: False]

  • +
  • tol – Tolerance for errors due to distortions, as a galsim.Angle. +[default: 0.5*galsim.arcsec]

  • +
+
+
Returns:
+

A CelestialCoord object indicating the center of the focal plane array.

+
+
+
+ +
+
+galsim.roman.applyNonlinearity(img)[source]
+

Applies the Roman nonlinearity function to the supplied image im.

+

For more information about nonlinearity, see the docstring for galsim.Image.applyNonlinearity. +Unlike that routine, this one does not require any arguments, since it uses the nonlinearity +function defined within the Roman module.

+

After calling this method, the Image instance img is transformed to include the +nonlinearity.

+
+
Parameters:
+

img – The Image to be transformed.

+
+
+
+ +
+
+galsim.roman.addReciprocityFailure(img, exptime=139.8)[source]
+

Accounts for the reciprocity failure for the Roman directors and includes it in the original +Image img directly.

+

For more information about reciprocity failure, see the docstring for +galsim.Image.addReciprocityFailure. Unlike that routine, this one does not need the parameters +for reciprocity failure to be provided, though it still takes exposure time as an optional +argument.

+
+
Parameters:
+
    +
  • img – The Image to be transformed.

  • +
  • exptime – The exposure time (t) in seconds, which goes into the expression for +reciprocity failure given in the docstring. If None, then the routine +will use the default Roman exposure time in galsim.roman.exptime. +[default: 139.8]

  • +
+
+
+
+ +
+
+galsim.roman.applyIPC(img, edge_treatment='extend', fill_value=None)[source]
+

Applies the effect of interpixel capacitance (IPC) to the Image instance.

+

For more information about IPC, see the docstring for galsim.Image.applyIPC. Unlike that +routine, this one does not need the IPC kernel to be specified, since it uses the IPC kernel +defined within the Roman module.

+
+
Parameters:
+
    +
  • img – The Image to be transformed.

  • +
  • edge_treatment – Specifies the method of handling edges and should be one of +‘crop’, ‘extend’ or ‘wrap’. See galsim.Image.applyIPC docstring +for more information. +[default: ‘extend’]

  • +
  • fill_value – Specifies the value (including nan) to fill the edges with when +edge_treatment is ‘crop’. If unspecified or set to ‘None’, the +original pixel values are retained at the edges. If +edge_treatment is not ‘crop’, then this is ignored.

  • +
+
+
+
+ +
+
+galsim.roman.applyPersistence(img, prev_exposures, method='fermi')[source]
+

This method applies either of the two different persistence models: ‘linear’ and ‘fermi’. +Slew between pointings and consecutive resets after illumination are not considered.

+
+
‘linear’ persistence model

Applies the persistence effect to the Image instance by adding a small fraction of the +previous exposures (up to 8) supplied as the ‘prev_exposures’ argument. +For more information about persistence, see galsim.Image.applyPersistence. +Unlike that routine, this one does not need the coefficients to be specified. However, +the list of previous 8 exposures will have to be supplied. Earlier exposures, if +supplied, will be ignored.

+
+
‘fermi’ persistence model

Applies the persistence effect to the Image instance by adding the accumulated persistence +dark current of previous exposures supplied as the ‘prev_exposures’ argument. +Unlike galsim.Image.applyPersistence, this one does not use constant coefficients but a +fermi model plus a linear tail below half of saturation.

+

For more info about the fermi model, see:

+

http://www.stsci.edu/hst/wfc3/ins_performance/persistence/

+
+
+
+
Parameters:
+
    +
  • img – The Image to be transformed.

  • +
  • prev_exposures – List of Image instances in the order of exposures, with the recent +exposure being the first element. In the linear model, the exposures +exceeding the limit (8 exposures) will be ignored.

  • +
  • method – The persistence model (‘linear’ or ‘fermi’) to be applied. +[default: ‘fermi’]

  • +
+
+
+
+ +
+
+galsim.roman.allDetectorEffects(img, prev_exposures=(), rng=None, exptime=139.8)[source]
+

This utility applies all sources of noise and detector effects for Roman that are implemented +in GalSim. In terms of noise, this includes the Poisson noise due to the signal (sky + +background), dark current, and read noise. The detector effects that are included are +reciprocity failure, quantization, persistence, nonlinearity, and interpixel capacitance. It +also includes the necessary factors of gain. In short, the user should be able to pass in an +Image with all sources of signal (background plus astronomical objects), and the Image will be +modified to include all subsequent steps in the image generation process for Roman that are +implemented in GalSim. However, to include the effect of persistence, the user needs to provide +a list of recent exposures (without the readout effects) and the routine +returns an updated list of recent exposures.

+
+
Parameters:
+
    +
  • img – The Image to be modified.

  • +
  • prev_exposures – List of Image instances in the order of exposures, with +the recent exposure being the first element. [default: ()]

  • +
  • rng – An optional galsim.BaseDeviate to use for the addition of noise. If +None, a new one will be initialized. [default: None]

  • +
  • exptime – The exposure time, in seconds. If None, then the Roman default +exposure time will be used. [default: 139.8]

  • +
+
+
Returns:
+

Updated list of previous exposures Image instances.

+
+
Return type:
+

prev_exposures

+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/sb.html b/docs/_build/html/sb.html new file mode 100644 index 00000000000..054bad51786 --- /dev/null +++ b/docs/_build/html/sb.html @@ -0,0 +1,229 @@ + + + + + + + Surface Brightness Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Surface Brightness Profiles

+

There are many possible ways to define a surface brightness profile of an astronomical +object (stars, galaxies, etc.) in GalSim. We have classes for common PSF models (e.g. +Moffat and Airy), analytic galaxy profiles (e.g. Exponential and Sersic), interpolation +of an arbitrary input image (InterpolatedImage), some other more complicated models, +and ways to combine models as sums or convolutions. Models can also be arbitrarily +stretched, rotated, and dilated in various ways.

+

The classes for these various surface brightness profiles are all subclasses of the +GSObject base class. See that section for details about most of the public API +methods that are defined for all (or almost all) of these classes.

+

Next, we list the subclasses of GSObject organized by their intended use. These are +the classes you would actually use when building the profile for an astronomical object.

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/search.html b/docs/_build/html/search.html new file mode 100644 index 00000000000..28ca24979a1 --- /dev/null +++ b/docs/_build/html/search.html @@ -0,0 +1,141 @@ + + + + + + Search — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2023, GalSim-developers.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/docs/_build/html/searchindex.js b/docs/_build/html/searchindex.js new file mode 100644 index 00000000000..f2fade87a73 --- /dev/null +++ b/docs/_build/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["arbitrary", "bandpass", "bessel", "bounds", "catalog", "cd", "chromatic", "chromaticobject", "composite", "config", "config_galsim", "config_image", "config_input", "config_objects", "config_output", "config_process", "config_special", "config_stamp", "config_top", "config_values", "corr_noise", "cpp", "cpp_bounds", "cpp_hsm", "cpp_image", "cpp_interp", "cpp_math", "cpp_noise", "cpp_photon", "cpp_sb", "dcr", "des", "deviate", "errors", "fft", "fits", "gal", "gsobject", "gsparams", "history", "hsm", "image", "image_class", "index", "install", "install_conda", "install_pip", "integ", "interpolant", "misc", "nfwhalo", "noise", "older", "overview", "phase_psf", "photon", "photon_array", "photon_ops", "pos", "powerspectrum", "pse", "psf", "random", "real_gal", "roman", "sb", "sed", "sensor", "shared", "shear", "simple", "spectral", "table", "transform", "tutorials", "units", "utilities", "wcs", "wl", "zernike"], "filenames": ["arbitrary.rst", "bandpass.rst", "bessel.rst", "bounds.rst", "catalog.rst", "cd.rst", "chromatic.rst", "chromaticobject.rst", "composite.rst", "config.rst", "config_galsim.rst", "config_image.rst", "config_input.rst", "config_objects.rst", "config_output.rst", "config_process.rst", "config_special.rst", "config_stamp.rst", "config_top.rst", "config_values.rst", "corr_noise.rst", "cpp.rst", "cpp_bounds.rst", "cpp_hsm.rst", "cpp_image.rst", "cpp_interp.rst", "cpp_math.rst", "cpp_noise.rst", "cpp_photon.rst", "cpp_sb.rst", "dcr.rst", "des.rst", "deviate.rst", "errors.rst", "fft.rst", "fits.rst", "gal.rst", "gsobject.rst", "gsparams.rst", "history.rst", "hsm.rst", "image.rst", "image_class.rst", "index.rst", "install.rst", "install_conda.rst", "install_pip.rst", "integ.rst", "interpolant.rst", "misc.rst", "nfwhalo.rst", "noise.rst", "older.rst", "overview.rst", "phase_psf.rst", "photon.rst", "photon_array.rst", "photon_ops.rst", "pos.rst", "powerspectrum.rst", "pse.rst", "psf.rst", "random.rst", "real_gal.rst", "roman.rst", "sb.rst", "sed.rst", "sensor.rst", "shared.rst", "shear.rst", "simple.rst", "spectral.rst", "table.rst", "transform.rst", "tutorials.rst", "units.rst", "utilities.rst", "wcs.rst", "wl.rst", "zernike.rst"], "titles": ["Arbitrary Profiles", "Bandpass Filters", "Bessel Functions", "Bounding boxes", "Catalogs and Input Dictionaries", "Charge Deflection Model", "Wavelength-dependent Profiles", "Chromatic Profiles", "Composite Profiles", "The Config Module", "The galsim Executable", "Config Image Field", "Config Input Field", "Config Objects", "Config Output Field", "Config Processing From Python", "Special Fields", "Config Stamp Field", "Top Level Fields", "Config Values", "Correlated Noise", "C++ Layer", "Positions and Bounds", "HSM Implementation", "C++ Images", "Interpolation Tools", "Math", "Noise-related Functionality", "Photons and Sensor Effects", "C++ Surface Brightness Profiles", "Differential Chromatic Refraction", "The DES Module", "Random Deviates", "Errors and Warnings", "Fourier Transforms", "Interfacing with FITS Files", "Galaxies", "The GSObject base class", "The GSParams class", "Revision History", "The HSM Module", "Images and Related Concepts", "The Image class", "Indices and tables", "Installation Instructions", "Installing With Conda", "Installing With Pip", "Integration", "Interpolants", "Miscellaneous Utilities", "NFW Halo Shears", "Noise Generators", "v2.5", "Overview", "Phase-screen PSFs", "Photon Shooting", "Photon Arrays", "Photon Operators", "Positions", "Power Spectrum Shears", "Power Spectrum Estimation", "Point-spread functions", "Noise and Random Values", "\u201cReal\u201d Galaxies", "The Roman Space Telescope Module", "Surface Brightness Profiles", "Spectral Energy Distributions", "Sensor Models", "Shared Data", "The Shear class", "Simple Profiles", "Spectral Correlated Noise", "Lookup Tables", "Transformed Profiles", "Tutorials", "Units", "Helper Functions and Classes", "World Coordinate Systems", "Weak Lensing", "Zernike Functions"], "terms": {"If": [0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 39, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 69, 72, 75, 77, 79], "none": [0, 1, 3, 4, 7, 8, 11, 12, 13, 14, 15, 17, 18, 19, 20, 23, 30, 31, 32, 33, 35, 36, 37, 38, 40, 42, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 70, 71, 72, 73, 75, 77], "abov": [0, 1, 3, 4, 5, 7, 8, 10, 11, 12, 13, 14, 16, 17, 19, 20, 29, 35, 36, 37, 38, 40, 42, 44, 46, 48, 49, 51, 53, 54, 55, 56, 59, 61, 63, 64, 66, 67, 68, 72, 73, 75, 77, 79], "class": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 17, 20, 22, 23, 25, 26, 27, 28, 30, 31, 32, 33, 35, 36, 40, 41, 43, 46, 47, 48, 50, 51, 52, 54, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 70, 71, 72, 73, 74, 75, 78, 79], "seem": [0, 15, 44, 49, 53, 64], "appropri": [0, 6, 7, 8, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 26, 29, 30, 31, 32, 33, 35, 36, 37, 38, 42, 46, 47, 49, 52, 53, 54, 57, 59, 62, 63, 64, 66, 67, 68, 73, 74, 75, 77], "i": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79], "possibl": [0, 1, 4, 7, 11, 13, 14, 15, 16, 17, 19, 20, 21, 23, 24, 27, 32, 36, 37, 40, 41, 42, 46, 49, 52, 53, 54, 55, 59, 61, 63, 64, 65, 66, 67, 69, 70, 72, 75, 77], "defin": [0, 1, 3, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 23, 24, 26, 27, 28, 29, 31, 32, 33, 36, 37, 38, 40, 41, 42, 47, 48, 49, 50, 51, 52, 54, 56, 57, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 74, 75, 77, 79], "ani": [0, 1, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 35, 36, 37, 38, 39, 40, 42, 44, 46, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 72, 73, 74, 75, 77], "an": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77, 78, 79], "between": [0, 1, 5, 7, 11, 12, 13, 14, 16, 17, 19, 20, 22, 24, 25, 27, 29, 31, 32, 36, 37, 38, 40, 42, 47, 48, 50, 52, 54, 57, 59, 61, 63, 64, 71, 72, 75, 77], "pixel": [0, 3, 5, 7, 8, 9, 11, 12, 13, 14, 15, 17, 19, 20, 22, 23, 24, 25, 27, 28, 29, 31, 35, 36, 37, 38, 40, 41, 42, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 65, 67, 68, 71, 74, 75, 77], "posit": [0, 3, 5, 7, 11, 12, 13, 15, 17, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 33, 36, 37, 39, 40, 41, 42, 43, 47, 48, 49, 50, 52, 54, 55, 56, 57, 59, 61, 64, 66, 67, 69, 72, 74, 75, 77], "surfac": [0, 6, 7, 8, 9, 13, 14, 17, 18, 20, 21, 28, 31, 36, 37, 40, 42, 43, 47, 48, 49, 52, 54, 55, 56, 57, 61, 67, 70, 73, 75, 77], "bright": [0, 6, 7, 8, 9, 13, 14, 17, 18, 20, 21, 28, 31, 36, 37, 40, 42, 43, 47, 48, 49, 52, 54, 55, 56, 57, 61, 63, 64, 70, 73, 75, 77], "locat": [0, 14, 16, 17, 19, 20, 24, 25, 31, 36, 37, 40, 41, 42, 44, 46, 47, 48, 49, 53, 54, 56, 59, 61, 63, 64, 67, 68, 70, 74, 77], "similarli": [0, 19, 20, 29, 36, 37, 42, 49, 66, 72, 75, 79], "one": [0, 1, 3, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 45, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 66, 67, 69, 70, 72, 73, 74, 75, 77, 79], "can": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77, 78, 79], "us": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 47, 48, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79], "which": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 44, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 79], "ar": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 79], "differ": [0, 3, 4, 5, 6, 7, 10, 11, 13, 14, 15, 16, 17, 19, 20, 22, 24, 25, 27, 29, 31, 32, 35, 36, 37, 40, 42, 46, 49, 51, 52, 54, 55, 58, 59, 61, 62, 63, 64, 68, 70, 71, 72, 74, 75, 77], "complet": [0, 7, 10, 15, 17, 36, 40, 42, 44, 52, 53, 54, 59, 63], "basi": [0, 28, 29, 31, 79], "set": [0, 1, 3, 4, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 35, 36, 37, 38, 40, 41, 42, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 69, 72, 73, 74, 75, 77, 79], "instead": [0, 5, 7, 8, 11, 13, 14, 16, 17, 18, 19, 20, 24, 29, 31, 35, 36, 37, 38, 40, 42, 44, 46, 47, 49, 52, 54, 57, 59, 61, 64, 66, 68, 72, 74, 77, 79], "galsim": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79], "interpolatedimag": [0, 7, 13, 29, 37, 42, 48, 52, 54, 55, 56, 61, 63, 65, 74], "x_interpol": [0, 13, 20, 42, 63], "k_interpol": [0, 7, 13, 63], "normal": [0, 1, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 24, 25, 26, 29, 31, 32, 34, 35, 36, 37, 40, 41, 42, 44, 45, 46, 47, 49, 51, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 66, 68, 69, 72, 73, 75, 77, 79], "flux": [0, 1, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 25, 28, 29, 36, 37, 38, 40, 42, 43, 48, 49, 51, 52, 54, 55, 56, 57, 61, 63, 64, 66, 67, 70, 73, 74, 77], "scale": [0, 5, 7, 11, 12, 13, 17, 19, 20, 23, 25, 27, 28, 29, 31, 32, 35, 36, 37, 40, 41, 42, 47, 48, 49, 50, 51, 52, 53, 54, 56, 57, 59, 61, 63, 64, 70, 71, 72, 73, 74, 75, 77], "wc": [0, 5, 7, 12, 15, 17, 19, 20, 31, 35, 36, 37, 40, 42, 43, 46, 49, 52, 53, 57, 61, 63, 64, 67, 68, 71, 74, 75], "pad_factor": [0, 7, 13, 17, 54, 57, 61, 63], "4": [0, 1, 3, 5, 7, 8, 10, 13, 14, 16, 19, 20, 21, 23, 29, 32, 36, 37, 38, 39, 40, 42, 43, 44, 46, 47, 49, 54, 61, 63, 64, 66, 67, 68, 72, 75, 79], "0": [0, 1, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 48, 49, 50, 51, 54, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 74, 75, 77, 79], "noise_pad_s": [0, 7, 13, 63, 74], "noise_pad": [0, 13], "rng": [0, 7, 11, 12, 13, 15, 16, 17, 19, 20, 27, 28, 29, 32, 36, 37, 49, 51, 52, 54, 55, 56, 57, 59, 63, 64, 66, 67, 71, 74], "pad_imag": [0, 13], "calculate_stepk": [0, 13], "true": [0, 1, 3, 7, 8, 12, 13, 14, 15, 17, 19, 20, 22, 24, 25, 26, 28, 29, 31, 32, 34, 35, 36, 37, 40, 42, 47, 48, 49, 50, 52, 54, 56, 57, 59, 60, 61, 63, 64, 66, 67, 72, 73, 75, 77], "calculate_maxk": [0, 13], "use_cach": 0, "use_true_cent": [0, 13, 37], "depixel": [0, 24, 42, 52], "fals": [0, 1, 3, 7, 8, 11, 12, 13, 14, 15, 17, 19, 20, 22, 23, 24, 29, 32, 34, 35, 36, 37, 40, 42, 47, 48, 49, 52, 54, 55, 56, 59, 60, 61, 63, 64, 66, 67, 68, 72, 73, 75, 77], "offset": [0, 7, 11, 14, 15, 17, 19, 31, 36, 37, 49, 52, 64, 73, 74, 77], "gsparam": [0, 7, 8, 12, 13, 15, 17, 20, 25, 31, 33, 36, 37, 43, 48, 52, 54, 61, 63, 64, 65, 70, 73, 74], "_force_stepk": [0, 7, 54, 61], "_force_maxk": [0, 7, 54, 61], "hdu": [0, 4, 12, 13, 14, 15, 31, 35, 42, 52, 63, 74, 77], "sourc": [0, 1, 3, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 42, 43, 45, 46, 47, 48, 49, 50, 51, 52, 54, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 69, 70, 71, 72, 73, 75, 77, 79], "base": [0, 3, 5, 7, 8, 11, 12, 13, 14, 15, 17, 19, 20, 23, 24, 25, 26, 27, 28, 31, 32, 33, 36, 38, 39, 40, 42, 43, 47, 48, 49, 50, 51, 52, 53, 54, 57, 58, 59, 60, 61, 63, 64, 65, 67, 70, 73, 74, 75, 76, 79], "gsobject": [0, 6, 7, 13, 15, 17, 20, 31, 36, 38, 39, 40, 42, 43, 48, 49, 52, 54, 55, 56, 57, 59, 61, 63, 65, 66, 69, 70, 71, 74, 75, 77], "A": [0, 1, 3, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 40, 42, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 77, 79], "describ": [0, 3, 5, 7, 9, 10, 11, 12, 13, 16, 18, 19, 20, 22, 23, 29, 31, 36, 37, 40, 41, 42, 44, 50, 52, 54, 58, 59, 61, 62, 63, 64, 67, 70, 71, 72, 73, 74, 77, 79], "non": [0, 7, 10, 11, 13, 14, 15, 16, 17, 20, 22, 24, 25, 27, 28, 29, 30, 32, 37, 39, 40, 42, 46, 47, 49, 52, 54, 55, 56, 59, 61, 63, 64, 67, 69, 73, 74, 75, 77, 79], "parametr": [0, 5, 12, 13, 52, 53, 63, 74], "specifi": [0, 1, 3, 4, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 27, 29, 30, 31, 32, 35, 36, 37, 38, 39, 40, 42, 46, 48, 49, 50, 51, 52, 53, 54, 55, 57, 59, 61, 63, 64, 66, 67, 69, 70, 71, 72, 73, 74, 75, 77], "purpos": [0, 11, 12, 16, 17, 20, 29, 31, 36, 41, 42, 46, 59, 64], "carri": [0, 7, 23, 28, 29, 36, 37, 40, 42, 53, 59, 64], "out": [0, 1, 4, 5, 7, 11, 12, 14, 15, 19, 20, 22, 23, 24, 25, 27, 29, 31, 34, 36, 37, 40, 42, 48, 49, 52, 53, 56, 57, 59, 60, 61, 63, 64, 74, 75], "transform": [0, 1, 5, 6, 7, 8, 11, 13, 15, 17, 20, 30, 31, 36, 37, 38, 42, 43, 46, 47, 52, 53, 54, 59, 61, 64, 65, 66, 69, 76, 77], "The": [0, 1, 2, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 39, 41, 43, 44, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 65, 66, 67, 68, 70, 72, 73, 74, 75, 77, 78, 79], "you": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 29, 31, 32, 33, 35, 36, 37, 38, 39, 41, 42, 44, 45, 46, 48, 49, 51, 53, 54, 55, 57, 58, 59, 61, 62, 63, 64, 65, 67, 68, 69, 72, 73, 74, 75, 77, 79], "have": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 76, 77, 79], "descript": [0, 4, 7, 16, 18, 19, 20, 23, 29, 35, 42, 49, 52, 59, 60, 63, 69, 79], "object": [0, 1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 14, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 33, 35, 36, 37, 38, 40, 42, 43, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72, 73, 74, 75, 77, 78, 79], "wish": [0, 11, 20, 29, 37, 40, 49, 54, 59, 63, 64, 72, 75], "manipul": [0, 20, 52, 54, 57, 72, 78], "method": [0, 1, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 20, 22, 23, 24, 27, 29, 31, 32, 35, 36, 37, 38, 40, 42, 46, 47, 49, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 65, 66, 67, 69, 72, 73, 74, 75, 77], "shear": [0, 7, 11, 12, 13, 14, 15, 17, 19, 20, 23, 29, 36, 37, 40, 42, 43, 49, 52, 53, 58, 60, 63, 73, 74, 77, 78], "magnifi": [0, 7, 13, 20, 36, 37, 69, 74], "shift": [0, 3, 5, 6, 7, 11, 13, 14, 15, 19, 20, 22, 24, 29, 34, 36, 37, 40, 42, 49, 52, 57, 64, 66, 73, 74, 77], "etc": [0, 1, 3, 6, 7, 8, 11, 14, 15, 16, 17, 18, 19, 29, 35, 36, 37, 42, 49, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 65, 73, 77], "note": [0, 1, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 24, 25, 26, 27, 28, 29, 31, 32, 35, 36, 37, 40, 42, 44, 46, 48, 49, 51, 54, 56, 58, 59, 60, 61, 63, 64, 66, 67, 69, 70, 71, 72, 73, 75, 77, 79], "when": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 24, 25, 26, 27, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 42, 46, 48, 49, 52, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 71, 72, 73, 74, 75, 77], "convolv": [0, 7, 8, 13, 17, 18, 20, 28, 29, 31, 36, 37, 40, 42, 49, 52, 54, 56, 63, 64, 71, 73, 74], "real": [0, 6, 7, 8, 11, 12, 13, 14, 15, 17, 20, 24, 25, 27, 29, 32, 34, 36, 37, 38, 40, 41, 42, 43, 48, 49, 52, 53, 54, 55, 59, 61, 64, 65, 69, 74, 77], "convolut": [0, 7, 13, 17, 18, 20, 28, 29, 37, 40, 42, 49, 52, 53, 54, 55, 63, 65, 72, 74, 75], "recommend": [0, 7, 16, 20, 29, 36, 49, 54, 59, 61, 63, 64, 74], "sinc": [0, 1, 2, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 32, 35, 36, 37, 40, 42, 46, 48, 49, 52, 54, 55, 56, 57, 59, 61, 63, 64, 66, 67, 69, 73, 74, 75, 76, 77, 79], "typic": [0, 1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 24, 25, 26, 28, 29, 31, 32, 33, 36, 37, 40, 42, 47, 48, 49, 54, 55, 56, 57, 58, 59, 61, 63, 64, 67, 68, 70, 73, 74, 75, 77, 79], "great": [0, 77], "deal": [0, 12, 31, 37, 59], "slower": [0, 1, 8, 19, 29, 37, 49, 54, 61, 64, 66, 67, 68], "than": [0, 1, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 24, 25, 27, 29, 30, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 48, 49, 51, 52, 53, 54, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 72, 74, 75, 77], "thi": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 77, 79], "kind": [0, 3, 4, 6, 7, 9, 11, 12, 13, 14, 16, 17, 18, 19, 20, 29, 32, 33, 36, 37, 38, 42, 47, 49, 51, 53, 54, 55, 57, 58, 61, 62, 63, 66, 74, 77, 79], "There": [0, 6, 7, 8, 9, 10, 11, 13, 14, 16, 17, 19, 20, 21, 23, 24, 26, 27, 29, 32, 36, 37, 40, 42, 44, 46, 49, 53, 54, 55, 57, 61, 63, 64, 65, 66, 67, 69, 72, 74, 75, 77], "three": [0, 7, 21, 27, 29, 32, 35, 36, 37, 40, 42, 54, 59, 61, 63, 70, 74, 77], "option": [0, 1, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 24, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 74, 75, 77], "determin": [0, 4, 7, 10, 11, 12, 15, 17, 19, 20, 25, 29, 35, 36, 37, 40, 42, 46, 49, 54, 59, 63, 64, 72, 73, 74, 77, 79], "simpli": [0, 3, 6, 7, 13, 14, 17, 19, 20, 21, 22, 31, 37, 40, 44, 46, 53, 54, 55, 63, 64, 67, 73, 75], "valu": [0, 1, 3, 4, 5, 7, 8, 9, 11, 12, 13, 14, 16, 17, 18, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 68, 69, 70, 71, 72, 74, 75, 77, 78, 79], "explicitli": [0, 1, 7, 8, 11, 12, 13, 14, 15, 17, 19, 24, 32, 35, 38, 40, 42, 47, 49, 52, 54, 57, 59, 63, 73, 75], "taken": [0, 1, 7, 11, 12, 13, 14, 15, 19, 20, 29, 35, 37, 42, 49, 50, 51, 55, 59, 61, 68, 74, 75, 77, 79], "sum": [0, 5, 7, 13, 16, 19, 20, 23, 24, 28, 29, 37, 40, 42, 49, 52, 54, 55, 56, 65, 69, 73, 74], "input": [0, 1, 5, 6, 7, 8, 9, 11, 13, 14, 16, 17, 18, 19, 20, 22, 23, 24, 25, 28, 29, 32, 33, 34, 36, 37, 40, 42, 43, 46, 48, 49, 50, 51, 52, 53, 54, 56, 57, 59, 61, 63, 64, 65, 66, 67, 72, 73, 74, 75, 76, 77, 78], "correspond": [0, 1, 3, 4, 5, 7, 11, 13, 15, 17, 19, 20, 26, 28, 29, 32, 36, 37, 38, 40, 42, 44, 49, 51, 52, 54, 59, 60, 61, 63, 64, 67, 69, 71, 72, 74, 75, 77, 79], "wa": [0, 1, 7, 11, 12, 14, 15, 17, 19, 20, 23, 24, 29, 31, 33, 35, 36, 37, 40, 42, 46, 48, 49, 51, 52, 53, 54, 55, 56, 59, 60, 61, 63, 64, 66, 72, 75, 77, 78], "drawn": [0, 6, 7, 8, 10, 11, 13, 16, 17, 18, 25, 29, 30, 31, 36, 37, 40, 42, 47, 49, 52, 54, 57, 61, 62, 64, 66, 71, 74], "drawimag": [0, 6, 7, 15, 17, 20, 36, 37, 40, 42, 47, 49, 52, 54, 55, 57, 59, 63, 64, 70, 73, 74, 75], "no_pixel": [0, 17, 31, 37, 40, 42, 49, 52, 55, 61, 64, 74], "default": [0, 1, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 70, 72, 73, 74, 75, 77, 79], "given": [0, 1, 2, 3, 4, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 22, 24, 25, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 40, 42, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 77], "sb": [0, 13, 17, 20, 29, 37, 40, 42, 52, 61, 74], "treat": [0, 15, 17, 20, 29, 35, 40, 42, 47, 49, 51, 54, 59, 66, 75, 77], "sampl": [0, 1, 7, 12, 13, 17, 19, 20, 25, 29, 32, 37, 38, 40, 42, 47, 48, 49, 52, 54, 57, 59, 61, 63, 64, 66, 68, 72, 74], "each": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 25, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 42, 44, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 72, 74, 77, 79], "multipli": [0, 1, 6, 7, 8, 17, 28, 29, 36, 37, 40, 42, 50, 51, 52, 58, 59, 63, 64, 66, 67, 72, 73, 75, 79], "area": [0, 3, 7, 11, 12, 13, 17, 22, 24, 26, 28, 29, 36, 37, 49, 52, 54, 55, 56, 59, 60, 61, 63, 64, 66, 67, 69, 75, 77, 79], "also": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 31, 32, 35, 36, 37, 38, 40, 41, 42, 44, 45, 46, 48, 49, 51, 52, 53, 54, 56, 57, 59, 61, 62, 63, 64, 65, 66, 67, 69, 72, 73, 74, 75, 77, 79], "were": [0, 3, 5, 7, 14, 19, 23, 29, 37, 39, 40, 49, 52, 55, 59, 61, 63, 64, 68, 72, 74, 77], "integr": [0, 1, 7, 8, 13, 17, 25, 29, 31, 36, 37, 38, 42, 43, 48, 49, 52, 53, 54, 55, 59, 64, 66, 72, 74, 75, 76, 79], "auto": [0, 1, 8, 14, 15, 17, 35, 37, 38, 42, 60, 77], "phot": [0, 7, 11, 13, 17, 31, 36, 37, 54, 55, 61, 63, 73, 74], "fft": [0, 7, 8, 13, 17, 24, 25, 29, 31, 33, 34, 37, 38, 42, 48, 52, 54, 61, 74], "real_spac": [0, 7, 8, 17, 29, 37, 38, 52, 61, 74], "where": [0, 1, 3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 29, 30, 31, 32, 33, 36, 37, 40, 42, 46, 47, 48, 49, 51, 52, 53, 54, 55, 59, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 74, 75, 77, 79], "natur": [0, 4, 10, 17, 37, 38, 54, 55, 59, 61, 64, 71, 72], "ha": [0, 1, 3, 4, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 22, 24, 25, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39, 40, 42, 44, 46, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 61, 62, 63, 64, 66, 67, 70, 73, 74, 77], "over": [0, 1, 7, 10, 12, 13, 15, 17, 19, 20, 23, 26, 29, 31, 37, 40, 42, 47, 49, 52, 53, 54, 55, 57, 59, 61, 63, 64, 66, 72, 74, 75, 77, 79], "howev": [0, 1, 2, 3, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 25, 26, 27, 29, 30, 31, 32, 35, 36, 37, 38, 40, 42, 44, 46, 47, 48, 49, 51, 53, 54, 55, 59, 61, 63, 64, 67, 68, 72, 74, 75, 77, 79], "result": [0, 1, 5, 7, 11, 14, 15, 17, 19, 20, 23, 25, 26, 28, 29, 31, 32, 34, 36, 37, 38, 40, 42, 47, 48, 49, 52, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 71, 72, 73, 74, 77, 79], "includ": [0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 24, 29, 31, 32, 33, 35, 36, 37, 40, 42, 44, 46, 47, 49, 51, 52, 53, 54, 55, 56, 58, 59, 61, 63, 64, 66, 67, 68, 69, 72, 73, 74, 75, 77, 78], "equival": [0, 2, 3, 4, 7, 8, 11, 13, 15, 16, 17, 19, 20, 24, 29, 30, 31, 32, 34, 35, 36, 37, 40, 42, 49, 50, 51, 52, 54, 56, 59, 61, 64, 66, 69, 70, 71, 72, 73, 74, 75, 77, 79], "often": [0, 1, 7, 12, 13, 14, 17, 18, 20, 21, 31, 32, 34, 35, 37, 38, 40, 42, 46, 55, 59, 61, 64, 75, 77], "accept": [0, 6, 7, 12, 23, 30, 37, 42, 49, 51, 52, 53, 57, 59, 63, 66, 72], "other": [0, 1, 3, 5, 6, 7, 8, 9, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 50, 52, 53, 54, 55, 56, 59, 61, 63, 64, 65, 66, 67, 69, 72, 74, 75, 76, 77], "usual": [0, 1, 5, 7, 8, 11, 12, 13, 14, 15, 17, 18, 19, 20, 23, 24, 25, 27, 29, 31, 34, 35, 36, 37, 42, 44, 46, 48, 51, 54, 59, 61, 64, 66, 67, 68, 72, 73, 75, 77], "One": [0, 7, 12, 17, 18, 19, 21, 25, 36, 38, 40, 54, 56, 61, 63, 64, 66, 70, 72, 74], "just": [0, 1, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 26, 27, 31, 32, 33, 35, 36, 37, 40, 41, 42, 49, 51, 52, 53, 54, 56, 57, 59, 67, 69, 70, 72, 74, 75, 77], "need": [0, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 31, 32, 33, 34, 36, 37, 38, 39, 40, 42, 44, 45, 46, 48, 49, 52, 53, 54, 55, 56, 57, 58, 59, 61, 63, 64, 66, 67, 68, 72, 73, 74, 75, 76, 77, 79], "rememb": [0, 12, 14, 31, 54, 59], "draw": [0, 1, 6, 7, 11, 12, 13, 14, 15, 17, 18, 19, 20, 25, 27, 29, 31, 32, 36, 37, 38, 40, 42, 47, 48, 49, 51, 52, 53, 54, 57, 59, 61, 63, 64, 66, 74, 75], "final": [0, 1, 3, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 24, 25, 26, 28, 29, 31, 37, 40, 42, 46, 49, 51, 52, 54, 55, 56, 59, 61, 63, 66, 72, 74, 77], "avoid": [0, 2, 3, 7, 12, 20, 29, 36, 37, 49, 52, 54, 57, 61, 63], "second": [0, 1, 3, 7, 11, 12, 13, 14, 15, 17, 19, 20, 22, 23, 26, 29, 32, 36, 37, 40, 42, 49, 51, 54, 56, 57, 59, 61, 63, 64, 66, 69, 72, 74, 75, 77, 79], "time": [0, 5, 7, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 23, 27, 29, 31, 32, 33, 36, 37, 38, 40, 42, 44, 46, 49, 50, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 72, 74, 75, 77], "In": [0, 1, 3, 4, 6, 7, 8, 11, 12, 14, 15, 16, 17, 18, 19, 20, 23, 26, 27, 29, 31, 34, 35, 36, 37, 38, 40, 42, 46, 47, 48, 49, 50, 53, 54, 57, 58, 59, 61, 63, 64, 66, 67, 68, 69, 72, 74, 75, 77], "particular": [0, 1, 6, 7, 10, 11, 12, 13, 14, 15, 16, 19, 27, 32, 33, 37, 40, 41, 46, 49, 50, 54, 59, 63, 64, 66, 72, 74, 75, 77], "should": [0, 1, 2, 3, 4, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 28, 29, 31, 32, 34, 35, 36, 37, 38, 39, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 57, 59, 61, 63, 64, 66, 67, 68, 70, 72, 75, 77], "conjunct": [0, 7, 10, 37, 54, 74], "photon": [0, 1, 6, 7, 11, 13, 14, 15, 21, 25, 29, 30, 31, 36, 37, 38, 42, 43, 48, 51, 52, 53, 54, 61, 64, 66, 67, 70, 73, 74, 75], "shoot": [0, 7, 13, 14, 17, 25, 28, 29, 31, 36, 37, 38, 43, 48, 52, 53, 54, 56, 61, 74], "same": [0, 1, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 27, 28, 29, 31, 32, 35, 36, 37, 40, 42, 44, 47, 49, 51, 52, 53, 54, 56, 59, 60, 61, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77, 79], "reason": [0, 7, 9, 14, 15, 17, 19, 20, 23, 25, 29, 30, 31, 36, 37, 38, 40, 42, 48, 56, 57, 59, 60, 61, 63, 64, 74, 77], "want": [0, 1, 2, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 25, 26, 27, 31, 33, 35, 36, 37, 38, 39, 40, 42, 44, 45, 46, 48, 49, 51, 53, 54, 55, 59, 61, 62, 63, 64, 67, 70, 73, 74, 75, 77], "try": [0, 7, 8, 11, 12, 14, 15, 17, 23, 24, 29, 31, 33, 35, 37, 38, 40, 46, 49, 51, 52, 54, 55, 64, 68, 77], "remov": [0, 1, 12, 15, 20, 39, 40, 49, 52, 54, 56, 62, 63, 66], "effect": [0, 1, 5, 6, 7, 11, 12, 13, 15, 17, 18, 19, 20, 21, 23, 29, 30, 31, 32, 35, 36, 37, 38, 40, 42, 43, 49, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 67, 69, 73, 74, 75, 77, 79], "model": [0, 1, 7, 8, 11, 12, 13, 15, 20, 29, 37, 42, 43, 47, 51, 52, 53, 54, 55, 59, 61, 62, 63, 64, 65, 73, 74, 75, 76, 77], "pre": [0, 7, 17, 20, 40, 52, 54, 56], "call": [0, 1, 5, 6, 7, 8, 11, 12, 13, 14, 15, 17, 18, 19, 20, 23, 26, 27, 29, 31, 32, 34, 35, 36, 37, 38, 40, 42, 46, 49, 51, 52, 53, 54, 55, 59, 60, 63, 64, 67, 68, 69, 72, 75, 77], "automat": [0, 1, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 31, 35, 37, 38, 42, 44, 46, 47, 52, 53, 54, 55, 57, 59, 61, 63, 64, 66, 68, 72, 74, 77], "we": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 24, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 49, 51, 52, 53, 54, 55, 56, 59, 60, 61, 63, 64, 65, 66, 68, 69, 70, 72, 74, 75, 76, 77, 79], "lanczo": [0, 12, 13, 20, 25, 29, 48, 52, 59, 72, 76], "best": [0, 7, 17, 19, 20, 23, 25, 31, 40, 42, 48, 54, 61, 64, 72, 77], "higher": [0, 7, 13, 17, 29, 42, 49, 52, 60, 63, 64], "order": [0, 4, 5, 7, 11, 12, 13, 14, 15, 17, 19, 20, 23, 25, 26, 28, 29, 31, 37, 40, 42, 44, 47, 48, 49, 50, 52, 53, 54, 57, 59, 61, 63, 64, 72, 74, 75, 77], "tend": [0, 1, 8, 20, 42, 49, 66, 74], "work": [0, 6, 7, 11, 12, 13, 14, 15, 16, 17, 19, 20, 27, 32, 33, 35, 36, 37, 39, 40, 41, 42, 44, 45, 46, 49, 52, 53, 54, 57, 59, 63, 66, 72, 73, 75, 77], "better": [0, 5, 25, 29, 33, 37, 42, 48, 52, 54, 56, 64, 77], "here": [0, 1, 4, 5, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 24, 28, 29, 31, 36, 37, 38, 42, 46, 49, 51, 52, 54, 57, 59, 63, 64, 67, 73, 77, 79], "step": [0, 7, 14, 19, 23, 24, 26, 29, 32, 37, 38, 40, 42, 46, 49, 52, 54, 64, 74], "rather": [0, 1, 5, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 24, 27, 29, 32, 35, 36, 37, 39, 40, 42, 46, 49, 51, 52, 54, 56, 57, 59, 61, 63, 64, 66, 69, 72, 74, 75, 77], "slow": [0, 7, 12, 25, 37, 44, 48, 49, 52, 54, 61, 64, 67, 72, 74], "memori": [0, 7, 12, 14, 24, 33, 37, 38, 42, 49, 52, 54, 56, 63, 64, 67, 74, 77], "demand": [0, 20, 56], "so": [0, 2, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 40, 42, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 72, 74, 75, 77, 79], "caution": [0, 32, 54], "But": [0, 7, 11, 12, 13, 14, 15, 16, 17, 18, 21, 24, 25, 29, 36, 37, 41, 42, 46, 47, 48, 49, 59, 63, 64, 67, 74, 75, 77], "repres": [0, 1, 3, 6, 7, 8, 9, 10, 13, 19, 20, 23, 25, 27, 28, 29, 31, 36, 37, 40, 42, 47, 48, 52, 54, 55, 56, 57, 58, 59, 61, 63, 64, 66, 69, 70, 71, 72, 73, 74, 75, 77, 79], "underli": [0, 1, 5, 7, 20, 27, 32, 35, 40, 42, 49, 59, 60, 61, 63, 66, 77], "without": [0, 11, 15, 16, 17, 18, 20, 24, 29, 32, 34, 36, 37, 42, 44, 46, 50, 52, 54, 56, 59, 63, 64, 69, 72, 73, 75, 77], "It": [0, 4, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 42, 44, 46, 48, 49, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 67, 68, 69, 70, 72, 74, 75, 77], "therefor": [0, 7, 20, 27, 29, 31, 36, 42, 46, 59, 63, 64, 75, 77, 79], "rotat": [0, 7, 13, 15, 17, 20, 29, 36, 37, 40, 42, 49, 52, 53, 54, 57, 61, 64, 65, 69, 73, 74, 77, 79], "And": [0, 6, 13, 18, 21, 37, 53, 55, 58, 74, 77], "render": [0, 7, 8, 11, 13, 14, 17, 20, 29, 35, 36, 37, 38, 52, 55, 57, 63, 64], "do": [0, 1, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 29, 31, 33, 35, 36, 37, 40, 42, 44, 45, 46, 47, 48, 49, 52, 53, 55, 57, 59, 60, 61, 63, 64, 66, 68, 72, 73, 74, 75, 77], "involv": [0, 7, 17, 18, 29, 33, 37, 52, 63, 77], "undersampl": [0, 49, 64], "noisi": [0, 36, 55], "mai": [0, 1, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 39, 40, 42, 44, 46, 49, 51, 52, 53, 54, 56, 57, 59, 61, 62, 63, 64, 66, 67, 68, 70, 72, 73, 75, 76, 77], "necessarili": [0, 3, 7, 13, 14, 17, 19, 24, 25, 29, 31, 33, 36, 37, 48, 49, 55, 59, 73, 74, 77], "well": [0, 1, 7, 8, 9, 11, 12, 14, 16, 17, 18, 22, 24, 29, 36, 37, 38, 42, 44, 49, 51, 53, 54, 59, 61, 63, 67, 74, 75, 77], "user": [0, 1, 2, 4, 5, 7, 11, 14, 15, 16, 18, 20, 23, 29, 31, 32, 33, 35, 37, 38, 39, 40, 42, 44, 46, 52, 53, 54, 56, 59, 60, 61, 63, 64, 70, 72, 74, 75, 77], "some": [0, 1, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 23, 24, 27, 29, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77], "care": [0, 7, 20, 24, 29, 32, 35, 37, 42, 54, 55, 59, 64, 75], "valid": [0, 1, 4, 11, 12, 13, 14, 15, 16, 17, 19, 20, 24, 26, 31, 34, 35, 36, 37, 38, 40, 42, 47, 48, 50, 51, 52, 53, 54, 56, 59, 66, 69, 75, 77], "suffici": [0, 12, 40, 54, 63, 72, 74], "accur": [0, 1, 5, 7, 8, 17, 19, 20, 25, 29, 37, 38, 48, 49, 52, 53, 54, 59, 64, 66, 67, 68, 72, 77, 78], "your": [0, 1, 5, 11, 12, 13, 16, 17, 19, 20, 33, 34, 37, 38, 39, 42, 43, 44, 45, 46, 49, 52, 53, 54, 55, 62, 63, 67, 68, 74, 75, 77], "case": [0, 1, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 25, 26, 27, 28, 29, 31, 32, 34, 35, 36, 37, 38, 39, 40, 42, 46, 47, 48, 49, 51, 52, 54, 55, 57, 59, 61, 63, 64, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77], "associ": [0, 7, 36, 52, 54, 59, 63, 69, 71], "paramet": [0, 1, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 42, 43, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 69, 70, 71, 72, 73, 74, 75, 77, 79], "provid": [0, 4, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 24, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 40, 42, 46, 50, 51, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 72, 77], "overrid": [0, 7, 11, 12, 13, 14, 16, 17, 20, 24, 29, 30, 37, 40, 54], "nativ": [0, 4, 13, 15, 18, 40, 49, 52, 64, 66, 77], "e": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 30, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 70, 72, 73, 74, 75, 77, 79], "g": [0, 1, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 25, 26, 27, 29, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 44, 45, 46, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 65, 66, 67, 68, 69, 72, 74, 75, 77, 79], "resampl": 0, "quintic": [0, 7, 12, 13, 19, 25, 29, 48, 52, 54, 59, 61, 63, 72, 76], "choos": [0, 7, 11, 17, 29, 32, 37, 49, 54, 60, 61, 63, 64, 74, 75], "two": [0, 1, 3, 5, 7, 8, 10, 11, 13, 14, 15, 16, 17, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 42, 48, 49, 50, 51, 52, 53, 54, 55, 58, 59, 60, 61, 63, 64, 66, 69, 70, 72, 74, 75, 77, 79], "quantiti": [0, 1, 14, 19, 29, 39, 40, 42, 51, 54, 57, 59, 61, 64, 66], "affect": [0, 1, 5, 6, 13, 20, 29, 37, 40, 42, 54, 66, 67], "k": [0, 7, 11, 12, 13, 14, 15, 17, 19, 20, 24, 25, 26, 27, 29, 32, 34, 37, 38, 42, 47, 48, 49, 52, 54, 57, 59, 60, 61, 63, 64, 66, 74, 79], "amount": [0, 1, 7, 10, 13, 15, 17, 20, 22, 29, 36, 37, 38, 42, 51, 54, 59, 67, 73, 77], "pad": [0, 7, 13, 17, 19, 29, 42, 52, 54, 61, 63, 72, 75], "around": [0, 11, 12, 13, 15, 17, 22, 35, 37, 49, 50, 52, 54, 56, 59, 63, 64, 74, 77, 78], "origin": [0, 1, 3, 7, 8, 11, 12, 13, 14, 15, 17, 19, 20, 22, 24, 25, 29, 30, 31, 35, 36, 37, 40, 42, 48, 49, 51, 52, 54, 56, 59, 61, 63, 64, 66, 67, 69, 70, 72, 73, 74, 75, 77, 78], "chosen": [0, 11, 20, 29, 37, 54, 59, 60, 63, 64], "test": [0, 1, 2, 3, 7, 12, 13, 15, 17, 20, 29, 37, 40, 42, 43, 46, 52, 54, 59, 60, 63, 64, 74, 75, 76], "branch": [0, 45, 46, 69, 74], "389": [0, 52, 63], "reach": [0, 7, 29, 52, 59], "good": [0, 7, 8, 11, 12, 13, 17, 20, 25, 29, 37, 38, 40, 42, 48, 53, 54, 57, 61, 63, 64, 72, 73, 77], "accuraci": [0, 7, 8, 23, 25, 29, 37, 38, 40, 47, 48, 49, 52, 54, 61, 63, 64, 67, 73, 74], "being": [0, 1, 3, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 29, 30, 31, 33, 36, 37, 40, 42, 47, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 62, 63, 64, 66, 67, 68, 72, 73, 74, 75, 77], "excess": 0, "particularli": [0, 7, 35, 37, 40, 42, 49, 53, 54, 59, 63, 66, 74, 75], "wari": 0, "about": [0, 3, 4, 7, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 26, 27, 28, 29, 31, 32, 33, 36, 37, 38, 39, 40, 42, 44, 46, 49, 52, 53, 54, 55, 59, 61, 63, 64, 65, 66, 68, 73, 74, 75, 76, 77], "chang": [0, 1, 5, 7, 11, 12, 13, 14, 15, 16, 19, 20, 21, 24, 25, 27, 29, 30, 33, 35, 36, 37, 38, 40, 42, 43, 46, 49, 52, 54, 55, 56, 57, 59, 60, 63, 64, 66, 67, 69, 74, 75, 77], "from": [0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 30, 31, 32, 35, 36, 37, 38, 40, 42, 43, 44, 45, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 66, 67, 68, 69, 72, 73, 74, 75, 77, 78, 79], "freedom": [0, 19, 27, 32], "factor": [0, 3, 5, 7, 13, 17, 20, 22, 23, 28, 29, 36, 37, 40, 42, 49, 50, 51, 53, 54, 56, 57, 59, 60, 61, 63, 64, 67, 72, 73, 74, 75], "warn": [0, 4, 13, 15, 17, 32, 35, 40, 43, 49, 52, 54, 61, 63, 72, 77], "rais": [0, 8, 10, 15, 17, 20, 32, 33, 35, 37, 38, 42, 49, 52, 54, 61, 63, 64, 66, 72, 77, 79], "code": [0, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 26, 29, 33, 35, 36, 37, 38, 40, 42, 43, 46, 49, 52, 53, 54, 55, 56, 59, 60, 61, 63, 64, 68, 74, 75, 76, 77], "modifi": [0, 5, 13, 16, 20, 23, 24, 29, 33, 36, 37, 40, 42, 44, 46, 48, 49, 52, 54, 55, 59, 63, 64, 73, 74], "combin": [0, 1, 7, 8, 13, 17, 19, 20, 21, 28, 33, 36, 37, 38, 40, 42, 49, 51, 52, 56, 61, 63, 64, 65, 72, 73, 79], "known": [0, 11, 15, 17, 26, 27, 29, 42, 46, 49, 59, 67, 72], "give": [0, 1, 4, 5, 6, 7, 10, 11, 13, 15, 17, 18, 19, 20, 25, 29, 32, 33, 35, 36, 37, 39, 40, 42, 48, 51, 56, 59, 60, 63, 64, 67, 73, 75, 77, 79], "signific": [0, 1, 7, 13, 42, 49, 52, 54, 59, 63, 64, 66, 74], "error": [0, 1, 4, 10, 11, 13, 14, 15, 17, 18, 19, 23, 26, 29, 32, 37, 38, 40, 42, 43, 44, 47, 49, 52, 54, 64, 66, 68, 77], "more": [0, 1, 3, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 25, 26, 28, 29, 31, 32, 33, 35, 36, 37, 38, 39, 40, 41, 42, 44, 46, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 65, 67, 68, 71, 72, 73, 74, 75, 77], "detail": [0, 1, 3, 7, 8, 11, 12, 13, 16, 19, 20, 21, 25, 29, 31, 32, 35, 36, 37, 39, 40, 42, 44, 46, 48, 52, 53, 54, 58, 59, 61, 63, 64, 65, 68, 71, 72, 75, 77, 79], "found": [0, 1, 11, 15, 20, 32, 36, 37, 42, 46, 48, 49, 52, 54, 63, 64, 66, 77], "http": [0, 20, 25, 27, 31, 32, 33, 36, 42, 46, 47, 48, 49, 53, 54, 60, 61, 63, 64, 66, 68, 73, 75, 77, 79], "arxiv": [0, 25, 36, 42, 48, 63, 66], "org": [0, 25, 27, 32, 36, 42, 46, 47, 48, 54, 60, 61, 63, 66, 77, 79], "ab": [0, 1, 25, 26, 28, 29, 32, 36, 37, 42, 48, 53, 56, 59, 63, 64, 66, 73], "1401": [0, 25, 48, 63], "2636": [0, 25, 48, 63], "especi": [0, 7, 13, 19, 20, 46, 52, 54, 63, 64, 67], "tabl": [0, 1, 4, 12, 17, 20, 28, 29, 32, 36, 37, 38, 52, 54, 56, 57, 60, 63, 64, 66, 67, 68, 74, 76], "1": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 42, 43, 46, 47, 48, 49, 50, 51, 54, 55, 56, 57, 58, 59, 61, 63, 64, 66, 67, 68, 69, 70, 72, 73, 75, 77, 79], "comment": [0, 4, 9, 12, 35, 44, 52, 63], "github": [0, 33, 34, 36, 44, 45, 46, 49, 53, 63, 64, 75, 77], "com": [0, 33, 36, 45, 46, 49, 53, 63, 64, 68, 75, 77], "develop": [0, 31, 33, 36, 43, 45, 46, 60, 63, 64, 73], "issu": [0, 33, 36, 37, 44, 49, 52, 53, 63, 72, 74], "issuecom": [0, 63], "26166621": [0, 63], "follow": [0, 4, 7, 10, 11, 12, 13, 14, 15, 17, 19, 20, 21, 22, 23, 26, 29, 32, 35, 36, 37, 38, 40, 42, 45, 46, 51, 53, 54, 55, 56, 59, 61, 63, 64, 68, 69, 72, 75, 77, 79], "nois": [0, 4, 6, 7, 9, 12, 13, 14, 15, 16, 17, 18, 21, 25, 28, 29, 31, 36, 37, 42, 43, 49, 52, 53, 55, 60, 63, 64, 74], "desir": [0, 7, 11, 12, 13, 17, 19, 20, 23, 26, 28, 29, 32, 35, 37, 40, 42, 47, 49, 51, 54, 59, 60, 61, 63, 64, 66, 74, 75, 77], "To": [0, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 24, 31, 32, 35, 36, 37, 40, 42, 44, 53, 54, 61, 63, 64, 69, 72], "target": [0, 7, 8, 20, 26, 28, 29, 37, 42, 49, 52, 56, 64, 66, 74, 75], "size": [0, 3, 7, 11, 13, 14, 15, 16, 17, 19, 20, 22, 23, 24, 25, 28, 29, 31, 32, 33, 34, 35, 36, 37, 38, 40, 42, 43, 47, 48, 49, 52, 53, 54, 56, 57, 59, 61, 63, 64, 66, 67, 70, 74, 77, 79], "gaussian": [0, 7, 8, 9, 11, 12, 13, 19, 20, 23, 27, 29, 32, 36, 37, 40, 42, 49, 51, 59, 63, 64, 65, 74], "random": [0, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20, 25, 28, 29, 36, 37, 43, 49, 51, 52, 54, 55, 56, 57, 59, 63, 66, 67, 71, 74], "varianc": [0, 4, 11, 12, 13, 14, 15, 17, 20, 23, 27, 32, 37, 40, 42, 51, 52, 59, 63, 71, 74], "correl": [0, 6, 7, 8, 11, 13, 28, 29, 32, 43, 49, 51, 52, 53, 54, 56, 59, 62, 63, 64], "field": [0, 4, 5, 7, 9, 10, 20, 22, 29, 31, 35, 37, 39, 40, 42, 43, 49, 50, 52, 54, 59, 60, 63, 64, 69, 74, 78, 79], "either": [0, 1, 3, 4, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 27, 29, 30, 31, 33, 35, 36, 37, 38, 40, 42, 44, 46, 48, 49, 50, 51, 52, 53, 54, 57, 59, 61, 63, 64, 67, 69, 72, 73, 75, 77, 79], "basecorrelatednois": [0, 7, 8, 11, 20, 37, 42, 52, 62, 63], "instanc": [0, 1, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 31, 32, 33, 35, 36, 37, 38, 39, 40, 42, 46, 47, 49, 50, 51, 52, 54, 55, 56, 57, 58, 59, 61, 63, 64, 66, 67, 69, 71, 72, 73, 75, 77], "deriv": [0, 5, 7, 8, 11, 12, 13, 14, 15, 17, 20, 24, 27, 29, 36, 37, 40, 42, 52, 54, 61, 67, 68, 71, 72], "string": [0, 1, 3, 4, 7, 11, 12, 13, 14, 15, 16, 19, 21, 23, 24, 25, 27, 39, 40, 48, 49, 52, 54, 58, 59, 61, 63, 64, 66, 72, 75, 77, 79], "interpret": [0, 7, 19, 36, 37, 61, 63], "filenam": [0, 4, 11, 13, 14, 32, 49, 52, 61, 68, 74], "correlatednois": [0, 20, 27, 52, 62, 71], "pass": [0, 1, 6, 7, 11, 12, 13, 14, 15, 19, 27, 29, 30, 31, 32, 35, 37, 40, 42, 44, 49, 50, 52, 54, 61, 64, 66, 67, 72, 77], "number": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 35, 36, 37, 38, 40, 42, 44, 46, 47, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 62, 63, 64, 66, 67, 69, 71, 72, 73, 74, 75, 76, 77], "gener": [0, 5, 7, 11, 12, 14, 16, 17, 18, 19, 20, 22, 25, 27, 29, 32, 33, 34, 36, 37, 40, 42, 43, 47, 48, 49, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 66, 67, 68, 71, 72, 74, 75, 77, 78, 79], "determinist": [0, 11, 13, 27, 32, 64], "By": [0, 1, 7, 20, 40, 42, 44, 51, 59, 60, 61, 64, 66], "recalcul": [0, 1, 11, 13, 67], "point": [0, 1, 3, 4, 5, 6, 7, 11, 12, 13, 14, 16, 17, 18, 19, 20, 22, 24, 25, 26, 29, 32, 35, 36, 37, 40, 43, 47, 48, 49, 52, 56, 57, 58, 59, 60, 64, 65, 66, 67, 68, 72, 73, 75, 77, 79], "further": [0, 5, 9, 15, 20, 29, 30, 32, 53, 54, 64, 74], "most": [0, 2, 3, 5, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 29, 30, 32, 35, 36, 37, 38, 39, 40, 41, 42, 44, 46, 49, 51, 52, 54, 55, 56, 57, 58, 59, 61, 63, 64, 65, 75, 77], "conserv": [0, 25, 29, 37, 42, 48, 69, 77], "For": [0, 1, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 22, 26, 27, 29, 30, 34, 35, 36, 37, 38, 39, 40, 41, 42, 44, 46, 47, 53, 54, 55, 58, 59, 60, 61, 63, 64, 66, 68, 72, 73, 74, 75, 77, 79], "galaxi": [0, 7, 8, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 22, 23, 31, 32, 37, 40, 42, 43, 52, 53, 55, 57, 59, 61, 64, 65, 66, 69, 74, 75, 77], "psf": [0, 6, 7, 8, 9, 11, 14, 15, 16, 17, 18, 19, 20, 23, 36, 37, 40, 43, 52, 53, 55, 57, 63, 64, 65, 66, 68, 71, 73, 74, 75], "easili": [0, 13, 36, 37, 40, 42, 59, 67], "make": [0, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 29, 32, 35, 36, 37, 38, 40, 42, 46, 49, 51, 52, 53, 54, 58, 59, 61, 63, 64, 67, 72, 74, 75, 77], "sever": [0, 1, 7, 9, 10, 11, 13, 14, 15, 16, 17, 18, 24, 26, 27, 29, 32, 36, 37, 38, 40, 42, 49, 52, 54, 56, 66, 74, 75, 77], "04": 0, "turn": [0, 10, 11, 14, 15, 17, 18, 20, 28, 29, 37, 40, 42, 47, 49, 54, 56, 67, 74, 77], "off": [0, 1, 3, 6, 8, 10, 11, 14, 17, 20, 23, 29, 35, 37, 38, 40, 46, 49, 51, 52, 54, 58, 59, 60, 67, 70, 74, 77], "contain": [0, 5, 7, 11, 12, 13, 14, 16, 17, 19, 20, 23, 24, 25, 29, 31, 32, 35, 37, 40, 41, 42, 49, 54, 57, 59, 60, 61, 63, 64, 67, 68, 72, 74, 75], "high": [0, 7, 13, 25, 37, 38, 42, 48, 49, 53, 54, 61, 64], "": [0, 1, 6, 7, 12, 13, 14, 15, 17, 19, 20, 24, 27, 29, 30, 32, 33, 35, 36, 37, 38, 40, 42, 43, 47, 48, 49, 50, 51, 54, 55, 56, 57, 59, 61, 63, 64, 66, 68, 69, 70, 72, 74, 75, 79], "n": [0, 1, 5, 6, 7, 8, 10, 11, 12, 13, 16, 17, 19, 20, 24, 25, 27, 28, 29, 30, 32, 34, 36, 37, 38, 42, 44, 47, 48, 49, 52, 53, 54, 56, 60, 61, 63, 66, 74, 77, 79], "exampl": [0, 3, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 19, 20, 27, 32, 36, 37, 40, 42, 43, 47, 49, 51, 52, 53, 54, 58, 59, 60, 61, 63, 64, 69, 72, 73, 74, 75, 77, 79], "interpolated_imag": 0, "initi": [0, 1, 3, 5, 6, 7, 11, 12, 14, 15, 17, 18, 19, 20, 23, 24, 26, 27, 28, 29, 32, 35, 36, 37, 40, 42, 52, 54, 56, 58, 59, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 75, 77], "comparison": [0, 7, 20, 29, 54, 59], "zero": [0, 1, 7, 11, 13, 17, 19, 20, 23, 24, 25, 26, 27, 28, 29, 32, 36, 37, 40, 42, 49, 50, 52, 54, 56, 59, 61, 64, 66, 67, 69, 70, 72, 79], "itself": [0, 7, 8, 11, 12, 14, 17, 24, 29, 36, 37, 42, 45, 51, 52, 55, 59, 60, 63, 64, 67, 74], "compar": [0, 7, 13, 20, 36, 40, 42, 49, 54, 59, 61, 63, 64, 66, 74], "im1": [0, 24, 37], "im2": [0, 24, 37, 42], "snippet": [0, 37, 49, 63], "execut": [0, 9, 11, 12, 13, 14, 15, 17, 19, 42, 43, 44, 46, 52, 53, 66, 68, 74, 77], "directori": [0, 4, 9, 11, 12, 13, 14, 15, 16, 17, 19, 21, 31, 35, 42, 44, 46, 49, 52, 53, 60, 63, 67, 68, 73, 74, 77], "fit": [0, 4, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 23, 31, 37, 40, 42, 43, 52, 53, 54, 56, 61, 63, 67, 68, 74, 75, 76, 77, 79], "read": [0, 1, 4, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 22, 24, 31, 38, 40, 42, 44, 46, 51, 52, 53, 56, 59, 63, 64, 66, 72, 74, 75, 76, 77], "data": [0, 4, 7, 11, 14, 15, 16, 20, 23, 24, 25, 27, 29, 31, 32, 35, 37, 40, 42, 43, 48, 49, 52, 53, 55, 56, 60, 64, 65, 72, 74, 77, 79], "147246": 0, "0_150": 0, "416558_1": 0, "998697_masknois": 0, "int_im1": 0, "int_im2": 0, "blankimg": 0, "imagef": [0, 29, 41, 42, 74], "1000": [0, 16, 23, 37, 40, 56, 63], "examin": [0, 36], "clearli": [0, 37], "show": [0, 10, 36, 42, 59, 63, 64, 74], "how": [0, 1, 5, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 24, 25, 26, 27, 29, 33, 35, 36, 37, 38, 40, 41, 42, 43, 44, 46, 48, 49, 51, 52, 54, 55, 59, 60, 61, 62, 63, 64, 66, 68, 69, 71, 72, 73, 74, 75], "similar": [0, 7, 11, 12, 14, 17, 19, 20, 29, 35, 36, 37, 46, 49, 52, 54, 63, 74, 76, 77], "lead": [0, 1, 13, 14, 15, 19, 20, 29, 36, 37, 42, 49, 52, 53, 54, 64, 66, 69, 72, 75, 79], "appear": [0, 16, 19, 29, 30, 37, 57, 64, 75], "re": [0, 4, 20, 27, 29, 40, 46, 54, 60, 63, 72], "construct": [0, 4, 7, 8, 11, 12, 13, 14, 15, 17, 19, 20, 24, 27, 28, 29, 31, 32, 35, 36, 37, 38, 40, 42, 49, 50, 51, 52, 54, 56, 57, 59, 61, 63, 69, 72, 73, 74, 77, 78, 79], "indic": [0, 4, 7, 12, 13, 14, 15, 18, 19, 23, 28, 29, 30, 32, 33, 35, 36, 38, 40, 41, 42, 49, 53, 54, 56, 57, 58, 61, 63, 64, 66, 67, 71, 72, 73, 74, 79], "file": [0, 1, 4, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 32, 33, 37, 39, 40, 42, 43, 44, 45, 46, 48, 49, 52, 53, 54, 56, 57, 59, 60, 61, 63, 64, 66, 67, 72, 74, 76, 77], "latter": [0, 3, 7, 11, 15, 27, 35, 37, 38, 40, 41, 42, 49, 50, 54, 57, 59, 63, 64, 72], "kwarg": [0, 3, 7, 8, 11, 12, 13, 15, 17, 20, 30, 33, 36, 37, 38, 42, 47, 48, 49, 52, 54, 57, 58, 59, 63, 64, 66, 69, 72, 73, 77], "nearest": [0, 7, 12, 13, 19, 25, 28, 42, 47, 48, 54, 59, 61, 63, 72, 76], "linear": [0, 1, 5, 7, 12, 13, 17, 19, 20, 23, 25, 29, 32, 36, 37, 40, 42, 46, 47, 48, 49, 52, 54, 57, 59, 61, 63, 64, 66, 70, 72, 74, 76, 77, 79], "cubic": [0, 7, 12, 13, 19, 25, 48, 54, 59, 61, 63, 72, 74, 76], "lanczosn": [0, 7, 12, 13, 19, 48, 54, 61, 63], "integ": [0, 3, 4, 7, 11, 12, 13, 15, 20, 22, 23, 24, 25, 26, 27, 29, 32, 35, 37, 39, 42, 47, 48, 49, 52, 54, 58, 59, 61, 63, 64], "strongli": [0, 7, 63], "leav": [0, 1, 7, 11, 14, 16, 20, 24, 37, 42, 46, 49, 54, 57, 61, 63, 64, 66], "its": [0, 1, 4, 5, 6, 7, 8, 10, 13, 15, 16, 17, 18, 19, 20, 24, 25, 27, 29, 31, 32, 36, 37, 40, 42, 44, 47, 48, 49, 52, 54, 55, 57, 59, 61, 63, 64, 66, 67, 69, 70, 73, 74, 75, 77], "see": [0, 1, 3, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 25, 27, 29, 32, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 48, 49, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 71, 72, 73, 74, 77, 79], "text": [0, 7, 12, 19, 35, 63, 77], "f": [0, 1, 10, 11, 13, 17, 19, 25, 26, 29, 32, 36, 42, 47, 48, 49, 52, 54, 57, 61, 63, 66, 67, 72, 77, 79], "mean": [0, 4, 5, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 23, 25, 27, 29, 31, 32, 34, 35, 36, 37, 40, 42, 44, 46, 49, 52, 53, 54, 55, 57, 59, 60, 61, 63, 67, 69, 72, 74, 75, 77], "equal": [0, 3, 5, 7, 11, 13, 17, 20, 22, 25, 28, 29, 34, 37, 40, 42, 47, 49, 52, 54, 55, 63, 69, 72, 75, 77, 79], "total": [0, 9, 10, 12, 13, 15, 17, 20, 23, 25, 28, 29, 36, 37, 38, 40, 42, 48, 49, 54, 55, 56, 57, 59, 60, 61, 63, 64, 67, 68, 70, 73, 74, 75], "distribut": [0, 6, 13, 17, 19, 20, 27, 29, 32, 36, 37, 40, 43, 45, 55, 57, 62, 63, 64, 68, 74], "overridden": [0, 7, 11, 14, 17, 54], "explicit": [0, 7, 11, 15, 17, 27, 29, 35, 36, 37, 52, 59], "store": [0, 1, 3, 4, 7, 12, 15, 16, 19, 20, 22, 24, 25, 26, 27, 29, 31, 35, 38, 40, 42, 52, 54, 55, 56, 59, 60, 63, 64, 67, 71, 72], "take": [0, 1, 3, 7, 8, 9, 11, 14, 15, 17, 19, 20, 24, 26, 29, 31, 32, 35, 36, 37, 38, 40, 42, 44, 49, 51, 52, 54, 56, 58, 59, 63, 64, 66, 67, 72, 75, 77], "At": [0, 11, 12, 13, 18, 20, 36, 37, 42, 53, 56, 63, 77], "impli": [0, 12, 13, 29, 59, 63, 66], "arcsec": [0, 7, 9, 11, 12, 13, 15, 17, 19, 20, 25, 29, 31, 36, 37, 40, 41, 42, 50, 53, 54, 57, 59, 61, 63, 64, 70, 75, 77], "import": [0, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 21, 29, 31, 32, 35, 37, 49, 52, 54, 59, 60, 62, 63, 64, 66, 75, 77], "plan": [0, 7, 12, 52, 63, 64, 74], "whiten": [0, 7, 11, 13, 15, 17, 20, 37, 42, 52, 53, 63, 74], "sure": [0, 4, 7, 11, 12, 13, 14, 15, 20, 24, 26, 37, 38, 46, 49, 51, 54, 63, 77], "larger": [0, 7, 12, 13, 17, 20, 29, 33, 37, 38, 42, 49, 53, 54, 59, 61, 63, 67, 74], "postag": [0, 7, 11, 12, 13, 14, 15, 17, 18, 19, 23, 65, 74], "stamp": [0, 3, 7, 9, 11, 12, 13, 14, 16, 18, 19, 23, 31, 37, 43, 52, 56, 57, 65, 74], "onto": [0, 7, 11, 12, 14, 15, 17, 18, 20, 24, 25, 31, 37, 42, 55, 63, 64, 67, 71, 77], "properti": [0, 1, 3, 4, 7, 8, 11, 12, 13, 14, 16, 17, 18, 20, 29, 32, 36, 37, 40, 42, 48, 49, 51, 52, 53, 54, 56, 59, 61, 63, 66, 69, 70, 72, 73, 74, 75, 77, 79], "wai": [0, 3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 24, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 42, 44, 46, 49, 50, 51, 52, 53, 54, 55, 57, 59, 61, 62, 65, 69, 73, 74, 77, 79], "float": [0, 1, 2, 3, 4, 7, 11, 13, 15, 19, 22, 23, 24, 28, 29, 32, 35, 40, 42, 47, 48, 50, 52, 54, 57, 58, 59, 61, 72, 74, 75, 77], "uncorrel": [0, 11, 20, 28, 52, 55, 71], "inform": [0, 4, 7, 9, 10, 12, 13, 14, 15, 16, 18, 19, 20, 23, 24, 26, 28, 29, 31, 32, 35, 36, 37, 39, 40, 42, 44, 46, 49, 52, 53, 54, 55, 57, 59, 61, 63, 64, 66, 67, 68, 73, 74, 77], "power": [0, 5, 7, 12, 13, 17, 19, 20, 37, 42, 43, 49, 52, 53, 54, 57, 61, 66, 71, 72, 74, 78], "spectrum": [0, 1, 7, 11, 12, 13, 19, 20, 29, 36, 37, 43, 52, 53, 54, 61, 66, 71, 74, 75, 78], "keyword": [0, 7, 8, 20, 30, 32, 35, 36, 37, 38, 40, 42, 49, 52, 54, 57, 59, 60, 61, 63, 64, 66, 72, 77], "preced": [0, 15, 17, 19, 46, 54, 56], "calcul": [0, 1, 5, 7, 11, 12, 13, 15, 16, 17, 19, 20, 21, 25, 29, 36, 37, 38, 40, 42, 46, 47, 48, 50, 52, 53, 54, 55, 59, 60, 61, 63, 64, 66, 67, 72, 73, 74, 75, 76, 77], "proper": [0, 23, 53], "first": [0, 1, 3, 7, 9, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 26, 27, 29, 32, 35, 36, 37, 39, 40, 42, 46, 48, 49, 51, 52, 54, 55, 56, 59, 61, 63, 64, 66, 69, 72, 74, 77, 79], "keep": [0, 1, 8, 12, 13, 19, 22, 24, 29, 32, 35, 37, 40, 42, 43, 49, 52, 59, 61, 64, 66, 74, 77], "mind": [0, 59, 63, 64, 77], "function": [0, 1, 3, 6, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 25, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 42, 43, 46, 47, 48, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 62, 63, 65, 66, 67, 68, 69, 71, 72, 73, 74, 75, 77, 78], "intern": [0, 1, 7, 11, 13, 15, 17, 20, 27, 29, 32, 36, 37, 49, 51, 52, 54, 57, 59, 61, 63, 64, 66, 70, 74, 75, 77], "within": [0, 3, 4, 9, 10, 12, 16, 17, 19, 23, 24, 26, 28, 31, 36, 37, 40, 42, 46, 53, 54, 55, 56, 57, 59, 60, 64, 70, 74, 75, 77], "neglig": 0, "overhead": [0, 37, 42, 52, 69, 75], "b": [0, 5, 12, 13, 19, 24, 26, 27, 29, 31, 32, 36, 37, 40, 49, 59, 60, 64, 69, 77], "d": [0, 3, 4, 13, 19, 22, 24, 26, 29, 32, 35, 36, 42, 49, 53, 58, 59, 61, 63, 72, 75, 77], "repeatedli": [0, 12], "below": [0, 1, 6, 7, 9, 11, 12, 13, 14, 17, 19, 20, 36, 37, 39, 40, 42, 52, 54, 56, 59, 60, 63, 64, 66, 67, 68, 69, 72, 75], "prevent": [0, 5, 11, 14, 24, 29, 32, 38, 40, 52], "repeat": [0, 11, 16, 19, 24, 42, 49, 64, 74], "whether": [0, 3, 4, 7, 8, 11, 12, 13, 14, 15, 17, 18, 19, 20, 22, 23, 24, 25, 28, 29, 31, 32, 33, 34, 35, 36, 37, 40, 42, 47, 48, 49, 50, 54, 56, 59, 61, 63, 64, 66, 67, 72, 73, 75, 77], "cach": [0, 7, 13, 15, 24, 27, 29, 32, 36, 42, 52, 61, 63, 64, 76], "save": [0, 7, 11, 14, 17, 23, 27, 31, 32, 37, 40, 49, 59, 63, 64], "build": [0, 4, 7, 11, 12, 13, 14, 16, 17, 18, 19, 21, 31, 35, 36, 42, 46, 49, 52, 54, 59, 60, 63, 64, 65, 67, 72, 74, 75, 77], "suppli": [0, 1, 3, 6, 7, 15, 20, 27, 29, 32, 36, 37, 42, 49, 52, 54, 59, 61, 64, 77], "basedevi": [0, 7, 19, 20, 27, 28, 29, 32, 36, 37, 49, 51, 52, 54, 55, 56, 57, 59, 62, 63, 64, 66, 67, 74], "Such": [0, 18, 37, 49, 55], "creat": [0, 1, 7, 8, 11, 12, 13, 14, 15, 17, 20, 24, 27, 29, 32, 36, 37, 39, 41, 42, 47, 48, 49, 52, 54, 55, 56, 57, 59, 61, 63, 64, 66, 67, 68, 70, 72, 73, 74, 75, 77, 79], "seed": [0, 11, 15, 16, 20, 27, 32, 37, 39, 52, 54, 57, 74], "ignor": [0, 7, 11, 12, 13, 14, 15, 17, 19, 29, 31, 35, 37, 40, 42, 48, 49, 54, 59, 60, 61, 63, 64, 73, 75, 79], "both": [0, 1, 3, 6, 7, 8, 11, 13, 15, 17, 19, 21, 23, 24, 26, 27, 29, 31, 32, 33, 36, 37, 42, 44, 46, 47, 49, 52, 54, 55, 56, 57, 59, 61, 62, 63, 64, 66, 72, 74, 75, 77, 79], "ensur": [0, 7, 8, 11, 20, 21, 32, 42, 49, 52, 54, 59, 61], "roughli": [0, 7, 13, 20, 29, 33, 36, 54, 63, 64], "allow": [0, 1, 4, 7, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 22, 24, 29, 31, 32, 33, 36, 37, 38, 39, 40, 42, 44, 49, 51, 52, 54, 55, 56, 58, 59, 60, 63, 64, 66, 70, 75, 77, 79], "flexibl": [0, 40], "represent": [0, 1, 7, 11, 20, 23, 29, 49, 52, 59, 61, 63, 64, 66, 69, 75], "sky": [0, 11, 13, 15, 17, 19, 20, 23, 29, 31, 36, 37, 40, 41, 42, 51, 52, 54, 55, 59, 60, 61, 63, 64, 67, 74, 75, 77], "level": [0, 1, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 24, 36, 37, 40, 42, 43, 46, 49, 51, 52, 54, 62, 63, 67, 74, 77], "thei": [0, 2, 6, 7, 8, 11, 12, 13, 14, 15, 16, 18, 19, 20, 21, 23, 24, 27, 29, 30, 31, 32, 35, 36, 37, 40, 42, 44, 46, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 66, 67, 68, 72, 74, 76, 77, 79], "after": [0, 1, 4, 7, 10, 11, 12, 13, 14, 15, 17, 19, 20, 23, 25, 26, 29, 31, 32, 35, 36, 37, 38, 39, 40, 42, 45, 49, 55, 57, 59, 63, 64, 66, 74, 75], "perform": [0, 11, 13, 14, 15, 17, 24, 25, 26, 31, 37, 38, 42, 46, 47, 48, 49, 52, 54, 55, 56, 59, 60, 61, 66, 74, 77], "extent": [0, 5, 20, 25, 29, 33, 42, 48, 54, 55, 59, 60, 64], "optim": [0, 7, 13, 17, 40, 48, 53, 55, 63, 64, 72, 73], "stepsiz": 0, "lookup": [0, 20, 29, 36, 37, 38, 43, 52, 54, 60, 67, 76], "know": [0, 7, 11, 12, 13, 14, 16, 17, 18, 19, 20, 26, 27, 29, 31, 37, 46, 54, 59, 63, 64, 76, 77], "priori": [0, 29], "maximum": [0, 5, 11, 15, 19, 23, 24, 25, 26, 28, 29, 32, 33, 36, 37, 38, 40, 42, 48, 49, 54, 56, 59, 67, 72, 77, 79], "stepk": [0, 7, 29, 37, 38, 52, 54, 61], "bool": [0, 12, 13, 15, 19, 21, 22, 23, 24, 25, 26, 28, 29, 49, 52, 77], "still": [0, 1, 7, 10, 11, 16, 20, 24, 25, 27, 32, 35, 36, 37, 40, 42, 45, 48, 49, 54, 59, 61, 63, 64, 66, 67, 68, 72, 74, 75, 77], "go": [0, 9, 11, 12, 13, 14, 15, 18, 19, 25, 26, 29, 36, 37, 42, 49, 52, 59, 75, 77], "highest": [0, 7, 8], "spatial": [0, 6, 7, 11, 20, 40, 42, 54, 61, 63], "frequenc": [0, 7, 8, 25, 29, 38, 48, 54, 59, 60, 61, 66], "maxk": [0, 7, 8, 29, 37, 52], "center": [0, 3, 5, 11, 12, 13, 15, 17, 19, 20, 22, 23, 29, 34, 36, 37, 40, 42, 48, 49, 50, 52, 54, 59, 61, 63, 64, 67, 74, 75, 77], "nomin": [0, 11, 13, 15, 17, 22, 25, 29, 37, 42, 48, 57, 59, 64, 67, 75], "would": [0, 2, 3, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 24, 26, 29, 31, 32, 33, 35, 36, 37, 38, 40, 42, 44, 46, 49, 51, 52, 53, 54, 56, 57, 59, 61, 63, 64, 65, 68, 70, 72, 73, 74, 75, 77], "get": [0, 1, 3, 4, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 23, 24, 26, 27, 29, 31, 32, 35, 36, 37, 40, 42, 44, 49, 50, 51, 52, 53, 54, 55, 56, 57, 59, 60, 63, 64, 72, 74, 75, 77], "discuss": [0, 7, 36, 37, 40, 42, 49, 55, 59, 64, 69], "rel": [0, 1, 6, 11, 12, 13, 15, 16, 17, 19, 20, 26, 29, 36, 37, 38, 40, 47, 49, 50, 54, 61, 64, 66, 67, 68, 72, 74, 77], "argument": [0, 1, 3, 6, 7, 8, 11, 12, 13, 15, 19, 20, 23, 25, 26, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 38, 39, 40, 42, 48, 49, 52, 54, 55, 57, 59, 61, 63, 64, 66, 70, 72, 73, 75, 77, 79], "select": [0, 1, 7, 11, 13, 19, 35, 49, 52, 63, 64, 66, 74, 79], "folding_threshold": [0, 8, 29, 37, 38, 52, 54, 63, 64], "forc": [0, 20, 29, 42, 61, 63], "maxk_threshold": [0, 29, 37, 38, 54, 61, 63], "help": [0, 1, 7, 10, 11, 12, 13, 14, 15, 17, 19, 21, 29, 37, 38, 54, 59, 62, 63, 77], "reduc": [0, 7, 19, 20, 37, 42, 49, 50, 52, 54, 59, 62, 64, 69], "artifact": [0, 14, 37, 53, 54], "manner": [0, 42, 49], "current": [0, 3, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 21, 22, 24, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 39, 40, 42, 43, 44, 46, 48, 49, 51, 52, 54, 55, 56, 57, 59, 60, 61, 63, 64, 67, 70, 72, 73, 74, 77], "unobtain": 0, "lower": [0, 3, 11, 16, 19, 26, 29, 36, 38, 40, 42, 46, 47, 49, 59, 60, 63, 64], "withgsparam": [0, 7, 8, 20, 37, 38, 48, 52, 54, 63, 73], "version": [0, 7, 8, 10, 11, 13, 14, 15, 17, 20, 24, 26, 27, 29, 31, 32, 34, 35, 36, 37, 40, 42, 43, 44, 46, 48, 49, 51, 52, 53, 54, 58, 61, 63, 64, 70, 72, 73, 74, 75, 77], "obj": [0, 6, 7, 8, 12, 13, 29, 36, 37, 49, 52, 54, 61, 63, 70, 73, 74, 79], "name": [0, 4, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 23, 31, 32, 33, 35, 36, 37, 38, 42, 44, 46, 48, 49, 52, 54, 56, 57, 59, 61, 63, 64, 67, 68, 73, 74, 75, 77], "style": [0, 9, 14, 19, 37, 53, 54, 63], "all": [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 49, 51, 52, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 65, 66, 67, 68, 69, 72, 73, 74, 75, 77, 79], "onli": [0, 1, 3, 7, 8, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 24, 25, 26, 27, 28, 29, 31, 32, 34, 35, 36, 37, 38, 40, 42, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 63, 64, 66, 67, 69, 70, 72, 73, 74, 75, 77, 79], "updat": [0, 7, 8, 12, 14, 15, 16, 17, 20, 21, 24, 28, 33, 35, 37, 38, 42, 46, 52, 53, 54, 56, 63, 64, 68, 73, 77], "_interpolatedimag": [0, 52], "128": [0, 38], "8192": [0, 38, 52, 54], "005": [0, 38, 54], "5": [0, 1, 6, 7, 10, 12, 13, 17, 18, 19, 20, 23, 25, 29, 32, 36, 37, 38, 40, 42, 43, 46, 48, 49, 54, 58, 59, 61, 63, 64, 67, 68, 69, 72, 75, 77], "001": [0, 38], "1e": [0, 23, 38, 40, 42, 47, 66], "05": [0, 13, 16, 17, 36, 37, 38, 40, 54, 57, 61, 63, 69, 75], "0001": [0, 1, 36, 38, 49, 66], "06": [0, 38, 40, 42, 47, 64], "08": [0, 38], "force_stepk": [0, 29, 52, 61], "force_maxk": 0, "approxim": [0, 1, 7, 11, 12, 13, 14, 15, 19, 20, 23, 25, 29, 37, 38, 40, 42, 48, 49, 52, 54, 55, 60, 61, 63, 64, 66, 72, 73, 77], "fewer": [0, 1, 37, 42, 49, 55, 66], "saniti": [0, 3, 7, 37, 42, 50, 54, 55, 59, 66, 69, 72, 73], "check": [0, 3, 4, 7, 8, 13, 15, 17, 20, 21, 22, 24, 28, 29, 31, 32, 35, 37, 38, 40, 42, 46, 49, 50, 51, 52, 53, 54, 55, 56, 59, 64, 66, 69, 72, 73, 75, 77], "notabl": [0, 37, 52], "reduct": 0, "must": [0, 1, 3, 5, 7, 10, 11, 12, 13, 15, 17, 19, 20, 24, 25, 26, 27, 28, 29, 31, 32, 33, 34, 35, 36, 37, 40, 42, 47, 49, 50, 51, 53, 54, 56, 57, 59, 60, 61, 63, 64, 66, 67, 72, 73, 77, 79], "alreadi": [0, 1, 4, 7, 11, 13, 14, 15, 17, 19, 20, 29, 31, 32, 35, 37, 42, 46, 49, 51, 52, 53, 55, 63, 64, 67, 74, 77], "appli": [0, 5, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 25, 29, 34, 35, 36, 37, 40, 42, 47, 48, 51, 53, 54, 55, 56, 57, 59, 63, 64, 73, 74, 75, 77], "them": [0, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 25, 27, 29, 30, 32, 35, 37, 42, 44, 46, 49, 52, 53, 54, 56, 59, 63, 64, 66, 67, 74, 75, 77], "otherwis": [0, 11, 13, 14, 15, 20, 27, 29, 35, 36, 40, 42, 46, 48, 49, 54, 59, 64, 72, 77], "2pi": [0, 12, 19, 29, 42, 75], "image_s": [0, 12, 74], "krang": [0, 48], "pixel_scal": [0, 9, 11, 15, 19, 20, 36, 37, 49, 61, 63, 64, 74], "cannot": [0, 11, 13, 17, 19, 20, 24, 25, 29, 36, 37, 42, 46, 54, 56, 58, 64, 72, 73], "rescal": [0, 7, 12, 13, 14, 17, 20, 28, 29, 36, 37, 54, 56, 61, 63, 70], "recent": [0, 7, 37, 46, 49, 52, 63, 64, 77], "return": [0, 1, 3, 4, 5, 6, 7, 8, 11, 12, 13, 14, 15, 17, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 34, 35, 36, 37, 38, 40, 42, 47, 48, 49, 50, 51, 52, 54, 55, 56, 57, 58, 59, 61, 63, 64, 66, 67, 69, 70, 71, 72, 73, 75, 77, 79], "wherev": [0, 16, 68], "possibli": [0, 7, 11, 13, 14, 15, 16, 19, 20, 35, 37, 42, 44, 46, 50, 52, 54, 59, 64, 66, 75, 76, 77], "govern": [0, 14, 29, 37, 38, 40, 61], "mimic": [0, 63, 74], "behavior": [0, 1, 8, 11, 12, 14, 17, 20, 24, 27, 28, 32, 36, 37, 39, 52, 54, 55, 64, 67, 72, 77], "regular": [0, 1, 4, 11, 13, 14, 15, 17, 19, 35, 37, 49, 52, 53, 66, 69, 73, 77], "setcent": [0, 17, 42, 77], "befor": [0, 7, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20, 23, 24, 29, 31, 36, 37, 40, 42, 46, 49, 51, 52, 53, 54, 55, 57, 59, 60, 61, 63, 64, 66, 67, 75], "adjust": [0, 6, 7, 11, 16, 17, 25, 29, 33, 36, 37, 40, 54, 57, 61, 64], "odd": [0, 5, 20, 32, 37, 52], "doe": [0, 1, 4, 5, 7, 11, 12, 13, 15, 17, 19, 20, 25, 29, 31, 32, 34, 35, 36, 37, 40, 42, 44, 48, 49, 52, 54, 55, 56, 59, 63, 64, 67, 69, 72, 77], "noth": [0, 5, 11, 14, 15, 24, 26, 36, 46, 59, 61, 75], "even": [0, 3, 7, 11, 12, 14, 15, 17, 19, 20, 22, 24, 25, 29, 30, 31, 32, 33, 34, 36, 37, 40, 42, 46, 48, 49, 52, 54, 55, 58, 61, 63, 64, 69, 72, 73, 75, 77], "dimens": [0, 5, 7, 11, 13, 14, 15, 17, 20, 25, 29, 33, 36, 37, 48, 49, 52, 54, 57, 59, 60, 61, 63, 66, 72, 77, 79], "interpolatedkimag": [0, 7, 52], "kimag": [0, 29, 37], "real_kimag": 0, "imag_kimag": 0, "real_hdu": 0, "imag_hdu": 0, "complex": [0, 7, 24, 29, 34, 37, 42, 52, 69], "imaginari": [0, 42], "part": [0, 3, 5, 6, 7, 10, 13, 14, 15, 16, 17, 19, 26, 29, 34, 35, 37, 42, 46, 49, 52, 53, 54, 62, 63, 64, 76, 77], "neither": [0, 11, 12, 15, 36, 75], "nor": [0, 18, 31], "implement": [0, 1, 2, 5, 6, 7, 11, 12, 13, 14, 15, 16, 17, 20, 21, 24, 25, 27, 28, 29, 30, 32, 33, 34, 36, 37, 40, 43, 44, 46, 48, 49, 51, 52, 53, 54, 55, 56, 57, 61, 63, 64, 67, 72, 75, 77], "pleas": [0, 7, 31, 33, 37, 44, 53, 59, 63], "submit": [0, 54], "requir": [0, 1, 3, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 17, 18, 19, 20, 23, 24, 27, 29, 31, 32, 33, 34, 35, 36, 37, 40, 42, 44, 46, 49, 52, 54, 55, 59, 60, 61, 63, 64, 66, 67, 69, 70, 73, 75, 77, 79], "precis": [0, 1, 4, 7, 11, 12, 13, 16, 20, 23, 32, 36, 37, 40, 42, 46, 49, 52, 54, 59, 61, 63, 69, 75], "those": [0, 7, 14, 15, 16, 20, 21, 23, 29, 31, 36, 37, 40, 41, 42, 49, 53, 56, 57, 59, 61, 63, 64, 74, 77], "drawkimag": [0, 7, 20, 37, 47, 52], "command": [0, 11, 12, 13, 14, 15, 17, 19, 20, 21, 29, 35, 44, 46, 49, 52, 53, 68, 74, 77], "produc": [0, 7, 15, 16, 19, 20, 27, 29, 31, 32, 36, 37, 38, 42, 44, 54, 57, 61, 72, 74], "essenti": [0, 22, 27, 29, 32, 37, 39, 42, 54, 59, 63, 74, 77], "returns_a_gsobject": 0, "dtype": [0, 11, 20, 34, 37, 42, 49, 52, 56], "numpi": [0, 1, 3, 4, 5, 7, 11, 14, 19, 20, 24, 28, 31, 32, 34, 35, 37, 42, 46, 47, 50, 52, 54, 56, 57, 59, 60, 61, 63, 66, 69, 72, 73, 75, 76, 77, 79], "complex64": [0, 37, 42], "complex128": [0, 34, 37, 42], "imagecf": [0, 41, 42, 52], "imagecd": [0, 41, 42, 52], "respect": [0, 4, 5, 7, 11, 13, 16, 17, 20, 22, 23, 29, 40, 46, 48, 49, 54, 57, 59, 61, 63, 64, 73, 75, 77], "permit": [0, 7, 54, 59, 64, 74, 75], "simpl": [0, 1, 3, 7, 11, 12, 13, 14, 15, 17, 19, 20, 26, 35, 36, 37, 39, 42, 43, 46, 49, 51, 52, 53, 54, 55, 59, 61, 64, 65, 67, 74, 75, 77], "pixelscal": [0, 11, 20, 35, 37, 40, 42, 64, 75, 77], "offsetwc": [0, 74, 77], "unless": [0, 7, 8, 11, 12, 13, 14, 17, 19, 20, 25, 31, 32, 37, 42, 49, 52, 54, 64, 73], "furthermor": [0, 4, 8, 19, 20, 29, 37, 46, 77], "hermitian": [0, 34, 42], "look": [0, 6, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 42, 46, 49, 54, 59, 63, 64, 77], "output": [0, 1, 4, 7, 9, 10, 11, 12, 13, 16, 18, 19, 20, 22, 26, 27, 28, 29, 30, 31, 32, 34, 35, 37, 42, 43, 44, 47, 49, 51, 52, 53, 54, 56, 59, 60, 63, 64, 71, 72, 74, 77], "been": [0, 3, 4, 7, 10, 11, 14, 15, 19, 20, 22, 24, 27, 29, 31, 32, 33, 36, 37, 40, 42, 46, 49, 51, 52, 53, 54, 56, 59, 64, 66], "compromis": [0, 25, 48, 64], "speed": [0, 1, 7, 13, 21, 25, 29, 34, 37, 38, 40, 44, 48, 52, 53, 54, 55, 63, 77], "though": [0, 7, 11, 24, 29, 30, 36, 37, 42, 54, 58, 59, 60, 61, 63, 64, 66, 72, 77], "extens": [0, 4, 10, 12, 13, 14, 15, 19, 35, 42, 52, 53, 63, 74, 77], "interpolated_kimag": 0, "attribut": [0, 1, 4, 7, 10, 12, 16, 19, 20, 22, 31, 33, 35, 36, 37, 38, 39, 40, 42, 43, 49, 51, 52, 54, 56, 58, 61, 63, 66, 69, 73, 74, 75, 77, 79], "coarser": 0, "increas": [0, 7, 11, 14, 20, 37, 38, 40, 49, 52, 54, 55, 56, 61, 67, 72, 77], "effici": [0, 1, 6, 7, 8, 12, 14, 15, 17, 24, 25, 27, 29, 36, 37, 42, 44, 46, 48, 49, 51, 52, 56, 64, 67, 69, 74, 75, 77], "expens": [0, 7, 37, 42, 54, 55, 61], "decreas": [0, 30, 36, 38, 42, 49, 54, 63], "separ": [0, 3, 5, 6, 7, 10, 11, 13, 14, 16, 17, 19, 20, 28, 29, 31, 36, 37, 46, 49, 52, 54, 59, 60, 61, 64, 69, 74, 75, 77, 79], "neighbor": [0, 25, 37, 38, 42, 48, 74], "copi": [0, 15, 20, 21, 22, 24, 25, 27, 29, 32, 38, 42, 46, 49, 51, 52, 56, 59, 63], "dft": [0, 8, 29, 49, 59], "docstr": [0, 7, 13, 20, 32, 36, 37, 38, 40, 42, 49, 54, 59, 60, 61, 64, 66, 73, 77], "These": [0, 2, 6, 7, 12, 13, 14, 15, 16, 18, 19, 20, 29, 30, 31, 32, 34, 35, 37, 40, 42, 44, 46, 48, 51, 53, 54, 55, 59, 61, 63, 64, 65, 68, 69, 74, 75, 76, 77], "refer": [0, 13, 16, 17, 19, 20, 24, 29, 36, 37, 40, 42, 50, 54, 56, 61, 64, 66, 73, 77], "_interpolatedkimag": 0, "sigma": [0, 8, 9, 11, 13, 16, 19, 23, 27, 29, 32, 36, 37, 40, 42, 49, 51, 64, 70, 71, 74], "bvec": [0, 29], "polar": [0, 19, 29, 31, 79], "term": [0, 12, 13, 15, 17, 19, 20, 25, 29, 31, 36, 37, 42, 48, 49, 51, 52, 54, 57, 59, 61, 63, 64, 67, 69, 77, 79], "eigenfunct": 0, "2": [0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 16, 17, 19, 20, 21, 23, 24, 26, 27, 29, 32, 33, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 46, 47, 48, 49, 53, 54, 55, 57, 59, 60, 61, 63, 64, 66, 67, 68, 69, 70, 72, 75, 77, 79], "quantum": [0, 1, 6, 37, 51, 64, 75], "harmon": [0, 59], "oscil": [0, 54], "laguerr": [0, 29], "polynomi": [0, 7, 25, 49, 52, 54, 61, 72, 77, 79], "bernstein": [0, 23, 25, 29, 40, 48, 66, 74], "jarvi": [0, 23, 40, 53], "2002": [0, 23, 40], "massei": 0, "refregi": 0, "2005": [0, 40, 47], "notat": [0, 16, 27, 32, 52, 60, 77], "overal": [0, 7, 12, 13, 16, 20, 29, 37, 59, 61, 73, 77], "length": [0, 7, 14, 15, 29, 48, 49, 54, 56, 59, 64, 66, 72, 79], "vector": [0, 22, 25, 26, 29, 31, 49, 66, 69, 77, 79], "coeffici": [0, 5, 31, 49, 52, 54, 61, 64, 77, 79], "index": [0, 4, 7, 11, 12, 13, 14, 15, 16, 17, 19, 28, 29, 30, 33, 36, 39, 40, 43, 46, 49, 52, 56, 57, 61, 63, 64, 66, 74, 79], "p": [0, 4, 10, 12, 19, 24, 25, 27, 29, 32, 42, 48, 52, 59, 60, 61, 67, 74, 77], "q": [0, 19, 23, 37, 42, 63, 69, 74, 77], "m": [0, 10, 19, 20, 21, 22, 23, 24, 26, 29, 34, 40, 42, 46, 49, 52, 53, 54, 56, 61, 64, 75, 77, 79], "solut": [0, 26, 35], "quanta": 0, "neg": [0, 11, 17, 25, 26, 28, 29, 32, 37, 42, 47, 48, 49, 52, 54, 55, 56, 57, 61, 77], "angular": [0, 50, 52, 59, 61, 75, 77], "momentum": 0, "Then": [0, 3, 6, 11, 12, 13, 14, 15, 16, 17, 19, 21, 24, 26, 27, 32, 37, 42, 49, 54, 55, 56, 72, 75], "2d": [0, 3, 13, 20, 22, 24, 25, 29, 32, 34, 36, 37, 42, 49, 52, 58, 59, 60, 70, 72], "coordin": [0, 7, 11, 12, 13, 15, 17, 19, 20, 28, 29, 30, 31, 36, 37, 40, 41, 42, 43, 49, 50, 52, 54, 56, 57, 58, 61, 64, 66, 67, 69, 75, 79], "r": [0, 5, 11, 13, 19, 25, 29, 36, 42, 44, 46, 47, 48, 49, 53, 54, 61, 67, 68, 69, 70, 74, 77, 79], "theta": [0, 5, 7, 19, 20, 26, 27, 29, 32, 36, 37, 49, 54, 59, 69, 74, 75, 77, 79], "frac": [0, 32, 36, 42, 54, 61, 69, 70], "sum_": [0, 25], "pq": 0, "b_": 0, "psi_": 0, "psi_pq": 0, "sqrt": [0, 7, 29, 32, 37, 42, 54, 59, 69, 73, 77, 79], "pi": [0, 13, 25, 29, 32, 38, 48, 49, 59, 60, 61, 63, 67, 69, 75, 77, 79], "exp": [0, 13, 19, 29, 36, 61, 69], "l_q": 0, "x": [0, 1, 3, 5, 7, 10, 11, 13, 15, 17, 19, 20, 21, 22, 23, 24, 25, 26, 28, 29, 31, 32, 36, 37, 40, 41, 42, 46, 47, 48, 49, 50, 52, 53, 54, 56, 58, 59, 60, 61, 64, 66, 67, 69, 72, 74, 75, 77, 79], "coeffient": 0, "pure": [0, 25, 27, 32, 48, 54, 57, 74], "qp": 0, "conjug": [0, 34, 42], "pp": 0, "blank": [0, 12, 15, 20, 35, 37], "3": [0, 3, 6, 7, 8, 10, 11, 13, 15, 19, 20, 21, 23, 26, 27, 29, 32, 34, 35, 36, 37, 38, 39, 40, 42, 43, 44, 46, 49, 50, 53, 54, 59, 61, 63, 64, 67, 68, 69, 72, 75, 77, 79], "subscript": 0, "b00": 0, "b10": 0, "im": [0, 11, 15, 20, 24, 35, 37, 42, 49, 52, 63, 64, 68, 74, 77], "b20": 0, "b11": 0, "b30": 0, "b21": 0, "progress": [0, 7, 10, 11, 12, 13, 14, 15, 17, 31, 63, 74], "start": [0, 4, 11, 12, 13, 14, 15, 16, 17, 19, 20, 27, 28, 32, 35, 42, 44, 46, 49, 52, 53, 54, 56, 57, 61, 67, 74, 75, 79], "down": [0, 11, 12, 13, 16, 19, 37, 49, 52, 67, 77], "intrins": [0, 7, 11, 13, 37, 42, 49, 74], "spot": [0, 19, 64], "list": [0, 4, 6, 7, 8, 9, 11, 12, 13, 14, 15, 16, 18, 19, 26, 28, 29, 31, 33, 35, 37, 38, 39, 40, 42, 45, 46, 49, 50, 52, 53, 54, 55, 56, 57, 59, 61, 63, 64, 65, 66, 72, 73, 74, 75, 77, 79], "standard": [0, 9, 10, 11, 12, 13, 19, 22, 27, 29, 35, 39, 40, 42, 46, 52, 61, 77], "unit": [0, 1, 2, 6, 7, 11, 12, 13, 15, 17, 19, 20, 23, 25, 29, 30, 31, 36, 37, 39, 40, 42, 43, 48, 49, 50, 51, 52, 54, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 73, 74, 77, 79], "classmethod": [0, 7, 20, 42, 56, 63, 67, 72], "mirror": [0, 6, 13, 54, 61, 64, 74], "sequenc": [0, 11, 13, 15, 16, 17, 19, 27, 32, 39, 49, 52, 53, 54, 64, 72, 74, 79], "match": [0, 3, 4, 7, 11, 13, 16, 17, 20, 21, 24, 28, 36, 40, 42, 49, 51, 52, 54, 57, 58, 61, 63, 64, 67, 69, 72, 79], "fitshapelet": [0, 52], "im3": [0, 24], "close": [0, 11, 14, 17, 19, 24, 26, 29, 35, 36, 42, 46, 47, 52, 59, 64, 74, 77], "improv": [0, 1, 7, 15, 21, 37, 44, 46, 48, 49, 52, 53, 64], "true_cent": [0, 3, 37, 40, 42, 52, 74], "assum": [0, 1, 4, 5, 7, 12, 13, 14, 17, 18, 20, 25, 26, 28, 29, 31, 34, 36, 37, 40, 42, 49, 50, 54, 56, 57, 59, 60, 61, 63, 64, 69, 71, 75, 77], "getnm": 0, "accord": [0, 5, 6, 7, 8, 11, 13, 15, 16, 17, 18, 19, 20, 26, 27, 28, 29, 32, 36, 37, 42, 48, 51, 52, 54, 55, 56, 57, 59, 61, 62, 64, 67, 69, 74, 78], "tupl": [0, 7, 11, 13, 15, 19, 20, 30, 35, 36, 37, 49, 50, 54, 59, 61, 63, 66, 71, 72, 73, 75, 77, 79], "b_pq": 0, "getpq": 0, "throughput": [1, 6, 7, 47, 54, 64, 66, 68], "wave_typ": [1, 6, 11, 13, 52, 63, 66, 68, 74], "blue_limit": [1, 6, 7, 11, 47, 49, 66], "red_limit": [1, 6, 7, 11, 47, 49, 66], "zeropoint": [1, 7, 11, 52, 63, 64, 66], "interpol": [1, 7, 12, 13, 17, 19, 20, 21, 27, 28, 29, 31, 32, 37, 38, 42, 43, 49, 52, 54, 55, 56, 57, 59, 61, 63, 64, 65, 66, 72, 76], "_wave_list": [1, 66], "_tp": 1, "transmiss": [1, 6, 64], "fraction": [1, 12, 13, 17, 19, 29, 36, 37, 38, 40, 42, 48, 49, 54, 57, 61, 63, 64, 74], "incid": [1, 17, 37, 42, 51, 54, 55, 56, 61, 67], "light": [1, 12, 13, 19, 29, 35, 36, 37, 38, 40, 42, 49, 54, 61, 63, 64, 70, 75], "wavelength": [1, 7, 11, 13, 17, 28, 29, 30, 37, 43, 47, 52, 53, 54, 55, 56, 57, 61, 64, 66, 67, 68, 74, 75], "entir": [1, 6, 7, 11, 20, 29, 32, 36, 37, 49, 54, 61, 63, 64, 66, 67, 68], "optic": [1, 6, 7, 13, 37, 52, 53, 54, 63, 64, 65, 74], "path": [1, 11, 16, 19, 20, 46, 54, 63, 67], "atmospher": [1, 6, 7, 13, 17, 30, 52, 53, 54, 57, 61, 65, 74], "reflect": [1, 6, 15, 37, 40, 56, 57, 64], "refract": [1, 7, 17, 43, 52, 54, 55, 56, 57, 61, 66, 74, 76], "ccd": [1, 7, 11, 37, 42, 51, 52, 57, 64, 67, 74], "intermedi": [1, 7, 11, 37, 42, 52, 54, 59, 63, 77], "piec": [1, 6, 40, 42], "thereof": [1, 6, 11, 12, 14, 52], "individu": [1, 6, 7, 8, 15, 17, 18, 20, 35, 37, 42, 46, 52, 54, 59, 61, 65, 74, 79], "compon": [1, 7, 8, 13, 14, 15, 16, 20, 23, 29, 31, 36, 37, 40, 49, 50, 51, 54, 58, 59, 60, 63, 69, 71, 73, 74, 75, 77], "through": [1, 6, 7, 9, 12, 13, 15, 18, 24, 29, 30, 36, 37, 39, 44, 47, 49, 53, 54, 57, 59, 64, 66, 71, 74, 75, 77], "oper": [1, 3, 7, 8, 12, 13, 20, 22, 24, 25, 26, 27, 29, 32, 35, 36, 37, 40, 42, 43, 46, 48, 52, 53, 54, 55, 56, 58, 69, 72, 73, 75, 77, 79], "form": [1, 6, 7, 13, 19, 20, 27, 29, 36, 42, 52, 59, 64, 66, 67, 73, 74, 75, 77, 79], "new": [1, 3, 7, 10, 12, 14, 15, 16, 17, 19, 20, 21, 24, 26, 27, 29, 32, 33, 35, 36, 37, 42, 46, 49, 51, 52, 53, 54, 56, 59, 61, 63, 64, 66, 70, 72, 73, 74, 77, 79], "composit": [1, 24, 43, 65, 75], "callabl": [1, 7, 32, 42, 49, 57, 59, 60, 66, 72], "dimensionless": [1, 6, 7, 13, 33, 36, 37, 40, 59, 66, 75], "nm": [1, 6, 7, 11, 13, 17, 19, 29, 37, 54, 56, 57, 61, 64, 66, 68, 75], "immut": [1, 29, 33, 37, 42, 52, 66], "unalt": [1, 66], "infer": [1, 4, 15, 54, 61, 64, 72, 77], "lookupt": [1, 7, 11, 19, 32, 42, 52, 59, 60, 66, 67, 72, 74, 76], "column": [1, 4, 5, 11, 14, 19, 20, 24, 32, 42, 54, 56, 64, 67, 72, 74], "outsid": [1, 29, 31, 33, 36, 42, 52, 59, 61, 66, 67, 70, 72], "interv": [1, 7, 17, 27, 32, 47, 54, 57, 59, 61], "regardless": [1, 13, 14, 20, 37, 42, 59, 77], "scalar": [1, 3, 20, 22, 24, 47, 49, 50, 54, 59, 66, 72, 74, 77, 79], "sed": [1, 6, 7, 8, 17, 19, 33, 36, 37, 39, 43, 49, 52, 54, 56, 57, 61, 63, 66, 70, 71, 74], "product": [1, 7, 28, 29, 47, 49, 67, 72, 79], "python": [1, 2, 4, 7, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 24, 27, 28, 29, 34, 35, 39, 40, 43, 44, 45, 49, 52, 53, 54, 58, 59, 64, 66, 68, 74, 77, 79], "effective_wavelength": 1, "weight": [1, 7, 11, 13, 14, 15, 19, 23, 25, 31, 40, 42, 47, 49, 52, 54, 59, 60, 63, 74], "averag": [1, 12, 49, 59, 60, 63, 64, 68, 77], "independ": [1, 5, 7, 27, 42, 77], "our": [1, 2, 7, 9, 12, 13, 19, 20, 29, 30, 34, 35, 37, 42, 44, 49, 52, 53, 59, 60, 63, 74, 75, 77, 78], "definit": [1, 7, 10, 12, 13, 15, 17, 25, 26, 27, 29, 37, 40, 41, 42, 49, 54, 59, 63, 67, 69, 77], "arrai": [1, 4, 5, 7, 11, 17, 20, 23, 24, 25, 27, 29, 30, 31, 32, 34, 35, 36, 37, 40, 41, 42, 43, 47, 48, 49, 50, 52, 54, 55, 57, 59, 60, 61, 63, 64, 66, 69, 72, 73, 75, 77, 79], "wave_list": [1, 7, 47, 49, 52], "maintain": [1, 19, 32, 35, 46], "union": 1, "multiplicand": 1, "although": [1, 7, 17, 18, 19, 20, 27, 29, 36, 37, 42, 44, 49, 61, 63, 67, 72, 74], "limit": [1, 6, 7, 12, 13, 14, 20, 24, 25, 26, 28, 29, 36, 37, 38, 42, 47, 48, 49, 52, 54, 55, 56, 59, 61, 63, 64, 66, 74, 77], "rang": [1, 3, 6, 7, 13, 15, 19, 23, 25, 26, 29, 33, 36, 40, 41, 42, 48, 49, 52, 53, 54, 57, 59, 64, 66, 72, 75], "choic": [1, 7, 12, 13, 23, 25, 29, 37, 40, 42, 48, 49, 59, 61, 63, 69, 72, 75, 77], "chromaticobject": [1, 6, 7, 8, 36, 37, 47, 49, 52, 54, 55, 57, 61, 63, 66, 70, 73, 74, 75], "act": [1, 7, 16, 27, 37, 42, 49, 57, 66, 69, 77], "like": [1, 4, 5, 6, 7, 9, 11, 12, 13, 15, 16, 19, 20, 21, 24, 27, 29, 32, 33, 35, 36, 37, 40, 41, 42, 46, 47, 49, 51, 52, 53, 54, 56, 59, 61, 63, 64, 66, 73, 74, 77, 79], "evalu": [1, 7, 11, 13, 14, 15, 16, 17, 19, 25, 36, 37, 39, 47, 49, 50, 52, 54, 61, 66, 72, 74, 77, 79], "wave": [1, 7, 28, 30, 52, 54, 64, 66, 74], "via": [1, 3, 7, 11, 14, 19, 20, 23, 35, 36, 37, 40, 42, 44, 45, 46, 52, 53, 54, 57, 61, 66, 72, 75, 77], "eval": [1, 11, 12, 13, 14, 16, 49, 52, 59, 66, 74, 77], "lambda": [1, 6, 7, 11, 13, 19, 27, 29, 32, 42, 49, 52, 54, 57, 59, 61, 66, 75, 77], "8": [1, 4, 7, 8, 13, 16, 19, 38, 43, 44, 46, 49, 52, 54, 59, 63, 64, 66, 67, 68, 72, 75], "800": [1, 19, 66], "reserv": [1, 10, 13, 49], "word": [1, 42, 54, 77], "nanomet": [1, 6, 7, 13, 17, 30, 54, 57, 61, 64, 66], "ang": [1, 6, 11, 13, 63, 66], "angstrom": [1, 66, 68], "astropi": [1, 19, 35, 39, 42, 46, 52, 54, 57, 61, 66, 75, 77], "distanc": [1, 5, 7, 11, 12, 17, 19, 20, 25, 29, 36, 37, 49, 50, 54, 57, 59, 61, 63, 75, 77], "unimport": [1, 54], "directli": [1, 5, 7, 13, 17, 18, 19, 20, 23, 24, 31, 35, 36, 37, 42, 46, 51, 52, 53, 54, 55, 56, 59, 61, 63, 64, 68, 70, 77], "convert": [1, 6, 11, 13, 15, 19, 29, 35, 37, 39, 40, 42, 49, 52, 59, 61, 64, 66, 67, 68, 72, 74, 75, 77, 79], "propag": [1, 7, 8, 15, 16, 37, 64, 73], "__call__": [1, 32, 42, 47, 59, 66, 72, 77, 79], "cm": [1, 6, 7, 12, 13, 29, 36, 37, 54, 55, 61, 63, 64, 66, 68, 70, 75], "magnitud": [1, 6, 7, 11, 19, 52, 54, 63, 66, 69, 74, 75], "mag": [1, 13, 64, 74], "log10": [1, 64], "withzeropoint": [1, 7, 63, 66], "divid": [1, 10, 22, 28, 29, 42, 56, 64, 75], "old": [1, 39, 49, 52], "hard": [1, 7, 8, 11, 17, 21, 29, 37, 59, 60], "cut": [1, 11, 12, 23, 40, 52, 59, 60, 63], "blue": [1, 7, 11, 57], "side": [1, 5, 11, 15, 20, 26, 37, 49, 54, 57, 59, 67, 68, 74, 77], "red": [1, 7, 11, 57, 68], "vega": [1, 68], "what": [1, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 23, 24, 27, 29, 31, 33, 37, 40, 42, 46, 49, 51, 52, 53, 54, 59, 60, 61, 64, 66, 67, 69, 74, 77], "calculateeffectivewavelength": 1, "trapezoid": [1, 7, 47, 72], "rule": [1, 7, 22, 29, 47, 52, 64, 72], "alia": [1, 16, 24, 29, 37, 42, 72, 75], "self": [1, 7, 12, 14, 31, 32, 35, 37, 42, 49, 51, 54, 56, 61, 66, 72, 77], "thin": [1, 11, 49, 52, 63, 64, 66, 68, 74], "rel_err": [1, 11, 47, 49, 52, 66, 74], "trim_zero": [1, 49, 66], "preserve_rang": [1, 49, 66], "fast_search": [1, 49, 66, 68], "preserve_zp": 1, "tabul": [1, 12, 19, 49, 52, 59, 66, 72, 74], "while": [1, 7, 11, 16, 20, 21, 24, 27, 29, 31, 32, 35, 36, 37, 40, 42, 44, 46, 49, 53, 54, 59, 63, 66, 69, 79], "That": [1, 16, 21, 36, 37, 42, 64], "preserv": [1, 7, 13, 15, 29, 35, 36, 37, 42, 49, 52, 53, 59, 66, 69, 77], "elimin": [1, 12, 63, 79], "mani": [1, 7, 12, 13, 14, 15, 17, 19, 24, 25, 32, 34, 35, 36, 37, 40, 42, 44, 46, 49, 52, 53, 54, 55, 59, 61, 65, 74, 77], "process": [1, 7, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 26, 30, 31, 32, 33, 37, 42, 43, 46, 49, 51, 52, 54, 55, 59, 61, 63, 64, 67, 68, 69, 74, 75, 77], "up": [1, 3, 4, 7, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 24, 27, 29, 31, 32, 36, 37, 40, 42, 43, 44, 46, 47, 49, 51, 52, 54, 59, 61, 64, 67, 74, 75, 77, 79], "weigh": 1, "against": [1, 7, 32, 54], "fidel": [1, 52, 53, 54, 61], "routin": [1, 7, 20, 23, 27, 29, 32, 35, 37, 40, 42, 49, 52, 63, 64, 68, 77, 78], "assign": [1, 7, 12, 17, 22, 24, 28, 29, 37, 49, 55, 56, 57, 63, 66, 74], "justif": 1, "10": [1, 5, 7, 12, 13, 23, 26, 29, 32, 36, 37, 40, 42, 43, 46, 52, 53, 54, 60, 61, 63, 64, 68, 77], "6": [1, 13, 18, 19, 23, 26, 29, 32, 36, 38, 40, 42, 43, 47, 49, 54, 61, 63, 64, 68, 72, 77], "lot": [1, 10, 15, 35, 37, 49, 52, 54, 64, 67], "extrem": [1, 13, 25, 38, 48, 52, 69, 75], "redund": [1, 49, 66], "trail": [1, 49, 58, 66, 72], "last": [1, 13, 14, 15, 19, 24, 27, 37, 40, 42, 46, 49, 54, 59, 66, 72, 74, 75, 77], "retain": [1, 20, 42, 49, 64, 66], "trim_leading_zero": [1, 49, 66], "trim": [1, 49, 66], "Or": [1, 7, 11, 16, 19, 20, 31, 36, 37, 42, 44, 46, 49, 53, 64, 66, 68, 75, 77], "end": [1, 3, 4, 7, 10, 11, 14, 15, 17, 24, 26, 31, 35, 36, 42, 46, 47, 49, 53, 59, 66, 69, 77], "region": [1, 3, 13, 20, 22, 26, 29, 37, 49, 54, 55, 59, 60, 63, 66, 67, 79], "algorithm": [1, 7, 20, 26, 29, 33, 36, 37, 38, 40, 49, 52, 53, 54, 66, 73], "fast": [1, 7, 17, 20, 46, 49, 54, 66], "o": [1, 11, 12, 15, 16, 19, 20, 37, 44, 49, 52, 63, 66], "yield": [1, 49, 54, 66, 72, 79], "meet": [1, 49, 66], "somewhat": [1, 7, 19, 36, 37, 54, 59, 61, 64, 68, 75, 79], "robust": [1, 52, 54], "comput": [1, 6, 7, 10, 12, 17, 19, 20, 23, 27, 29, 30, 34, 37, 40, 42, 44, 47, 48, 49, 50, 54, 59, 60, 61, 66, 72, 73], "low": [1, 7, 20, 42, 49, 54, 55], "leak": [1, 52], "truncat": [1, 8, 13, 17, 25, 29, 36, 37, 38, 47, 52, 54, 61, 64, 68, 74], "relative_throughput": [1, 74], "less": [1, 7, 11, 12, 14, 17, 19, 20, 29, 37, 39, 54, 63, 64, 66, 67, 68, 75, 77], "avail": [1, 4, 5, 7, 11, 14, 15, 18, 20, 34, 35, 36, 37, 38, 40, 42, 45, 46, 52, 54, 63, 64, 75, 77, 79], "happen": [1, 7, 12, 14, 19, 20, 24, 37, 38, 40, 42, 49, 52, 54, 69, 75, 77], "becaus": [1, 7, 8, 11, 14, 15, 19, 20, 25, 29, 32, 37, 40, 42, 46, 48, 54, 55, 59, 63, 64, 77, 79], "difficult": [1, 7, 49, 63], "predict": [1, 7, 57, 66], "arbitrarili": [1, 37, 54, 61, 64, 65, 72, 79], "littl": [1, 19, 36, 49, 59], "observ": [1, 6, 7, 12, 13, 16, 17, 19, 20, 23, 30, 36, 37, 40, 41, 42, 49, 51, 57, 59, 60, 63, 64, 66, 74, 75, 77], "boolean": [1, 3, 7, 13, 14, 19, 22, 28, 29, 54, 61, 66, 72], "varieti": [1, 3, 6, 29, 32, 40, 53, 55, 62, 69], "st": [1, 43, 64], "hst": [1, 7, 11, 13, 20, 43, 52, 53, 63, 64, 74], "stmag": 1, "probabl": [2, 4, 5, 7, 11, 13, 17, 19, 27, 29, 32, 36, 37, 46, 47, 49, 52, 54, 62, 64, 75, 77], "super": 2, "scipi": [2, 39, 52], "c": [2, 11, 19, 23, 30, 34, 43, 44, 45, 49, 50, 52, 53, 58, 69, 77, 79], "depend": [2, 4, 7, 11, 13, 14, 17, 19, 20, 29, 31, 35, 36, 37, 40, 42, 43, 45, 52, 53, 54, 57, 59, 61, 63, 64, 66, 67, 72, 74, 75, 77, 79], "u": [2, 7, 11, 19, 20, 23, 24, 25, 29, 32, 36, 37, 40, 42, 47, 48, 49, 52, 53, 54, 57, 59, 63, 66, 68, 77, 79], "own": [2, 6, 7, 11, 12, 13, 16, 17, 19, 20, 21, 24, 29, 32, 35, 42, 46, 54, 63, 74], "interfac": [2, 20, 24, 43, 46, 53, 57, 76, 77], "mostli": [2, 14, 15, 34, 48, 49, 51, 74], "enabl": [2, 8, 10, 13, 20, 29, 36, 37, 40, 42, 46, 52, 54, 55, 61, 63], "correct": [2, 5, 7, 13, 14, 16, 17, 20, 23, 25, 29, 31, 37, 40, 42, 48, 52, 54, 56, 57, 63, 64, 68, 74], "j0": [2, 26, 76], "arg0": 2, "j1": [2, 26, 28, 44, 76], "jv": [2, 76], "arg1": 2, "jn": [2, 25, 76], "kv": [2, 76], "kn": [2, 76], "yv": [2, 76], "yn": [2, 76], "iv": [2, 54, 76], "j0_root": [2, 76], "int": [2, 3, 4, 7, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 32, 35, 37, 40, 42, 48, 49, 52, 54, 72], "jv_root": [2, 52, 76], "next": [2, 12, 13, 15, 16, 19, 26, 29, 32, 33, 42, 63, 65, 72], "few": [2, 8, 10, 12, 13, 14, 15, 16, 19, 32, 35, 36, 37, 42, 46, 49, 52, 54, 62, 70, 74, 75, 77, 79], "realli": [2, 4, 10, 11, 12, 14, 15, 25, 28, 29, 37, 40, 42, 46, 48, 49, 59, 75, 77], "relat": [2, 7, 11, 12, 13, 14, 15, 17, 18, 19, 21, 22, 23, 32, 37, 40, 42, 43, 50, 52, 54, 55, 61, 63, 64, 73, 76, 77], "expos": [2, 52, 54], "layer": [2, 19, 24, 28, 29, 34, 37, 39, 43, 44, 46, 49, 52, 54, 58, 67], "si": [2, 26, 30, 52, 76], "ci": [2, 26, 52, 76], "gammainc": [2, 52, 76], "imag": [3, 5, 6, 7, 9, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 25, 27, 28, 29, 31, 32, 33, 35, 36, 37, 38, 39, 40, 43, 44, 47, 48, 49, 51, 52, 53, 54, 55, 56, 57, 58, 59, 61, 62, 63, 64, 65, 66, 67, 71, 74, 75, 77], "rectangl": [3, 22, 29, 54], "slightli": [3, 7, 29, 30, 36, 42, 46, 49, 51, 58, 63, 75], "boundsd": [3, 41, 42], "y": [3, 4, 5, 7, 11, 13, 15, 17, 19, 20, 22, 23, 24, 25, 26, 28, 29, 31, 36, 37, 40, 41, 42, 47, 49, 50, 52, 53, 54, 56, 58, 59, 61, 64, 67, 68, 69, 72, 74, 77, 79], "boundsi": [3, 19, 37, 40, 41, 42, 52, 74], "four": [3, 7, 9, 22, 25, 29, 36, 37, 48, 49, 57, 61, 67, 75, 77], "xmin": [3, 22, 24, 25, 26, 29, 35, 40, 42, 74], "xmax": [3, 22, 24, 25, 26, 29, 42, 74], "ymin": [3, 22, 24, 26, 29, 35, 40, 42, 74], "ymax": [3, 22, 24, 26, 29, 42, 74], "addit": [3, 7, 11, 12, 14, 15, 16, 18, 19, 20, 22, 29, 36, 37, 38, 40, 42, 46, 47, 48, 49, 51, 53, 54, 57, 59, 61, 63, 64, 69, 72, 74, 77], "switch": [3, 17, 20, 22, 37, 52, 54, 55, 63, 64], "sai": [3, 12, 15, 16, 19, 22, 37, 53, 54, 61, 75, 77], "undefin": [3, 7, 17, 20, 22, 27, 33, 37, 42, 66], "min": [3, 16, 19, 22, 24, 26, 33, 47, 54, 59, 63, 72, 74], "max": [3, 13, 16, 19, 22, 24, 26, 29, 33, 42, 47, 59, 61, 63, 70, 72, 74], "direct": [3, 5, 11, 13, 14, 15, 16, 17, 19, 20, 21, 22, 24, 27, 34, 37, 42, 49, 52, 54, 56, 57, 59, 61, 64, 67, 72, 74, 77, 79], "imin": 3, "imax": [3, 42], "jmin": 3, "jmax": [3, 79], "typeerror": [3, 33, 37, 49, 77], "except": [3, 4, 7, 8, 10, 13, 14, 15, 16, 17, 20, 23, 24, 25, 29, 33, 35, 36, 37, 38, 40, 42, 45, 52, 53, 54, 55, 57, 59, 61, 63, 64, 66, 72, 77, 79], "anoth": [3, 7, 8, 9, 11, 12, 14, 15, 16, 19, 20, 24, 27, 28, 29, 32, 35, 36, 37, 42, 44, 46, 49, 54, 56, 59, 60, 63, 64, 72, 73, 74, 75, 77], "positiond": [3, 7, 15, 19, 31, 36, 37, 40, 41, 42, 49, 50, 52, 58, 59, 64, 67, 73, 74, 77], "positioni": [3, 7, 19, 36, 37, 41, 42, 49, 52, 58, 67, 77], "type": [3, 4, 7, 9, 10, 15, 16, 18, 20, 22, 23, 24, 25, 26, 27, 29, 31, 32, 35, 36, 37, 39, 40, 42, 44, 46, 48, 49, 51, 52, 53, 54, 56, 57, 58, 63, 64, 66, 72, 73, 74, 77], "lazili": 3, "isdefin": [3, 22, 74], "upper": [3, 26, 27, 28, 36, 47, 49, 56], "onc": [3, 7, 11, 14, 15, 16, 17, 19, 25, 32, 36, 37, 42, 49, 54, 56, 59, 64, 74, 77], "again": [3, 13, 14, 15, 17, 19, 31, 36, 37, 46, 49, 59, 63, 74], "add": [3, 4, 7, 8, 11, 12, 13, 14, 15, 16, 17, 19, 20, 22, 26, 27, 28, 29, 31, 32, 35, 37, 42, 44, 46, 48, 49, 51, 52, 53, 54, 56, 57, 58, 61, 62, 64, 67, 72, 74, 77, 79], "pos1": [3, 22, 58], "pos2": [3, 22, 58], "ad": [3, 4, 7, 8, 11, 13, 15, 16, 17, 19, 20, 28, 29, 31, 32, 36, 37, 39, 40, 42, 47, 49, 51, 52, 53, 54, 56, 64, 66, 74, 79], "find": [3, 7, 12, 18, 21, 22, 26, 31, 33, 40, 42, 44, 46, 49, 53, 54, 55, 57, 59, 61, 63, 64, 72, 77], "intersect": [3, 17, 22, 56, 57], "overlap": [3, 11, 14, 38, 74], "bounds1": 3, "bounds2": 3, "might": [3, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 24, 26, 29, 31, 37, 40, 46, 49, 51, 53, 54, 55, 56, 59, 61, 63, 64, 68, 72, 74, 77], "fall": [3, 22, 33, 37, 38, 42, 55, 64, 72, 75, 77], "edg": [3, 7, 8, 11, 13, 17, 28, 29, 36, 37, 38, 42, 52, 54, 56, 57, 59, 60, 61, 64], "enclos": [3, 22, 29, 36, 37, 42], "bit": [3, 11, 13, 19, 22, 27, 32, 34, 35, 37, 42, 49, 61, 74, 75, 77], "correctli": [3, 4, 12, 13, 22, 24, 35, 37, 44, 46, 49, 52, 53, 54, 55, 77], "count": [3, 7, 9, 12, 13, 14, 15, 17, 19, 22, 24, 28, 37, 42, 51, 63, 64, 67], "central": [3, 13, 17, 20, 29, 37, 42, 53, 54, 57, 59, 61, 66], "right": [3, 5, 7, 11, 12, 15, 16, 17, 21, 25, 26, 27, 32, 33, 35, 36, 37, 42, 46, 49, 54, 61, 63, 64, 69, 75, 77, 79], "expand": [3, 4, 7, 20, 22, 26, 29, 36, 37, 46, 49, 52, 63, 77], "factor_x": 3, "factor_i": 3, "grow": [3, 61], "getxmax": [3, 22, 24], "getxmin": [3, 22, 24], "getymax": [3, 22, 24], "getymin": [3, 22, 24], "arg": [3, 7, 8, 15, 20, 25, 29, 36, 37, 42, 49, 54, 58, 69, 72, 77], "pair": [3, 17, 24, 25, 29, 35, 49, 52, 59, 64, 71, 72, 74, 77], "lie": [3, 23, 64, 75], "100": [3, 7, 16, 19, 32, 36, 37, 40, 42, 55, 59, 60, 61, 63, 67, 74, 75], "50": [3, 7], "150": 3, "left": [3, 5, 7, 11, 19, 20, 24, 26, 32, 36, 37, 40, 42, 49, 54, 61, 69, 70, 79], "delta": [3, 12, 13, 18, 22, 24, 25, 29, 42, 48, 59, 61, 65, 76], "32": [3, 32, 37, 38, 54, 67, 68, 75, 77], "37": [3, 15, 49, 75], "49": [3, 37], "9": [3, 13, 19, 42, 43, 44, 52, 61, 64, 68], "alwai": [3, 4, 7, 11, 13, 14, 15, 17, 19, 20, 27, 29, 31, 32, 34, 36, 37, 39, 42, 46, 54, 56, 58, 61, 64, 66, 72, 75, 77], "withbord": [3, 22, 52], "dx": [3, 5, 7, 11, 20, 22, 24, 25, 27, 29, 36, 37, 42, 48, 49, 52, 56, 57, 60, 72, 73, 74, 77], "dy": [3, 5, 7, 11, 20, 22, 24, 25, 36, 37, 42, 56, 57, 72, 73, 74, 77], "width": [3, 13, 29, 37, 40, 42, 52, 59, 61, 70], "border": [3, 5, 11, 19, 22, 64, 74], "doc": [3, 12, 15, 21, 39, 49, 52, 58, 63, 77], "numpyshap": [3, 52], "util": [3, 30, 43, 52, 54, 59, 64, 68, 76], "shape": [3, 5, 7, 11, 13, 14, 16, 19, 20, 22, 23, 24, 27, 29, 32, 34, 35, 36, 42, 43, 49, 51, 52, 54, 59, 67, 69, 70, 72, 74, 77, 79], "_boundsi": [3, 41], "constructor": [3, 12, 14, 15, 22, 23, 24, 25, 26, 27, 29, 31, 37, 39, 42, 48, 50, 51, 52, 54, 56, 59, 63, 69, 75], "skip": [3, 13, 14, 15, 17, 24, 31, 32, 44, 46, 49, 52, 67, 69, 74], "pars": [3, 9, 10, 11, 12, 15, 17, 19, 30, 35, 39, 42, 49, 52, 53, 64, 74, 75, 77], "_boundsd": [3, 41], "file_nam": [4, 9, 10, 11, 12, 13, 14, 15, 16, 19, 20, 31, 32, 35, 42, 52, 56, 59, 63, 72, 74, 77], "dir": [4, 9, 11, 12, 14, 16, 31, 35, 42, 63, 74, 77], "file_typ": [4, 10, 12, 15], "row": [4, 5, 11, 12, 20, 24, 37, 42, 52, 55, 56, 64, 67, 72], "built": [4, 7, 10, 11, 12, 13, 14, 15, 17, 19, 31, 37, 46, 52, 54, 75, 78], "item": [4, 7, 8, 9, 11, 12, 13, 14, 15, 16, 17, 18, 19, 35, 39, 45, 49, 52, 54, 74, 75], "half_light_radiu": [4, 6, 7, 8, 13, 16, 19, 20, 36, 37, 38, 61, 70, 74, 75], "ascii": [4, 12, 19, 32, 46], "charact": [4, 12, 19, 49], "nobject": [4, 11, 13, 15, 16, 19, 63, 74], "ncol": [4, 24, 42], "isfit": 4, "col": [4, 19, 24, 52, 67, 74], "str": [4, 13, 14, 19, 27, 49, 52, 63, 74, 75, 77], "whatev": [4, 7, 11, 12, 13, 14, 15, 19, 27, 35, 37, 46, 49, 67, 77], "binari": [4, 12, 56], "getfloat": 4, "getint": 4, "readascii": 4, "readfit": 4, "outputcatalog": [4, 52, 74, 76], "_row": 4, "_sort_kei": 4, "truth": [4, 14, 19, 52, 74], "simul": [4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 31, 32, 33, 36, 41, 42, 43, 49, 52, 53, 54, 55, 57, 62, 63, 64, 67], "done": [4, 7, 8, 11, 14, 15, 16, 18, 19, 21, 23, 24, 26, 28, 29, 31, 35, 37, 46, 48, 54, 55, 60, 63, 64, 72, 74], "addrow": 4, "compat": [4, 7, 10, 21, 33, 46, 52, 64, 66, 77], "far": [4, 7, 25, 26, 27, 37, 57, 74, 75], "accumul": [4, 11, 17, 28, 37, 52, 54, 55, 56, 64, 67], "sort_kei": 4, "write": [4, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 22, 24, 27, 29, 32, 37, 42, 43, 45, 46, 52, 53, 56, 63, 68, 74, 76, 77], "lose": 4, "mismatch": 4, "per": [4, 5, 11, 14, 15, 17, 19, 23, 25, 27, 32, 35, 37, 40, 42, 51, 54, 55, 64], "sort": [4, 7, 11, 13, 14, 26, 31, 49], "getncol": [4, 24], "sef": 4, "getnobject": [4, 12, 15], "getnam": 4, "gettyp": 4, "makedata": 4, "written": [4, 10, 11, 13, 14, 15, 27, 31, 35, 42, 54, 56, 74, 77], "settyp": 4, "prec": [4, 75], "writeascii": 4, "writefit": 4, "writefitshdu": 4, "dict": [4, 11, 12, 13, 14, 15, 16, 17, 18, 19, 31, 33, 35, 47, 49, 52, 54, 64, 74, 76, 77], "key_split": [4, 12, 19], "behav": [4, 11, 42], "facilit": [4, 23, 29, 54, 77], "hierarchi": [4, 9, 10, 16, 52, 77], "chain": [4, 15], "kei": [4, 11, 12, 13, 14, 15, 16, 17, 19, 23, 25, 33, 35, 48, 52, 54, 56, 63, 64, 71, 77], "togeth": [4, 7, 8, 11, 13, 17, 19, 29, 42, 47, 54, 66, 75, 79], "access": [4, 7, 11, 13, 14, 15, 19, 21, 23, 24, 28, 33, 34, 35, 37, 40, 42, 44, 49, 52, 53, 54, 56, 61, 63, 64, 69, 74, 77], "element": [4, 15, 19, 20, 24, 27, 29, 34, 42, 49, 51, 52, 54, 56, 64, 66], "noise_model": 4, "much": [4, 8, 11, 13, 14, 17, 20, 21, 27, 29, 33, 35, 36, 37, 38, 42, 44, 46, 49, 54, 55, 59, 64, 72, 74, 75, 77], "easier": [4, 9, 15, 19, 29, 35], "arbitrari": [4, 7, 11, 12, 13, 14, 20, 24, 32, 36, 37, 43, 49, 50, 51, 52, 53, 54, 56, 61, 63, 64, 65, 72, 75, 77], "caveat": [4, 5, 7, 11, 13, 14, 15, 21, 37, 54, 59, 63], "prescript": [4, 54], "whose": [4, 5, 7, 12, 13, 15, 20, 28, 29, 40, 42, 51, 63, 71, 77], "won": [4, 11, 12, 14, 15, 17, 20, 21, 31, 37, 54, 77], "t": [4, 5, 7, 8, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 28, 29, 30, 31, 32, 33, 36, 37, 42, 44, 46, 48, 49, 52, 53, 54, 60, 61, 63, 64, 75, 77, 79], "rare": [4, 17, 46, 52], "occurr": [4, 33], "workaround": [4, 52], "pickl": [4, 12, 14, 29, 52, 54, 76], "yaml": [4, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 44, 46, 52, 53, 74], "json": [4, 9, 10, 12, 15, 52, 53], "j": [4, 10, 20, 24, 25, 28, 29, 36, 37, 42, 49, 52, 54, 61, 77, 79], "split": [4, 11, 12, 14, 15, 17, 19, 26, 29, 32, 42, 52, 54], "cf": [4, 11, 13, 14, 15, 19, 20, 35, 37, 47, 49, 53, 63, 77], "featur": [4, 7, 12, 15, 16, 20, 33, 34, 35, 37, 40, 42, 44, 49, 52, 53, 54, 74], "basic": [5, 7, 9, 13, 15, 17, 18, 20, 29, 33, 37, 43, 50, 51, 52, 55, 59, 60, 63, 64, 74, 77, 78], "antilogu": 5, "veri": [5, 7, 10, 11, 13, 14, 17, 18, 19, 20, 25, 31, 33, 35, 36, 37, 40, 42, 46, 52, 53, 54, 55, 59, 64, 74, 77], "quick": 5, "dirti": 5, "brighter": [5, 11, 37, 42, 53, 55, 67], "fatter": [5, 11, 37, 53, 55, 67], "now": [5, 14, 15, 20, 25, 29, 35, 36, 37, 39, 40, 44, 46, 52, 63, 74], "siliconsensor": [5, 11, 37, 52, 55, 67, 68], "tri": [5, 15, 24, 32, 33, 53, 54, 77], "physic": [5, 7, 17, 20, 29, 37, 42, 54, 57, 61, 64, 75], "cdmodel": [5, 52], "basecdmodel": [5, 76], "a_l": 5, "a_r": 5, "a_b": 5, "a_t": 5, "symmetri": [5, 11, 17, 20, 29, 42, 53], "relationship": [5, 11, 20, 27, 37, 42], "boundari": [5, 20, 38, 42, 52, 54, 67, 68], "et": [5, 11, 12, 20, 37, 40, 54, 55, 60, 61, 63, 74, 77], "al": [5, 11, 12, 20, 37, 40, 54, 55, 60, 61, 63, 74, 77], "2014": [5, 54], "__init__": [5, 12, 20, 60, 75, 77], "instanti": [5, 7, 11, 12, 14, 17, 20, 24, 51, 52, 54, 61], "matric": [5, 20, 52, 69], "proport": [5, 7, 37, 57, 61], "contribut": [5, 6, 7, 15, 29, 33, 36, 37, 42, 51, 53, 54, 60, 64], "neighbour": 5, "superpos": 5, "l": [5, 10, 19, 21], "bottom": [5, 11, 42, 49, 67], "top": [5, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 42, 43, 49, 52, 56, 64, 67, 70, 74], "po": [5, 19, 22, 24, 31, 33, 35, 37, 42, 50, 58, 59, 74, 77], "opposit": [5, 26, 42, 49, 52, 64], "smaller": [5, 10, 12, 13, 20, 24, 29, 36, 37, 38, 42, 48, 54, 59, 61, 63], "entri": [5, 11, 12, 13, 14, 15, 19, 33, 35, 42, 49, 52, 63, 64], "realist": [5, 42, 52, 53, 54, 62, 65, 74], "powerlawcd": [5, 76], "fulfil": 5, "condit": [5, 12, 38, 40, 42, 49, 54, 72, 79], "gain": [5, 7, 8, 11, 15, 20, 37, 42, 51, 52, 55, 64, 67, 74, 75], "a_": 5, "measur": [5, 7, 13, 17, 20, 23, 29, 30, 31, 32, 37, 42, 43, 51, 52, 55, 56, 57, 64, 66, 74, 75, 77], "flat": [5, 7, 52, 59, 60, 75], "eqn": 5, "intend": [5, 13, 19, 21, 25, 29, 36, 37, 48, 54, 55, 57, 65, 72, 74, 77, 79], "account": [5, 7, 11, 13, 17, 24, 37, 40, 42, 52, 53, 55, 57, 59, 60, 63, 64, 67, 74, 77], "gain_ratio": 5, "applyforward": 5, "applybackward": 5, "matrix": [5, 7, 11, 23, 27, 29, 36, 37, 52, 66, 69, 73, 77, 79], "backward": [5, 10, 46, 51, 52, 64], "ratio": [5, 7, 13, 17, 19, 36, 37, 42, 49, 50, 57, 61, 63, 66, 69, 73, 75], "gain_imag": 5, "gain_flat": 5, "common": [5, 11, 15, 17, 18, 20, 27, 37, 41, 42, 51, 53, 59, 64, 65, 75, 77], "scienc": [5, 11, 13, 20, 40, 63, 64], "forward": [5, 54, 71], "r0": [5, 13, 29, 52, 54, 61], "t0": [5, 17, 54, 57], "rx": 5, "tx": 5, "alpha": [5, 7, 13, 17, 27, 32, 42, 54, 57, 66, 68], "strength": [5, 11, 67], "law": [5, 7, 13, 17, 42, 57, 59, 66, 72], "six": [5, 74], "adjac": [5, 12, 42, 63], "corner": [5, 11, 24, 37], "due": [5, 7, 11, 13, 17, 20, 25, 30, 32, 36, 37, 40, 42, 49, 53, 54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 69], "awai": [5, 7, 11, 37, 42, 47, 64, 67], "sin": [5, 25, 48, 69, 75, 77], "r_distanc": 5, "amplitud": [5, 19, 23, 29, 37, 38, 48, 52, 54, 61, 67, 71, 72, 73], "angl": [5, 7, 13, 15, 17, 19, 20, 28, 29, 30, 36, 37, 43, 49, 52, 54, 55, 56, 57, 59, 61, 64, 66, 69, 74, 77, 79], "line": [5, 29, 37, 46, 49, 52, 74, 77], "sign": [5, 19, 29, 38, 42, 52, 54, 59, 61, 75, 77], "convent": [5, 11, 13, 19, 35, 40, 42, 49, 52, 54, 59, 61, 64, 77, 79], "pix": [5, 7, 15, 29, 52, 64], "along": [5, 12, 13, 14, 15, 17, 19, 24, 26, 29, 31, 35, 36, 40, 42, 46, 52, 54, 57, 61, 63, 64, 67, 68, 74, 75, 77, 79], "diagon": [5, 20, 52], "expon": [5, 49, 54, 61], "astronom": [6, 20, 35, 41, 42, 51, 53, 64, 65, 75], "emit": [6, 15, 33, 52, 54, 61], "potenti": [6, 7, 14, 37, 40, 42, 49, 52, 54, 67], "complic": [6, 7, 11, 13, 16, 19, 20, 37, 41, 42, 47, 49, 52, 54, 59, 61, 63, 65, 73, 74, 75], "spectral": [6, 7, 13, 33, 43, 54, 57, 75], "energi": [6, 21, 29, 31, 43, 50, 57], "spread": [6, 7, 12, 18, 29, 43, 57, 63, 65], "bandpass": [6, 7, 13, 17, 19, 37, 39, 43, 47, 49, 52, 53, 56, 57, 59, 61, 63, 64, 66, 71, 74, 75], "filter": [6, 7, 12, 13, 17, 25, 39, 42, 43, 47, 48, 52, 53, 57, 59, 64, 66, 71, 74], "variabl": [6, 11, 12, 15, 16, 25, 29, 40, 42, 46, 51, 53, 68, 74, 77], "electron": [6, 11, 37, 42, 51, 67, 74], "sensor": [6, 17, 21, 37, 40, 43, 52, 55, 57, 64], "tool": [6, 7, 21, 43], "chromat": [6, 8, 11, 13, 17, 19, 37, 43, 47, 52, 56, 57, 63, 64, 66, 71, 73, 74, 76], "sec": [6, 54, 64], "erg": [6, 37, 66, 68, 75], "hz": [6, 66, 75], "could": [6, 7, 10, 11, 12, 13, 14, 16, 17, 18, 19, 24, 25, 29, 35, 37, 40, 42, 46, 48, 49, 52, 54, 56, 59, 63, 64, 74, 75, 77], "system": [6, 10, 11, 12, 14, 20, 27, 32, 36, 37, 40, 41, 43, 44, 46, 49, 52, 54, 66, 67, 79], "respons": [6, 13, 17, 23, 29, 35, 37, 42, 48, 49, 54, 61, 63, 64, 70], "lens": [6, 7, 12, 13, 19, 20, 29, 36, 37, 43, 50, 52, 53, 55, 59, 63, 69, 74], "detector": [6, 42, 49, 51, 52, 53, 56, 64, 67, 74], "necessari": [6, 7, 9, 12, 14, 15, 17, 18, 20, 24, 26, 27, 29, 31, 35, 37, 49, 54, 55, 59, 61, 63, 64, 69, 75, 77], "simplest": [6, 11, 41, 44, 67, 72, 77], "everi": [6, 7, 11, 15, 19, 20, 25, 27, 29, 36, 37, 40, 42, 46, 49, 51, 54, 61, 70], "sersic": [6, 7, 8, 13, 16, 17, 29, 37, 38, 52, 55, 63, 65, 74], "cww_sbc_ext": [6, 68], "flux_typ": [6, 13, 52, 66, 68, 74, 75], "flambda": [6, 7, 13, 66, 74, 75], "chromatic_object": 6, "subclass": [6, 7, 11, 12, 13, 14, 17, 24, 25, 27, 29, 32, 37, 49, 51, 52, 63, 65, 77], "larg": [6, 7, 8, 10, 11, 13, 17, 20, 25, 29, 33, 35, 36, 37, 38, 40, 42, 48, 49, 52, 54, 59, 60, 63, 64, 67, 74], "gband": 6, "w": [6, 13, 32, 42, 47, 50, 77], "410": 6, "550": [6, 53], "chromatic_obj": [6, 7], "dilat": [6, 7, 13, 15, 20, 36, 37, 53, 57, 63, 65, 73, 74], "diffract": [6, 7, 13, 29, 54, 61, 64], "psf500": 6, "airi": [6, 7, 13, 17, 29, 52, 53, 54, 65, 74, 75], "lam_over_diam": [6, 7, 13, 19, 61, 74], "chromatic_psf": 6, "500": [6, 7, 13, 16, 29, 54, 61, 66], "emissionlin": [6, 52, 66], "chromaticatmospher": [6, 7, 13, 30, 57, 74], "chromaticopticalpsf": [6, 7, 52, 64], "chromaticairi": [6, 7, 52], "chromaticrealgalaxi": [6, 7, 52, 71], "interpolatedchromaticobject": [6, 7, 39], "chromaticsum": [6, 7, 8, 52, 71], "chromaticconvolut": [6, 7, 8, 39, 52], "chromaticdeconvolut": [6, 7, 73], "chromaticautoconvolut": [6, 7, 8], "chromaticautocorrel": [6, 7, 8], "chromatictransform": [6, 7, 52, 73], "chromaticfouriersqrtprofil": [6, 7, 73], "covariancespectrum": [6, 52, 71], "variou": [7, 8, 9, 11, 12, 14, 15, 16, 24, 27, 29, 30, 32, 35, 37, 38, 51, 52, 53, 54, 56, 63, 64, 65, 73, 74, 75, 76, 77, 78], "color": [7, 52, 63, 77], "gradient": [7, 25, 52, 54, 59, 61, 72, 79], "ongo": 7, "yet": [7, 11, 13, 14, 15, 17, 26, 33, 35, 52, 56], "ones": [7, 11, 12, 14, 17, 19, 38, 39, 44, 59, 63, 64, 74], "faint": [7, 63, 74], "primarili": [7, 8, 20, 21, 55, 59, 77], "serv": [7, 51, 64, 67, 74], "exist": [7, 8, 14, 15, 16, 20, 22, 27, 31, 32, 35, 37, 38, 42, 49, 52, 53, 54, 56, 58, 62, 63, 64, 71, 77], "newli": 7, "nearli": [7, 10, 17, 20, 25, 27, 28, 29, 37, 40, 52, 53, 55, 72, 75, 77], "withflux": [7, 36, 37, 54, 61, 66, 70, 74, 75], "gsobj": [7, 36, 37], "fwhm": [7, 13, 19, 20, 29, 37, 40, 42, 54, 57, 61, 66, 70, 74, 75], "chrom_obj": [7, 36, 37], "consist": [7, 8, 11, 13, 14, 15, 19, 23, 29, 35, 36, 37, 40, 42, 49, 52, 54, 59, 61, 64, 75, 77, 79], "multi": [7, 10, 11, 12, 14, 15, 35, 53, 74], "bulg": [7, 8, 13, 16, 37, 40, 63, 74], "disk": [7, 8, 13, 14, 16, 29, 36, 37, 40, 54, 61, 63, 74, 77], "bulge_s": 7, "user_function_to_get_bulge_spectrum": 7, "disk_s": 7, "user_function_to_get_disk_spectrum": 7, "bulge_mono": 7, "devaucouleur": [7, 8, 13, 16, 36, 74], "disk_mono": 7, "exponenti": [7, 8, 13, 16, 17, 27, 29, 32, 65, 74], "gal": [7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 29, 37, 38, 49, 52, 63, 73, 74], "densiti": [7, 13, 50, 54, 66, 74], "withmagnitud": [7, 66, 75], "dimension": [7, 25, 26, 29, 34, 47, 54, 66, 72], "chrom_obj2": [7, 36, 37], "eventu": [7, 12, 14, 36, 37, 51, 54, 57, 63], "wrap": [7, 8, 15, 24, 25, 37, 42, 49, 52, 54, 59, 64, 72, 73, 75], "distinct": [7, 13, 15, 16, 54], "star": [7, 13, 17, 18, 31, 37, 53, 57, 61, 63, 64, 65, 66, 68, 74, 75, 77], "solid": [7, 37], "besid": [7, 31], "former": [7, 26, 31, 35, 38, 40, 41, 42, 63, 66], "categori": 7, "restrict": [7, 8, 34, 36, 38, 61], "had": [7, 15, 27, 29, 35, 36, 37, 39, 45, 49, 52, 60, 74], "evaluateatwavelength": [7, 47], "monochromat": [7, 47, 54, 66], "expedit": 7, "interplai": 7, "xvalu": [7, 29, 36, 37, 54, 61], "withfluxdens": [7, 66], "__mul__": [7, 37, 51, 66, 79], "flux_ratio": [7, 29, 36, 37, 52, 73, 77], "univari": [7, 49], "track": [7, 22, 29, 37, 40, 42, 52, 66], "On": [7, 15, 20, 21, 37, 42, 46, 49, 51, 54], "hand": [7, 20, 35, 49, 54, 68], "applyto": [7, 20, 37, 51, 57], "photon_arrai": [7, 37, 56, 57], "local_wc": [7, 37, 57, 77], "duck": [7, 37], "photonop": [7, 17, 37, 55, 56, 57], "photon_op": [7, 17, 37, 52, 55, 57], "photonarrai": [7, 25, 28, 29, 37, 52, 54, 55, 56, 57, 67], "localwc": [7, 31, 37, 57, 77], "local": [7, 11, 12, 13, 14, 15, 17, 19, 20, 28, 31, 36, 37, 40, 42, 46, 52, 57, 72, 77], "bundl": [7, 17, 32, 37, 57], "atredshift": [7, 37, 52, 66, 74], "redshift": [7, 12, 13, 19, 37, 50, 52, 63, 66, 74], "calculatecentroid": [7, 52], "centroid": [7, 13, 15, 23, 29, 37, 40, 42, 52], "made": [7, 13, 14, 15, 16, 20, 27, 28, 29, 35, 36, 37, 38, 40, 42, 49, 52, 54, 57, 61, 63, 67, 74], "calculateflux": [7, 52, 66], "bolometr": [7, 66], "infin": [7, 25, 29, 47, 48, 66], "calculatemagnitud": [7, 66], "thu": [7, 8, 12, 13, 14, 19, 20, 23, 29, 31, 35, 36, 37, 42, 47, 49, 54, 59, 64, 66, 74, 75, 77], "quadrat": [7, 47], "specif": [7, 9, 10, 11, 12, 13, 14, 15, 17, 19, 20, 29, 31, 33, 35, 36, 37, 39, 42, 43, 44, 46, 47, 52, 53, 54, 56, 57, 59, 63, 64, 69, 74, 76, 77, 79], "finish": [7, 12, 37, 44, 54], "task": [7, 11, 15, 17], "context": [7, 14, 20, 24, 33, 42, 52, 54, 61, 75, 76], "sampleintegr": [7, 47, 76], "len": [7, 13, 15, 19, 20, 36, 37, 49, 52, 56, 59, 69, 72, 74], "analyt": [7, 13, 25, 29, 36, 48, 54, 59, 60, 65], "continuousintegr": [7, 47, 76], "integrand": [7, 47], "250": [7, 47], "space": [7, 8, 13, 14, 15, 17, 19, 20, 24, 25, 29, 30, 36, 37, 38, 42, 43, 47, 48, 49, 52, 53, 54, 56, 59, 60, 61, 63, 65, 67, 69, 72, 74, 75], "quadrul": [7, 47, 76], "midpoint": [7, 47], "midptrul": [7, 47, 76], "recomput": [7, 23, 40, 42, 60], "finit": [7, 17, 25, 29, 36, 37, 38, 47, 54, 55, 59, 60, 72, 77], "group": [7, 15, 28, 29, 52, 53], "hit": [7, 17, 37, 49, 67], "resiz": [7, 20, 24, 37, 42, 49, 52], "resize_multiplier_cach": 7, "exact": [7, 13, 25, 29, 37, 42, 48, 54, 63, 64, 77], "fourier": [7, 8, 13, 17, 20, 25, 29, 36, 37, 38, 42, 43, 46, 48, 49, 52, 53, 54, 59, 61, 65, 71, 76], "exactli": [7, 13, 15, 25, 29, 30, 36, 49, 54, 55, 61, 63, 70, 77], "analog": [7, 20, 33, 37, 42, 51, 59, 71, 75], "bound": [7, 11, 15, 17, 19, 20, 21, 23, 24, 25, 26, 27, 28, 29, 31, 33, 35, 37, 40, 41, 42, 43, 47, 49, 52, 56, 59, 67, 74, 77], "doesn": [7, 8, 11, 14, 20, 24, 26, 27, 32, 36, 37, 42, 46, 49, 61, 77, 79], "usag": [7, 15, 18, 20, 23, 26, 29, 31, 32, 34, 35, 36, 37, 44, 47, 49, 52, 54, 59, 60, 63, 77], "conceptu": [7, 36, 37], "As": [7, 9, 14, 18, 27, 35, 36, 37, 38, 40, 41, 42, 44, 46, 53, 54, 57, 59, 61, 63, 77, 78, 79], "subsequ": [7, 13, 15, 19, 20, 31, 32, 35, 37, 42, 53, 54, 64, 66], "costli": [7, 20], "relev": [7, 9, 11, 12, 14, 15, 18, 29, 30, 31, 32, 33, 36, 37, 38, 40, 42, 46, 48, 54, 55, 59, 63, 64, 66, 67, 68, 74, 75, 77], "grid": [7, 11, 12, 20, 23, 29, 32, 38, 49, 52, 59, 60, 72, 74, 78], "later": [7, 11, 13, 14, 19, 27, 31, 36, 37, 39, 40, 46, 59, 64], "reus": [7, 13, 19, 42, 54, 60, 64], "thumb": 7, "benefit": 7, "wherea": [7, 29, 40, 63], "scheme": [7, 19, 29, 35, 42, 77, 79], "extrapol": [7, 31, 72], "beyond": [7, 12, 25, 29, 31, 36, 37, 48, 49, 53, 54, 60, 61, 63, 68, 72], "disabl": [7, 52, 55], "use_exact_s": 7, "speedup": [7, 63], "full": [7, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 24, 35, 37, 40, 42, 49, 53, 54, 57, 59, 61, 63, 64, 66, 70, 74, 77], "kolmogorov": [7, 13, 29, 38, 40, 52, 53, 57, 65, 66, 74], "achromat": [7, 13, 37, 64], "applic": [7, 14, 20, 25, 37, 40, 42, 54, 59, 63, 64, 69, 74], "advantag": [7, 8, 36, 49, 64, 77], "attempt": [7, 20, 23, 33, 40, 42, 54, 57, 59, 61, 77], "unset": 7, "clever": 7, "abl": [7, 10, 13, 16, 19, 20, 27, 31, 35, 36, 42, 46, 52, 63, 64, 77], "contrast": [7, 23, 54, 64], "benefici": 7, "multipl": [7, 8, 10, 11, 12, 14, 15, 17, 19, 20, 22, 23, 24, 28, 31, 32, 35, 36, 37, 42, 44, 46, 47, 49, 52, 54, 56, 57, 60, 61, 63, 64, 69, 74], "least": [7, 8, 9, 11, 12, 20, 29, 34, 37, 38, 40, 42, 45, 49, 54, 59, 63, 64, 66, 72, 74, 77], "precomput": [7, 36, 60], "guidanc": [7, 59], "experiment": 7, "warrant": 7, "span": [7, 17, 49], "cover": [7, 37, 52, 64], "linearli": [7, 11, 20, 54], "oversample_fac": 7, "oversampl": [7, 13, 17, 29, 54, 57, 61], "nyquist": [7, 13, 17, 20, 29, 37, 42, 49, 54, 59, 64], "whichev": [7, 17, 37, 77], "costlier": 7, "g1": [7, 14, 19, 20, 23, 36, 37, 40, 49, 50, 58, 59, 60, 69, 74, 77], "g2": [7, 14, 19, 20, 23, 36, 37, 40, 49, 50, 58, 59, 60, 69, 74, 77], "mu": [7, 19, 20, 32, 37, 50, 59, 69, 74], "magnif": [7, 12, 13, 19, 20, 29, 37, 50, 52, 53, 59, 69, 74, 78], "powerspectrum": [7, 11, 12, 37, 52, 59, 60, 74, 78], "nfwhalo": [7, 11, 12, 19, 37, 50, 52, 74, 78], "nfw": [7, 12, 19, 37, 43, 53, 74, 78], "dark": [7, 31, 37, 42, 50, 64], "matter": [7, 11, 19, 37, 50, 57, 75], "halo": [7, 12, 19, 37, 43, 53, 74, 78], "gravit": [7, 15, 74], "parallel": [7, 14, 15, 37, 44, 49, 53, 54, 74], "subtend": [7, 37, 75], "fix": [7, 17, 32, 37, 42, 49, 52, 53, 60, 64, 77], "static": [7, 38, 40, 42, 48, 54, 75, 77], "maxsiz": [7, 49], "ve": [7, 27, 49], "anticlockwis": [7, 20, 36, 37], "distort": [7, 11, 19, 23, 29, 36, 37, 40, 42, 49, 52, 59, 64, 67, 69, 74, 77], "telescop": [7, 13, 17, 19, 20, 29, 37, 42, 43, 52, 53, 54, 57, 61, 63, 74, 75, 79], "eg": [7, 20, 36, 37], "singl": [7, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 22, 24, 25, 29, 31, 35, 36, 37, 40, 42, 49, 50, 52, 54, 56, 59, 63, 64, 68, 69, 70, 72, 74, 75, 77, 79], "horizont": [7, 36, 37, 54, 56], "vertic": [7, 13, 17, 22, 36, 37, 54, 56, 57, 61], "dudx": [7, 11, 20, 29, 36, 37, 73, 74, 77], "dudi": [7, 11, 20, 29, 36, 37, 73, 74, 77], "dvdx": [7, 11, 20, 29, 36, 37, 73, 74, 77], "dvdy": [7, 11, 20, 29, 36, 37, 73, 74, 77], "jacobian": [7, 11, 20, 29, 36, 37, 42, 52, 73, 77], "trasnform": 7, "du": [7, 11, 20, 36, 37, 77], "coord": [7, 20, 36, 37, 46, 50, 52, 54, 57, 59, 61, 64, 74, 75, 77], "v": [7, 10, 11, 20, 25, 34, 36, 37, 40, 42, 48, 49, 54, 57, 59, 63, 77, 79], "dv": [7, 11, 20, 36, 37, 77], "target_flux": [7, 66], "target_flux_dens": [7, 66], "target_magnitud": [7, 66], "withscaledflux": [7, 36, 37, 54, 61, 70], "base_obj": 7, "base_wavelength": [7, 13, 17, 57, 66, 74], "scale_unit": [7, 13, 17, 52, 54, 57, 61, 63, 75], "differenti": [7, 13, 17, 43, 56, 57, 66, 74, 76], "dcr": [7, 13, 17, 30, 56, 57, 66], "land": [7, 28, 37, 55, 56, 57], "closer": [7, 12, 19, 26, 37, 57, 59, 63], "zenith": [7, 13, 17, 30, 57, 66], "turbul": [7, 13, 29, 53, 54, 57, 61, 66, 74], "parallact": [7, 13, 17, 30, 57, 66], "north": [7, 30, 36, 37, 40, 42, 57, 66, 77], "east": [7, 30, 57, 66, 77], "zenith_angl": [7, 13, 17, 30, 57, 66, 74], "parallactic_angl": [7, 13, 17, 30, 57, 66], "obj_coord": [7, 30, 57, 66], "zenith_coord": [7, 13, 17, 30, 57, 66], "celestialcoord": [7, 11, 13, 15, 19, 30, 52, 57, 64, 66, 74, 75, 77], "hour": [7, 13, 14, 15, 17, 19, 30, 57, 66, 75, 77], "latitud": [7, 13, 17, 30, 57, 64, 66, 74, 77], "temperatur": [7, 13, 17, 30, 42, 57, 64, 66], "pressur": [7, 13, 17, 30, 57, 66], "water": [7, 13, 17, 30, 57, 66], "vapor": [7, 13, 17, 30, 57, 66], "expect": [7, 11, 13, 14, 15, 19, 20, 27, 28, 29, 30, 31, 32, 33, 37, 40, 46, 49, 51, 54, 55, 56, 57, 59, 64, 77], "lsst": [7, 16, 30, 52, 54, 57, 67, 68, 74], "cerro": [7, 30, 57], "pachon": [7, 30, 57], "chile": [7, 30, 57], "broadli": [7, 29, 30, 54, 57], "observatori": [7, 13, 29, 30, 57, 61, 64, 68], "NOT": [7, 29, 31, 37, 42, 46, 64, 73], "thing": [7, 12, 13, 14, 15, 16, 17, 18, 20, 24, 27, 29, 32, 35, 37, 42, 49, 52, 54, 57, 58, 59, 63, 73, 74, 75, 77], "too": [7, 24, 29, 33, 37, 38, 40, 49, 54, 61, 64, 77], "cours": [7, 11, 14, 20, 37, 42, 54, 58, 64, 74, 77], "deltafunct": [7, 13, 29, 52, 70, 74], "psf_sed": 7, "final_star": 7, "bp": 7, "fiduci": [7, 17, 57, 64], "celesti": [7, 11, 17, 30, 43, 52, 57, 66], "air": [7, 13, 17, 30, 57, 66], "kilopasc": [7, 30, 57, 66], "69": [7, 13, 17, 30, 57, 66], "328": [7, 13, 17, 30, 57, 66], "kpa": [7, 13, 17, 57, 66], "kelvin": [7, 17, 30, 57, 66], "293": [7, 13, 17, 30, 52, 57, 66], "15": [7, 11, 13, 17, 30, 40, 42, 49, 54, 57, 58, 60, 61, 66, 68], "h2o_pressur": [7, 13, 17, 30, 57, 66], "067": [7, 13, 17, 30, 57, 66], "build_obj": 7, "don": [7, 11, 14, 15, 17, 18, 19, 21, 24, 26, 27, 29, 36, 37, 42, 44, 46, 49, 52, 53, 54, 63, 64, 77], "picklabl": [7, 15, 49, 52], "quit": [7, 8, 11, 25, 29, 37, 44, 48, 49, 54, 59, 63, 77], "fly": 7, "lam": [7, 13, 17, 27, 29, 52, 54, 57, 61, 63, 74, 75, 77], "diam": [7, 13, 17, 29, 52, 54, 57, 61, 63, 74, 75], "aberr": [7, 13, 17, 52, 53, 54, 61, 64, 74], "meant": [7, 53, 63, 64], "plai": [7, 36], "role": [7, 16, 36, 77], "diamet": [7, 13, 17, 50, 54, 57, 61, 63, 64, 75], "defocu": [7, 13, 54, 61, 64, 74, 79], "coma": [7, 54, 61, 64, 74], "impact": [7, 36, 56, 59, 63, 79], "aspect": [7, 9, 14, 16, 17, 64, 67, 74], "obscur": [7, 13, 16, 17, 29, 53, 54, 57, 61, 63, 64, 74], "strut": [7, 13, 17, 52, 53, 54, 57, 61, 64], "apertur": [7, 17, 29, 54, 57, 61, 64, 65], "implicitli": [7, 17, 38, 40, 42, 59, 77], "principl": [7, 64], "ideal": [7, 18, 25, 29, 34, 49, 54, 59, 60, 64, 74], "across": [7, 10, 11, 14, 15, 20, 29, 32, 35, 40, 42, 52, 54, 60, 64, 67, 75], "converg": [7, 23, 29, 40, 42, 50, 52, 57, 59, 69, 78], "moder": [7, 54], "suffic": 7, "statement": [7, 11, 13, 15, 17, 37, 63], "25": [7, 12, 13, 40, 54, 61, 63, 68, 69, 74, 75], "commonli": [7, 57], "satisfi": [7, 40, 61, 72, 79], "stringent": [7, 29], "geometric_shoot": [7, 52, 54, 61], "screen": [7, 43, 52, 53, 61, 65], "who": [7, 16, 29, 37, 40, 46, 53, 59, 63, 64, 75], "prefer": [7, 9, 13, 20, 27, 32, 37, 40, 42, 45, 53, 54, 55, 75, 77], "whole": [7, 10, 11, 12, 13, 16, 20, 49, 54], "exposur": [7, 11, 12, 13, 16, 17, 31, 37, 42, 54, 57, 61, 63, 64, 75], "setup": [7, 11, 12, 14, 15, 17, 19, 26, 44, 45, 46, 52, 53, 54, 55, 59, 60], "amort": 7, "meter": [7, 13, 17, 54, 56, 57, 61, 64, 75], "format": [7, 9, 13, 14, 18, 19, 22, 23, 40, 42, 49, 52, 53, 59, 63, 64, 65, 74], "opticalpsf": [7, 13, 52, 54, 61, 63, 64, 74, 79], "geometr": [7, 26, 37, 52, 54, 61], "phase": [7, 43, 52, 53, 61, 65, 69], "computation": [7, 20, 54], "real_galaxy_catalog": [7, 63, 74], "id": [7, 13, 31, 56, 63, 74], "logger": [7, 11, 12, 13, 14, 15, 17, 31, 49, 52, 63, 64, 74], "band": [7, 25, 48, 55, 59, 63, 64, 68, 74], "train": [7, 52, 63], "dataset": [7, 52, 63], "mode": [7, 10, 12, 15, 29, 38, 54, 59, 60, 71, 72, 79], "decomposit": [7, 31, 65, 77], "thought": [7, 36, 69, 77], "constrain": [7, 38], "deconvolut": [7, 13, 17, 29, 37, 53, 55, 63, 73], "spirit": [7, 17], "realgalaxi": [7, 11, 12, 13, 15, 17, 20, 37, 52, 53, 63, 74], "unavail": [7, 31, 63], "fundament": [7, 18, 26, 41, 52, 75], "seri": [7, 9, 27, 32, 44, 49, 52, 53, 54, 59, 79], "resolut": [7, 13, 23, 37, 40, 49, 52, 54, 63, 74], "makefromimag": [7, 37, 56, 63], "factori": [7, 8, 37, 48, 49, 52, 72, 73, 77], "crg": 7, "img": [7, 42, 49, 52, 64], "xi": [7, 63], "altern": [7, 12, 13, 19, 29, 36, 37, 46, 51, 63, 64, 66, 68, 77], "realgalaxycatalog": [7, 33, 52, 63, 68, 74], "1st": [7, 23, 49], "catalog": [7, 11, 12, 13, 14, 15, 17, 18, 19, 22, 33, 43, 52, 53, 65, 68, 74, 76, 77], "futur": [7, 13, 19, 40, 42, 46, 52, 54, 59, 61, 64, 68], "constant": [7, 11, 13, 15, 16, 19, 20, 25, 26, 29, 42, 48, 50, 52, 54, 59, 64, 70, 72, 74, 77, 79], "area_norm": [7, 63], "longer": [7, 15, 35, 36, 37, 40, 52, 63, 68], "collect": [7, 12, 13, 15, 37, 40, 56, 63, 64, 75], "exptim": [7, 13, 17, 37, 52, 54, 55, 57, 63, 64, 75], "absolut": [7, 24, 25, 26, 28, 29, 30, 37, 38, 47, 56, 67], "technic": [7, 15, 16, 17, 18, 19, 29, 30, 37, 46, 54, 60, 63, 64, 72, 73], "signal": [7, 13, 14, 17, 25, 42, 48, 63, 64], "consider": [7, 36, 59, 63], "certain": [7, 29, 36, 42, 46, 49, 52, 64], "suitabl": [7, 42, 63], "wide": [7, 29, 41, 42, 49, 64], "attach": [7, 37, 64], "hold": [7, 14, 15, 23, 26, 27, 28, 32, 42, 49, 54, 56, 61, 71], "assert": [7, 21, 37, 49, 52, 66, 77], "hasattr": 7, "noise1": 7, "replac": [7, 12, 13, 16, 25, 27, 29, 34, 35, 37, 42, 49, 59, 63, 64, 72], "bandpass2": [7, 74], "image2": [7, 42], "fat": [7, 56], "solv": [7, 26], "camera": [7, 63, 64, 67], "cosmo": [7, 11, 12, 13, 19, 20, 43, 50, 52, 53, 60, 65, 74], "45238": [7, 63], "93416": [7, 63], "iter": [7, 23, 24, 40, 42, 49, 54, 63, 64], "character": [7, 20, 23, 29, 36, 40, 61, 63, 64, 70], "predrawn": 7, "deinterpol": 7, "procedur": [7, 11, 46, 49], "sometim": [7, 10, 13, 14, 15, 16, 17, 18, 19, 26, 27, 29, 31, 33, 35, 42, 52, 54, 77], "seen": [7, 36, 64, 75], "place": [7, 11, 14, 15, 16, 17, 19, 20, 21, 23, 37, 42, 46, 49, 51, 52, 53, 58, 59, 60, 63, 66, 74, 77], "from_imag": [7, 39], "initiazli": 7, "wavelenght": 7, "calucl": 7, "ident": [7, 10, 16, 27, 32, 36, 37, 38, 54, 61, 63, 72, 74, 77], "caus": [7, 11, 12, 17, 32, 37, 38, 42, 49, 52], "small": [7, 12, 13, 15, 20, 25, 29, 37, 40, 42, 52, 54, 59, 61, 63, 64, 67], "_from_imag": 7, "step_k": 7, "alik": 7, "max_k": 7, "intial": [7, 24], "unnam": [7, 8, 28, 35], "propagate_gsparam": [7, 8, 73], "idea": [7, 8, 54, 57, 61, 72, 73], "summand": 7, "wast": 7, "chromatic": 7, "identifi": [7, 64], "obj_list": [7, 8, 31, 49, 52], "sed_fil": [7, 8], "syntax": [7, 8, 13, 20, 24, 32, 33, 35, 40, 42, 46, 49, 51, 52], "decid": [7, 8, 12, 13, 14, 28, 33, 37, 38, 60, 73], "iimult": 7, "portion": [7, 11, 12, 13, 14, 15, 17, 19, 25, 28, 42, 54, 77], "push": [7, 37], "insepar": [7, 39], "resize_effective_prof_cach": 7, "deconvolv": [7, 13, 20, 29, 63, 73, 74], "kernel": [7, 20, 25, 29, 42, 48, 54, 55, 61, 64, 73], "unspecifi": [7, 8, 42, 64, 73], "inherit": [7, 49, 73], "special": [7, 8, 9, 11, 12, 13, 14, 17, 19, 24, 29, 31, 36, 37, 43, 49, 59, 63, 74], "fact": [7, 8, 13, 19, 20, 26, 29, 35, 37, 40, 42, 59, 63, 64, 75, 77], "autoconvolv": [7, 8], "180": [7, 8, 17, 20, 42, 64, 75, 77], "degre": [7, 8, 11, 12, 13, 15, 17, 19, 20, 23, 27, 29, 30, 32, 36, 42, 54, 57, 60, 61, 64, 69, 74, 75, 77], "autocorrel": [7, 8], "jac": [7, 29, 37, 73, 77], "_redshift": 7, "affin": [7, 11, 29, 36, 37, 53, 65, 74, 77], "translat": [7, 29, 36, 37, 64, 73], "2x2": [7, 29, 66, 73, 77], "squar": [7, 11, 13, 17, 19, 20, 26, 27, 29, 32, 37, 42, 52, 54, 59, 60, 61, 63, 65, 69, 70], "root": [7, 14, 15, 19, 26, 29, 54, 65, 74], "fouriersqrt": [7, 52, 53, 73], "fourier_sqrt": [7, 73], "inde": [7, 20, 35, 36, 37, 77], "7": [8, 11, 12, 13, 19, 20, 32, 39, 40, 42, 43, 44, 46, 49, 50, 52, 54, 63, 64, 68, 72], "smallest": [8, 24, 75, 77], "numer": [8, 21, 25, 29, 35, 36, 37, 38, 46, 49, 52, 53, 64, 71, 75], "xvalue_accuraci": [8, 29, 38], "largest": [8, 75, 77], "sens": [8, 14, 15, 17, 19, 42, 52, 58, 64, 77], "stricter": [8, 40], "threshold": [8, 29, 37, 49, 73], "fainter": 8, "looser": 8, "toler": [8, 25, 26, 37, 38, 52, 55, 64], "inspect": [8, 73], "discret": [8, 20, 29, 34, 36, 37, 54, 57, 59], "back": [8, 14, 15, 17, 19, 28, 29, 30, 37, 49, 56, 67, 72, 77], "asid": [8, 37], "trivial": [8, 13, 17, 20, 25, 29, 37, 40, 42, 48, 52], "implementaion": [8, 17], "moffat": [8, 13, 17, 20, 29, 37, 38, 52, 53, 55, 65, 74], "ring": [8, 11, 15, 17, 25, 29, 37, 38, 48, 52, 67, 74], "di": [8, 29], "fairli": [8, 29, 35, 42, 46, 49, 54, 64, 74], "slowli": [8, 29, 40, 49, 54], "quicker": [8, 29, 75], "autoconvolut": 8, "psf_sq": 8, "orig_obj": [8, 73], "createrot": [8, 52], "convorrel": 8, "configur": [9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 31, 33, 46, 52, 53, 54, 59, 61, 64, 74], "dictionari": [9, 12, 16, 19, 43, 49, 53, 64, 71, 76], "structur": [9, 12, 21, 31, 35, 40, 49, 52, 71], "occasion": [9, 37, 53], "deep": [9, 13, 15, 24, 42, 63], "nice": [9, 49], "anyth": [9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 24, 29, 32, 37, 47, 49, 52, 53, 55, 56], "tutori": [9, 43, 44, 53], "goe": [9, 14, 15, 16, 17, 24, 42, 63, 64, 77], "demo": [9, 11, 43, 44, 52, 53, 63, 64], "concret": [9, 29], "demo1": [9, 10, 44, 53, 74], "aforement": 9, "strip": [9, 15], "essenc": 9, "e5": [9, 19], "30": [9, 19, 49, 63, 64, 66, 69, 77], "deviat": [9, 19, 20, 37, 40, 42, 43, 49, 51, 52, 59, 62, 64], "output_yaml": 9, "implicit": [9, 14, 29, 37, 39, 42, 74], "omit": [9, 10, 11, 12, 13, 15, 18, 27, 31, 32, 49, 77], "profil": [9, 10, 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 31, 37, 38, 40, 42, 43, 47, 49, 51, 52, 53, 54, 55, 56, 74, 75, 77, 78], "run": [10, 11, 12, 13, 14, 16, 17, 19, 21, 32, 33, 40, 42, 43, 46, 49, 52, 53, 54, 61, 63, 74, 79], "conveni": [10, 15, 16, 19, 24, 29, 30, 31, 32, 35, 36, 37, 42, 47, 49, 51, 54, 59, 61, 63, 64, 75, 77, 79], "edit": [10, 77], "e4": 10, "demo1_1e4": 10, "demo1_2e4": 10, "demo1_3e4": 10, "demo1_4e4": 10, "notic": [10, 20, 63, 64], "node": [10, 14, 25, 42], "core": [10, 21, 36], "parcel": 10, "sub": [10, 11, 13, 14, 15, 17, 19, 22, 24, 32, 37, 42, 49, 52, 64, 68, 74], "easi": [10, 14, 60, 63, 74, 75, 77], "machin": [10, 14, 15, 46, 68], "send": [10, 14, 15, 47, 53], "queue": [10, 11, 14, 42], "h": [10, 12, 13, 21, 29, 50, 52, 63, 75], "messag": [10, 13, 15, 19, 23, 33, 40, 63], "disagre": 10, "trust": [10, 37, 46, 75], "verbos": [10, 27, 52, 63, 75], "log": [10, 11, 12, 13, 14, 15, 17, 19, 31, 32, 42, 46, 49, 59, 60, 63, 69, 72], "modest": [10, 63, 74], "debug": [10, 11, 13, 14, 15, 17], "diagnos": [10, 14], "runtim": 10, "problem": [10, 12, 13, 14, 15, 17, 19, 36, 44, 52, 60], "log_fil": 10, "stdout": 10, "modul": [10, 11, 12, 13, 14, 15, 17, 37, 39, 43, 44, 46, 49, 52, 53, 59, 60, 66, 68, 71, 74, 75, 77], "supersed": [10, 38, 49], "spend": 10, "njob": [10, 15, 52], "except_abort": [10, 15, 52], "abort": [10, 15], "whenev": [10, 11, 12, 29, 37, 38], "continu": [10, 15, 33, 36, 46, 53, 54, 59, 72, 77], "float_valu": [11, 12, 13, 14, 17, 18], "everyth": [11, 15, 26, 60, 63, 64], "radian": [11, 12, 19, 25, 29, 30, 37, 54, 59, 60, 61, 64, 69, 74, 75, 77, 79], "someth": [11, 12, 13, 14, 15, 16, 17, 19, 24, 27, 31, 32, 33, 35, 37, 40, 42, 46, 47, 49, 52, 53, 54, 59, 61, 66, 75], "els": [11, 12, 14, 17, 20, 24, 29, 31, 42, 46, 55, 56, 64, 75], "sky_level": [11, 16, 37, 51, 55, 74, 77], "sky_level_pixel": 11, "background": [11, 20, 37, 42, 64, 67, 74], "adu": [11, 13, 15, 37, 42, 51, 74, 75, 77], "index_convent": [11, 52, 74], "str_valu": [11, 12, 13, 14, 17], "counter": [11, 13, 17, 27, 42, 49, 54, 57, 61, 77], "intuit": [11, 12, 20, 24, 35, 39, 51], "peopl": [11, 59], "consid": [11, 14, 22, 28, 31, 36, 37, 40, 42, 44, 59, 63, 64, 66, 72, 74, 77], "unnecessari": [11, 24, 26, 52], "fortran": [11, 49, 77], "np": [11, 19, 32, 34, 48, 49, 52, 54, 59, 63, 64, 72, 75, 77, 79], "float32": [11, 20, 37, 42], "random_se": [11, 15, 16, 19, 39, 74], "int_valu": [11, 12, 13, 14, 17, 18], "scrambl": 11, "specifii": 11, "cadenc": [11, 16, 42], "13": [11, 43, 58, 68, 75], "rng_num": [11, 15, 74], "dev": [11, 27, 32, 46, 51, 52, 74], "urandom": [11, 27, 32, 52], "nproc": [11, 14, 15, 54, 74], "processor": [11, 14, 15], "figur": [11, 12, 14, 15, 27, 42, 59, 61], "cpu": [11, 14, 42, 49], "timeout": [11, 14, 15, 44, 52], "900": [11, 15], "job": [11, 14, 15, 17, 44, 52], "multiprocess": [11, 12, 14, 15, 31, 49, 52, 54], "hang": [11, 14, 49], "forev": [11, 14, 19], "snafu": [11, 14], "minut": [11, 54, 75], "extra": [11, 13, 17, 31, 32, 36, 37, 42, 46, 51, 55, 57, 64, 74, 77], "encompass": [11, 17], "xsize": [11, 15, 17, 74], "ysize": [11, 15, 17, 74], "world_po": [11, 15, 16, 17, 19, 52, 64, 68, 74, 77], "pos_valu": [11, 12, 13, 14, 17, 74], "world": [11, 12, 15, 17, 19, 20, 31, 37, 41, 42, 43, 64], "unconnect": 11, "image_po": [11, 15, 16, 17, 19, 31, 40, 42, 52, 74, 77], "tile": [11, 12, 17, 19, 35, 42, 59, 74], "nx_tile": [11, 74], "ny_til": [11, 74], "stamp_siz": [11, 19, 74], "stamp_xsiz": [11, 15, 17, 19, 74], "stamp_ys": [11, 15, 17, 19, 74], "xborder": [11, 74], "yborder": [11, 74], "fill": [11, 20, 24, 32, 42, 49, 64, 79], "proce": [11, 16, 54, 60], "scatter": [11, 12, 19, 52, 74], "contigu": [11, 24, 42, 49], "xy": [11, 19, 23, 49, 74], "instal": [11, 12, 13, 14, 16, 17, 19, 21, 32, 35, 43, 52, 63, 64, 68, 77], "distro": [11, 12, 13, 14, 16, 17, 19], "imagebuild": 11, "signatur": [11, 13, 14, 17, 19, 21, 46, 52], "addnois": [11, 15, 17, 20, 42, 51, 52, 74], "image_num": [11, 14, 15, 16, 19, 31, 74], "obj_num": [11, 14, 15, 16, 17, 19, 31, 39, 52], "current_var": [11, 15, 17], "op": [11, 24, 26, 27, 42, 52, 57], "buildstamp": [11, 15], "properli": [11, 14, 17, 24, 35, 37, 42, 49, 52, 53, 56, 61, 64, 67, 69, 77], "buildbandpass": 11, "thre": 11, "load": [11, 12, 13, 14, 15, 16, 17, 19, 31, 46, 49, 52, 54, 63, 64, 68, 74], "gasim": [11, 75], "buildimag": [11, 14, 15, 31, 74], "buildsensor": 11, "getnobj": 11, "approx": [11, 14, 15, 42], "overestim": [11, 14, 15, 37], "ok": [11, 14, 15, 26, 42], "maketask": [11, 17], "mechan": [11, 16, 19, 21, 36, 42, 52, 77], "mess": 11, "blend": [11, 14, 17, 63, 74], "perhap": [11, 12, 19, 20, 37], "makestamptask": [11, 15], "enumer": [11, 15, 17, 25], "actual": [11, 12, 13, 15, 16, 17, 19, 20, 24, 27, 29, 32, 36, 37, 42, 49, 52, 54, 55, 56, 59, 60, 62, 63, 64, 65, 69, 73, 74, 75, 77], "present": [11, 13, 14, 15, 17, 19, 20, 33, 35, 36, 37, 40, 42, 50, 51, 54, 55, 74, 77], "regist": [11, 12, 13, 14, 15, 16, 17, 19], "trigger": [11, 12, 13, 14, 17, 19, 38, 52], "builder": [11, 12, 13, 14, 15, 17, 31, 52], "registerimagetyp": 11, "customimag": 11, "customimageload": 11, "image_typ": 11, "apparatu": [11, 12, 13, 14, 17, 19, 74], "open": [11, 14, 17, 31, 33, 35, 44, 53, 54, 68, 77], "tell": [11, 12, 13, 14, 16, 17, 19, 23, 26, 31, 33, 35, 37, 42, 46, 54, 59, 61, 63, 64, 69, 77], "parser": [11, 12, 13, 14, 15, 17, 19], "my_custom_imag": 11, "py": [11, 12, 13, 14, 17, 19, 20, 27, 37, 40, 44, 45, 46, 49, 52, 53, 60, 63, 64, 68, 73, 74, 77], "registr": [11, 12, 13, 14, 17, 19, 52], "click": [11, 12, 13, 14, 17, 53], "link": [11, 12, 13, 14, 17, 32, 43, 46, 53, 63, 64], "image_scatt": 11, "scatteredimagebuild": 11, "image_til": 11, "tiledimagebuild": 11, "rm": [11, 51, 64], "poisson": [11, 14, 17, 19, 27, 32, 37, 42, 51, 52, 55, 64, 67, 74], "read_nois": [11, 51, 64, 74], "f814w": [11, 13, 20, 63, 68], "leauthaud": [11, 20], "2007": [11, 20, 74], "packag": [11, 14, 16, 21, 23, 34, 35, 40, 44, 46, 52, 53, 54, 74, 75, 77], "share": [11, 12, 20, 21, 24, 27, 32, 36, 42, 43, 49, 51, 52, 54, 63], "acs_i_unrot_sci_20_cf": [11, 20, 68], "let": [11, 12, 13, 16, 18, 19, 20, 25, 26, 31, 35, 36, 37, 38, 42, 46, 49, 52, 54, 55, 59, 63], "cosmos_scal": [11, 20], "03": [11, 20, 69], "ac": [11, 13, 20, 47, 63, 68], "coadd": [11, 20, 53], "adopt": [11, 20, 27, 29, 32, 42, 49, 54, 57, 59, 61, 63, 64, 79], "from_fil": [11, 19, 20, 52, 72], "modif": [11, 15, 16, 40, 52, 56, 59], "bool_valu": [11, 12, 13, 14, 17], "white": [11, 19, 20, 42], "subtract": [11, 20, 22, 37, 51, 52, 64, 79], "request": [11, 12, 15, 17, 20, 29, 33, 34, 35, 36, 42, 49, 52, 53, 59, 64, 66], "coher": 11, "seamlessli": 11, "symmetr": [11, 13, 15, 17, 20, 25, 27, 37, 42, 49, 50, 74, 77], "impos": [11, 12, 20, 40, 42, 63], "fold": [11, 20, 29, 37, 38, 42, 51, 53, 54, 57, 61], "achiev": [11, 20, 29, 32, 42, 56], "fulli": [11, 14, 20, 64], "noisebuild": 11, "abstract": [11, 24, 29, 35, 47], "draw_method": [11, 13, 14, 15, 17, 52, 74], "addnoisevari": [11, 15], "include_obj_var": [11, 14, 15], "map": [11, 14, 15, 19, 26, 31, 32, 40, 42, 49, 52, 56, 77], "iamg": 11, "main": [11, 12, 14, 15, 16, 17, 20, 21, 31, 37, 41, 42, 54, 74, 77], "implemen": 11, "getnoisevari": 11, "ccdnois": [11, 42, 51, 52, 62, 74], "registernoisetyp": 11, "customnois": 11, "customnoisebuild": 11, "noise_typ": 11, "input_typ": [11, 12, 13, 15, 17, 19], "utilis": [11, 13, 17, 19], "my_custom_nois": 11, "gaussiannoisebuild": 11, "poissonnoisebuild": 11, "ccdnoisebuild": 11, "cosmosnoisebuild": 11, "correlatednoisebuild": 11, "mention": [11, 37, 38, 67], "connect": [11, 12, 14, 15, 32, 35, 36, 37, 59, 77], "uniform": [11, 15, 19, 20, 27, 32, 37, 40, 41, 42, 49, 54, 55, 57, 60, 67, 72, 77], "parallelogram": 11, "rhombi": 11, "shear_valu": [11, 13, 17, 74], "uvfunct": [11, 74, 77], "invers": [11, 14, 15, 24, 25, 29, 34, 37, 42, 48, 59, 60, 73, 77], "ufunc": [11, 74, 77], "vfunc": [11, 74, 77], "xfunc": [11, 74, 77], "yfunc": [11, 74, 77], "radecfunct": [11, 77], "ra": [11, 13, 15, 19, 52, 64, 74, 75, 77], "dec": [11, 13, 15, 19, 52, 64, 74, 75, 77], "ra_func": [11, 77], "rafunc": 11, "dec_func": [11, 77], "decfunc": 11, "unusu": [11, 40, 42], "success": [11, 23, 27, 32, 40, 42, 53, 77], "document": [11, 15, 21, 25, 29, 32, 36, 37, 40, 42, 48, 49, 52, 59, 63, 64, 74], "fitswc": [11, 52, 77], "tan": [11, 52, 74, 75, 77], "tangent": [11, 15, 17, 19, 36, 37, 56, 77], "plane": [11, 13, 15, 17, 19, 20, 29, 36, 37, 42, 49, 52, 54, 56, 57, 58, 61, 64, 67, 77], "project": [11, 13, 19, 21, 31, 52, 53, 64, 76, 77], "sphere": [11, 77], "ascens": [11, 77], "declin": [11, 77], "angle_valu": [11, 13, 17], "arcmin": [11, 12, 19, 54, 74, 75], "deg": [11, 19, 75, 77], "rad": [11, 19, 52, 75, 77], "hr": [11, 19, 75], "world_origin": [11, 74, 77], "Not": [11, 14, 19, 35, 37, 77], "wcsbuilder": 11, "buildwc": [11, 15], "registerwcstyp": 11, "customwc": 11, "customwcsbuild": 11, "wcs_type": [11, 77], "fitshead": [11, 12, 19, 33, 35, 42, 52, 77], "fits_head": [11, 12, 19, 35], "my_custom_wc": 11, "des_wc": [11, 12, 74], "des_slowloc": 11, "des_loc": [11, 74], "faster": [11, 13, 17, 29, 34, 36, 37, 42, 46, 49, 52, 53, 54, 63, 64, 66, 72, 77], "med": [11, 12, 14, 16, 43, 52, 74], "simplewcsbuild": 11, "init_func": [11, 12], "getkwarg": [11, 12], "req_param": 11, "originwcsbuild": 11, "origin_init_func": 11, "tanwcsbuild": 11, "tanwc": [11, 74, 77], "handl": [11, 12, 14, 15, 17, 23, 31, 35, 37, 38, 39, 42, 46, 52, 53, 54, 59, 61, 64, 67, 72, 74, 75, 77, 78], "affinetransform": [11, 35, 74, 77], "paramt": 11, "listwcsbuild": 11, "modular": [11, 13, 43], "filebandpass": 11, "unit_valu": [11, 13], "variat": [11, 13, 16, 19, 37, 40, 42, 49, 63, 77], "quantity_valu": [11, 13, 17], "bandpassbuild": 11, "registerbandpasstyp": 11, "custombandpass": 11, "custombandpassbuild": 11, "bandpass_typ": 11, "my_custom_bandpass": 11, "convers": [11, 12, 20, 23, 37, 42, 52, 64, 66, 67, 75, 77], "silicon": [11, 37, 52, 57, 67], "depth": [11, 17, 52, 57, 63, 67], "probabilist": [11, 67], "drift": [11, 54, 64, 67], "repuls": [11, 67], "previous": [11, 49, 67], "lsst_itl_50_8": [11, 67], "naem": 11, "diffusion_factor": [11, 67], "diffus": [11, 37, 49, 52, 55, 64, 67], "qdist": [11, 28, 67], "charg": [11, 42, 43, 49, 52, 55, 64, 67, 74, 76], "nrecalc": [11, 67], "10000": [11, 67, 68], "treering_func": [11, 67], "table_valu": 11, "tree": [11, 52, 67], "pattern": [11, 14, 20, 29, 42, 54, 64, 67], "radial": [11, 13, 19, 23, 29, 36, 37, 38, 40, 49, 54, 57, 61, 67, 70, 74, 79], "treering_cent": [11, 67], "transpos": [11, 28, 42, 52, 67], "bfe": 11, "elong": [11, 77], "sensorbuild": 11, "registersensortyp": 11, "customsensor": 11, "customsensorbuild": 11, "sensor_typ": 11, "my_custom_sensor": 11, "primari": [12, 13, 14, 15, 35, 77], "galaxy_const": [12, 19], "pick": [12, 19, 32, 37, 63], "header": [12, 13, 19, 21, 31, 42, 46, 52, 53, 63, 74, 76, 77], "section": [12, 14, 19, 31, 63, 65], "compress": [12, 13, 35, 42, 52, 53, 74, 77], "blah": 12, "fz": [12, 35, 42, 74], "rice": [12, 35, 42, 53, 74, 77], "support": [12, 13, 16, 17, 32, 35, 39, 44, 46, 52, 53, 54, 57, 61, 77], "gzip": [12, 35, 42, 52, 53, 74], "bzip2": [12, 35, 42, 52, 53, 74], "gzip_til": [12, 35, 42], "hcompress": [12, 35, 42], "plio": [12, 35, 42], "text_fil": [12, 35, 52, 77], "head": [12, 19, 32, 35, 77], "scamp": [12, 35, 77], "real_catalog": [12, 13, 74], "prefix": [12, 46, 63], "download": [12, 46, 52, 53, 64, 65, 68, 74, 77], "galsim_download_cosmo": [12, 52, 63, 68], "23": [12, 13, 15, 42, 63, 64, 68, 74], "preload": [12, 52, 63], "bulk": [12, 17, 53, 63], "referenc": [12, 52, 63], "cosmos_catalog": [12, 13, 19, 52, 74], "cosmoscatalog": [12, 52, 63, 68, 74], "use_r": [12, 13, 63, 74], "bother": [12, 17, 49, 54], "exclusion_level": [12, 63], "margin": [12, 63], "qualiti": [12, 52, 63], "minim": [12, 38, 63], "mandelbaum": [12, 40, 53, 63], "2012": [12, 29, 63, 66, 74], "mnra": [12, 63], "420": [12, 63], "1518": [12, 63], "No": [12, 17, 35, 37, 40, 50, 52, 63], "bad_stamp": [12, 63], "failur": [12, 14, 17, 33, 40, 42, 52, 63, 64, 74], "subset": [12, 20, 24, 34, 42, 59, 63, 64], "bad_fit": [12, 63], "plu": [12, 16, 23, 29, 41, 51, 54, 61, 63, 64, 74, 75], "min_hlr": [12, 63], "exclud": [12, 19, 29, 38, 49, 63], "half": [12, 13, 19, 29, 36, 37, 38, 42, 52, 61, 63, 64, 70], "radiu": [12, 13, 15, 17, 19, 29, 36, 37, 38, 42, 47, 50, 57, 61, 63, 70, 74, 75, 77, 79], "max_hlr": [12, 63], "min_flux": [12, 63], "max_flux": [12, 56, 63], "galaxy_sampl": [12, 13, 19, 52], "samplegalaxi": [12, 13, 52], "orig_exptim": [12, 63], "orig_area": [12, 63], "cut_ratio": [12, 63], "exclus": [12, 63], "peak": [12, 20, 37, 63], "sn_limit": [12, 63], "estim": [12, 14, 20, 23, 25, 26, 28, 29, 33, 37, 38, 40, 42, 43, 47, 49, 52, 55, 63, 64, 72, 77, 78], "ellipt": [12, 15, 19, 23, 29, 36, 40, 42, 49, 55, 63, 69], "min_mask_dist": [12, 63], "mask": [12, 14, 23, 31, 40, 42, 60, 63, 64, 68, 74], "minimum": [12, 13, 19, 20, 26, 29, 32, 33, 38, 54, 59, 63, 72, 77], "nfw_halo": [12, 19, 74], "nfwhaloshear": [12, 13, 19, 74], "nfwhalomagnif": [12, 13, 19, 74], "mass": [12, 19, 50, 74, 78], "msolar": 12, "conc": [12, 50, 74], "concentr": [12, 50], "virial": [12, 50], "halo_po": [12, 50], "omega_m": [12, 50, 74], "omega_lam": [12, 50, 74], "power_spectrum": [12, 19, 52, 74], "powerspectrumshear": [12, 19, 74], "powerspectrummagnif": [12, 19], "e_power_funct": [12, 59, 74], "b_power_funct": [12, 59, 74], "delta2": [12, 59, 74], "canon": [12, 16], "spectra": [12, 20, 59, 60], "grid_spac": [12, 52, 59, 60, 74], "ngrid": [12, 52, 59, 60, 74], "file_num": [12, 14, 15, 16, 19, 31, 74], "initial_imag": [12, 52], "fo": 12, "read_head": [12, 35, 52], "inputcatalog": [12, 52], "num": [12, 13, 15, 17, 19, 74], "myinputdata": 12, "mayb": [12, 16, 19, 22, 57], "advanc": [12, 34, 43, 49], "def": [12, 13, 19, 47, 49, 54], "loader": 12, "inputload": [12, 52], "has_nobj": [12, 52], "file_scop": 12, "takes_logg": 12, "use_proxi": 12, "worker_init": 12, "worker_initarg": 12, "framework": [12, 31, 37, 52, 54], "practic": [12, 20, 37, 46, 59], "enough": [12, 15, 26, 37, 38, 49, 54, 64], "lazi": [12, 34], "delai": [12, 52, 54], "getapproxnobject": 12, "pretti": [12, 21, 24, 49, 74, 77], "egregi": 12, "scope": [12, 15, 19, 24, 29], "until": [12, 23, 24, 26, 33, 40, 42, 46, 52, 54, 77], "proxi": [12, 15, 49, 52, 59], "commic": 12, "decsrib": 12, "atmosphericscreen": [12, 52, 54, 65, 76], "global": [12, 20, 54, 77], "worker": [12, 54], "_req_param": 12, "_opt_param": 12, "_single_param": 12, "_takes_rng": 12, "safe": [12, 13, 14, 15, 19, 29, 31, 32, 52, 61], "rebuilt": [12, 17, 19], "input_obj": 12, "iobj": 12, "setupimag": [12, 14], "powerspectrumload": 12, "useproxi": 12, "registerinputtyp": 12, "custominput": 12, "custominputload": 12, "my_custom_input": 12, "de": [12, 13, 14, 43, 52, 65], "des_psfex": [12, 13, 31, 52, 74], "des_shapelet": [12, 13, 31, 74], "draw_psf": [12, 13, 16, 74], "input_cosmo": 12, "sampleload": 12, "cl": [12, 49], "input_field": 12, "_buildcosmosgalaxi": 12, "input_nfw": 12, "nfwloader": 12, "_generatefromnfwhaloshear": 12, "value_typ": [12, 15, 19], "_generatefromnfwhalomagnif": 12, "input_powerspectrum": 12, "_generatefrompowerspectrumshear": 12, "_generatefrompowerspectrummagnif": 12, "input_r": 12, "_buildrealgalaxi": 12, "param_nam": [12, 15], "_buildrealgalaxyorigin": 12, "design": [13, 19, 20, 36, 37, 44, 52, 53, 57, 59, 61, 64, 74, 77], "enforc": [13, 20, 21, 26, 36, 37, 42, 59, 77], "farther": [13, 16, 77], "bullet": 13, "sim": [13, 36, 54, 61, 70, 74], "r_0": [13, 29, 36, 54, 61], "beta": [13, 19, 20, 27, 29, 32, 37, 61, 64, 69, 74, 77], "scale_radiu": [13, 29, 36, 61, 74], "trunc": [13, 29, 36, 61, 74], "telescope_diamet": 13, "obstruct": 13, "secondari": [13, 54, 61, 74], "8839": [13, 61], "lam_over_r0": [13, 29, 61], "fri": [13, 29, 54, 61], "r0_500": [13, 52, 54, 61], "lieu": [13, 16, 36, 52], "noll": [13, 54, 61, 64, 79], "astig1": [13, 54, 61, 74], "astigmat": [13, 54, 61, 64, 74], "astig2": [13, 54, 61, 74], "coma1": [13, 54, 61, 74], "coma2": [13, 54, 61, 74], "trefoil1": [13, 54, 61, 74], "trefoil2": [13, 54, 61, 74], "spher": [13, 54, 61], "11": [13, 43, 54, 60, 61, 63, 68, 79], "With": [13, 21, 36, 37, 43, 44, 74], "circular_pupil": [13, 17, 54, 57, 61], "pupil": [13, 17, 29, 54, 56, 57, 61, 64, 68, 79], "circular": [13, 17, 23, 29, 40, 42, 49, 52, 54, 57, 59, 61, 70, 74], "put": [13, 14, 16, 17, 19, 21, 32, 35, 37, 39, 46, 49, 63, 74, 75, 77], "suppress_warn": [13, 32, 35, 49, 54, 61, 77], "suppress": [13, 32, 35, 49, 54, 59, 61, 77], "alias": [13, 29, 37, 38, 42, 54, 57, 59, 61, 64, 66], "max_siz": [13, 52], "wing": [13, 36], "never": [13, 24, 29, 37, 54], "nstrut": [13, 16, 17, 54, 57, 61, 74], "strut_thick": [13, 17, 54, 57, 61, 74], "thick": [13, 17, 54, 57, 59, 61], "strut_angl": [13, 16, 17, 54, 57, 61, 74], "clockwis": [13, 17, 42, 49, 54, 57, 61, 77], "rest": [13, 15, 16, 17, 19, 20, 27, 75], "pupil_plane_im": [13, 17, 52, 54, 57, 61], "geometri": [13, 17, 54, 60, 61, 64, 77], "pupil_angl": [13, 17, 54, 57, 61], "base_profil": [13, 74], "r_e": [13, 36], "flux_untrunc": [13, 29, 36], "untrunc": [13, 29, 36], "aka": [13, 17, 32, 42, 52, 68, 77], "spergel": [13, 29, 52, 65], "2010": [13, 29, 36, 52, 61, 74], "paper": [13, 25, 40, 48, 54], "nu": [13, 26, 29, 36, 47, 54], "k_": [13, 29, 36], "bessel": [13, 29, 36, 43, 47, 52, 76], "wrong": [13, 15, 52, 55, 57, 73], "realgalaxyorigin": [13, 52], "_not_": [13, 69], "unlik": [13, 19, 20, 31, 35, 37, 56, 63, 64], "cosmosgalaxi": [13, 52, 63, 74], "gal_typ": [13, 63, 74], "str_vale": 13, "float_val": [13, 17], "potag": 13, "sersic_prec": [13, 52, 63], "significantli": [13, 40, 52, 54, 59, 63, 72], "smallish": [13, 63], "round": [13, 29, 42, 52, 58, 63, 77], "inclinedexponenti": [13, 36, 52, 74], "3d": [13, 35, 36, 77], "z": [13, 25, 29, 36, 50, 56, 57, 63, 66, 68, 72, 74, 77, 79], "mathrm": [13, 32, 36, 61, 72, 79], "sech": [13, 29, 36], "h_": [13, 36], "r_": [13, 36, 79], "inclin": [13, 28, 29, 56, 65, 74], "scale_height": [13, 29, 36], "axi": [13, 19, 29, 36, 49, 57, 59, 64, 69, 77, 79], "face": [13, 29, 36, 64], "90": [13, 17, 36, 37, 42, 64, 74, 75, 77], "scale_h_over_r": [13, 36, 74], "height": [13, 29, 36, 57, 70], "inclinedsers": [13, 36, 52], "disc": 13, "scene": [13, 32, 37, 46, 52, 65, 74], "randomknot": [13, 36, 52, 74], "knot": [13, 65], "smooth": [13, 25, 48, 72], "npoint": [13, 17, 19, 32, 36, 57, 66, 72, 74], "realiz": [13, 14, 20, 29, 36, 49, 51, 52, 55, 59, 74], "gs_scale": 13, "sbinterpolatedimag": [13, 25, 29, 52], "uncompress": [13, 35], "box": [13, 17, 22, 24, 25, 29, 31, 35, 37, 41, 42, 43, 49, 52, 65], "rectangular": [13, 29, 54, 61], "boxcar": [13, 25, 29, 48], "heavisid": 13, "tophat": [13, 29, 52, 65], "commut": [13, 19, 66, 77], "occur": [13, 40, 42, 54, 61, 69], "packet": 13, "travel": [13, 37], "univers": [13, 50], "ellip": [13, 14, 15, 16, 74], "outer": [13, 17, 26, 29, 42, 49, 54, 57, 61, 64, 72, 79], "scale_flux": 13, "aggreg": [13, 54], "permiss": [13, 14, 17, 37, 42, 56, 63, 69], "r_gal": 13, "r_psf": 13, "radii": [13, 36, 37, 79], "randomli": [13, 17, 20, 25, 37, 63, 66, 74], "signal_to_nois": [13, 14, 52, 74], "trickier": 13, "buildcustomobject": 13, "customobject": 13, "registerobjecttyp": 13, "type_nam": [13, 15, 19], "build_func": 13, "regener": [13, 19], "input_dict": 13, "getinputobj": [13, 15, 19], "sensibl": [13, 18, 19, 29, 42], "invalid": [13, 17, 27, 31, 33, 36, 42, 52], "my_custom_object": 13, "_buildadd": 13, "_buildconvolv": 13, "_buildlist": 13, "_buildopticalpsf": 13, "files": 13, "fnu": [13, 66, 75], "fphoton": [13, 66, 75], "norm_flux_dens": [13, 74], "norm_wavelength": [13, 74], "norm_flux": [13, 74], "norm_bandpass": [13, 74], "sedbuild": 13, "builds": 13, "registersedtyp": 13, "customs": 13, "customsedbuild": 13, "sed_typ": 13, "my_custom_s": 13, "among": [14, 64, 74, 77], "creation": [14, 29, 63], "tradeoff": [14, 29, 37, 38, 40], "awar": [14, 20, 37, 63], "assembl": [14, 54], "commun": [14, 43], "domin": [14, 17, 37, 61, 64], "carefulli": [14, 54, 59], "openmp": [14, 52, 54, 76], "thread": [14, 49, 52, 54], "64": [14, 42, 63], "spawn": [14, 16, 28, 44, 54, 56], "unabl": [14, 17], "pip": [14, 21, 43, 44, 52, 53, 77], "conda": [14, 21, 43, 44, 53], "threadpoolctl": 14, "backend": [14, 34, 35, 37], "my_test": 14, "nfile": [14, 15, 19], "3600": [14, 75], "fifth": [14, 48], "ffile_num": 14, "dure": [14, 28, 29, 42, 46, 49, 54, 61, 69], "noclobb": [14, 52, 74], "clean": [14, 15], "fail": [14, 15, 17, 40, 42, 46, 49, 52], "insuffici": 14, "retry_io": [14, 52], "retri": [14, 15, 17, 52], "troubl": [14, 46, 53], "concurr": 14, "big": [14, 37, 63], "oserror": 14, "wait": [14, 54], "multifit": [14, 74], "nimag": [14, 15, 74], "datacub": [14, 74], "cube": [14, 26, 35, 53, 63, 74], "third": [14, 15, 19, 29, 74, 77], "outputbuild": [14, 31], "addextraoutputhdu": [14, 15], "canaddhdu": 14, "getfilenam": 14, "default_ext": [14, 15], "getnfil": [14, 15], "getnimag": [14, 31], "getnobjperimag": 14, "nobj0": [14, 15], "nobj1": [14, 15], "nobj2": [14, 15], "setupconfigrng": [14, 15], "hook": [14, 52], "writeextraoutput": [14, 15], "writefil": [14, 31, 35, 42, 52], "tack": 14, "registeroutputtyp": 14, "customoutput": 14, "customoutputbuild": 14, "output_typ": [14, 15], "my_custom_output": 14, "medsbuild": [14, 31], "output_datacub": 14, "datacubebuild": 14, "output_multifit": 14, "multifitsbuild": 14, "noiseless": 14, "badpix": [14, 19, 31, 40, 42, 74], "bad": [14, 31, 37, 40, 42, 49, 52, 74], "abil": [14, 17, 32, 40, 52, 55], "defect": [14, 53], "ordereddict": 14, "manual": [14, 16, 37, 44, 51, 52, 54, 55, 63], "grab": [14, 49, 53], "fine": [14, 21, 32, 37, 51, 55, 59, 61], "e1": [14, 19, 23, 40, 49, 54, 61, 69, 74, 77], "e2": [14, 19, 23, 40, 49, 54, 61, 69, 74, 77], "straight": [14, 77], "999": 14, "fashion": 14, "extraoutputbuild": [14, 31], "flow": [14, 54, 67], "scratch": [14, 31, 37, 42], "prepar": [14, 21], "worth": [14, 17, 26, 36, 59, 77], "duplic": [14, 16, 27, 32, 35, 52], "stage": [14, 42], "persist": [14, 15, 24, 42, 49, 52, 54, 64], "ensurefin": 14, "main_data": [14, 15, 31], "helper": [14, 15, 24, 37, 43, 52, 54, 59, 64, 67, 77], "empti": [14, 17, 22, 40, 69], "processimag": 14, "start_image_num": [14, 15], "isn": [14, 15, 29, 37, 54, 63, 75, 77], "processskippedstamp": 14, "processstamp": [14, 31], "ca": [14, 77], "writehdu": 14, "registerextraoutput": 14, "customextraoutput": 14, "customextraoutputbuild": 14, "custom_extra_output": 14, "blendset": [14, 16, 17, 74], "deblend": [14, 74], "deblend_m": 14, "cgc": [14, 16, 17, 74], "noise_fre": [14, 74], "extra_psf": 14, "extrapsfbuild": 14, "tiledimag": 14, "wouldn": 14, "extra_truth": 14, "truthbuild": 14, "extra_weight": 14, "weightbuild": 14, "bias": [14, 20], "extra_badpix": 14, "badpixbuild": 14, "placehold": 14, "satur": [14, 53, 64], "demo8": [15, 74], "readconfig": [15, 52], "config_fil": 15, "demo6": [15, 63, 74], "demo9": [15, 74], "merg": [15, 49, 52], "subseq": 15, "copyconfig": 15, "balk": 15, "encapsul": [15, 26, 37, 38, 42, 51, 56], "semant": [15, 21, 24, 46, 53], "importmodul": 15, "gdict": 15, "brought": 15, "custom": [15, 16, 33, 52, 55, 74], "processtempl": 15, "templat": [15, 22, 23, 24, 26, 28, 29, 52, 54, 58, 74], "processalltempl": 15, "new_param": [15, 40, 42], "ask": [15, 25, 53, 63], "report": [15, 25, 27, 40, 49, 51, 77], "buildfil": [15, 52, 74], "getnimagesforfil": 15, "getnobjforfil": 15, "setupconfigfilenum": 15, "index_kei": [15, 19, 74], "start_obj_num": [15, 19], "setupconfigimages": 15, "image_xs": [15, 19], "image_ys": [15, 19], "image_origin": [15, 19], "image_cent": [15, 19], "image_bound": [15, 19, 23, 40], "ispixelscal": [15, 77], "world_cent": [15, 19], "current_imag": 15, "setupconfigimagenum": 15, "getnobjforimag": 15, "flattennoisevari": 15, "full_imag": [15, 37], "bring": 15, "anywher": [15, 16, 36, 37], "basewc": [15, 19, 37, 52, 77], "addski": 15, "calculatenoisevari": [15, 52], "getski": 15, "presenc": [15, 29], "do_nois": 15, "themselv": [15, 42], "setupconfigstamps": 15, "stampbuild": [15, 17, 52], "locatestamp": [15, 17], "unknown": [15, 17, 42], "setupconfigobjnum": 15, "belong": [15, 20], "drawbas": 15, "prof": [15, 17, 36, 37, 77], "free": [15, 33, 34, 40, 42, 53], "add_to_imag": [15, 20, 37], "setup_onli": [15, 37], "buildgsobject": [15, 52], "updategsparam": 15, "transformobject": 15, "tranform": 15, "skipthisobject": 15, "throw": [15, 31, 40], "went": 15, "move": [15, 17, 19, 34, 36, 37, 52, 56, 60, 68, 74, 77], "activ": [15, 46, 55, 64], "parsevalu": 15, "getcurrentvalu": 15, "extend": [15, 25, 26, 29, 35, 36, 37, 40, 42, 48, 52, 54, 55, 57, 61, 64, 68, 72], "evaluatecurrentvalu": 15, "setdefaultindex": 15, "checkallparam": 15, "req": 15, "opt": [15, 37, 46, 54, 61, 72, 79], "getallparam": 15, "parseworldpo": 15, "euclidean": [15, 17, 43], "radec": [15, 19], "randomcircl": [15, 19, 52, 74], "12": [15, 26, 39, 42, 43, 46, 47, 49, 54, 59, 68, 75, 77], "inner_radiu": [15, 19, 52, 74], "processinput": [15, 74], "file_scope_onli": 15, "safe_onli": 15, "_input_obj": 15, "multilp": 15, "mark": [15, 54, 57], "processinputnobject": 15, "increment": [15, 19, 54, 63], "valid_input_typ": 15, "inconsist": [15, 49, 52], "reli": [15, 39, 42, 60], "magic": 15, "setupinput": 15, "hasn": 15, "setupinputsforimag": 15, "setupextraoutput": 15, "manag": [15, 24, 44, 54, 75, 76], "extra_build": 15, "valid_extra_output": 15, "setupextraoutputsforimag": 15, "processextraoutputsforstamp": 15, "processextraoutputsforimag": 15, "checknoextraoutputhdu": 15, "getfinalextraoutput": 15, "loggerwrapp": 15, "info": [15, 26, 49, 59, 64], "gratuit": [15, 74], "wrapper": [15, 29, 35], "getloggerproxi": 15, "pipe": 15, "readyaml": 15, "readjson": 15, "config_dict": 15, "mergeconfig": 15, "config1": 15, "config2": 15, "conflict": 15, "convertnon": 15, "removecurr": 15, "keep_saf": 15, "clear": [15, 19, 20, 24, 27, 29, 32, 35, 37, 64], "obj_num_in_fil": [15, 16, 19, 52], "updatenproc": 15, "ntot": 15, "ncpu": 15, "parserandomse": 15, "seed_offset": 15, "propagateindexkeyrngnum": 15, "rng_index_kei": [15, 52], "parseextendedkei": 15, "travers": 15, "getfromconfig": 15, "setinconfig": 15, "updateconfig": 15, "job_func": 15, "done_func": 15, "except_func": 15, "nomenclatur": 15, "gather": 15, "upon": 15, "proc": [15, 25, 48], "encount": [15, 56], "ex": 15, "tr": [15, 37], "caught": 15, "traceback": [15, 37], "getindex": 15, "is_sequ": 15, "getrng": 15, "tag": [15, 18, 52, 53], "cleanconfig": 15, "keep_curr": 15, "underscor": [15, 52, 69], "ancillari": [15, 19, 24, 54, 68], "pointer": [15, 24, 27, 29, 37], "print": [15, 21, 40, 49, 72, 75, 77], "infinit": [15, 25, 36, 47, 48, 54, 59, 72], "loop": [15, 52], "setdefaultext": 15, "ext": [15, 19, 74], "retryio": 15, "func": [15, 26, 47, 49, 72], "ntri": 15, "makeimagetask": 15, "sequenti": [15, 32, 49, 56], "registerinputconnectedtyp": 15, "coupl": [16, 42, 52], "almost": [16, 42, 49, 54, 61, 63, 65, 70], "page": [16, 34, 43, 63, 77], "config": [16, 18, 31, 33, 40, 43, 44, 46, 48, 52, 53, 74], "sy": 16, "somewher": [16, 17, 39], "highlight": [16, 49], "demo11": [16, 60, 63, 74], "content": [16, 25, 28, 29, 37, 42, 48, 56], "my_sim": 16, "sersic_sim": 16, "unchang": [16, 20, 36, 42], "did": [16, 33, 74], "colon": [16, 75], "eta1eta2": [16, 19, 74], "eta1": [16, 19, 69, 74], "randomgaussian": [16, 19, 74], "eta2": [16, 19, 69, 74], "site": [16, 29, 31, 61], "my_default_sim": 16, "my_modul": 16, "12345": 16, "objs_500": 16, "registertempl": [16, 52], "module_dir": 16, "dirnam": 16, "__file__": 16, "default_sim_fil": 16, "join": [16, 20, 63, 77], "template_nam": 16, "ship": [16, 67, 68], "friendli": 16, "awkward": 16, "lsstdesc": [16, 46, 52, 75, 77], "imsim": 16, "rgc": [16, 63, 74], "cgc_psf": [16, 74], "major": [16, 33, 46, 52, 53, 69, 75, 77], "valid_index_kei": 16, "obj_num_rng": 16, "image_num_rng": 16, "rate": [16, 42, 49, 54, 63], "unneccessari": 17, "_either_": 17, "revert": [17, 37], "shot": [17, 25, 29, 37, 54, 55, 73], "max_extra_nois": [17, 37, 52, 55, 74], "stop": [17, 61], "n_photon": [17, 37, 52, 54, 55], "poisson_flux": [17, 37, 52, 55], "vari": [17, 20, 29, 36, 37, 42, 49, 52, 54, 64, 67, 74, 77], "statist": [17, 20, 28, 37, 55], "scenario": [17, 37, 63, 64], "view": [17, 20, 24, 37, 42, 52, 54, 64, 74], "chip": [17, 64], "retry_failur": [17, 52, 74], "come": [17, 19, 20, 21, 29, 30, 37, 45, 54, 55, 59, 63, 64, 77], "twice": [17, 31, 54, 74], "skip_failur": [17, 52], "sky_valu": 17, "sky_po": [17, 19, 52], "quick_skip": [17, 52], "precalcul": 17, "obj_rng": [17, 52], "fresh": [17, 27], "min_flux_frac": 17, "reject": [17, 29, 40, 74], "presum": [17, 20, 46, 67], "min_snr": 17, "max_snr": 17, "full_rot": [17, 74], "rotation": [17, 20], "360": [17, 54], "applysnrscal": 17, "scale_factor": 17, "getsnrscal": 17, "buildpsf": 17, "buildprofil": 17, "getdrawmethod": 17, "param": [17, 19, 29], "getoffset": [17, 29], "stamp_offset": 17, "getskip": 17, "followin": 17, "index_po": 17, "stamp_imag": 17, "stamp_cent": 17, "makestamp": 17, "quickskip": 17, "preset": 17, "reset": [17, 19, 27, 32, 42, 54, 64, 66], "confus": [17, 29, 39, 51, 54, 77], "shouldn": [17, 49], "setuprng": 17, "updateorigin": 17, "updateskip": 17, "registerstamptyp": 17, "customstamp": 17, "customstampbuild": 17, "my_custom_stamp": 17, "stamp_r": 17, "ringbuild": 17, "wavelengthsampl": [17, 37, 52, 55, 56, 57], "distdevi": [17, 32, 52, 57, 62, 66, 74], "fratioangl": [17, 37, 52, 55, 56, 57], "dxdz": [17, 28, 56, 57], "dydz": [17, 28, 56, 57], "fratio": [17, 57], "photondcr": [17, 52, 55, 56, 57], "alon": [17, 52], "celestialwc": [17, 19, 77], "focusdepth": [17, 52, 55, 57], "focu": [17, 54, 61], "short": [17, 35, 54, 64], "index_ratio": [17, 57], "materi": [17, 42, 64], "pupilimagesampl": [17, 52, 55, 57], "pupil_plane_scal": [17, 54, 57, 61, 64], "pupil_plane_s": [17, 54, 57, 61], "pupilannulussampl": [17, 52, 55, 57], "annular": [17, 52, 54, 57, 60, 61, 79], "entranc": [17, 54, 56, 57], "r_outer": [17, 57, 79], "annulu": [17, 19, 57, 59, 61, 79], "r_inner": [17, 57, 79], "inner": [17, 19, 26, 57, 79], "timesampl": [17, 52, 55, 57], "uniformli": [17, 19, 32, 42, 49, 54, 57, 72, 77], "photonopbuild": 17, "buildphotonop": 17, "registerphotonoptyp": 17, "customphotonop": 17, "customphotonopbuild": 17, "photon_op_typ": 17, "my_custom_photon_op": 17, "_neither_": 18, "intent": [18, 28, 49], "uncommon": 18, "_something_": 18, "action": [18, 20, 32, 44, 54, 77], "question": [18, 19, 49, 53, 71, 72], "obvious": [18, 19, 64], "gal_set": 18, "seven": 19, "ital": 19, "input_cat": 19, "clip": [19, 32, 54, 68], "randompoisson": [19, 52], "randombinomi": [19, 52], "binomi": [19, 27, 32, 49], "coin": [19, 27, 32], "flip": [19, 27, 32, 42, 52, 59, 77], "randomweibul": [19, 52], "weibul": [19, 27, 32], "wikipedia": [19, 27, 32, 36, 42, 54, 61, 77, 79], "articl": [19, 27, 32], "randomgamma": [19, 52], "gamma": [19, 27, 32, 52, 55, 59], "randomchi2": [19, 52], "chi": [19, 27, 32], "randomdistribut": [19, 52, 74], "x_min": [19, 26, 32, 72, 74], "x_max": [19, 26, 32, 72, 74], "256": [19, 20, 32, 49, 59], "cumul": [19, 32, 54], "cdf": [19, 32], "max_mu": 19, "greater": [19, 20, 29, 30, 60], "strong": 19, "break": [19, 21, 35, 37], "anywai": 19, "crazi": 19, "cosmosvalu": [19, 52], "samplevalu": [19, 52], "nitem": [19, 74], "reproduc": [19, 25, 59], "nest": [19, 49], "silent": [19, 31, 35, 40, 42], "drop": [19, 29, 34, 36, 47, 52, 61, 67, 70, 72], "becom": [19, 20, 54, 61, 64, 77], "insensit": [19, 66], "ye": [19, 77], "v1": [19, 39, 49], "numberedfil": [19, 74], "rootnnnnext": 19, "file0001": 19, "file0002": 19, "digit": [19, 37, 42, 51, 74, 75], "formattedstr": [19, 52, 74], "akin": [19, 77], "printf": 19, "image_": 19, "f_": [19, 61], "insert": [19, 25, 42, 56], "letter": [19, 25], "arcminut": 19, "arcsecond": [19, 75], "abbrevi": 19, "45": [19, 23, 42, 58, 69, 77], "e1e2": [19, 74], "cartesian": [19, 49, 79], "ebeta": [19, 74], "g1g2": [19, 74], "gbeta": 19, "conform": [19, 40, 42, 67, 69], "etabeta": 19, "eta": [19, 40, 42, 69], "qbeta": [19, 74], "g_a": 19, "g_b": 19, "closest": [19, 54, 57, 61], "farthest": [19, 54, 61], "comma": 19, "rtheta": [19, 74], "circl": [19, 61, 69, 74, 77, 79], "preferenti": 19, "unari": [19, 22], "spline": [19, 25, 32, 52, 59, 72], "x_log": [19, 72], "abscissa": [19, 72], "f_log": [19, 72], "ordin": [19, 72], "206265": [19, 37, 61], "041253": 19, "demo3": [19, 74], "yourself": [19, 21, 42, 44, 55], "clearer": [19, 51], "5m": [19, 54], "oppos": [19, 54], "demo10": [19, 63, 74], "euclideanwc": [19, 77], "uv_po": 19, "leftmost": 19, "previou": [19, 23, 37, 40, 42, 53, 64, 67, 72, 74, 77], "gaussiandevi": [19, 27, 32, 49, 62, 74], "math": [19, 21, 36, 43, 52, 61, 74, 75, 76, 77], "express": [19, 42, 52, 57, 59, 64, 79], "declar": [19, 24, 75, 77], "fnormal": 19, "fr": [19, 68], "liter": 19, "eval_vari": [19, 74], "fpixel_scal": 19, "istamp_s": 19, "infil": 19, "append": [19, 35, 42, 48, 49, 54, 72], "eval_base_vari": 19, "namespac": [19, 23, 52, 75, 77], "coadd_wc": 19, "avil": 19, "folllow": 19, "cumbersom": 19, "streamlin": 19, "situat": [19, 40, 54, 61, 77], "aid": 19, "readabl": [19, 35, 42, 52, 74, 77, 79], "generatecustomvalu": 19, "registervaluetyp": 19, "customvalu": 19, "gen_func": 19, "valid_typ": 19, "upstream": [19, 35], "my_custom_valu": 19, "log_norm": 19, "hsm_shape_measur": [19, 74], "excluded_random": 19, "great3_reject": 19, "patch": [20, 53, 59], "uncorrelatednois": [20, 52, 62], "induc": [20, 74], "getcosmosnois": [20, 52, 62, 68], "drizzl": 20, "uncorrect": 20, "weak": [20, 43, 53, 55, 63, 69], "whitennois": [20, 42, 52, 74], "symmetrizenois": [20, 42, 52, 74], "goal": [20, 64], "bia": [20, 23, 64, 67], "guarante": [20, 21, 32, 42, 46, 63, 77], "propos": 20, "confirm": [20, 35, 53], "covari": [20, 27, 71], "phyical": 20, "correlated_nois": [20, 74], "nx": [20, 22, 25, 26, 28, 35, 37, 42, 52, 54, 72], "ny": [20, 22, 25, 28, 35, 37, 42, 52, 54, 72], "complicated_wc": 20, "period": [20, 25, 29, 37, 38, 42, 54, 59, 67, 72], "convolvedwith": 20, "hubbl": [20, 63, 74], "slope": [20, 28, 29, 57], "cn": [20, 74], "ground": [20, 29, 74], "demonstr": [20, 43, 60, 74], "parent": [20, 24, 51, 52], "cn_copi": 20, "new_rng": [20, 51], "mild": 20, "reimplement": 20, "uniti": [20, 29, 42, 54, 61], "accompani": [20, 37, 63], "dk": [20, 29, 37, 42, 59], "expans": [20, 25, 48, 49, 50, 52, 54, 59, 77], "lag": [20, 54], "cross": [20, 26, 46, 52, 60, 72], "bilinear": 20, "empir": [20, 42, 55, 64], "inaccur": [20, 36], "regim": [20, 61], "whitenimag": [20, 42], "shown": [20, 42, 74], "treatment": [20, 52, 67, 72], "survei": [20, 31, 36, 52, 63, 64], "gave": 20, "getvari": [20, 42, 51, 52], "corr": 20, "symmetrizeimag": [20, 42], "anisotropi": 20, "interest": [20, 24, 31, 37, 40, 42, 53, 61, 63, 64, 74, 75], "theoret": [20, 36, 42, 59], "suggest": [20, 40, 53, 63], "residu": [20, 23, 40, 64], "devel": [20, 59, 60, 64, 68, 71], "extern": [20, 63, 64, 77], "compare_whitening_subtract": 20, "didn": [20, 32, 33, 60], "Of": [20, 37, 64], "cost": [20, 26, 75], "withscaledvari": [20, 51], "variance_ratio": [20, 51], "withvari": [20, 51], "proportion": 20, "correct_period": 20, "subtract_mean": 20, "assumpt": [20, 37, 40, 42, 59, 63], "introduc": [20, 25, 44, 48, 54, 64, 74], "dilut": 20, "inter": 20, "underestim": 20, "popul": [20, 40, 63], "imperfectli": 20, "familiar": [20, 32], "legal": 20, "centr": [20, 29], "var": [20, 42, 59, 63], "cn1": 20, "cn2": 20, "arithmet": [20, 52, 58, 75], "divis": [20, 22], "cn3": 20, "cn4": 20, "cn5": 20, "operand": [20, 66, 79], "world_pix": 20, "toworld": [20, 52, 74, 77], "unrot": 20, "astro": 20, "caltech": [20, 64], "edu": [20, 53, 63, 64, 68, 73, 77], "html": [20, 46, 61, 64, 68, 77], "stack": 20, "team": [20, 74], "make_cosmos_cfimag": 20, "meta_data": [20, 43, 49, 52, 63, 67, 68], "share_dir": [20, 43, 49, 52, 63, 67, 68], "expressli": 20, "300": 20, "substitut": 20, "filestr": 20, "123456": 20, "offici": [21, 44, 46, 52, 64], "strictli": [21, 40, 72], "api": [21, 35, 37, 42, 46, 49, 51, 52, 53, 65], "minor": [21, 29, 36, 46, 52, 53, 69, 77], "huge": [21, 61], "upgrad": [21, 33, 46], "bugfix": [21, 53], "abi": 21, "releas": [21, 24, 42, 44, 46, 53, 63], "recompil": 21, "haven": [21, 77], "doxygen": 21, "breath": 21, "shoehorn": 21, "sphinx": [21, 52], "bare": 21, "bone": 21, "sorri": 21, "pr": [21, 40], "appreci": [21, 44], "compil": [21, 24, 27, 44, 46, 49, 52], "public": [21, 22, 23, 24, 25, 26, 27, 28, 29, 37, 46, 53, 60, 63, 65], "include_dir": 21, "hsm": [21, 33, 37, 42, 43, 52, 74], "librari": [21, 32, 52], "lib": [21, 46], "anaconda": [21, 44, 45, 46, 77], "environ": [21, 46, 68], "env": 21, "myenv": 21, "instruct": [21, 42, 43, 53, 63], "unvers": 21, "osx": [21, 44, 46], "libgalsim": [21, 46], "dylib": [21, 46], "linux": [21, 44, 46, 52], "lgalsim": 21, "remain": [21, 27, 29, 36, 42, 49], "macro": 21, "galsim_major": 21, "galsim_minor": 21, "galsim_revis": 21, "major_vers": 21, "minor_vers": 21, "revis": [21, 43, 53], "std": [21, 22, 23, 24, 25, 26, 27, 29], "fucntion": 21, "inlin": [21, 22, 23, 24, 25, 26, 27, 28, 29], "check_vers": 21, "const": [22, 23, 24, 25, 26, 27, 28, 29], "xin": 22, "yin": 22, "rh": [22, 24, 25, 27, 28, 29, 42, 49, 56, 79], "typenam": [22, 23, 24, 26, 28, 29], "t2": 22, "selfpromot": 22, "overload": [22, 27, 77], "algebra": [22, 46], "negat": [22, 29, 79], "promot": 22, "void": [22, 23, 24, 25, 26, 27, 28, 29], "ostream": [22, 26], "fout": 22, "stream": [22, 28], "istream": 22, "fin": [22, 35], "member": [22, 23, 26, 29, 53], "publicli": [22, 64], "visibl": [22, 59], "friend": [22, 29], "lh": [22, 49], "x1": [22, 74], "x2": [22, 74, 75], "y1": 22, "y2": 22, "destructor": [22, 24, 25, 27, 29], "setxmin": 22, "setxmax": 22, "setymin": 22, "setymax": 22, "queri": [22, 54, 61, 64, 77], "doubl": [22, 23, 24, 25, 26, 27, 28, 29, 35, 37, 64, 72, 74, 79], "truecent": [22, 52], "rec": 22, "addbord": [22, 52], "makeexpand": 22, "makeshift": 22, "issameshapea": 22, "estimateshearview": 23, "shapedata": [23, 40, 42, 52], "baseimag": [23, 24, 28, 29], "gal_imag": [23, 40], "psf_imag": [23, 40], "gal_mask_imag": 23, "sky_var": [23, 40], "char": [23, 27], "shear_est": [23, 40], "regauss": [23, 40], "recompute_flux": [23, 40], "guess_sig_g": [23, 40], "guess_sig_psf": [23, 40], "0e": 23, "guess_centroid": [23, 40, 42], "hsmparam": [23, 40, 42, 52], "repackag": 23, "general_shear_estim": 23, "uncertainti": [23, 40], "bj": [23, 40], "ksb": [23, 40, 52], "unweight": [23, 37, 40, 42, 49], "unmask": [23, 40], "quartic": [23, 40], "guess": [23, 26, 40, 42, 54, 61], "criterion": [23, 40, 42, 61], "moment": [23, 37, 40, 42, 49, 52, 66], "findadaptivemomview": 23, "object_imag": [23, 40, 42], "object_mask_imag": 23, "guess_sig": [23, 40, 42], "round_moment": [23, 40, 42], "adapt": [23, 29, 40, 42, 47, 54, 77], "find_ellipmom_2": 23, "struct": [23, 26, 29], "_nsig_rg": 23, "_nsig_rg2": 23, "_max_moment_nsig2": 23, "_regauss_too_smal": 23, "_adapt_ord": 23, "_convergence_threshold": 23, "long": [23, 27, 49, 52, 54, 61, 64, 77], "_max_mom2_it": 23, "_num_iter_default": 23, "_bound_correct_wt": 23, "_max_amo": 23, "_max_ashift": 23, "_ksb_moments_max": 23, "_ksb_sig_weight": 23, "_ksb_sig_factor": 23, "_failed_mo": 23, "nsig_rg": [23, 40], "nsig_rg2": [23, 40], "max_moment_nsig2": [23, 40], "regauss_too_smal": [23, 40], "adapt_ord": [23, 40], "convergence_threshold": [23, 40, 52], "max_mom2_it": [23, 40], "num_iter_default": [23, 40], "bound_correct_wt": [23, 40], "max_amo": [23, 40, 42], "max_ashift": [23, 40], "ksb_moments_max": [23, 40], "ksb_sig_weight": [23, 40], "ksb_sig_factor": [23, 40], "failed_mo": [23, 40], "hsmerror": 23, "runtime_error": [23, 24], "thrown": [23, 24, 29, 36, 72], "unsign": [23, 32, 35, 52], "constimageview": [23, 24, 29, 52], "objectdata": 23, "gal_data": 23, "psf_data": 23, "flag": [23, 40, 46, 49], "0x1": 23, "0x2": 23, "0x4": 23, "0x8": 23, "0xe": 23, "statu": [23, 40], "x0": [23, 40, 42, 72, 77], "y0": [23, 40, 42, 72, 77], "mxx": [23, 49], "mxy": [23, 49], "myi": [23, 49], "rho4": 23, "num_it": 23, "diagnost": [23, 26], "intens": [23, 40, 46], "2a": 23, "xx": 23, "yy": 23, "fourth": [23, 40, 77], "meaning": 23, "moments_statu": [23, 40], "observed_e1": [23, 40], "observed_e2": [23, 40], "moments_sigma": [23, 40, 42], "det": [23, 29, 37, 40, 42, 73], "moments_amp": [23, 40], "moments_centroid": [23, 40], "moments_rho4": [23, 40], "moments_n_it": [23, 40], "correction_statu": [23, 40], "corrected_e1": [23, 40], "corrected_e2": [23, 40], "corrected_g1": [23, 40], "corrected_g2": [23, 40], "meas_typ": [23, 40], "corrected_shape_err": [23, 40], "sigma_gamma": [23, 40], "sigma_": 23, "correction_method": [23, 40], "resolution_factor": [23, 40], "r_2": [23, 40], "perfect": [23, 25, 29, 40, 48, 61], "psf_sigma": [23, 40], "psf_e1": 23, "psf_e2": 23, "error_messag": [23, 40], "align": [23, 64], "2nd": [23, 49], "m_xx": 23, "m_xy": 23, "m_yi": 23, "unresolv": 23, "resolv": [23, 27, 49, 63], "assignabletoimag": 24, "virtual": [24, 25, 27, 29], "assignto": 24, "imageview": [24, 27, 28, 29, 52], "getbound": 24, "imagealloc": 24, "cast": 24, "shared_ptr": [24, 27, 52], "delet": [24, 27, 42, 52, 63], "getown": 24, "lifetim": 24, "subimag": [24, 37, 42, 52], "getdata": 24, "getmaxptr": 24, "ok_ptr": 24, "ptr": 24, "stride": [24, 42, 52], "ptrdiff_t": 24, "getnel": 24, "alloc": [24, 28, 29, 42, 49, 52, 54, 56], "getstrid": 24, "getstep": 24, "getnrow": 24, "getnskip": 24, "iscontigu": [24, 37, 42], "shorthand": [24, 32, 52, 74, 77], "nonzerobound": 24, "logic": [24, 52], "xmin_new": 24, "xmax_new": 24, "ymin_new": 24, "ymax_new": 24, "accessor": [24, 28, 42], "xpo": 24, "ypo": 24, "uncheck": 24, "getptr": 24, "copyfrom": [24, 42, 52, 56], "sumel": 24, "trait": 24, "real_typ": 24, "maxabsel": 24, "sibl": 24, "uint16_t": 24, "uint32_t": 24, "int16_t": 24, "int32_t": 24, "cpp": 24, "null": 24, "nrow": [24, 42], "init_valu": [24, 42], "commensur": 24, "setzero": [24, 42], "invertself": [24, 42, 74], "quietli": 24, "new_bound": 24, "uniniti": [24, 42], "tie": 24, "shrink": 24, "setvalu": [24, 42], "mutabl": [24, 49, 56], "shallow": [24, 29, 42], "cheapli": 24, "tricki": 24, "stuff": [24, 49], "pysrc": 24, "interact": [24, 42, 59], "ownership": 24, "maxptr": [24, 52], "nelement": 24, "owner": 24, "depixelizeself": 24, "unit_integr": [24, 48], "worri": [24, 37, 64], "imageerror": 24, "imageboundserror": 24, "goodfftsiz": 24, "3x2": 24, "rfft": 24, "shift_in": [24, 34], "shift_out": [24, 34], "irfft": 24, "cfft": 24, "wrapimag": 24, "hermx": [24, 42], "hermi": [24, 42], "invertimag": 24, "cleardepixelizecach": 24, "solver": [24, 42], "domain": [25, 29, 37, 48], "cycl": [25, 46, 52, 64], "henc": [25, 42, 49, 54], "sincinterpol": [25, 48, 52, 76], "control": [25, 29, 32, 37, 43, 46, 52, 54, 55, 64, 72], "sampler": 25, "rebuild": [25, 59, 64], "xrang": [25, 48, 52], "ixrang": [25, 48, 52], "urang": 25, "xval": [25, 48], "xvalwrap": 25, "inf": [25, 42, 48, 54, 66], "xvalmani": 25, "uval": 25, "uvalmani": 25, "isexactatnod": 25, "getpositiveflux": [25, 29, 52], "getnegativeflux": [25, 29, 52], "getpositiveflux2d": 25, "getnegativeflux2d": 25, "uniformdevi": [25, 27, 32, 49, 62, 74], "ud": [25, 28, 32, 74], "displac": [25, 48, 57], "1d": [25, 72], "shuffl": [25, 28], "fluctuat": [25, 29, 54, 61], "makestr": 25, "crude": 25, "poorli": [25, 48, 59], "gruen": [25, 29, 48], "wiggl": 25, "poor": [25, 48], "3rd": [25, 48], "taylor": [25, 48], "ieee": [25, 48], "tran": [25, 48], "acoust": [25, 48], "speech": [25, 48], "29": [25, 48], "1153": [25, 48, 52], "1981": [25, 48, 61, 79], "piecewis": [25, 72], "mathemat": [25, 37, 48, 79], "spuriou": [25, 48], "kmax": [25, 48, 54, 59, 79], "formal": [25, 48, 54, 61], "trunction": [25, 48], "kvalu": [25, 29, 37, 48, 54, 61], "cutoff": [25, 48, 59], "conserve_dc": [25, 48, 52], "tweak": [25, 48, 54, 60], "dc": [25, 48], "getn": [25, 27, 29], "conservesdc": 25, "fluxdens": 25, "tablebuild": 25, "enum": 25, "floor": [25, 32, 64, 72], "ceil": [25, 72], "gsinterp": 25, "val": [25, 32, 72], "argmin": 25, "argmax": 25, "size_t": [25, 26, 28], "interp": 25, "interpmani": 25, "argvec": 25, "valvec": 25, "integrateproduct": 25, "xfact": 25, "addentri": 25, "table2d": 25, "xarg": 25, "yarg": 25, "dfdx": [25, 72], "dfdy": [25, 72], "d2fdxdy": [25, 72], "xvec": 25, "yvec": 25, "interpgrid": 25, "df": [25, 72], "gradientmani": 25, "dfdxvec": 25, "dfdyvec": 25, "gradientgrid": 25, "bracket": 26, "myfunc": 26, "r_max": [26, 67], "bracketupp": 26, "bracketlow": 26, "past": [26, 28, 54, 56], "bracketupperwithlimit": 26, "upper_limit": 26, "bracketlowerwithlimit": 26, "lower_limit": 26, "revers": [26, 49, 75, 77], "bisect": 26, "brent": 26, "setmethod": 26, "func_": 26, "lb_": 26, "ub_": 26, "setmaxstep": 26, "m_": 26, "setxtoler": 26, "tol": [26, 48, 52, 64], "getxtoler": 26, "setbound": 26, "lb": 26, "ub": 26, "search": [26, 43], "getlowerbound": 26, "getupperbound": 26, "evaluatebound": 26, "flower": 26, "fupper": 26, "hunt": 26, "monoton": [26, 32, 72], "bracket1": 26, "fa": 26, "fb": 26, "8212": [26, 27], "bracket1withlimit": 26, "finder": 26, "zbrent": 26, "sophist": [26, 53, 55, 67, 74], "cyl_bessel_j": 26, "cyl_bessel_i": 26, "cyl_bessel_k": 26, "getbesselroot0": 26, "getbesselroot": 26, "sinco": [26, 75], "sint": [26, 75], "gamma_p": 26, "coef": [26, 49, 79], "nc": 26, "horner2d": [26, 49, 52], "ncx": 26, "ncy": 26, "temp": [26, 49], "intregion": 26, "dbgout_": 26, "fxmap_": 26, "r2": 26, "subdivid": [26, 42], "children": 26, "biset": 26, "addsplit": 26, "discontinu": 26, "pole": [26, 77], "findzerocross": 26, "fxmap": 26, "getnsplit": 26, "geterr": 26, "getarea": 26, "setarea": 26, "usefxmap": 26, "dbgout": 26, "uf": 26, "int1d": [26, 47, 52, 76], "relerr": 26, "defrelerr": 26, "abserr": 26, "defabserr": 26, "reg": 26, "bf": 26, "int2d": 26, "borh": 26, "yreg": 26, "tf": 26, "int3d": 26, "zmin": 26, "zmax": 26, "zreg": 26, "middl": [26, 49, 61, 75], "hankel_trunc": 26, "maxr": 26, "nzero": 26, "hankel_inf": 26, "isnan": 26, "setompthread": 26, "num_thread": [26, 49], "getompthread": 26, "microsecond": 27, "arguemnt": 27, "binomialdevi": [27, 32, 62], "chi2devi": [27, 32, 62], "gammadevi": [27, 32, 62], "poissondevi": [27, 32, 52, 62, 74], "weibulldevi": [27, 32, 62], "lseed": 27, "dai": [27, 50], "rapid": 27, "str_c": 27, "serial": [27, 44, 52], "duplicate_ptr": 27, "couldn": [27, 77], "repr": [27, 49, 52], "prng": [27, 32], "rese": 27, "unaffect": [27, 40, 42], "obtain": [27, 29, 32, 49, 54, 59, 64, 68, 75], "discard": [27, 28, 32, 52, 56], "held": 27, "clearcach": [27, 32], "sync": [27, 32, 59], "reseed": 27, "ll": [27, 36, 77], "boost": [27, 32, 52], "raw": [27, 32, 52, 68, 72], "generate1": 27, "addgener": 27, "pseudo": [27, 32, 62], "getmean": 27, "getsigma": [27, 29], "setmean": 27, "setsigma": 27, "_normal": 27, "generatefromvari": 27, "trial": [27, 32], "getp": 27, "setn": 27, "setp": 27, "generatefromexpect": 27, "rayleigh": [27, 32], "en": [27, 32, 36, 42, 54, 61, 64, 77, 79], "wiki": [27, 32, 36, 42, 49, 53, 54, 61, 63, 64, 77, 79], "weibull_distribut": [27, 32], "geta": 27, "getb": [27, 31], "seta": 27, "behaviour": 27, "setb": 27, "gamma_distribut": [27, 32], "getk": 27, "gettheta": 27, "setk": 27, "settheta": 27, "squared_distribut": [27, 32], "calculatecovariancematrix": [27, 52], "cov": 27, "sbprofil": [27, 52], "sbp": [27, 29], "triangular": 27, "arriv": [28, 57], "getxarrai": 28, "getyarrai": 28, "getfluxarrai": 28, "getdxdzarrai": 28, "getdydzarrai": 28, "getwavelengtharrai": 28, "hasallocatedangl": [28, 56], "hasallocatedwavelength": [28, 56], "is_corr": [28, 56], "setphoton": 28, "characterist": [28, 29, 70], "getx": 28, "geti": 28, "getflux": [28, 29, 37, 52], "getdxdz": 28, "getdydz": 28, "getwavelength": 28, "gettotalflux": [28, 56], "settotalflux": [28, 56], "scaleflux": [28, 29, 52, 56], "scalexi": [28, 37, 56], "assignat": [28, 52, 56], "istart": [28, 56], "renorm": [28, 59, 63], "convolveshuffl": 28, "destroi": 28, "addto": [28, 52, 56], "bin": [28, 42, 52, 53, 56, 59, 60, 63, 64], "insid": [28, 37, 42, 56], "setfrom": 28, "maxflux": [28, 56], "todo": 28, "iscorrel": [28, 52, 56], "setcorrel": [28, 52, 56], "numvertic": 28, "numelec": 28, "diffstep": 28, "pixels": 28, "sensorthick": 28, "vertex_data": 28, "tr_radial_t": 28, "treeringcent": 28, "abs_length_t": 28, "insidepixel": 28, "ix": 28, "ii": [28, 29, 42, 53], "zconv": 28, "targetbound": 28, "off_edg": 28, "emptypolys": 28, "pixelinnerboundsdata": 28, "pixelouterboundsdata": 28, "horizontalboundarypointsdata": 28, "verticalboundarypointsdata": 28, "emptypolydata": 28, "scaleboundstopoli": 28, "polygon": 28, "emptypoli": 28, "calculateconversiondepth": 28, "photonshasallocatedwavelength": 28, "photonswavelength": 28, "abs_length_table_data": 28, "photonshasallocatedangl": 28, "photonsdxdz": 28, "photonsdydz": 28, "randomnumb": 28, "updatepixeldistort": 28, "calculatetreeringdistort": 28, "orig_cent": [28, 37, 67], "poli": 28, "i1": 28, "addtreeringdistort": 28, "subtractdelta": 28, "adddelta": 28, "i2": 28, "pixelarea": [28, 77], "fillwithpixelarea": 28, "use_flux": [28, 67], "applycd": 28, "cd": [28, 45, 46, 64, 77], "atom": 29, "sbdeltafunct": 29, "sbgaussian": 29, "sbsersic": 29, "sbairi": 29, "sbexponenti": 29, "sbbox": 29, "sbdevaucouleur": 29, "sbmoffat": 29, "sbtransform": 29, "sbadd": 29, "sbconvolv": 29, "sbdeconvolv": 29, "setflux": [29, 52], "sbautoconvolv": 29, "sbautocorrel": 29, "sbfouriersqrt": 29, "sbinclinedexponenti": 29, "sbinclinedsers": 29, "sbinterpolatedkimag": 29, "sbkolmogorov": 29, "sbsecondkick": 29, "sbshapelet": 29, "sbspergel": 29, "sbtophat": 29, "sbvonkarman": 29, "prof_list": 29, "push_back": 29, "strang": 29, "ever": [29, 54], "legitim": 29, "cleanup": [29, 35], "getgsparam": 29, "sberror": 29, "getxrang": 29, "getyrang": 29, "getyrangex": 29, "neglect": [29, 37, 54, 64], "nyquistdx": [29, 52], "getgoodimages": [29, 37], "formula": [29, 30, 77], "isaxisymmetr": [29, 52], "simplifi": [29, 48, 49, 52], "hashardedg": [29, 52], "isanalyticx": [29, 52], "immedi": [29, 37], "isanalytick": [29, 52], "maxsb": [29, 52], "fluxratio": 29, "rai": [29, 53], "gaurante": 29, "net": [29, 31, 47, 67, 69, 75], "cancel": [29, 42, 74], "Their": 29, "substanti": 29, "onedimensionaldevi": 29, "accru": [29, 37], "xoff": 29, "yoff": 29, "inout": 29, "imageviewf": 29, "imageviewd": 29, "imageviewi": 29, "drawk": [29, 52], "analag": [29, 69], "imageviewc": 29, "spec": [29, 66], "getwidth": 29, "getheight": 29, "plateau": 29, "getradiu": 29, "propto": 29, "r_scale": 29, "rd": 29, "getbeta": 29, "getfwhm": 29, "getscaleradiu": 29, "gethalflightradiu": 29, "gettrunc": 29, "ee": [29, 60], "schroeder": 29, "18": [29, 40, 64, 68], "relax": 29, "overli": [29, 32, 64], "lam_over_d": 29, "focal": [29, 52, 54, 57, 64, 67], "getlamoverd": 29, "getobscur": 29, "976": [29, 61], "racin": [29, 61], "1996": [29, 61], "pasp": [29, 61], "699": [29, 61, 74], "108": [29, 61], "20": [29, 32, 46, 54, 56, 64, 74], "excel": [29, 61], "quot": [29, 51, 61], "getlamoverr0": 29, "l0": [29, 54, 61], "dodelta": 29, "encircl": 29, "hlr": [29, 36], "getlam": 29, "getter": 29, "getr0": 29, "getl0": 29, "getscal": 29, "getdodelta": 29, "getdelta": 29, "structurefunct": 29, "vkxintegrand": 29, "kcrit": [29, 54], "gsparamsptr": 29, "critic": [29, 35, 50, 54, 72], "getkcrit": 29, "kvalueraw": 29, "xvalueraw": 29, "xvalueexact": 29, "vice": [29, 33, 42, 77], "versa": [29, 33, 42, 77], "specfi": 29, "normalizaton": 29, "simplif": 29, "perpendicular": 29, "getinclin": 29, "getscaleheight": 29, "h0": [29, 50], "addition": [29, 54, 56, 79], "sight": 29, "r_c": 29, "c_": [29, 59], "confusingli": 29, "spheric": [29, 50, 54, 59, 61, 64, 77], "FOR": [29, 36], "photometr": [29, 36, 63], "AND": [29, 36], "analysi": [29, 36, 68], "astrophi": [29, 36], "suppl": [29, 36], "191": [29, 36], "58": [29, 36, 54, 77], "65": [29, 36, 59], "doi": [29, 36, 60], "1088": [29, 36, 52], "0067": [29, 36], "0049": [29, 36], "logarithm": [29, 42, 59, 60, 72], "getnu": 29, "calculateintegratedflux": [29, 36], "calculatefluxradiu": [29, 36], "footprint": 29, "accordingli": [29, 66], "xinterp": 29, "kinterp": 29, "investig": 29, "4x": 29, "init_bound": 29, "nonzero_bound": 29, "unpad": 29, "getxinterp": 29, "getkinterp": 29, "getpadfactor": 29, "calculatestepk": [29, 52], "max_stepk": 29, "refin": 29, "knowledg": [29, 64], "calculatemaxk": 29, "max_maxk": 29, "getpaddedimag": 29, "getnonzeroimag": 29, "getimag": 29, "imagec": 29, "getkdata": 29, "shapelet": [29, 31, 52, 65], "lvector": 29, "gauss": [29, 37, 47], "getbvec": 29, "slist": 29, "getobj": 29, "beforehand": 29, "quadratur": [29, 47, 72], "rmax": [29, 47], "isrealspac": 29, "conrrel": 29, "_cen": 29, "row1": 29, "row2": 29, "positon": 29, "cen": 29, "sbin": 29, "ampscal": 29, "getjac": 29, "ma": 29, "mb": 29, "mc": 29, "md": 29, "getfluxsc": 29, "invert": 29, "subject": [29, 64], "deconvolvut": 29, "adapte": 29, "_minimum_fft_s": 29, "_maximum_fft_s": 29, "_folding_threshold": 29, "_stepk_minimum_hlr": 29, "_maxk_threshold": 29, "_kvalue_accuraci": 29, "_xvalue_accuraci": 29, "_table_spac": 29, "_realspace_relerr": 29, "_realspace_abserr": 29, "_integration_relerr": 29, "_integration_abserr": 29, "_shoot_accuraci": 29, "decis": [29, 38, 40], "minimum_fft_s": [29, 38], "willing": [29, 55], "maximum_fft_s": [29, 33, 37, 38, 52], "stepk_minimum_hlr": [29, 38], "constraint": [29, 38, 59, 64], "kvalue_accuraci": [29, 38], "table_spac": [29, 38], "hankel": [29, 36, 38, 47, 52, 76], "realspace_relerr": [29, 38], "realspace_abserr": [29, 38], "integration_relerr": [29, 38, 52], "integration_abserr": [29, 38, 52], "shoot_accuraci": [29, 38], "air_refractive_index_minus_on": [30, 76], "filippenko": 30, "1982": 30, "edlen": 30, "1953": 30, "coleman": [30, 68], "bozman": 30, "megger": 30, "1960": 30, "mmhg": 30, "plug": 30, "minu": [30, 50], "get_refract": [30, 76], "enter": [30, 61], "transit": 30, "appar": [30, 40, 77], "air_refractive_index": 30, "zenith_parallactic_angl": [30, 76], "parse_dcr_angl": [30, 76], "kw": 30, "aren": 30, "psfex": 31, "image_file_nam": 31, "princip": [31, 48, 73, 74], "_psfcat": 31, "softwar": [31, 53, 63, 64, 77], "emmanuel": 31, "bertin": 31, "web": [31, 64], "www": [31, 46, 47, 64, 68, 77], "astromat": 31, "fitpsf_file_nam": 31, "image_x": 31, "image_i": 31, "getpsf": [31, 52, 63, 64, 68, 74], "covers": 31, "getlocalwc": 31, "pyfit": [31, 35, 52, 77], "defualt": 31, "getpsfarrai": 31, "_fitpsf": 31, "anymor": 31, "histor": 31, "indexerror": [31, 33], "read_fit": 31, "multiexposureobject": 31, "writem": 31, "seg": [31, 52], "cutout_row": 31, "cutout_col": 31, "segment": [31, 52], "n_cutout": 31, "box_siz": 31, "offsetbuild": 31, "meds_get_offset": 31, "clobber": [31, 35, 42], "overwrit": [31, 35, 42, 63], "le": 32, "ge": 32, "mersenn": 32, "twister": 32, "48": [32, 42, 74], "platform": [32, 52], "suppos": 32, "paranoid": 32, "greatli": 32, "eas": 32, "program": [32, 35, 38, 52, 54, 63, 68], "variosu": 32, "behind": [32, 37, 46], "215324": 32, "3559052779": 32, "58736140513792634": 32, "ud2": 32, "_seed": 32, "_reset": 32, "add_gener": 32, "as_numpy_gener": [32, 52], "stabil": 32, "1234": [32, 52], "gen": 32, "norm": 32, "quickli": [32, 54, 59, 74, 75], "stai": 32, "detect": [32, 42, 64], "reliabl": [32, 52], "31415926": 32, "17100770119577646": 32, "u2": 32, "49095047544687986": 32, "10306670609861612": 32, "13129289541393518": 32, "generates_in_pair": 32, "slight": [32, 52, 74], "wrinkl": 32, "has_reliable_discard": 32, "stori": 32, "gaussian_distribut": 32, "5533754000847082": 32, "0218588970190354": 32, "generate_from_vari": 32, "poisson_distribut": 32, "94": [32, 63], "106": 32, "generate_from_expect": 32, "binomial_distribut": 32, "chi2": 32, "9182211987712385": 32, "644121724269535": 32, "gam": 32, "37508882726316": 32, "3504199388358704": 32, "1038481241018219": 32, "957052966368049": 32, "clip_neg": [32, 52], "endpoint": [32, 47], "understood": [32, 64], "1062533": 32, "4151921102709466": 32, "00909781188974034": 32, "alongsid": 32, "cumulatt": 32, "int_": [32, 72, 79], "infti": [32, 47, 61], "pdf": [32, 47, 52, 59, 60, 64, 71], "dt": [32, 54], "probabilti": 32, "galsimerror": [33, 37, 43, 52], "catch": [33, 37, 40], "runtimeerror": 33, "galsimvalueerror": [33, 43], "galsimkeyerror": [33, 43], "galsimindexerror": [33, 43], "galsimrangeerror": [33, 43], "galsimboundserror": [33, 43], "galsimundefinedboundserror": [33, 43], "galsimimmutableerror": [33, 43], "galsimincompatiblevalueserror": [33, 43], "incompat": 33, "galsimsederror": [33, 43], "galsimhsmerror": [33, 40, 42, 43], "galsimfftsizeerror": [33, 37, 43], "galsimconfigerror": [33, 43], "galsimconfigvalueerror": [33, 43], "galsimnotimplementederror": [33, 43], "galsimwarn": [33, 43, 52], "think": [33, 53, 59, 61, 75, 77], "galsimdeprecationwarn": [33, 43], "deprec": [33, 37, 48, 52], "encourag": 33, "allowed_valu": 33, "valueerror": [33, 72], "keyerror": 33, "exce": 33, "deem": 33, "mem": 33, "gb": [33, 37, 54, 63], "notimplementederror": [33, 37, 77], "feel": [33, 34, 53], "offer": 33, "userwarn": 33, "fftw": [34, 49, 53], "karrai": 34, "fft2": [34, 76], "xarrai": 34, "post": [34, 50, 54, 59], "ka1": 34, "ka2": 34, "coerc": [34, 75], "float64": [34, 37, 42, 56], "fftshift": 34, "ifft2": [34, 76], "a1": 34, "ka": 34, "a2": 34, "kx": [34, 37, 42, 49], "conjuat": 34, "ky": [34, 37, 42, 49], "rfft2": [34, 76], "ax": [34, 49, 64], "irfft2": [34, 76], "io": [35, 42, 52, 64, 77], "stabl": [35, 47, 54], "front": [35, 77], "hdu_list": [35, 42], "hdulist": [35, 42, 52], "therein": 35, "decompress": [35, 77], "gs_": 35, "absent": 35, "readfromfitshead": [35, 77], "primaryhdu": [35, 42], "imagehdu": 35, "gz": [35, 42, 46, 63, 64, 68, 74], "bz2": [35, 42, 46, 74], "readmulti": 35, "writemulti": [35, 52, 74], "fpack": 35, "funpack": 35, "fname": 35, "some_image_fil": 35, "readcub": 35, "image_list": 35, "readfil": 35, "closehdulist": 35, "writeto": [35, 42], "afterward": [35, 42, 66], "writecub": [35, 74], "plot": [35, 59], "ds9": [35, 42, 74, 77], "unmolest": 35, "del": [35, 54], "ppyfit": 35, "older": [35, 43], "apy_head": 35, "h1": 35, "h2": 35, "h3": 35, "h4": 35, "useblank": 35, "overwritten": [35, 49, 63], "iteritem": 35, "synonym": [35, 42, 79], "iterkei": 35, "itervalu": 35, "pop": 35, "dict2": 35, "ber": 35, "de_vaucouleur": 36, "_law": 36, "sersic_profil": 36, "begin": [36, 40, 42, 64, 69, 77, 79], "upcom": 36, "325": [36, 52], "450": 36, "moreov": [36, 61, 64], "566": [36, 53], "subtl": [36, 52, 64], "sersic_obj1": 36, "40": [36, 47, 74], "sersic_obj2": 36, "sersic_obj3": 36, "237": [36, 42], "3094228615618": 36, "142": 36, "54505376530574": 36, "30942286156187": 36, "011776164687304694": 36, "9795101383056892": 36, "34": [36, 77], "56595186009519": 36, "miss": [36, 42, 49, 52, 77], "003262738739834598": 36, "004754602453641744": 36, "accommod": [36, 54], "010507575186637": 36, "786692612210923": 36, "accomod": 36, "38": [36, 37], "311372735390016": 36, "160062547614234": 36, "985044085834393": 36, "calculatehlrfactor": 36, "circumst": 36, "disk_half_light_radiu": [36, 52], "regard": 36, "peaki": 36, "promin": [36, 40], "diverg": 36, "3x": [36, 59], "50x": 36, "2x": 36, "uncach": 36, "85": [36, 37], "difficulti": 36, "irregular": [36, 74], "rough": [36, 77], "sent": 36, "input_half_light_radiu": 36, "1312": 36, "5514v3": 36, "trace": [36, 37, 42], "calculatehlr": [36, 37, 42, 52], "caller": [36, 37], "establish": [36, 37], "west": [36, 37, 40, 42, 46, 77], "simpler": [36, 37, 42, 61, 74], "conv": 37, "shft": 37, "nyquist_scal": [37, 52], "has_hard_edg": [37, 52], "is_axisymmetr": [37, 52], "is_analytic_x": [37, 52], "is_analytic_k": [37, 52], "kpo": 37, "_original_": 37, "ancestor": 37, "inher": 37, "4096": [37, 52, 64], "02": 37, "stdin": 37, "1666": 37, "added_photon": 37, "drawfft": 37, "draw_imag": 37, "1877": 37, "wrap_siz": 37, "drawfft_makekimag": 37, "1802": 37, "nk": 37, "12288": 37, "big_fft_param": 37, "12300": 37, "high_res_sers": 37, "compound": [37, 40, 73], "__add__": [37, 79], "__sub__": [37, 79], "__rmul__": [37, 79], "__div__": [37, 51], "_xvalu": 37, "_kvalu": 37, "_shear": [37, 69, 78], "_shift": [37, 42], "_drawreal": [37, 52], "flux_scal": 37, "drawreal": [37, 52], "c_contigu": [37, 56], "_calculate_nphoton": 37, "drawphot": [37, 55], "_shoot": 37, "_drawkimag": [37, 52], "calculatefwhm": [37, 42, 52], "calculatemomentradiu": [37, 42, 52], "99": 37, "percent": [37, 59, 64], "flux_frac": [37, 42], "wors": 37, "overkil": 37, "r90": 37, "grain": [37, 55], "discrimin": 37, "rtype": [37, 42], "q_ij": 37, "qxx": 37, "qyi": 37, "i_cent": 37, "j_center": 37, "findadaptivemom": [37, 40, 42, 74], "image_profil": [37, 77], "original_wc": 37, "toimag": [37, 52, 74, 77], "original_profil": 37, "drawfft_finish": 37, "n_subsampl": 37, "maxn": [37, 55], "save_photon": [37, 52, 55], "surface_op": [37, 52], "2048": 37, "absenc": 37, "995": 37, "kronrod": [37, 47], "patterson": [37, 47], "techniqu": [37, 55], "2015": [37, 54, 55, 64, 73], "why": [37, 55, 60, 63, 64], "qualit": 37, "aribtrari": 37, "added_flux": [37, 74], "illustr": [37, 53, 64], "influenc": [37, 42, 74], "9999630988657515": 37, "99996305": 37, "9999630988657525": 37, "996315": 37, "9999973790505298": 37, "399": 37, "9989": 37, "998158": 37, "pull": [37, 53], "electr": 37, "toward": [37, 40, 42, 52, 56, 64, 77], "substrat": 37, "deposit": 37, "repel": 37, "descent": 37, "subsampl": [37, 42, 52, 63], "lost": 37, "eqival": 37, "trade": 37, "axial": [37, 77], "makephot": [37, 52], "max_sb": [37, 52], "ineffici": 37, "mainli": 37, "negative_flux": [37, 48, 52], "positive_flux": [37, 48, 52], "basenois": [37, 42, 51, 62], "allowed_flux_vari": [38, 52], "81": 38, "range_division_for_extrema": 38, "small_fraction_of_flux": [38, 52], "gsp": 38, "200": [38, 50], "accident": [38, 39], "crash": 38, "gsp_list": 38, "withparam": 38, "fittedsipwc": [39, 52, 77], "1253": 39, "1305": 39, "1309": 39, "1311": 39, "1294": 39, "1296": 39, "posixpath": 39, "1270": 39, "1304": 39, "prism": 39, "grism": [39, 64], "roman": [39, 42, 43, 52, 53, 74], "1307": 39, "1299": 39, "1300": 39, "doublezernik": [39, 52, 76, 79], "1283": 39, "1303": 39, "1302": 39, "1306": 39, "v0": [39, 77], "hirata": [40, 42, 64], "seljak": [40, 42], "2003": [40, 42], "cite": 40, "kaiser": [40, 73], "squir": 40, "broadhurst": 40, "1995": 40, "under": [40, 47, 53, 79], "strict": [40, 42], "use_sky_coord": [40, 42], "ixx": [40, 42], "iyi": [40, 42], "ixi": [40, 42], "orient": [40, 42, 59, 64, 74], "iuu": [40, 42], "ivv": [40, 42], "iuv": [40, 42], "applywc": [40, 42, 52], "estimateshear": [40, 42, 52, 74], "my_gaussian": [40, 42], "my_gaussian_imag": [40, 42], "my_moment": [40, 42], "observed_shap": [40, 42], "01": [40, 42, 49, 56, 67], "0e5": [40, 42], "nonzero": [40, 42], "sky_coordin": [40, 42], "remap": 40, "welcom": 40, "someon": 40, "batch": [40, 55], "final_imag": 40, "final_epsf_imag": 40, "0438925349133": 40, "85747392701e": 40, "result_corrected_e2": 40, "09934103488922119": 40, "746108423463568e": 40, "09975": 40, "n_imag": 40, "n_fail": 40, "this_imag": 40, "this_final_epsf_imag": 40, "0j": 40, "psf_shape": 40, "400": [40, 75], "8000": [40, 67], "crudest": 40, "usabl": [40, 46, 54], "flatten": [40, 48, 54], "_imag": [41, 42], "imagei": [41, 42, 52], "imageui": [41, 42], "imageu": [41, 42], "implic": [42, 59, 63, 69, 77], "astronomi": [42, 77], "saoimag": 42, "sextractor": 42, "row_num": 42, "col_num": 42, "insul": 42, "concern": [42, 59], "uint16": 42, "uint32": 42, "int16": 42, "int32": [42, 52], "remind": 42, "make_const": 42, "image_float": 42, "image_doubl": 42, "valid_dtyp": 42, "new_scal": 42, "new_wc": 42, "getvalu": [42, 63], "new_ixi": 42, "__getitem__": 42, "__setitem__": 42, "17": [42, 46, 60, 68], "_wrap": 42, "_view": 42, "_getvalu": 42, "_setvalu": 42, "_addvalu": 42, "addvalu": 42, "_fill": 42, "_invertself": 42, "addnoisesnr": [42, 52], "snr": 42, "preserve_flux": 42, "great08": [42, 55, 74], "equiv": [42, 77], "poissonnois": [42, 51, 52, 62, 74], "variablegaussiannois": [42, 51, 52, 62, 74], "addreciprocityfailur": [42, 64, 74], "exp_tim": 42, "base_flux": 42, "reciproc": [42, 64, 74], "photographi": 42, "sensit": [42, 74], "photograph": 42, "film": 42, "1893": 42, "reciprocity_": 42, "suffer": 42, "hgcdte": [42, 64], "infrar": 42, "astrometri": 42, "lesser": [42, 64], "exhibit": 42, "lack": 42, "p_r": 42, "log_": 42, "prime": [42, 54, 61, 69], "calibr": 42, "decad": 42, "motiv": 42, "h2rg": 42, "fig": 42, "1106": 42, "1090": [42, 52], "nan": [42, 57, 64], "nonlinear": [42, 53, 64], "applyipc": [42, 64, 74], "ipc_kernel": [42, 64], "edge_treat": [42, 64], "fill_valu": [42, 64], "kernel_nonneg": 42, "kernel_norm": 42, "interpixel": [42, 49, 64, 74], "capacit": [42, 49, 64, 74], "nir": [42, 64, 74], "voltag": [42, 64, 74], "3x3": [42, 64], "anisotrop": 42, "crop": [42, 64], "applynonlinear": [42, 49, 52, 64, 74], "nlfunc": 42, "classic": 42, "aris": [42, 55, 71, 74], "junction": 42, "attenu": 42, "inclus": 42, "beta1": 42, "beta2": 42, "applypersist": [42, 64], "coeff": 42, "retent": [42, 64], "trap": 42, "laboratori": [42, 51], "cmo": 42, "readout": [42, 64, 67], "prior": [42, 55], "dither": [42, 49, 52, 64], "latest": [42, 44, 46, 53, 64, 77], "oldest": 42, "block": [42, 49], "attributeerror": 42, "calculate_fft": 42, "calculate_inverse_fft": 42, "conj": 42, "xcen": 42, "ycen": 42, "16": [42, 46, 49, 54, 68, 77], "reshap": 42, "56": [42, 63], "72": [42, 52], "clear_depixelize_cach": 42, "alt_imag": 42, "100x100": [42, 59], "npix": [42, 54], "flip_lr": [42, 52], "flip_ud": [42, 52], "get_pixel_cent": 42, "good_fft_siz": 42, "input_s": 42, "iscomplex": 42, "isconst": 42, "isinteg": [42, 49], "setorigin": [42, 74, 77], "26": [42, 63], "outer_bound": 42, "quantiz": [42, 64, 74], "devic": 42, "qe": [42, 75], "ipc": [42, 64], "int_imag": 42, "replaceneg": 42, "replace_valu": 42, "tini": [42, 61], "undesir": 42, "rot_180": [42, 52], "rot_ccw": [42, 52], "rot_cw": [42, 52], "new_pixel_scal": 42, "234": 42, "456": 42, "232": 42, "235": 42, "454": [42, 52], "457": [42, 52], "459": [42, 52], "setsubimag": 42, "tranpos": 42, "unlin": 42, "halfwai": 42, "55": 42, "71": [42, 61, 79], "understand": [42, 54, 64, 69], "im_ful": 42, "ft": 42, "n2": 42, "im_wrap": 42, "pristin": 42, "wrapped_imag": 42, "toolkit": 43, "overview": 43, "date": [43, 64, 67, 77], "script": [43, 46, 49, 52, 63, 64, 68, 73, 74], "summari": [43, 52, 54, 61], "capabl": 43, "concept": [43, 56], "zernik": [43, 52, 53, 54, 61, 64, 76], "deflect": [43, 52, 54, 76], "miscellan": [43, 76], "histori": 43, "v2": 43, "regularli": 44, "mac": [44, 46, 53], "pypi": [44, 77], "forg": [44, 45, 53], "experi": 44, "hear": 44, "posix": 44, "compliant": 44, "window": 44, "suit": [44, 51, 52, 53, 76], "pytest": [44, 52], "test_requir": 44, "txt": [44, 45, 46, 68], "sudo": [44, 46, 53], "plugin": 44, "xdist": 44, "60": [44, 52, 75], "run_all_test": 44, "demo2": [44, 74], "learn": 44, "subdirectori": [44, 46, 49, 53, 63], "prerequisit": 45, "channel": 45, "conda_requir": 45, "pybind11": [45, 46, 52], "git": [45, 46, 53], "clone": [45, 46, 53], "privileg": 46, "home": [46, 61], "library_path": 46, "ld_library_path": 46, "environment": 46, "export": 46, "libary_path": 46, "starlink": [46, 77], "pyyaml": [46, 52], "panda": 46, "quickest": 46, "pyast": [46, 77], "fastest": [46, 49], "earlier": [46, 64, 75, 77], "url": 46, "wget": 46, "tar": [46, 63, 64, 68], "xfz": 46, "c_include_path": 46, "fftw_dir": 46, "shell": 46, "login": [46, 53], "bash_profil": 46, "usr": 46, "modern": 46, "hide": 46, "libfftw3": 46, "crai": 46, "pe": 46, "lib64": 46, "loadabl": 46, "fftw3": 46, "sw": [46, 77], "port": [46, 77], "eigen_dir": 46, "tuxfamili": 46, "php": [46, 63], "bitbucket": 46, "xfj": 46, "cp": 46, "5a0156e40feb": 46, "md5": 46, "hash": [46, 49], "eigen3": 46, "libeigen3": 46, "foremost": 46, "strive": 46, "hew": 46, "_galsim": 46, "build_shared_clib": 46, "shared_clib": 46, "abs_err": 47, "33333333333333337": 47, "666666666666667": 47, "66666666666666674": 47, "jstor": 47, "2004583": 47, "e10": 47, "int_0": 47, "j_": 47, "dr": [47, 67], "outlin": 47, "ogata": 47, "kurim": 47, "kyoto": 47, "jp": [47, 63], "prim": 47, "41": 47, "integrationrul": [47, 76], "calculateweight": 47, "apporpri": 47, "trapzrul": [47, 76], "imageintegr": [47, 52, 76], "wavelengthh": 47, "drawimagekwarg": 47, "dok": 47, "apart": 47, "use_endpoint": 47, "predefin": 47, "trapz": [47, 52, 72, 76], "interior": 47, "from_nam": [48, 75], "kval": 48, "max_len": 48, "5th": 48, "bunch": [49, 59], "lazy_properti": 49, "fget": 49, "slow_function_to_be_used_as_a_properti": 49, "answer": [49, 53, 55], "stackoverflow": [49, 53], "6849299": 49, "doc_inherit": 49, "mthd": 49, "live": [49, 59, 63], "some_method": 49, "activest": 49, "python_index_1": 49, "mj": 49, "recip": 49, "bug": [49, 52, 53], "timer": 49, "took": [49, 60, 63], "watch": 49, "get_omp_thread": 49, "set_omp_thread": 49, "compli": 49, "single_thread": [49, 54], "pool": [49, 54], "n_cpu": 49, "especaili": 49, "gcc": 49, "fork": [49, 53, 54], "omp": [49, 52], "lru_cach": 49, "user_funct": 49, "1024": [49, 52, 64], "stolen": 49, "577970": 49, "dynam": [49, 52], "slow_funct": 49, "k1": [49, 59], "destruct": 49, "pickle_shar": [49, 52, 54], "state": [49, 54], "galsim_obj_with_shared_st": 49, "phase_screen": [49, 54], "dump": [49, 54], "restart": [49, 54], "unload": 49, "weakmethod": 49, "weakref": 49, "81253": 49, "orderedweakref": 49, "ref": 49, "heap": 49, "simplegener": 49, "horner": [49, 52], "bx": 49, "cx": 49, "Will": 49, "_horner": 49, "triangl": [49, 77], "bivari": [49, 79], "_horner2d": 49, "temporari": [49, 68], "descend": [49, 54, 61], "ncr": 49, "distiguish": 49, "distinguish": 49, "algorith": 49, "largish": 49, "rotate_xi": 49, "x_rot": 49, "y_rot": 49, "g1g2_to_e1e2": 49, "ellips": [49, 69], "printopt": 49, "temporarili": 49, "2891790": 49, "long_arr": 49, "roll2d": 49, "roll": 49, "irol": 49, "jroll": 49, "kxky": 49, "array_shap": 49, "fftfreq": 49, "idl": 49, "meshgrid": [49, 59, 77], "merge_sort": [49, 52], "uniqu": [49, 63], "concaten": [49, 56], "check_pickl": 49, "irrepr": 49, "recov": [49, 61, 64], "hashabl": 49, "deepcopi": 49, "check_all_diff": 49, "check_hash": 49, "unequ": [49, 72], "obj1": [49, 74], "obj2": [49, 74], "capturelog": 49, "exit": [49, 63], "sortbi": 49, "tottim": 49, "nline": 49, "pstat": 49, "int64": 49, "listifi": 49, "lit": 49, "dol_to_lod": 49, "dol": 49, "scalar_str": 49, "broadcast": [49, 52, 54], "_functions_": 49, "okai": 49, "mix": [49, 69, 74], "f2": 49, "ensure_dir": 49, "race": 49, "makedir": 49, "interleaveimag": [49, 64], "im_list": 49, "add_flux": 49, "catch_offset_error": 49, "interleav": [49, 52], "multidrizzl": 49, "imcom": 49, "disadvantag": 49, "ther": 49, "acheiv": 49, "arang": [49, 59, 63, 72, 77], "deinterleaveimag": 49, "conserve_flux": 49, "extract": [49, 75], "gal_pix": 49, "img_new": 49, "reconstruct": [49, 77], "thin_tabulated_valu": [49, 68], "x_new": 49, "y_new": 49, "old_thin_tabulated_valu": 49, "739": [49, 52], "josh": [49, 53], "wrote": 49, "buggi": 49, "mike": [49, 53], "parse_pos_arg": 49, "name1": 49, "name2": 49, "rand_arr": 49, "convert_interpol": 49, "structure_funct": 49, "angularli": 49, "phi": [49, 54, 75], "langl": 49, "rangl": 49, "combine_wave_list": [49, 52], "math_ev": 49, "other_modul": 49, "unweighted_mo": 49, "0th": [49, 79], "mx": 49, "my": 49, "m0": 49, "unweighted_shap": 49, "rsqr": 49, "rand_with_replac": 49, "n_choic": 49, "_n_rng_call": [49, 63], "check_share_fil": 49, "subdir": 49, "correct_filenam": 49, "find_out_of_bounds_posit": 49, "engin": [50, 52, 59], "cosmologi": [50, 78], "emploi": [50, 61], "matthia": 50, "bartelmann": 50, "libastro": 50, "overdens": 50, "m_solar": 50, "omega_matt": 50, "omega_lambda": 50, "_getshear": [50, 59], "pos_x": [50, 59], "pos_i": [50, 59], "z_": 50, "getshear": [50, 52, 59, 74], "_getconverg": [50, 59], "getconverg": [50, 52, 59], "_getmagnif": [50, 59], "getmagnif": [50, 52, 59, 74], "_getlens": [50, 59], "getlens": [50, 52, 59], "xlist": [50, 59], "ylist": [50, 59], "kappa": [50, 52, 59, 69], "da": [50, 79], "lambdacdm": 50, "radiat": 50, "quintess": 50, "curvatur": 50, "z_ref": 50, "mpc": 50, "3000": 50, "gaussiannois": [51, 52, 62, 74], "deviatenois": [51, 52, 62, 74], "isinst": [51, 77], "gaussian_nois": 51, "noise_copi": 51, "poisson_nois": 51, "stand": [51, 52], "ccd_nois": 51, "shut": 51, "var_imag": [51, 74], "variable_nois": 51, "dev_nois": 51, "breviti": 52, "changelog": 52, "parenthes": 52, "w149": [52, 68], "w146": 52, "1017": 52, "1191": 52, "lowercas": 52, "1245": 52, "1259": 52, "favor": 52, "1190": 52, "1237": 52, "1238": 52, "1239": 52, "1250": 52, "1221": 52, "1240": 52, "1243": 52, "emiss": 52, "1247": 52, "1249": 52, "1251": 52, "1257": 52, "gpu": 52, "offload": 52, "1212": 52, "1217": 52, "1218": 52, "1222": 52, "1224": 52, "1230": 52, "1229": 52, "1236": 52, "1241": 52, "sped": 52, "1246": 52, "1187": 52, "overflow": 52, "1208": 52, "1210": 52, "1220": 52, "1223": 52, "absorpt": 52, "1227": 52, "1228": 52, "1235": 52, "inaccuraci": [52, 63], "1231": 52, "phasescreenpsf": [52, 54, 65], "1242": 52, "1254": 52, "1256": 52, "1258": 52, "cppellips": 52, "astronomicalconst": 52, "1129": 52, "attributedict": 52, "1149": 52, "1154": 52, "1174": 52, "731": 52, "795": [52, 53], "954": 52, "1138": 52, "1143": 52, "1148": 52, "1160": 52, "1162": 52, "1163": 52, "1166": 52, "1167": 52, "1169": 52, "1183": 52, "1184": 52, "1193": 52, "1195": 52, "log_format": 52, "1201": 52, "1202": 52, "1206": 52, "galaxysampl": [52, 63], "1139": 52, "1146": 52, "pupil_u": [52, 54, 56], "pupil_v": [52, 54, 56], "1147": 52, "1155": 52, "sheartoworld": [52, 77], "sheartoimag": [52, 77], "1158": 52, "1172": 52, "1176": 52, "1178": 52, "1067": 52, "1179": 52, "1180": 52, "1118": 52, "1137": 52, "1140": 52, "1141": 52, "1177": 52, "1213": 52, "1132": 52, "1156": 52, "1157": 52, "1185": 52, "1197": 52, "1198": 52, "nobj": 52, "extra_object": 52, "fault": 52, "1216": 52, "gracefulli": 52, "recurs": 52, "1233": 52, "1082": 52, "eigen": 52, "1086": 52, "540": [52, 63], "withorigin": [52, 77], "1073": 52, "1084": 52, "wfirst": 52, "ii_pad_factor": [52, 54, 61], "1089": 52, "high_accuraci": [52, 64], "approximate_strut": [52, 64], "1093": 52, "1095": 52, "midpt": 52, "1098": 52, "lookuptable2d": [52, 54, 72, 76], "1103": 52, "fft_sign": [52, 54, 61], "1104": 52, "1151": 52, "510": 52, "demo12": [52, 74], "demo13": [52, 53, 64, 74], "1121": 52, "1083": 52, "random_float": 52, "_nobjects_onli": 52, "romansca": [52, 74], "romanbandpass": 52, "romanpsf": [52, 74], "1057": 52, "vonkarman": [52, 54, 61], "1059": 52, "1065": 52, "1069": 52, "lab": [52, 54, 64], "1077": 52, "1081": 52, "1078": 52, "pupil_bin": [52, 64], "1092": 52, "gsfitswc": [52, 77], "sip": [52, 77], "wcss": 52, "1094": 52, "integrate_product": [52, 72], "1099": 52, "tpv": [52, 77], "1101": 52, "userscreen": [52, 54, 65], "1102": 52, "describe_zernik": [52, 76, 79], "1120": 52, "934": 52, "935": 52, "pv": 52, "1110": 52, "1124": 52, "1126": 52, "1054": 52, "1058": 52, "1061": 52, "1064": 52, "1091": 52, "1128": 52, "samplewavelength": [52, 56, 57, 66], "1131": 52, "1142": 52, "1164": 52, "_basecorrelatednois": 52, "160": 52, "randomwalk": 52, "977": 52, "1038": 52, "reload": 52, "919": 52, "1048": 52, "galsim_share_dir": [52, 68], "1014": 52, "host": [52, 77], "zenodo": [52, 63], "1033": 52, "877": 52, "22": [52, 64], "fov": 52, "fermi": [52, 64], "992": 52, "1005": 52, "1006": 52, "1008": 52, "1026": 52, "1047": 52, "laplacian": [52, 79], "hessian": [52, 79], "1053": 52, "backport": 52, "472": 52, "1029": 52, "makeskyimag": [52, 64, 74, 77], "1030": 52, "1036": 52, "1041": 52, "clang": 52, "problemat": 52, "1079": 52, "990": 52, "range_division_for_extreama": 52, "993": 52, "821": 52, "982": 52, "981": 52, "996": 52, "scon": 52, "1009": 52, "1020": 52, "unnecessarili": 52, "safe_load": 52, "v5": 52, "1025": 52, "809": 52, "755": 52, "2016": 52, "tmv": 52, "alldetectoreffect": [52, 64], "deproject": [52, 77], "galsim_yaml": 52, "galsim_json": 52, "broken": [52, 59], "964": 52, "968": 52, "864": 52, "855": 52, "955": 52, "writer": 52, "cutout": [52, 59], "928": 52, "942": 52, "963": 52, "kappakaisersquir": 52, "subsamplegrid": 52, "submodul": 52, "832": 52, "951": 52, "phasescreen": 52, "secondkick": [52, 54, 65], "940": 52, "calculate_pixel_area": [52, 67], "580": 52, "getgal": 52, "640": 52, "reorgan": 52, "789": 52, "799": 52, "824": 52, "opticalscreen": [52, 54, 65, 79], "873": 52, "884": 52, "runner": 52, "nosetest": 52, "892": 52, "pariti": 52, "mistak": 52, "675": 52, "792": 52, "848": 52, "880": 52, "endless": 52, "952": 52, "simreal": 52, "787": 52, "wmult": 52, "makepsf": [52, 54], "mipdt": 52, "887": 52, "904": 52, "hms_angl": 52, "dms_angl": 52, "shapelets": 52, "905": 52, "objlist": 52, "coef_arrai": 52, "nyquistscal": 52, "renam": 52, "533": 52, "aegi": 52, "candel": 52, "715": 52, "722": 52, "771": 52, "776": 52, "782": 52, "811": 52, "819": [52, 54], "822": 52, "assignphotonangl": 52, "823": 52, "827": 52, "835": 52, "840": 52, "865": 52, "buildgrid": [52, 59, 60, 74], "get_xyz": [52, 77], "celetialcoord": 52, "from_xyz": [52, 77], "917": 52, "calculatenoisevar": 52, "820": 52, "formerli": 52, "745": 52, "534": 52, "768": 52, "602": 52, "654": 52, "683": 52, "686": 52, "713": 52, "720": 52, "724": 52, "741": 52, "763": 52, "764": 52, "784": 52, "698": 52, "301": 52, "691": 52, "308": 52, "465": 52, "phasescreenlist": [52, 54, 65], "549": 52, "rewrot": 52, "554": 52, "694": 52, "657": 52, "660": 52, "makegalaxi": [52, 63, 74], "693": 52, "709": 52, "723": 52, "735": 52, "748": 52, "711": 52, "750": 52, "jobnum": 52, "774": 52, "616": 52, "218": [52, 54], "interpolant2d": 52, "interpolantxi": 52, "multipleimag": 52, "642": 52, "__rdiv__": 52, "643": 52, "630": 52, "666": 52, "unpickl": [52, 54], "__eq__": 52, "__ne__": 52, "__hash__": 52, "501": 52, "555": 52, "558": 52, "590": 52, "mission": 52, "makeobj": 52, "635": 52, "612": 52, "618": 52, "626": 52, "639": 52, "672": 52, "anglebetween": [52, 77], "incorrectli": 52, "645": 52, "670": 52, "applywhiteningto": 52, "529": 52, "547": 52, "calculatedcrmomentshift": [52, 66], "calculatechromaticseeingratio": 52, "addreciprocityfaiur": 52, "552": 52, "alias_threshold": 52, "562": 52, "581": 52, "524": 52, "601": 52, "603": 52, "563": 52, "486": 52, "537": 52, "repo": 52, "548": 52, "571": 52, "572": 52, "byteord": 52, "594": 52, "599": 52, "604": 52, "hidden": 52, "__version__": 52, "pep": 52, "610": 52, "364": 52, "shoot_relerr": 52, "shoot_abserr": 52, "535": 52, "createshear": 52, "succinct": 52, "511": 52, "fitimag": 52, "drawshoot": 52, "467": 52, "409": 52, "478": 52, "508": 52, "526": 52, "344": 52, "528": 52, "536": 52, "479": 52, "482": 52, "487": 52, "424": 52, "442": 52, "461": 52, "466": 52, "474": 52, "470": 52, "machineri": [52, 74], "488": 52, "497": 52, "460": 52, "430": 52, "439": 52, "449": 52, "376": 52, "350": 52, "388": 52, "trefoil": [52, 54, 61, 64], "302": 52, "390": 52, "248": 52, "304": 52, "kmin_factor": [52, 59], "kmax_factor": [52, 59], "377": 52, "powerspectrumestim": [52, 60, 78], "pse": [52, 60, 78], "382": 52, "343": 52, "426": 52, "365": 52, "352": 52, "380": 52, "417": 52, "linkflag": 52, "objectss": 52, "407": 52, "432": 52, "238": 52, "305": 52, "306": [52, 59], "get_cosmos_corrfunc": 52, "345": 52, "349": 52, "319": 52, "330": 52, "299": 52, "333": 52, "297": 52, "315": 52, "327": 52, "331": 52, "332": 52, "revamp": 52, "243": 52, "285": 52, "289": 52, "288": [52, 74], "291": 52, "295": 52, "294": 52, "impetu": 53, "challeng": [53, 55, 59, 78], "great3": [53, 63], "barnabytprow": [53, 63], "adsab": [53, 63, 73], "harvard": [53, 63, 73, 77], "2015a": 53, "26c": 53, "121r": 53, "tarbal": [53, 63], "zip": 53, "repositori": [53, 59, 60, 63, 64], "fink": [53, 77], "macport": 53, "homebrew": 53, "rst": 53, "licens": 53, "bsd": 53, "mail": 53, "organ": [53, 65, 74], "googl": 53, "announc": 53, "receiv": 53, "notif": 53, "subscrib": 53, "email": 53, "googlegroup": 53, "junk": 53, "repli": 53, "unsubscrib": 53, "unsubscript": 53, "mikejarvis17": 53, "gmail": 53, "rachel": 53, "rmandelb": 53, "andrew": 53, "dot": [53, 79], "cmu": 53, "meyer": 53, "jmeyers314": 53, "year": [53, 75], "grate": 53, "alert": [53, 61], "notifi": 53, "chanc": 53, "avenu": 53, "contact": 53, "address": 53, "sit": [53, 60], "make_coadd": [53, 73], "psf_wf_movi": 53, "movi": 53, "wavefront": [53, 54, 79], "20data": [53, 63], "iii": 53, "wavlength": 53, "vignet": [53, 56], "fring": [53, 56], "cosmic": 53, "bleed": 53, "553": 53, "828": 53, "extinct": 53, "dust": 53, "541": 53, "669": 53, "808": 53, "205": 53, "875": 53, "equat": [54, 59, 60, 66], "cal": 54, "strategi": 54, "illumin": [54, 57, 61, 64], "von": [54, 65], "karman": [54, 65], "evolv": 54, "frozen": 54, "altitud": 54, "fraunhof": 54, "compens": 54, "screen_list": 54, "embed": 54, "cage": [54, 61], "evenli": [54, 59, 61], "therebi": 54, "aper": [54, 61], "prioriti": [54, 61], "arrang": [54, 57, 61], "scale_s": 54, "samplepupil": 54, "screen_siz": 54, "screen_scal": 54, "vx": 54, "vy": 54, "time_step": 54, "mp_context": 54, "wind": 54, "boil": 54, "peterson": 54, "kick": [54, 77], "consum": 54, "subprocess": 54, "highli": 54, "launch": 54, "initwork": [54, 65], "initworkerarg": [54, 65], "mp": 54, "atm": 54, "morearg": 54, "fn": 54, "ctx": 54, "get_context": 54, "safest": 54, "ctor": 54, "initarg": 54, "apply_async": 54, "dummypsf": 54, "screen_kmax": 54, "trick": 54, "myscreen": 54, "pkl": 54, "wb": 54, "rb": 54, "meta": 54, "km": 54, "sea": 54, "smoothli": 54, "approach": [54, 59], "veloc": 54, "freshli": 54, "clock": 54, "entropi": 54, "spie": 54, "remembr": 54, "autoregress": 54, "srikar": 54, "srinath": 54, "univ": 54, "california": 54, "santa": 54, "cruz": 54, "lisa": 54, "poyneer": 54, "lawrenc": 54, "livermor": 54, "nation": 54, "alexand": 54, "rudi": 54, "ucsc": 54, "ammon": 54, "llnl": 54, "publish": 54, "proceed": 54, "volum": 54, "9148": 54, "septemb": 54, "kmin": [54, 59], "wavefront_gradi": 54, "dwdu": 54, "dwdv": 54, "tip": [54, 61], "tilt": [54, 61, 77], "annular_zernik": [54, 61], "lam_0": 54, "soc": [54, 61, 79], "am": [54, 61, 79], "66": [54, 61, 79], "207": [54, 61, 79], "211": [54, 61, 79], "1976": [54, 61, 79], "brief": [54, 61], "zernike_polynomi": [54, 61, 79], "arrow": [54, 61], "piston": [54, 61, 79], "unus": [54, 61, 79], "silli": 54, "_bar": 54, "instantan": 54, "025": 54, "studi": [54, 63], "second_kick": 54, "believ": [54, 61], "zemax": [54, 61], "decam": [54, 61], "prohibit": 54, "interfer": 54, "dub": 54, "tune": 54, "uninstanti": 54, "rapidli": [54, 60], "paradigm": 54, "acknowledg": 54, "phosim": 54, "label": [54, 64, 77], "incur": 54, "reader": 54, "apjss": 54, "vol": [54, 61], "nanmet": 54, "angleunit": [54, 59, 61, 75], "accomplish": 54, "blow": 54, "psi": 54, "023": 54, "l_0": [54, 61], "asymptot": 54, "longest": 54, "r0_500_effect": 54, "r0_weight": 54, "jee": 54, "tyson": 54, "2011": [54, 60], "laptop": 54, "byte": 54, "ram": [54, 63], "73": [54, 77], "89": 54, "46": 54, "652": 54, "172": 54, "055": 54, "074": 54, "022": 54, "700nm": 54, "700": [54, 61, 75], "img1": 54, "million": 54, "img2": 54, "1e6": 54, "balanc": [54, 63], "reset_shared_screen": [54, 65], "successfulli": 55, "great10": [55, 59, 60, 74], "n_": 55, "f_i": 55, "x_i": 55, "stochast": [55, 67], "driver": 55, "dielectr": 56, "medium": 56, "vacuum": [56, 57], "consequ": [56, 66], "recreat": 56, "allocateangl": 56, "allocatepupil": 56, "allocatetim": 56, "allocatewavelength": 56, "target_indic": 56, "slice": [56, 79], "source_indic": 56, "do_xi": 56, "do_flux": 56, "do_oth": 56, "s1": 56, "s2": 56, "dz": [56, 57], "fromarrai": 56, "constrast": 56, "hasallocatedpupil": 56, "hasallocatedtim": 56, "photons2": 56, "nphoton": [56, 66], "focus": 57, "defocus": 57, "conic": 57, "beam": 57, "intra": 57, "sheared_po": 58, "getmatrix": [58, 69, 77], "arbitari": 59, "lensing_engin": [59, 60], "cell": 59, "kept": [59, 63], "k2": 59, "coverag": 59, "cosmolog": [59, 74], "calculatexi": 59, "cosmologist": 59, "ell": [59, 60], "pitch": 59, "p_e": [59, 60], "p_b": [59, 60], "curl": 59, "get_converg": 59, "bandlimit": 59, "denot": 59, "observation": 59, "lensing_p": 59, "theorytoobserv": [59, 78], "sharp": 59, "pictur": 59, "frame": [59, 66], "amelior": 59, "soft": 59, "arctan": 59, "soften": 59, "gradual": 59, "came": 59, "my_p": [59, 60], "1413231": 59, "p_k": 59, "tab_pk": 59, "g1_r": 59, "g2_r": 59, "iff": 59, "n_theta": 59, "xi_": 59, "xi_p": 59, "xi_m": 59, "advis": [59, 75], "guidelin": [59, 64], "ten": 59, "412": 59, "141": 59, "313": 59, "241": 59, "342": 59, "75": 59, "199": 59, "225": 59, "489": 59, "poslist": 59, "periodi": 59, "nrandcallsforbuildgrid": 59, "powerspectrumr": [59, 78], "pixel_s": 59, "spin": [59, 60], "alter": 59, "incorrect": 59, "gd": [59, 74], "gamma1": 59, "gamma2": 59, "joe": 60, "zuntz": 60, "assort": 60, "privat": 60, "sky_size_deg": 60, "nbin": 60, "interchang": 60, "rebin": 60, "logarithim": 60, "grid_siz": 60, "n_ell": 60, "icosmo": 60, "my_tab": 60, "fid": 60, "zmed1": 60, "00": 60, "specifythem": 60, "my_ps": 60, "p_eb": 60, "eb": 60, "_bin_pow": 60, "handbook": 60, "kitch": [60, 74], "1214": 60, "aoas484": 60, "21": [60, 75], "weight_e": 60, "weight_bb": 60, "theory_func": 60, "bb": 60, "airy_disc": 61, "fnal": 61, "gov": [61, 64], "neilsen": 61, "notebook": 61, "astropsf": 61, "Its": [61, 63], "transfer": 61, "500nm": 61, "atmospheric_se": 61, "the_kolmogorov_model_of_turbul": 61, "do_delta": 61, "quantit": 61, "rightarrow": 61, "martinez": 61, "516": 61, "conan": 61, "2008": 61, "josa": 61, "blindli": 61, "086": 61, "vonkaman": 61, "bypass": 61, "imposs": 61, "delta_amplitud": 61, "_circular_": 61, "orthogon": [61, 77], "_annular_": 61, "mahajan": [61, 79], "optical_psf": 61, "stronger": [61, 67], "inject": 62, "astrophys": 63, "real_galaxy_catalog_23": 63, "5_exampl": 63, "flux_rescal": 63, "passband": 63, "amplifi": [63, 75], "real_galaxi": 63, "imposit": 63, "criteria": 63, "subtleti": 63, "terribli": 63, "simplist": 63, "000": 63, "Be": 63, "unpack": 63, "symlink": 63, "getgalimag": 63, "getpsfimag": 63, "getbandpass": [63, 64, 68, 74], "getindexforid": 63, "getnois": 63, "getnoiseproperti": 63, "throughout": [63, 79], "recogn": 63, "real_galaxy_catalog_25": 63, "2_fit": 63, "2_select": 63, "cat": [63, 74], "im_siz": 63, "pix_scal": 63, "bp_file": 63, "wfc_f814w": [63, 68], "dat": [63, 67, 68], "cosmos_cat": [63, 74], "bigger": 63, "real_gal_list": 63, "param_gal_list": 63, "ind": 63, "real_gal": 63, "param_g": 63, "im_real": 63, "im_param": 63, "im_real_": 63, "im_param_": 63, "spent": 63, "240": 63, "33": 63, "_use_sampl": 63, "catalog_select": 63, "canmaker": 63, "getparametricrecord": 63, "record": 63, "getrealparam": 63, "n_random": 63, "bear": 63, "photo": 63, "mock": 63, "ridicul": 63, "selectrandomindex": 63, "87": 63, "3242143": 63, "ball": 63, "cosmos_23": [63, 68], "5_training_sampl": [63, 68], "readm": 63, "cosmos_25": [63, 68], "2_training_sampl": [63, 68], "nolink": 63, "quiet": 63, "reinstal": 63, "rerun": 63, "mere": 63, "symbol": 63, "2017arxiv171000885m": 63, "nearbi": 63, "mtk": 63, "nao": 63, "pdr1": 63, "se": [64, 77], "sca": [64, 68], "assembli": 64, "n_sca": 64, "diagram": 64, "languag": 64, "collecting_area": 64, "epoch": [64, 77], "n_dither": 64, "dark_curr": 64, "nonlinearity_beta": 64, "ordinarili": 64, "counts_out": 64, "counts_in": 64, "reciprocity_alpha": 64, "5e": 64, "newer": 64, "thermal_background": 64, "thermal": 64, "282": [64, 77], "f184": [64, 68], "pupil_plane_fil": 64, "anyon": 64, "wfc": 64, "stray_light_fract": 64, "strai": 64, "zodiac": 64, "worst": 64, "persistence_coeffici": 64, "eight": 64, "persistence_fermi_param": 64, "n_pix_tot": 64, "n_pix": 64, "capacitor": 64, "monitor": 64, "jitter_rm": 64, "jitter": 64, "realiti": 64, "charge_diffus": 64, "getskylevel": [64, 74], "incorpor": [64, 69], "getwc": [64, 68, 74], "findsca": 64, "gap": 64, "allowedpo": [64, 74], "sun": [64, 77], "bestpa": 64, "ab_zeropoint": 64, "default_thin_trunc": 64, "include_all_band": 64, "loss": 64, "obei": 64, "micron": 64, "instrument": 64, "delta_zp": 64, "spreadsheet": 64, "roman_effarea_20201130": 64, "xlsx": 64, "gsfc": 64, "nasa": 64, "wfi_techn": 64, "roman_bandpass": 64, "f184_bp": 64, "dispers": 64, "2025": 64, "chri": 64, "tapir": 64, "chirata": 64, "uncertain": 64, "sensibli": 64, "eclipt": [64, 77], "longitud": [64, 77], "fair": 64, "decent": 64, "earth": [64, 75, 77], "orbit": 64, "obliqu": [64, 77], "datetim": [64, 77], "vernal": 64, "equinox": 64, "sca_po": 64, "n_wave": 64, "extra_aberr": 64, "webbpsf": 64, "readthedoc": 64, "abber": 64, "wfi": 64, "wim_zernikes_cycle9": 64, "csv": 64, "spider": 64, "settabl": 64, "512": 64, "spike": 64, "degrad": 64, "roman_psf": 64, "_make_apertur": 64, "galim": 64, "webpag": 64, "parse_roman_zernikes_1217": 64, "boxi": 64, "outskirt": 64, "1293": 64, "mid": 64, "guid": 64, "sdt_public": 64, "wp": 64, "readme_afta_c5_wfc_zernike_and_field_data": 64, "22nd": 64, "pa": 64, "pa_is_fpa": 64, "payload": 64, "swap": [64, 77], "solar": 64, "panel": 64, "consult": 64, "fpa": 64, "wcs_dict": 64, "include_bord": 64, "subroutin": [64, 77], "36": 64, "obviou": 64, "convertcent": 64, "42406840554768e": 64, "139": 64, "director": 64, "prev_exposur": 64, "slew": 64, "consecut": 64, "tail": 64, "stsci": [64, 68], "wfc3": [64, 68], "ins_perform": [64, 68], "exceed": 64, "stretch": [65, 73], "exponenati": 65, "vaucouleur": 65, "hsc": 65, "_blue_limit": 66, "_red_limit": 66, "5000": 66, "aa": 66, "ndarrai": 66, "wavenumb": 66, "spectral_dens": [66, 75], "_mul_s": 66, "_mul_bandpass": 66, "_mul_scalar": 66, "plaza": 66, "1204": 66, "1346": 66, "calculateseeingmomentratio": 66, "check_dimensionless": 66, "check_spectr": 66, "max_wav": 66, "resum": 67, "accumu": 67, "fell": 67, "_50_": 67, "50v": 67, "itl": [67, 68], "lsst_itl_50_32": 67, "lsst_itl_8": [67, 68], "lsst_e2v_50_8": 67, "e2v": 67, "lsst_e2v_50_32": 67, "lsst_e2v_8": 67, "asymmetr": 67, "tradit": [67, 77], "dope": 67, "cosin": 67, "simple_tr": 67, "poisson_ccd": 67, "cfg": 67, "containt": 67, "sinusoid": 67, "co": [67, 69, 75, 77], "prepend": [68, 77], "flam": 68, "specrum": 68, "lyra": 68, "calspec": 68, "crd": 68, "alpha_lyr_mod_001": 68, "2200": 68, "cww_e_ext": 68, "wu": 68, "weedman": 68, "1980": 68, "1400": 68, "bolzonella": 68, "miral": 68, "pello": 68, "2000": [68, 72, 77], "evolutionari": 68, "bruzual": 68, "charlot": 68, "1993": 68, "zphot": 68, "webast": 68, "ast": [68, 77], "ob": 68, "mip": 68, "hyperz": 68, "zphot_src_1": 68, "22050": 68, "gets": 68, "cww_e_ext_mor": 68, "cww_im_ext": 68, "cww_im_ext_mor": 68, "sbc": 68, "cww_sbc_ext_mor": 68, "acs_wfc_f435w": 68, "wfc_f435w": 68, "acs_wfc_f606w": 68, "wfc_f606w": 68, "acs_wfc_f775w": 68, "wfc_f775w": 68, "acs_wfc_f814w": 68, "acs_wfc_f850lp": 68, "wfc_f850lp": 68, "lsst_u": 68, "airmass": 68, "githubusercont": 68, "master": 68, "baselin": 68, "total_u": 68, "lsst_g": 68, "total_g": 68, "lsst_r": 68, "total_r": 68, "lsst_i": 68, "total_i": 68, "lsst_z": 68, "total_z": 68, "lsst_y": 68, "wfc3_uvis_f275w": 68, "wfc_uvis_f275w": 68, "uvi": 68, "f275w": 68, "uvis1": 68, "uvis2": 68, "throughput_t": 68, "tab": [68, 74], "wfc3_uvis_f336w": 68, "wfc_uvis_f336w": 68, "f336w": 68, "wfc3_ir_f105w": 68, "wfc_ir_f105w": 68, "ir": 68, "f105w": 68, "wfc3_ir_f125w": 68, "wfc_ir_f125w": 68, "f125w": 68, "wfc3_ir_f160w": 68, "wfc_ir_f160w": 68, "f160w": 68, "getlsstbandpass": 68, "getacsbandpass": 68, "getwfc3bandpass": 68, "lsst_itl_32": 68, "lsst_etv_32": 68, "etv": 68, "preliminari": 68, "roman_phas": 68, "a_srr_wfc_zernike_and_field_data_170727_01": 68, "a_srr_wfc_zernike_and_field_data_170727_02": 68, "a_srr_wfc_zernike_and_field_data_170727_03": 68, "a_srr_wfc_zernike_and_field_data_170727_04": 68, "a_srr_wfc_zernike_and_field_data_170727_05": 68, "a_srr_wfc_zernike_and_field_data_170727_06": 68, "a_srr_wfc_zernike_and_field_data_170727_07": 68, "a_srr_wfc_zernike_and_field_data_170727_08": 68, "a_srr_wfc_zernike_and_field_data_170727_09": 68, "a_srr_wfc_zernike_and_field_data_170727_10": 68, "a_srr_wfc_zernike_and_field_data_170727_11": 68, "a_srr_wfc_zernike_and_field_data_170727_12": 68, "a_srr_wfc_zernike_and_field_data_170727_13": 68, "a_srr_wfc_zernike_and_field_data_170727_14": 68, "14": [68, 77], "a_srr_wfc_zernike_and_field_data_170727_15": 68, "a_srr_wfc_zernike_and_field_data_170727_16": 68, "a_srr_wfc_zernike_and_field_data_170727_17": 68, "a_srr_wfc_zernike_and_field_data_170727_18": 68, "roman_srr_wfc_pupil_mask_shortwave_2048_reformat": 68, "shorter": 68, "z087": 68, "y106": 68, "j129": 68, "h158": 68, "roman_srr_wfc_pupil_mask_longwave_2048_reformat": 68, "afta_throughput": 68, "sip_7_6_8": 68, "g_1": 69, "g_2": 69, "likewis": 69, "07": 69, "03j": 69, "1j": 69, "gamma_1": 69, "gamma_2": 69, "simultan": 69, "esq": 69, "cc": [69, 77], "rotationwith": 69, "easiest": [69, 74], "shear3": [69, 74], "shear1": [69, 74], "shear2": [69, 74], "s_1": 69, "s_2": 69, "s_3": 69, "hat": [70, 74], "certainli": 70, "cgnote": 71, "tonois": 71, "calculatev": 72, "other_arg": 72, "smoother": 72, "from_func": 72, "wise": 72, "ish": 72, "lienar": 72, "x_": 72, "_lookupt": 72, "x_factor": 72, "edge_mod": 72, "newaxi": 72, "tab2d": 72, "catmul": 72, "rom": 72, "wrt": 72, "congruent": [72, 79], "_lookuptable2d": [72, 76], "xperiod": 72, "yperiod": 72, "transforamt": 73, "_transform": 73, "inv_psf": 73, "deconv_g": 73, "fouriersqrtprofil": 73, "ed": 73, "coaddit": 73, "nick": 73, "unpublish": 73, "zackai": 73, "ofek": 73, "2015arxiv151206879z": 73, "copiou": 74, "explan": 74, "image_epsf": 74, "obj3": 74, "world_profil": [74, 77], "demo4": 74, "demo5": 74, "bridl": 74, "lownois": 74, "sub_imag": 74, "slider": 74, "real_cat": 74, "image_dir": 74, "demo7": 74, "visual": 74, "scroll": 74, "cluster": 74, "distdev": 74, "truth_cat": 74, "nakajima": 74, "permut": 74, "list1": 74, "list2": 74, "bzip": 74, "psf_filenam": 74, "vn": 74, "dm": [74, 75], "hm": [74, 75], "scale_flu": 74, "e_power_fil": 74, "earli": 74, "dropout": 74, "sed2": 74, "bandpass3": 74, "psf_type": 74, "matur": 74, "great3reject": 74, "showcas": 74, "lognorm": 74, "kpc": 75, "m31": 75, "forth": 75, "stick": 75, "indirectli": 75, "collector": 75, "lump": 75, "intricaci": 75, "qualifi": 75, "27": 75, "gradian": 75, "five": 75, "5707963267948966": 75, "unit1": 75, "unit2": 75, "theta1": 75, "theta2": [75, 77], "trigonometri": [75, 77], "tant": 75, "trig": 75, "sep": 75, "plus_sign": 75, "dd": 75, "mm": 75, "ss": 75, "decim": 75, "from_dm": 75, "angle2": 75, "356999999999999": 75, "token": 75, "hh": 75, "beteen": 75, "whitespac": 75, "from_hm": 75, "357": 75, "19": 75, "99999999999998": 75, "340": 75, "_angl": 75, "honest": 75, "nonetheless": 75, "015707963267948967": 75, "valid_nam": 75, "noll_to_zern": [76, 79], "zernikerotmatrix": [76, 79], "zernikebasi": [76, 79], "zernikegradbas": [76, 79], "doublezernikebasi": [76, 79], "decor": 76, "utilti": 76, "lru": 76, "celestial_coordinate_system": 77, "imagin": 77, "uniformwc": 77, "shearwc": 77, "jacobianwc": 77, "offsetshearwc": 77, "astropywc": 77, "pyastwc": 77, "wcstoolswc": 77, "wcstool": 77, "iscelesti": 77, "isuniform": 77, "affine_wc": 77, "agre": 77, "min_linear_scal": 77, "minlinearscal": 77, "max_linear_scal": 77, "maxlinearscal": 77, "isloc": 77, "vicin": 77, "u0": 77, "fixcolor": 77, "everywher": 77, "south": 77, "semi": 77, "postoimag": 77, "postoworld": 77, "project_cent": 77, "gnomon": 77, "profiletoimag": 77, "profiletoworld": 77, "world_shear": 77, "image_shear": 77, "nw": 77, "ne": 77, "shiftorigin": 77, "world_pos1": 77, "wcs2": 77, "new_origin": 77, "world_pos2": 77, "wcs3": 77, "new_world_origin": 77, "world_pos3": 77, "uvtoxi": 77, "radectoxi": 77, "treecorr": 77, "xytouv": 77, "xytoradec": 77, "writetofitshead": 77, "surviv": 77, "trip": 77, "coincid": 77, "jac_wc": 77, "getdecomposit": 77, "singular": 77, "decompos": 77, "uses_color": 77, "thenc": 77, "_radians_": 77, "_required_": 77, "radec_func": 77, "py27": 77, "enthought": 77, "websit": 77, "wcsinfo": 77, "frameset": 77, "xy2ski": 77, "sky2xi": 77, "tdc": 77, "_data": 77, "_doiter": 77, "rigor": 77, "popular": 77, "currrent": 77, "stg": 77, "zea": 77, "arc": 77, "tnx": 77, "succe": 77, "crpix": 77, "crval": 77, "fits_wcs_typ": 77, "31": 77, "141592653589793": 77, "5410520681182421": 77, "cb": 77, "distanceto": 77, "106044260566366": 77, "43854858674231": 77, "sky_coord": 77, "748893571891069": 77, "452371275343261": 77, "21794987288635": 77, "coord2": 77, "coord3": 77, "sweep": 77, "steradian": 77, "deproject_rad": 77, "straightforward": 77, "coordiant": 77, "from_eclipt": 77, "from_galact": 77, "el": 77, "galact": 77, "greatcirclepoint": 77, "c1": 77, "c2": 77, "antipod": 77, "jac_deproject": 77, "bmatrix": 77, "j00": 77, "j01": 77, "j10": 77, "j11": 77, "textrm": 77, "jac_deproject_rad": 77, "24": 77, "precess": 77, "from_epoch": 77, "to_epoch": 77, "equatori": 77, "explanatori": 77, "supplement": 77, "ae": 77, "liesk": 77, "1977": 77, "1979": 77, "284": 77, "currect": 77, "mathworld": 77, "wolfram": 77, "gnomonicproject": 77, "stereograph": 77, "proeject": 77, "stereographicproject": 77, "lambert": 77, "azimuth": [77, 79], "lambertazimuthalequ": 77, "areaproject": 77, "postel": 77, "equidist": 77, "azimuthalequidistantproject": 77, "project_rad": 77, "radec_to_xyz": 77, "8571673007021123": 77, "0497271911386187e": 77, "5150380749100542": 77, "xyz_to_radec": 77, "return_r": 77, "839": 77, "123": 77, "530": 77, "14556615088111796": 77, "558616191048523": 77, "145566150881118": 77, "cpython": 77, "ugli": 77, "secur": 77, "bewar": 77, "untrust": 77, "encod": 77, "wcs1": 77, "modulo": 77, "great03": 78, "tangenti": 78, "orthonorm": 79, "z_i": 79, "z_j": 79, "delta_": 79, "z_0": 79, "__neg__": 79, "evalcartesian": 79, "evalcartesiangrad": 79, "evalpolar": 79, "rho": 79, "zrot": 79, "th": 79, "uv_out": 79, "uv_inn": 79, "xy_out": 79, "xy_inn": 79, "annuli": 79, "dz_": 79, "z_k": 79, "a_1": 79, "a_2": 79, "oei": 79, "a176988": 79, "rotcoef": 79, "z_2": 79, "z_3": 79, "mydatatofit": 79, "_": 79, "linalg": 79, "lstsq": 79, "resid": 79, "human": 79, "jth": 79}, "objects": {"": [[21, 0, 1, "c.GALSIM_MAJOR", "GALSIM_MAJOR"], [21, 0, 1, "c.GALSIM_MINOR", "GALSIM_MINOR"], [21, 0, 1, "c.GALSIM_REVISION", "GALSIM_REVISION"], [28, 1, 1, "_CPPv4N6galsim7ApplyCDEiPdPdPKd", "galsim::ApplyCD"], [28, 2, 1, "_CPPv4N6galsim7ApplyCDEiPdPdPKd", "galsim::ApplyCD::cd"], [28, 2, 1, "_CPPv4N6galsim7ApplyCDEiPdPdPKd", "galsim::ApplyCD::n"], [28, 2, 1, "_CPPv4N6galsim7ApplyCDEiPdPdPKd", "galsim::ApplyCD::x"], [28, 2, 1, "_CPPv4N6galsim7ApplyCDEiPdPdPKd", "galsim::ApplyCD::y"], [24, 3, 1, "_CPPv4I0EN6galsim17AssignableToImageE", "galsim::AssignableToImage"], [24, 4, 1, "_CPPv4I0EN6galsim17AssignableToImageE", "galsim::AssignableToImage::T"], [24, 1, 1, "_CPPv4NK6galsim17AssignableToImage8assignToE9ImageViewI1TE", "galsim::AssignableToImage::assignTo"], [24, 2, 1, "_CPPv4NK6galsim17AssignableToImage8assignToE9ImageViewI1TE", "galsim::AssignableToImage::assignTo::rhs"], [24, 1, 1, "_CPPv4NK6galsim17AssignableToImage9getBoundsEv", "galsim::AssignableToImage::getBounds"], [24, 1, 1, "_CPPv4N6galsim17AssignableToImageD0Ev", "galsim::AssignableToImage::~AssignableToImage"], [27, 3, 1, "_CPPv4N6galsim11BaseDeviateE", "galsim::BaseDeviate"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate11BaseDeviateEPKc", "galsim::BaseDeviate::BaseDeviate"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate11BaseDeviateERK11BaseDeviate", "galsim::BaseDeviate::BaseDeviate"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate11BaseDeviateEl", "galsim::BaseDeviate::BaseDeviate"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate11BaseDeviateEl", "galsim::BaseDeviate::BaseDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate11BaseDeviateERK11BaseDeviate", "galsim::BaseDeviate::BaseDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate11BaseDeviateEPKc", "galsim::BaseDeviate::BaseDeviate::str_c"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate11addGenerateExPd", "galsim::BaseDeviate::addGenerate"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate11addGenerateExPd", "galsim::BaseDeviate::addGenerate::N"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate11addGenerateExPd", "galsim::BaseDeviate::addGenerate::data"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate10clearCacheEv", "galsim::BaseDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate7discardEi", "galsim::BaseDeviate::discard"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate7discardEi", "galsim::BaseDeviate::discard::n"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate9duplicateEv", "galsim::BaseDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate13duplicate_ptrEv", "galsim::BaseDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate8generateExPd", "galsim::BaseDeviate::generate"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate9generate1Ev", "galsim::BaseDeviate::generate1"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate8generateExPd", "galsim::BaseDeviate::generate::N"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate8generateExPd", "galsim::BaseDeviate::generate::data"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviateclEv", "galsim::BaseDeviate::operator()"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate3rawEv", "galsim::BaseDeviate::raw"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate4reprEv", "galsim::BaseDeviate::repr"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate5resetERK11BaseDeviate", "galsim::BaseDeviate::reset"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate5resetEl", "galsim::BaseDeviate::reset"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate5resetERK11BaseDeviate", "galsim::BaseDeviate::reset::dev"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate5resetEl", "galsim::BaseDeviate::reset::lseed"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate4seedEl", "galsim::BaseDeviate::seed"], [27, 2, 1, "_CPPv4N6galsim11BaseDeviate4seedEl", "galsim::BaseDeviate::seed::lseed"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate9serializeEv", "galsim::BaseDeviate::serialize"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviate3strEv", "galsim::BaseDeviate::str"], [27, 1, 1, "_CPPv4N6galsim11BaseDeviateD0Ev", "galsim::BaseDeviate::~BaseDeviate"], [24, 3, 1, "_CPPv4I0EN6galsim9BaseImageE", "galsim::BaseImage"], [24, 4, 1, "_CPPv4I0EN6galsim9BaseImageE", "galsim::BaseImage::T"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage8assignToE9ImageViewI1TE", "galsim::BaseImage::assignTo"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage8assignToE9ImageViewI1TE", "galsim::BaseImage::assignTo::rhs"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage2atERK8PositionIiE", "galsim::BaseImage::at"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage2atEii", "galsim::BaseImage::at"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage2atERK8PositionIiE", "galsim::BaseImage::at::pos"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage2atEii", "galsim::BaseImage::at::xpos"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage2atEii", "galsim::BaseImage::at::ypos"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage4copyEv", "galsim::BaseImage::copy"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getDataEv", "galsim::BaseImage::getData"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage9getMaxPtrEv", "galsim::BaseImage::getMaxPtr"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getNColEv", "galsim::BaseImage::getNCol"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage12getNElementsEv", "galsim::BaseImage::getNElements"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getNRowEv", "galsim::BaseImage::getNRow"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage8getNSkipEv", "galsim::BaseImage::getNSkip"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage8getOwnerEv", "galsim::BaseImage::getOwner"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage6getPtrERK8PositionIiE", "galsim::BaseImage::getPtr"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage6getPtrEii", "galsim::BaseImage::getPtr"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage6getPtrERK8PositionIiE", "galsim::BaseImage::getPtr::pos"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage6getPtrEii", "galsim::BaseImage::getPtr::x"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage6getPtrEii", "galsim::BaseImage::getPtr::y"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getStepEv", "galsim::BaseImage::getStep"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage9getStrideEv", "galsim::BaseImage::getStride"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getXMaxEv", "galsim::BaseImage::getXMax"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getXMinEv", "galsim::BaseImage::getXMin"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getYMaxEv", "galsim::BaseImage::getYMax"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage7getYMinEv", "galsim::BaseImage::getYMin"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage12isContiguousEv", "galsim::BaseImage::isContiguous"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage13maxAbsElementEv", "galsim::BaseImage::maxAbsElement"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage13nonZeroBoundsEv", "galsim::BaseImage::nonZeroBounds"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage6ok_ptrEPK1T", "galsim::BaseImage::ok_ptr"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage6ok_ptrEPK1T", "galsim::BaseImage::ok_ptr::p"], [24, 1, 1, "_CPPv4NK6galsim9BaseImageclERK8PositionIiE", "galsim::BaseImage::operator()"], [24, 1, 1, "_CPPv4NK6galsim9BaseImageclEii", "galsim::BaseImage::operator()"], [24, 2, 1, "_CPPv4NK6galsim9BaseImageclERK8PositionIiE", "galsim::BaseImage::operator()::pos"], [24, 2, 1, "_CPPv4NK6galsim9BaseImageclEii", "galsim::BaseImage::operator()::xpos"], [24, 2, 1, "_CPPv4NK6galsim9BaseImageclEii", "galsim::BaseImage::operator()::ypos"], [24, 1, 1, "_CPPv4NK6galsim9BaseImageixERK6BoundsIiE", "galsim::BaseImage::operator[]"], [24, 2, 1, "_CPPv4NK6galsim9BaseImageixERK6BoundsIiE", "galsim::BaseImage::operator[]::bounds"], [24, 1, 1, "_CPPv4N6galsim9BaseImage5shiftERK8PositionIiE", "galsim::BaseImage::shift"], [24, 2, 1, "_CPPv4N6galsim9BaseImage5shiftERK8PositionIiE", "galsim::BaseImage::shift::delta"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage8subImageERK6BoundsIiE", "galsim::BaseImage::subImage"], [24, 2, 1, "_CPPv4NK6galsim9BaseImage8subImageERK6BoundsIiE", "galsim::BaseImage::subImage::bounds"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage11sumElementsEv", "galsim::BaseImage::sumElements"], [24, 1, 1, "_CPPv4NK6galsim9BaseImage4viewEv", "galsim::BaseImage::view"], [24, 1, 1, "_CPPv4N6galsim9BaseImageD0Ev", "galsim::BaseImage::~BaseImage"], [27, 3, 1, "_CPPv4N6galsim15BinomialDeviateE", "galsim::BinomialDeviate"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateEPKcid", "galsim::BinomialDeviate::BinomialDeviate"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK11BaseDeviateid", "galsim::BinomialDeviate::BinomialDeviate"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK15BinomialDeviate", "galsim::BinomialDeviate::BinomialDeviate"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateElid", "galsim::BinomialDeviate::BinomialDeviate"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateEPKcid", "galsim::BinomialDeviate::BinomialDeviate::N"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK11BaseDeviateid", "galsim::BinomialDeviate::BinomialDeviate::N"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateElid", "galsim::BinomialDeviate::BinomialDeviate::N"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateElid", "galsim::BinomialDeviate::BinomialDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateEPKcid", "galsim::BinomialDeviate::BinomialDeviate::p"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK11BaseDeviateid", "galsim::BinomialDeviate::BinomialDeviate::p"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateElid", "galsim::BinomialDeviate::BinomialDeviate::p"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK11BaseDeviateid", "galsim::BinomialDeviate::BinomialDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK15BinomialDeviate", "galsim::BinomialDeviate::BinomialDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateEPKcid", "galsim::BinomialDeviate::BinomialDeviate::str_c"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate10clearCacheEv", "galsim::BinomialDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate9duplicateEv", "galsim::BinomialDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate13duplicate_ptrEv", "galsim::BinomialDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate9generate1Ev", "galsim::BinomialDeviate::generate1"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate4getNEv", "galsim::BinomialDeviate::getN"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate4getPEv", "galsim::BinomialDeviate::getP"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate4setNEi", "galsim::BinomialDeviate::setN"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate4setNEi", "galsim::BinomialDeviate::setN::N"], [27, 1, 1, "_CPPv4N6galsim15BinomialDeviate4setPEd", "galsim::BinomialDeviate::setP"], [27, 2, 1, "_CPPv4N6galsim15BinomialDeviate4setPEd", "galsim::BinomialDeviate::setP::p"], [22, 3, 1, "_CPPv4I0EN6galsim6BoundsE", "galsim::Bounds"], [22, 1, 1, "_CPPv4N6galsim6Bounds6BoundsEK1TK1TK1TK1T", "galsim::Bounds::Bounds"], [22, 1, 1, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TE", "galsim::Bounds::Bounds"], [22, 1, 1, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TERK8PositionI1TE", "galsim::Bounds::Bounds"], [22, 1, 1, "_CPPv4N6galsim6Bounds6BoundsEv", "galsim::Bounds::Bounds"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TE", "galsim::Bounds::Bounds::pos"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TERK8PositionI1TE", "galsim::Bounds::Bounds::pos1"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TERK8PositionI1TE", "galsim::Bounds::Bounds::pos2"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsEK1TK1TK1TK1T", "galsim::Bounds::Bounds::x1"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsEK1TK1TK1TK1T", "galsim::Bounds::Bounds::x2"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsEK1TK1TK1TK1T", "galsim::Bounds::Bounds::y1"], [22, 2, 1, "_CPPv4N6galsim6Bounds6BoundsEK1TK1TK1TK1T", "galsim::Bounds::Bounds::y2"], [22, 4, 1, "_CPPv4I0EN6galsim6BoundsE", "galsim::Bounds::T"], [22, 1, 1, "_CPPv4N6galsim6Bounds9addBorderEK1T", "galsim::Bounds::addBorder"], [22, 2, 1, "_CPPv4N6galsim6Bounds9addBorderEK1T", "galsim::Bounds::addBorder::d"], [22, 1, 1, "_CPPv4NK6galsim6Bounds4areaEv", "galsim::Bounds::area"], [22, 1, 1, "_CPPv4NK6galsim6Bounds6centerEv", "galsim::Bounds::center"], [22, 1, 1, "_CPPv4NK6galsim6Bounds4copyEv", "galsim::Bounds::copy"], [22, 1, 1, "_CPPv4NK6galsim6Bounds6divideEii", "galsim::Bounds::divide"], [22, 2, 1, "_CPPv4NK6galsim6Bounds6divideEii", "galsim::Bounds::divide::nx"], [22, 2, 1, "_CPPv4NK6galsim6Bounds6divideEii", "galsim::Bounds::divide::ny"], [22, 1, 1, "_CPPv4N6galsim6Bounds6expandEKd", "galsim::Bounds::expand"], [22, 2, 1, "_CPPv4N6galsim6Bounds6expandEKd", "galsim::Bounds::expand::m"], [22, 1, 1, "_CPPv4NK6galsim6Bounds7getXMaxEv", "galsim::Bounds::getXMax"], [22, 1, 1, "_CPPv4NK6galsim6Bounds7getXMinEv", "galsim::Bounds::getXMin"], [22, 1, 1, "_CPPv4NK6galsim6Bounds7getYMaxEv", "galsim::Bounds::getYMax"], [22, 1, 1, "_CPPv4NK6galsim6Bounds7getYMinEv", "galsim::Bounds::getYMin"], [22, 1, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbK2T2K2T2", "galsim::Bounds::includes"], [22, 1, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbRK8PositionI2T2E", "galsim::Bounds::includes"], [22, 1, 1, "_CPPv4NK6galsim6Bounds8includesERK6BoundsI1TE", "galsim::Bounds::includes"], [22, 4, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbK2T2K2T2", "galsim::Bounds::includes::T2"], [22, 4, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbRK8PositionI2T2E", "galsim::Bounds::includes::T2"], [22, 2, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbRK8PositionI2T2E", "galsim::Bounds::includes::pos"], [22, 2, 1, "_CPPv4NK6galsim6Bounds8includesERK6BoundsI1TE", "galsim::Bounds::includes::rhs"], [22, 2, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbK2T2K2T2", "galsim::Bounds::includes::x"], [22, 2, 1, "_CPPv4I0ENK6galsim6Bounds8includesEbK2T2K2T2", "galsim::Bounds::includes::y"], [22, 1, 1, "_CPPv4NK6galsim6Bounds9isDefinedEv", "galsim::Bounds::isDefined"], [22, 1, 1, "_CPPv4NK6galsim6Bounds13isSameShapeAsERK6BoundsI1TE", "galsim::Bounds::isSameShapeAs"], [22, 2, 1, "_CPPv4NK6galsim6Bounds13isSameShapeAsERK6BoundsI1TE", "galsim::Bounds::isSameShapeAs::rhs"], [22, 1, 1, "_CPPv4NK6galsim6Bounds12makeExpandedEKd", "galsim::Bounds::makeExpanded"], [22, 2, 1, "_CPPv4NK6galsim6Bounds12makeExpandedEKd", "galsim::Bounds::makeExpanded::m"], [22, 1, 1, "_CPPv4NK6galsim6Bounds11makeShiftedEK1TK1T", "galsim::Bounds::makeShifted"], [22, 1, 1, "_CPPv4NK6galsim6Bounds11makeShiftedERK8PositionI1TE", "galsim::Bounds::makeShifted"], [22, 2, 1, "_CPPv4NK6galsim6Bounds11makeShiftedERK8PositionI1TE", "galsim::Bounds::makeShifted::delta"], [22, 2, 1, "_CPPv4NK6galsim6Bounds11makeShiftedEK1TK1T", "galsim::Bounds::makeShifted::dx"], [22, 2, 1, "_CPPv4NK6galsim6Bounds11makeShiftedEK1TK1T", "galsim::Bounds::makeShifted::dy"], [22, 1, 1, "_CPPv4NK6galsim6BoundsneERK6BoundsI1TE", "galsim::Bounds::operator!="], [22, 2, 1, "_CPPv4NK6galsim6BoundsneERK6BoundsI1TE", "galsim::Bounds::operator!=::rhs"], [22, 1, 1, "_CPPv4NK6galsim6BoundsanERK6BoundsI1TE", "galsim::Bounds::operator&"], [22, 2, 1, "_CPPv4NK6galsim6BoundsanERK6BoundsI1TE", "galsim::Bounds::operator&::rhs"], [22, 1, 1, "_CPPv4NK6galsim6BoundsplEK1T", "galsim::Bounds::operator+"], [22, 1, 1, "_CPPv4NK6galsim6BoundsplERK6BoundsI1TE", "galsim::Bounds::operator+"], [22, 1, 1, "_CPPv4NK6galsim6BoundsplERK8PositionI1TE", "galsim::Bounds::operator+"], [22, 2, 1, "_CPPv4NK6galsim6BoundsplEK1T", "galsim::Bounds::operator+::d"], [22, 2, 1, "_CPPv4NK6galsim6BoundsplERK8PositionI1TE", "galsim::Bounds::operator+::pos"], [22, 2, 1, "_CPPv4NK6galsim6BoundsplERK6BoundsI1TE", "galsim::Bounds::operator+::rec"], [22, 1, 1, "_CPPv4N6galsim6BoundspLEK1T", "galsim::Bounds::operator+="], [22, 1, 1, "_CPPv4N6galsim6BoundspLERK6BoundsI1TE", "galsim::Bounds::operator+="], [22, 1, 1, "_CPPv4N6galsim6BoundspLERK8PositionI1TE", "galsim::Bounds::operator+="], [22, 2, 1, "_CPPv4N6galsim6BoundspLEK1T", "galsim::Bounds::operator+=::d"], [22, 2, 1, "_CPPv4N6galsim6BoundspLERK8PositionI1TE", "galsim::Bounds::operator+=::pos"], [22, 2, 1, "_CPPv4N6galsim6BoundspLERK6BoundsI1TE", "galsim::Bounds::operator+=::rec"], [22, 1, 1, "_CPPv4NK6galsim6BoundseqERK6BoundsI1TE", "galsim::Bounds::operator=="], [22, 2, 1, "_CPPv4NK6galsim6BoundseqERK6BoundsI1TE", "galsim::Bounds::operator==::rhs"], [22, 1, 1, "_CPPv4NK6galsim6Bounds6originEv", "galsim::Bounds::origin"], [22, 1, 1, "_CPPv4N6galsim6Bounds4readERNSt7istreamE", "galsim::Bounds::read"], [22, 2, 1, "_CPPv4N6galsim6Bounds4readERNSt7istreamE", "galsim::Bounds::read::fin"], [22, 1, 1, "_CPPv4N6galsim6Bounds7setXMaxEK1T", "galsim::Bounds::setXMax"], [22, 2, 1, "_CPPv4N6galsim6Bounds7setXMaxEK1T", "galsim::Bounds::setXMax::x"], [22, 1, 1, "_CPPv4N6galsim6Bounds7setXMinEK1T", "galsim::Bounds::setXMin"], [22, 2, 1, "_CPPv4N6galsim6Bounds7setXMinEK1T", "galsim::Bounds::setXMin::x"], [22, 1, 1, "_CPPv4N6galsim6Bounds7setYMaxEK1T", "galsim::Bounds::setYMax"], [22, 2, 1, "_CPPv4N6galsim6Bounds7setYMaxEK1T", "galsim::Bounds::setYMax::y"], [22, 1, 1, "_CPPv4N6galsim6Bounds7setYMinEK1T", "galsim::Bounds::setYMin"], [22, 2, 1, "_CPPv4N6galsim6Bounds7setYMinEK1T", "galsim::Bounds::setYMin::y"], [22, 1, 1, "_CPPv4N6galsim6Bounds5shiftEK1TK1T", "galsim::Bounds::shift"], [22, 1, 1, "_CPPv4N6galsim6Bounds5shiftERK8PositionI1TE", "galsim::Bounds::shift"], [22, 2, 1, "_CPPv4N6galsim6Bounds5shiftERK8PositionI1TE", "galsim::Bounds::shift::delta"], [22, 2, 1, "_CPPv4N6galsim6Bounds5shiftEK1TK1T", "galsim::Bounds::shift::dx"], [22, 2, 1, "_CPPv4N6galsim6Bounds5shiftEK1TK1T", "galsim::Bounds::shift::dy"], [22, 1, 1, "_CPPv4NK6galsim6Bounds10trueCenterEv", "galsim::Bounds::trueCenter"], [22, 1, 1, "_CPPv4NK6galsim6Bounds10withBorderEK1T", "galsim::Bounds::withBorder"], [22, 2, 1, "_CPPv4NK6galsim6Bounds10withBorderEK1T", "galsim::Bounds::withBorder::d"], [22, 1, 1, "_CPPv4NK6galsim6Bounds5writeERNSt7ostreamE", "galsim::Bounds::write"], [22, 2, 1, "_CPPv4NK6galsim6Bounds5writeERNSt7ostreamE", "galsim::Bounds::write::fout"], [22, 1, 1, "_CPPv4N6galsim6BoundsD0Ev", "galsim::Bounds::~Bounds"], [27, 3, 1, "_CPPv4N6galsim11Chi2DeviateE", "galsim::Chi2Deviate"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEPKcd", "galsim::Chi2Deviate::Chi2Deviate"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11BaseDeviated", "galsim::Chi2Deviate::Chi2Deviate"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11Chi2Deviate", "galsim::Chi2Deviate::Chi2Deviate"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEld", "galsim::Chi2Deviate::Chi2Deviate"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEld", "galsim::Chi2Deviate::Chi2Deviate::lseed"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEPKcd", "galsim::Chi2Deviate::Chi2Deviate::n"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11BaseDeviated", "galsim::Chi2Deviate::Chi2Deviate::n"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEld", "galsim::Chi2Deviate::Chi2Deviate::n"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11BaseDeviated", "galsim::Chi2Deviate::Chi2Deviate::rhs"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11Chi2Deviate", "galsim::Chi2Deviate::Chi2Deviate::rhs"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEPKcd", "galsim::Chi2Deviate::Chi2Deviate::str_c"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate10clearCacheEv", "galsim::Chi2Deviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate9duplicateEv", "galsim::Chi2Deviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate13duplicate_ptrEv", "galsim::Chi2Deviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate9generate1Ev", "galsim::Chi2Deviate::generate1"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate4getNEv", "galsim::Chi2Deviate::getN"], [27, 1, 1, "_CPPv4N6galsim11Chi2Deviate4setNEd", "galsim::Chi2Deviate::setN"], [27, 2, 1, "_CPPv4N6galsim11Chi2Deviate4setNEd", "galsim::Chi2Deviate::setN::n"], [24, 1, 1, "_CPPv4N6galsim20ClearDepixelizeCacheEv", "galsim::ClearDepixelizeCache"], [24, 3, 1, "_CPPv4I0EN6galsim14ConstImageViewE", "galsim::ConstImageView"], [24, 1, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView"], [24, 1, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewERK14ConstImageViewI1TE", "galsim::ConstImageView::ConstImageView"], [24, 1, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewERK9BaseImageI1TE", "galsim::ConstImageView::ConstImageView"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::b"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::data"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::maxptr"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::nElements"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::owner"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewERK14ConstImageViewI1TE", "galsim::ConstImageView::ConstImageView::rhs"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewERK9BaseImageI1TE", "galsim::ConstImageView::ConstImageView::rhs"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::step"], [24, 2, 1, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ConstImageView::ConstImageView::stride"], [24, 4, 1, "_CPPv4I0EN6galsim14ConstImageViewE", "galsim::ConstImageView::T"], [24, 1, 1, "_CPPv4NK6galsim14ConstImageView4viewEv", "galsim::ConstImageView::view"], [25, 3, 1, "_CPPv4N6galsim5CubicE", "galsim::Cubic"], [25, 1, 1, "_CPPv4N6galsim5Cubic5CubicERK8GSParams", "galsim::Cubic::Cubic"], [25, 2, 1, "_CPPv4N6galsim5Cubic5CubicERK8GSParams", "galsim::Cubic::Cubic::gsparams"], [25, 1, 1, "_CPPv4NK6galsim5Cubic15getNegativeFluxEv", "galsim::Cubic::getNegativeFlux"], [25, 1, 1, "_CPPv4NK6galsim5Cubic15getPositiveFluxEv", "galsim::Cubic::getPositiveFlux"], [25, 1, 1, "_CPPv4NK6galsim5Cubic7ixrangeEv", "galsim::Cubic::ixrange"], [25, 1, 1, "_CPPv4NK6galsim5Cubic7makeStrEv", "galsim::Cubic::makeStr"], [25, 1, 1, "_CPPv4NK6galsim5Cubic6urangeEv", "galsim::Cubic::urange"], [25, 1, 1, "_CPPv4NK6galsim5Cubic4uvalEd", "galsim::Cubic::uval"], [25, 2, 1, "_CPPv4NK6galsim5Cubic4uvalEd", "galsim::Cubic::uval::u"], [25, 1, 1, "_CPPv4NK6galsim5Cubic6xrangeEv", "galsim::Cubic::xrange"], [25, 1, 1, "_CPPv4NK6galsim5Cubic4xvalEd", "galsim::Cubic::xval"], [25, 2, 1, "_CPPv4NK6galsim5Cubic4xvalEd", "galsim::Cubic::xval::x"], [25, 1, 1, "_CPPv4N6galsim5CubicD0Ev", "galsim::Cubic::~Cubic"], [25, 3, 1, "_CPPv4N6galsim5DeltaE", "galsim::Delta"], [25, 1, 1, "_CPPv4N6galsim5Delta5DeltaERK8GSParams", "galsim::Delta::Delta"], [25, 2, 1, "_CPPv4N6galsim5Delta5DeltaERK8GSParams", "galsim::Delta::Delta::gsparams"], [25, 1, 1, "_CPPv4NK6galsim5Delta15getNegativeFluxEv", "galsim::Delta::getNegativeFlux"], [25, 1, 1, "_CPPv4NK6galsim5Delta15getPositiveFluxEv", "galsim::Delta::getPositiveFlux"], [25, 1, 1, "_CPPv4NK6galsim5Delta7ixrangeEv", "galsim::Delta::ixrange"], [25, 1, 1, "_CPPv4NK6galsim5Delta7makeStrEv", "galsim::Delta::makeStr"], [25, 1, 1, "_CPPv4NK6galsim5Delta5shootER11PhotonArray14UniformDeviate", "galsim::Delta::shoot"], [25, 2, 1, "_CPPv4NK6galsim5Delta5shootER11PhotonArray14UniformDeviate", "galsim::Delta::shoot::photons"], [25, 2, 1, "_CPPv4NK6galsim5Delta5shootER11PhotonArray14UniformDeviate", "galsim::Delta::shoot::ud"], [25, 1, 1, "_CPPv4NK6galsim5Delta6urangeEv", "galsim::Delta::urange"], [25, 1, 1, "_CPPv4NK6galsim5Delta4uvalEd", "galsim::Delta::uval"], [25, 2, 1, "_CPPv4NK6galsim5Delta4uvalEd", "galsim::Delta::uval::u"], [25, 1, 1, "_CPPv4NK6galsim5Delta6xrangeEv", "galsim::Delta::xrange"], [25, 1, 1, "_CPPv4NK6galsim5Delta4xvalEd", "galsim::Delta::xval"], [25, 2, 1, "_CPPv4NK6galsim5Delta4xvalEd", "galsim::Delta::xval::x"], [25, 1, 1, "_CPPv4N6galsim5DeltaD0Ev", "galsim::Delta::~Delta"], [29, 3, 1, "_CPPv4N6galsim8GSParamsE", "galsim::GSParams"], [29, 1, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams"], [29, 1, 1, "_CPPv4N6galsim8GSParams8GSParamsEv", "galsim::GSParams::GSParams"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_folding_threshold"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_integration_abserr"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_integration_relerr"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_kvalue_accuracy"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_maximum_fft_size"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_maxk_threshold"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_minimum_fft_size"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_realspace_abserr"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_realspace_relerr"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_shoot_accuracy"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_stepk_minimum_hlr"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_table_spacing"], [29, 2, 1, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd", "galsim::GSParams::GSParams::_xvalue_accuracy"], [29, 5, 1, "_CPPv4N6galsim8GSParams17folding_thresholdE", "galsim::GSParams::folding_threshold"], [29, 5, 1, "_CPPv4N6galsim8GSParams18integration_abserrE", "galsim::GSParams::integration_abserr"], [29, 5, 1, "_CPPv4N6galsim8GSParams18integration_relerrE", "galsim::GSParams::integration_relerr"], [29, 5, 1, "_CPPv4N6galsim8GSParams15kvalue_accuracyE", "galsim::GSParams::kvalue_accuracy"], [29, 5, 1, "_CPPv4N6galsim8GSParams16maximum_fft_sizeE", "galsim::GSParams::maximum_fft_size"], [29, 5, 1, "_CPPv4N6galsim8GSParams14maxk_thresholdE", "galsim::GSParams::maxk_threshold"], [29, 5, 1, "_CPPv4N6galsim8GSParams16minimum_fft_sizeE", "galsim::GSParams::minimum_fft_size"], [29, 1, 1, "_CPPv4NK6galsim8GSParamsltERK8GSParams", "galsim::GSParams::operator<"], [29, 2, 1, "_CPPv4NK6galsim8GSParamsltERK8GSParams", "galsim::GSParams::operator<::rhs"], [29, 1, 1, "_CPPv4NK6galsim8GSParamseqERK8GSParams", "galsim::GSParams::operator=="], [29, 2, 1, "_CPPv4NK6galsim8GSParamseqERK8GSParams", "galsim::GSParams::operator==::rhs"], [29, 5, 1, "_CPPv4N6galsim8GSParams16realspace_abserrE", "galsim::GSParams::realspace_abserr"], [29, 5, 1, "_CPPv4N6galsim8GSParams16realspace_relerrE", "galsim::GSParams::realspace_relerr"], [29, 5, 1, "_CPPv4N6galsim8GSParams14shoot_accuracyE", "galsim::GSParams::shoot_accuracy"], [29, 5, 1, "_CPPv4N6galsim8GSParams17stepk_minimum_hlrE", "galsim::GSParams::stepk_minimum_hlr"], [29, 5, 1, "_CPPv4N6galsim8GSParams13table_spacingE", "galsim::GSParams::table_spacing"], [29, 5, 1, "_CPPv4N6galsim8GSParams15xvalue_accuracyE", "galsim::GSParams::xvalue_accuracy"], [27, 3, 1, "_CPPv4N6galsim12GammaDeviateE", "galsim::GammaDeviate"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEPKcdd", "galsim::GammaDeviate::GammaDeviate"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK11BaseDeviatedd", "galsim::GammaDeviate::GammaDeviate"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK12GammaDeviate", "galsim::GammaDeviate::GammaDeviate"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEldd", "galsim::GammaDeviate::GammaDeviate"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEPKcdd", "galsim::GammaDeviate::GammaDeviate::k"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK11BaseDeviatedd", "galsim::GammaDeviate::GammaDeviate::k"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEldd", "galsim::GammaDeviate::GammaDeviate::k"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEldd", "galsim::GammaDeviate::GammaDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK11BaseDeviatedd", "galsim::GammaDeviate::GammaDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK12GammaDeviate", "galsim::GammaDeviate::GammaDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEPKcdd", "galsim::GammaDeviate::GammaDeviate::str_c"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEPKcdd", "galsim::GammaDeviate::GammaDeviate::theta"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK11BaseDeviatedd", "galsim::GammaDeviate::GammaDeviate::theta"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEldd", "galsim::GammaDeviate::GammaDeviate::theta"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate10clearCacheEv", "galsim::GammaDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate9duplicateEv", "galsim::GammaDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate13duplicate_ptrEv", "galsim::GammaDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate9generate1Ev", "galsim::GammaDeviate::generate1"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate4getKEv", "galsim::GammaDeviate::getK"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate8getThetaEv", "galsim::GammaDeviate::getTheta"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate4setKEd", "galsim::GammaDeviate::setK"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate4setKEd", "galsim::GammaDeviate::setK::k"], [27, 1, 1, "_CPPv4N6galsim12GammaDeviate8setThetaEd", "galsim::GammaDeviate::setTheta"], [27, 2, 1, "_CPPv4N6galsim12GammaDeviate8setThetaEd", "galsim::GammaDeviate::setTheta::theta"], [27, 3, 1, "_CPPv4N6galsim15GaussianDeviateE", "galsim::GaussianDeviate"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEPKcdd", "galsim::GaussianDeviate::GaussianDeviate"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK11BaseDeviatedd", "galsim::GaussianDeviate::GaussianDeviate"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK15GaussianDeviate", "galsim::GaussianDeviate::GaussianDeviate"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEldd", "galsim::GaussianDeviate::GaussianDeviate"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEldd", "galsim::GaussianDeviate::GaussianDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEPKcdd", "galsim::GaussianDeviate::GaussianDeviate::mean"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK11BaseDeviatedd", "galsim::GaussianDeviate::GaussianDeviate::mean"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEldd", "galsim::GaussianDeviate::GaussianDeviate::mean"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK11BaseDeviatedd", "galsim::GaussianDeviate::GaussianDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK15GaussianDeviate", "galsim::GaussianDeviate::GaussianDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEPKcdd", "galsim::GaussianDeviate::GaussianDeviate::sigma"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK11BaseDeviatedd", "galsim::GaussianDeviate::GaussianDeviate::sigma"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEldd", "galsim::GaussianDeviate::GaussianDeviate::sigma"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEPKcdd", "galsim::GaussianDeviate::GaussianDeviate::str_c"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate10clearCacheEv", "galsim::GaussianDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate9duplicateEv", "galsim::GaussianDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate13duplicate_ptrEv", "galsim::GaussianDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate9generate1Ev", "galsim::GaussianDeviate::generate1"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate20generateFromVarianceExPd", "galsim::GaussianDeviate::generateFromVariance"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate20generateFromVarianceExPd", "galsim::GaussianDeviate::generateFromVariance::N"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate20generateFromVarianceExPd", "galsim::GaussianDeviate::generateFromVariance::data"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate7getMeanEv", "galsim::GaussianDeviate::getMean"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate8getSigmaEv", "galsim::GaussianDeviate::getSigma"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate7setMeanEd", "galsim::GaussianDeviate::setMean"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate7setMeanEd", "galsim::GaussianDeviate::setMean::mean"], [27, 1, 1, "_CPPv4N6galsim15GaussianDeviate8setSigmaEd", "galsim::GaussianDeviate::setSigma"], [27, 2, 1, "_CPPv4N6galsim15GaussianDeviate8setSigmaEd", "galsim::GaussianDeviate::setSigma::sigma"], [26, 1, 1, "_CPPv4N6galsim13GetOMPThreadsEv", "galsim::GetOMPThreads"], [24, 3, 1, "_CPPv4I0EN6galsim10ImageAllocE", "galsim::ImageAlloc"], [24, 1, 1, "_CPPv4I0EN6galsim10ImageAlloc10ImageAllocERK9BaseImageI1UE", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK10ImageAllocI1TE", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK17AssignableToImageI1TE", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE1T", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii1T", "galsim::ImageAlloc::ImageAlloc"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEv", "galsim::ImageAlloc::ImageAlloc"], [24, 4, 1, "_CPPv4I0EN6galsim10ImageAlloc10ImageAllocERK9BaseImageI1UE", "galsim::ImageAlloc::ImageAlloc::U"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE", "galsim::ImageAlloc::ImageAlloc::bounds"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE1T", "galsim::ImageAlloc::ImageAlloc::bounds"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE1T", "galsim::ImageAlloc::ImageAlloc::init_value"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii1T", "galsim::ImageAlloc::ImageAlloc::init_value"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii", "galsim::ImageAlloc::ImageAlloc::ncol"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii1T", "galsim::ImageAlloc::ImageAlloc::ncol"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii", "galsim::ImageAlloc::ImageAlloc::nrow"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii1T", "galsim::ImageAlloc::ImageAlloc::nrow"], [24, 2, 1, "_CPPv4I0EN6galsim10ImageAlloc10ImageAllocERK9BaseImageI1UE", "galsim::ImageAlloc::ImageAlloc::rhs"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK10ImageAllocI1TE", "galsim::ImageAlloc::ImageAlloc::rhs"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK17AssignableToImageI1TE", "galsim::ImageAlloc::ImageAlloc::rhs"], [24, 4, 1, "_CPPv4I0EN6galsim10ImageAllocE", "galsim::ImageAlloc::T"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc2atERK8PositionIiE", "galsim::ImageAlloc::at"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc2atEii", "galsim::ImageAlloc::at"], [24, 1, 1, "_CPPv4NK6galsim10ImageAlloc2atERK8PositionIiE", "galsim::ImageAlloc::at"], [24, 1, 1, "_CPPv4NK6galsim10ImageAlloc2atEii", "galsim::ImageAlloc::at"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc2atERK8PositionIiE", "galsim::ImageAlloc::at::pos"], [24, 2, 1, "_CPPv4NK6galsim10ImageAlloc2atERK8PositionIiE", "galsim::ImageAlloc::at::pos"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc2atEii", "galsim::ImageAlloc::at::xpos"], [24, 2, 1, "_CPPv4NK6galsim10ImageAlloc2atEii", "galsim::ImageAlloc::at::xpos"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc2atEii", "galsim::ImageAlloc::at::ypos"], [24, 2, 1, "_CPPv4NK6galsim10ImageAlloc2atEii", "galsim::ImageAlloc::at::ypos"], [24, 1, 1, "_CPPv4I0EN6galsim10ImageAlloc8copyFromEvRK9BaseImageI1UE", "galsim::ImageAlloc::copyFrom"], [24, 4, 1, "_CPPv4I0EN6galsim10ImageAlloc8copyFromEvRK9BaseImageI1UE", "galsim::ImageAlloc::copyFrom::U"], [24, 2, 1, "_CPPv4I0EN6galsim10ImageAlloc8copyFromEvRK9BaseImageI1UE", "galsim::ImageAlloc::copyFrom::rhs"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc4fillE1T", "galsim::ImageAlloc::fill"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc4fillE1T", "galsim::ImageAlloc::fill::x"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc7getDataEv", "galsim::ImageAlloc::getData"], [24, 1, 1, "_CPPv4NK6galsim10ImageAlloc7getDataEv", "galsim::ImageAlloc::getData"], [24, 1, 1, "_CPPv4NK6galsim10ImageAlloc9getMaxPtrEv", "galsim::ImageAlloc::getMaxPtr"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc10invertSelfEv", "galsim::ImageAlloc::invertSelf"], [24, 1, 1, "_CPPv4N6galsim10ImageAllocclERK8PositionIiE", "galsim::ImageAlloc::operator()"], [24, 1, 1, "_CPPv4N6galsim10ImageAllocclEii", "galsim::ImageAlloc::operator()"], [24, 1, 1, "_CPPv4NK6galsim10ImageAllocclERK8PositionIiE", "galsim::ImageAlloc::operator()"], [24, 1, 1, "_CPPv4NK6galsim10ImageAllocclEii", "galsim::ImageAlloc::operator()"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocclERK8PositionIiE", "galsim::ImageAlloc::operator()::pos"], [24, 2, 1, "_CPPv4NK6galsim10ImageAllocclERK8PositionIiE", "galsim::ImageAlloc::operator()::pos"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocclEii", "galsim::ImageAlloc::operator()::xpos"], [24, 2, 1, "_CPPv4NK6galsim10ImageAllocclEii", "galsim::ImageAlloc::operator()::xpos"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocclEii", "galsim::ImageAlloc::operator()::ypos"], [24, 2, 1, "_CPPv4NK6galsim10ImageAllocclEii", "galsim::ImageAlloc::operator()::ypos"], [24, 1, 1, "_CPPv4I0EN6galsim10ImageAllocaSER10ImageAllocI1TERK9BaseImageI1UE", "galsim::ImageAlloc::operator="], [24, 1, 1, "_CPPv4N6galsim10ImageAllocaSE1T", "galsim::ImageAlloc::operator="], [24, 1, 1, "_CPPv4N6galsim10ImageAllocaSERK10ImageAllocI1TE", "galsim::ImageAlloc::operator="], [24, 1, 1, "_CPPv4N6galsim10ImageAllocaSERK17AssignableToImageI1TE", "galsim::ImageAlloc::operator="], [24, 4, 1, "_CPPv4I0EN6galsim10ImageAllocaSER10ImageAllocI1TERK9BaseImageI1UE", "galsim::ImageAlloc::operator=::U"], [24, 2, 1, "_CPPv4I0EN6galsim10ImageAllocaSER10ImageAllocI1TERK9BaseImageI1UE", "galsim::ImageAlloc::operator=::rhs"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocaSERK10ImageAllocI1TE", "galsim::ImageAlloc::operator=::rhs"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocaSERK17AssignableToImageI1TE", "galsim::ImageAlloc::operator=::rhs"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocaSE1T", "galsim::ImageAlloc::operator=::x"], [24, 1, 1, "_CPPv4N6galsim10ImageAllocixERK6BoundsIiE", "galsim::ImageAlloc::operator[]"], [24, 1, 1, "_CPPv4NK6galsim10ImageAllocixERK6BoundsIiE", "galsim::ImageAlloc::operator[]"], [24, 2, 1, "_CPPv4N6galsim10ImageAllocixERK6BoundsIiE", "galsim::ImageAlloc::operator[]::bounds"], [24, 2, 1, "_CPPv4NK6galsim10ImageAllocixERK6BoundsIiE", "galsim::ImageAlloc::operator[]::bounds"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc6resizeERK6BoundsIiEb", "galsim::ImageAlloc::resize"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc6resizeERK6BoundsIiEb", "galsim::ImageAlloc::resize::new_bounds"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc6resizeERK6BoundsIiEb", "galsim::ImageAlloc::resize::release"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc8setValueEii1T", "galsim::ImageAlloc::setValue"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc8setValueEii1T", "galsim::ImageAlloc::setValue::value"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc8setValueEii1T", "galsim::ImageAlloc::setValue::x"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc8setValueEii1T", "galsim::ImageAlloc::setValue::y"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc7setZeroEv", "galsim::ImageAlloc::setZero"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc8subImageERK6BoundsIiE", "galsim::ImageAlloc::subImage"], [24, 1, 1, "_CPPv4NK6galsim10ImageAlloc8subImageERK6BoundsIiE", "galsim::ImageAlloc::subImage"], [24, 2, 1, "_CPPv4N6galsim10ImageAlloc8subImageERK6BoundsIiE", "galsim::ImageAlloc::subImage::bounds"], [24, 2, 1, "_CPPv4NK6galsim10ImageAlloc8subImageERK6BoundsIiE", "galsim::ImageAlloc::subImage::bounds"], [24, 1, 1, "_CPPv4N6galsim10ImageAlloc4viewEv", "galsim::ImageAlloc::view"], [24, 1, 1, "_CPPv4NK6galsim10ImageAlloc4viewEv", "galsim::ImageAlloc::view"], [24, 3, 1, "_CPPv4N6galsim16ImageBoundsErrorE", "galsim::ImageBoundsError"], [24, 1, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringE", "galsim::ImageBoundsError::ImageBoundsError"], [24, 1, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringEiii", "galsim::ImageBoundsError::ImageBoundsError"], [24, 1, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorEiiK6BoundsIiE", "galsim::ImageBoundsError::ImageBoundsError"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorEiiK6BoundsIiE", "galsim::ImageBoundsError::ImageBoundsError::b"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringE", "galsim::ImageBoundsError::ImageBoundsError::m"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringEiii", "galsim::ImageBoundsError::ImageBoundsError::m"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringEiii", "galsim::ImageBoundsError::ImageBoundsError::max"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringEiii", "galsim::ImageBoundsError::ImageBoundsError::min"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringEiii", "galsim::ImageBoundsError::ImageBoundsError::tried"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorEiiK6BoundsIiE", "galsim::ImageBoundsError::ImageBoundsError::x"], [24, 2, 1, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorEiiK6BoundsIiE", "galsim::ImageBoundsError::ImageBoundsError::y"], [24, 3, 1, "_CPPv4N6galsim10ImageErrorE", "galsim::ImageError"], [24, 1, 1, "_CPPv4N6galsim10ImageError10ImageErrorERKNSt6stringE", "galsim::ImageError::ImageError"], [24, 2, 1, "_CPPv4N6galsim10ImageError10ImageErrorERKNSt6stringE", "galsim::ImageError::ImageError::m"], [24, 3, 1, "_CPPv4I0EN6galsim9ImageViewE", "galsim::ImageView"], [24, 1, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView"], [24, 1, 1, "_CPPv4N6galsim9ImageView9ImageViewER10ImageAllocI1TE", "galsim::ImageView::ImageView"], [24, 1, 1, "_CPPv4N6galsim9ImageView9ImageViewERK9ImageViewI1TE", "galsim::ImageView::ImageView"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::b"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::data"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::maxptr"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::nElements"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::owner"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewER10ImageAllocI1TE", "galsim::ImageView::ImageView::rhs"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewERK9ImageViewI1TE", "galsim::ImageView::ImageView::rhs"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::step"], [24, 2, 1, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE", "galsim::ImageView::ImageView::stride"], [24, 4, 1, "_CPPv4I0EN6galsim9ImageViewE", "galsim::ImageView::T"], [24, 1, 1, "_CPPv4N6galsim9ImageView2atERK8PositionIiE", "galsim::ImageView::at"], [24, 1, 1, "_CPPv4N6galsim9ImageView2atEii", "galsim::ImageView::at"], [24, 2, 1, "_CPPv4N6galsim9ImageView2atERK8PositionIiE", "galsim::ImageView::at::pos"], [24, 2, 1, "_CPPv4N6galsim9ImageView2atEii", "galsim::ImageView::at::xpos"], [24, 2, 1, "_CPPv4N6galsim9ImageView2atEii", "galsim::ImageView::at::ypos"], [24, 1, 1, "_CPPv4I0EN6galsim9ImageView8copyFromEvRK9BaseImageI1UE", "galsim::ImageView::copyFrom"], [24, 1, 1, "_CPPv4N6galsim9ImageView8copyFromERK9BaseImageI1TE", "galsim::ImageView::copyFrom"], [24, 4, 1, "_CPPv4I0EN6galsim9ImageView8copyFromEvRK9BaseImageI1UE", "galsim::ImageView::copyFrom::U"], [24, 2, 1, "_CPPv4I0EN6galsim9ImageView8copyFromEvRK9BaseImageI1UE", "galsim::ImageView::copyFrom::rhs"], [24, 2, 1, "_CPPv4N6galsim9ImageView8copyFromERK9BaseImageI1TE", "galsim::ImageView::copyFrom::rhs"], [24, 1, 1, "_CPPv4N6galsim9ImageView14depixelizeSelfEPKdKi", "galsim::ImageView::depixelizeSelf"], [24, 2, 1, "_CPPv4N6galsim9ImageView14depixelizeSelfEPKdKi", "galsim::ImageView::depixelizeSelf::n"], [24, 2, 1, "_CPPv4N6galsim9ImageView14depixelizeSelfEPKdKi", "galsim::ImageView::depixelizeSelf::unit_integrals"], [24, 1, 1, "_CPPv4N6galsim9ImageView4fillE1T", "galsim::ImageView::fill"], [24, 2, 1, "_CPPv4N6galsim9ImageView4fillE1T", "galsim::ImageView::fill::x"], [24, 1, 1, "_CPPv4N6galsim9ImageView7getDataEv", "galsim::ImageView::getData"], [24, 1, 1, "_CPPv4N6galsim9ImageView9getMaxPtrEv", "galsim::ImageView::getMaxPtr"], [24, 1, 1, "_CPPv4N6galsim9ImageView10invertSelfEv", "galsim::ImageView::invertSelf"], [24, 1, 1, "_CPPv4N6galsim9ImageViewclERK8PositionIiE", "galsim::ImageView::operator()"], [24, 1, 1, "_CPPv4N6galsim9ImageViewclEii", "galsim::ImageView::operator()"], [24, 2, 1, "_CPPv4N6galsim9ImageViewclERK8PositionIiE", "galsim::ImageView::operator()::pos"], [24, 2, 1, "_CPPv4N6galsim9ImageViewclEii", "galsim::ImageView::operator()::xpos"], [24, 2, 1, "_CPPv4N6galsim9ImageViewclEii", "galsim::ImageView::operator()::ypos"], [24, 1, 1, "_CPPv4I0EN6galsim9ImageViewaSER9ImageViewI1TERK9BaseImageI1UE", "galsim::ImageView::operator="], [24, 1, 1, "_CPPv4N6galsim9ImageViewaSE1T", "galsim::ImageView::operator="], [24, 1, 1, "_CPPv4N6galsim9ImageViewaSERK17AssignableToImageI1TE", "galsim::ImageView::operator="], [24, 1, 1, "_CPPv4N6galsim9ImageViewaSERK9ImageViewI1TE", "galsim::ImageView::operator="], [24, 4, 1, "_CPPv4I0EN6galsim9ImageViewaSER9ImageViewI1TERK9BaseImageI1UE", "galsim::ImageView::operator=::U"], [24, 2, 1, "_CPPv4I0EN6galsim9ImageViewaSER9ImageViewI1TERK9BaseImageI1UE", "galsim::ImageView::operator=::rhs"], [24, 2, 1, "_CPPv4N6galsim9ImageViewaSERK17AssignableToImageI1TE", "galsim::ImageView::operator=::rhs"], [24, 2, 1, "_CPPv4N6galsim9ImageViewaSERK9ImageViewI1TE", "galsim::ImageView::operator=::rhs"], [24, 2, 1, "_CPPv4N6galsim9ImageViewaSE1T", "galsim::ImageView::operator=::x"], [24, 1, 1, "_CPPv4N6galsim9ImageViewixERK6BoundsIiE", "galsim::ImageView::operator[]"], [24, 2, 1, "_CPPv4N6galsim9ImageViewixERK6BoundsIiE", "galsim::ImageView::operator[]::bounds"], [24, 1, 1, "_CPPv4N6galsim9ImageView8setValueEii1T", "galsim::ImageView::setValue"], [24, 2, 1, "_CPPv4N6galsim9ImageView8setValueEii1T", "galsim::ImageView::setValue::value"], [24, 2, 1, "_CPPv4N6galsim9ImageView8setValueEii1T", "galsim::ImageView::setValue::x"], [24, 2, 1, "_CPPv4N6galsim9ImageView8setValueEii1T", "galsim::ImageView::setValue::y"], [24, 1, 1, "_CPPv4N6galsim9ImageView7setZeroEv", "galsim::ImageView::setZero"], [24, 1, 1, "_CPPv4N6galsim9ImageView8subImageERK6BoundsIiE", "galsim::ImageView::subImage"], [24, 2, 1, "_CPPv4N6galsim9ImageView8subImageERK6BoundsIiE", "galsim::ImageView::subImage::bounds"], [24, 1, 1, "_CPPv4N6galsim9ImageView4viewEv", "galsim::ImageView::view"], [25, 3, 1, "_CPPv4N6galsim11InterpolantE", "galsim::Interpolant"], [25, 1, 1, "_CPPv4N6galsim11Interpolant11InterpolantERK11Interpolant", "galsim::Interpolant::Interpolant"], [25, 1, 1, "_CPPv4N6galsim11Interpolant11InterpolantERK8GSParams", "galsim::Interpolant::Interpolant"], [25, 2, 1, "_CPPv4N6galsim11Interpolant11InterpolantERK8GSParams", "galsim::Interpolant::Interpolant::gsparams"], [25, 2, 1, "_CPPv4N6galsim11Interpolant11InterpolantERK11Interpolant", "galsim::Interpolant::Interpolant::rhs"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant15getNegativeFluxEv", "galsim::Interpolant::getNegativeFlux"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant17getNegativeFlux2dEv", "galsim::Interpolant::getNegativeFlux2d"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant15getPositiveFluxEv", "galsim::Interpolant::getPositiveFlux"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant17getPositiveFlux2dEv", "galsim::Interpolant::getPositiveFlux2d"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant14isExactAtNodesEv", "galsim::Interpolant::isExactAtNodes"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant7ixrangeEv", "galsim::Interpolant::ixrange"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant7makeStrEv", "galsim::Interpolant::makeStr"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant5shootER11PhotonArray14UniformDeviate", "galsim::Interpolant::shoot"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant5shootER11PhotonArray14UniformDeviate", "galsim::Interpolant::shoot::photons"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant5shootER11PhotonArray14UniformDeviate", "galsim::Interpolant::shoot::ud"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant6urangeEv", "galsim::Interpolant::urange"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant4uvalEd", "galsim::Interpolant::uval"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant4uvalEd", "galsim::Interpolant::uval::u"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant8uvalManyEPdi", "galsim::Interpolant::uvalMany"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant8uvalManyEPdi", "galsim::Interpolant::uvalMany::N"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant8uvalManyEPdi", "galsim::Interpolant::uvalMany::u"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant6xrangeEv", "galsim::Interpolant::xrange"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant4xvalEd", "galsim::Interpolant::xval"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant4xvalEd", "galsim::Interpolant::xval::x"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant8xvalManyEPdi", "galsim::Interpolant::xvalMany"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant8xvalManyEPdi", "galsim::Interpolant::xvalMany::N"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant8xvalManyEPdi", "galsim::Interpolant::xvalMany::x"], [25, 1, 1, "_CPPv4NK6galsim11Interpolant11xvalWrappedEdi", "galsim::Interpolant::xvalWrapped"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant11xvalWrappedEdi", "galsim::Interpolant::xvalWrapped::N"], [25, 2, 1, "_CPPv4NK6galsim11Interpolant11xvalWrappedEdi", "galsim::Interpolant::xvalWrapped::x"], [25, 1, 1, "_CPPv4N6galsim11InterpolantD0Ev", "galsim::Interpolant::~Interpolant"], [25, 3, 1, "_CPPv4N6galsim7LanczosE", "galsim::Lanczos"], [25, 1, 1, "_CPPv4N6galsim7Lanczos7LanczosEibRK8GSParams", "galsim::Lanczos::Lanczos"], [25, 2, 1, "_CPPv4N6galsim7Lanczos7LanczosEibRK8GSParams", "galsim::Lanczos::Lanczos::conserve_dc"], [25, 2, 1, "_CPPv4N6galsim7Lanczos7LanczosEibRK8GSParams", "galsim::Lanczos::Lanczos::gsparams"], [25, 2, 1, "_CPPv4N6galsim7Lanczos7LanczosEibRK8GSParams", "galsim::Lanczos::Lanczos::n"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos11conservesDCEv", "galsim::Lanczos::conservesDC"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos4getNEv", "galsim::Lanczos::getN"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos7ixrangeEv", "galsim::Lanczos::ixrange"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos7makeStrEv", "galsim::Lanczos::makeStr"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos6urangeEv", "galsim::Lanczos::urange"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos4uvalEd", "galsim::Lanczos::uval"], [25, 2, 1, "_CPPv4NK6galsim7Lanczos4uvalEd", "galsim::Lanczos::uval::u"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos6xrangeEv", "galsim::Lanczos::xrange"], [25, 1, 1, "_CPPv4NK6galsim7Lanczos4xvalEd", "galsim::Lanczos::xval"], [25, 2, 1, "_CPPv4NK6galsim7Lanczos4xvalEd", "galsim::Lanczos::xval::x"], [25, 1, 1, "_CPPv4N6galsim7LanczosD0Ev", "galsim::Lanczos::~Lanczos"], [25, 3, 1, "_CPPv4N6galsim6LinearE", "galsim::Linear"], [25, 1, 1, "_CPPv4N6galsim6Linear6LinearERK8GSParams", "galsim::Linear::Linear"], [25, 2, 1, "_CPPv4N6galsim6Linear6LinearERK8GSParams", "galsim::Linear::Linear::gsparams"], [25, 1, 1, "_CPPv4NK6galsim6Linear15getNegativeFluxEv", "galsim::Linear::getNegativeFlux"], [25, 1, 1, "_CPPv4NK6galsim6Linear15getPositiveFluxEv", "galsim::Linear::getPositiveFlux"], [25, 1, 1, "_CPPv4NK6galsim6Linear7ixrangeEv", "galsim::Linear::ixrange"], [25, 1, 1, "_CPPv4NK6galsim6Linear7makeStrEv", "galsim::Linear::makeStr"], [25, 1, 1, "_CPPv4NK6galsim6Linear5shootER11PhotonArray14UniformDeviate", "galsim::Linear::shoot"], [25, 2, 1, "_CPPv4NK6galsim6Linear5shootER11PhotonArray14UniformDeviate", "galsim::Linear::shoot::photons"], [25, 2, 1, "_CPPv4NK6galsim6Linear5shootER11PhotonArray14UniformDeviate", "galsim::Linear::shoot::ud"], [25, 1, 1, "_CPPv4NK6galsim6Linear6urangeEv", "galsim::Linear::urange"], [25, 1, 1, "_CPPv4NK6galsim6Linear4uvalEd", "galsim::Linear::uval"], [25, 2, 1, "_CPPv4NK6galsim6Linear4uvalEd", "galsim::Linear::uval::u"], [25, 1, 1, "_CPPv4NK6galsim6Linear6xrangeEv", "galsim::Linear::xrange"], [25, 1, 1, "_CPPv4NK6galsim6Linear4xvalEd", "galsim::Linear::xval"], [25, 2, 1, "_CPPv4NK6galsim6Linear4xvalEd", "galsim::Linear::xval::x"], [25, 1, 1, "_CPPv4N6galsim6LinearD0Ev", "galsim::Linear::~Linear"], [25, 3, 1, "_CPPv4N6galsim7NearestE", "galsim::Nearest"], [25, 1, 1, "_CPPv4N6galsim7Nearest7NearestERK8GSParams", "galsim::Nearest::Nearest"], [25, 2, 1, "_CPPv4N6galsim7Nearest7NearestERK8GSParams", "galsim::Nearest::Nearest::gsparams"], [25, 1, 1, "_CPPv4NK6galsim7Nearest15getNegativeFluxEv", "galsim::Nearest::getNegativeFlux"], [25, 1, 1, "_CPPv4NK6galsim7Nearest15getPositiveFluxEv", "galsim::Nearest::getPositiveFlux"], [25, 1, 1, "_CPPv4NK6galsim7Nearest7ixrangeEv", "galsim::Nearest::ixrange"], [25, 1, 1, "_CPPv4NK6galsim7Nearest7makeStrEv", "galsim::Nearest::makeStr"], [25, 1, 1, "_CPPv4NK6galsim7Nearest5shootER11PhotonArray14UniformDeviate", "galsim::Nearest::shoot"], [25, 2, 1, "_CPPv4NK6galsim7Nearest5shootER11PhotonArray14UniformDeviate", "galsim::Nearest::shoot::photons"], [25, 2, 1, "_CPPv4NK6galsim7Nearest5shootER11PhotonArray14UniformDeviate", "galsim::Nearest::shoot::ud"], [25, 1, 1, "_CPPv4NK6galsim7Nearest6urangeEv", "galsim::Nearest::urange"], [25, 1, 1, "_CPPv4NK6galsim7Nearest4uvalEd", "galsim::Nearest::uval"], [25, 2, 1, "_CPPv4NK6galsim7Nearest4uvalEd", "galsim::Nearest::uval::u"], [25, 1, 1, "_CPPv4NK6galsim7Nearest6xrangeEv", "galsim::Nearest::xrange"], [25, 1, 1, "_CPPv4NK6galsim7Nearest4xvalEd", "galsim::Nearest::xval"], [25, 2, 1, "_CPPv4NK6galsim7Nearest4xvalEd", "galsim::Nearest::xval::x"], [25, 1, 1, "_CPPv4N6galsim7NearestD0Ev", "galsim::Nearest::~Nearest"], [28, 3, 1, "_CPPv4N6galsim11PhotonArrayE", "galsim::PhotonArray"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayEi", "galsim::PhotonArray::PhotonArray"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::N"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayEi", "galsim::PhotonArray::PhotonArray::N"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::dxdz"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::dydz"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::flux"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::is_corr"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::wave"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::x"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb", "galsim::PhotonArray::PhotonArray::y"], [28, 1, 1, "_CPPv4I0ENK6galsim11PhotonArray5addToEd9ImageViewI1TE", "galsim::PhotonArray::addTo"], [28, 4, 1, "_CPPv4I0ENK6galsim11PhotonArray5addToEd9ImageViewI1TE", "galsim::PhotonArray::addTo::T"], [28, 2, 1, "_CPPv4I0ENK6galsim11PhotonArray5addToEd9ImageViewI1TE", "galsim::PhotonArray::addTo::target"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray8assignAtEiRK11PhotonArray", "galsim::PhotonArray::assignAt"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray8assignAtEiRK11PhotonArray", "galsim::PhotonArray::assignAt::istart"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray8assignAtEiRK11PhotonArray", "galsim::PhotonArray::assignAt::rhs"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray8convolveERK11PhotonArray11BaseDeviate", "galsim::PhotonArray::convolve"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray8convolveERK11PhotonArray11BaseDeviate", "galsim::PhotonArray::convolve::rhs"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray8convolveERK11PhotonArray11BaseDeviate", "galsim::PhotonArray::convolve::ud"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray15convolveShuffleERK11PhotonArray11BaseDeviate", "galsim::PhotonArray::convolveShuffle"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray15convolveShuffleERK11PhotonArray11BaseDeviate", "galsim::PhotonArray::convolveShuffle::rhs"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray15convolveShuffleERK11PhotonArray11BaseDeviate", "galsim::PhotonArray::convolveShuffle::rng"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray7getDXDZEi", "galsim::PhotonArray::getDXDZ"], [28, 2, 1, "_CPPv4NK6galsim11PhotonArray7getDXDZEi", "galsim::PhotonArray::getDXDZ::i"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray12getDXDZArrayEv", "galsim::PhotonArray::getDXDZArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray12getDXDZArrayEv", "galsim::PhotonArray::getDXDZArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray7getDYDZEi", "galsim::PhotonArray::getDYDZ"], [28, 2, 1, "_CPPv4NK6galsim11PhotonArray7getDYDZEi", "galsim::PhotonArray::getDYDZ::i"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray12getDYDZArrayEv", "galsim::PhotonArray::getDYDZArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray12getDYDZArrayEv", "galsim::PhotonArray::getDYDZArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray7getFluxEi", "galsim::PhotonArray::getFlux"], [28, 2, 1, "_CPPv4NK6galsim11PhotonArray7getFluxEi", "galsim::PhotonArray::getFlux::i"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray12getFluxArrayEv", "galsim::PhotonArray::getFluxArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray12getFluxArrayEv", "galsim::PhotonArray::getFluxArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray12getTotalFluxEv", "galsim::PhotonArray::getTotalFlux"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray13getWavelengthEi", "galsim::PhotonArray::getWavelength"], [28, 2, 1, "_CPPv4NK6galsim11PhotonArray13getWavelengthEi", "galsim::PhotonArray::getWavelength::i"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray18getWavelengthArrayEv", "galsim::PhotonArray::getWavelengthArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray18getWavelengthArrayEv", "galsim::PhotonArray::getWavelengthArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray4getXEi", "galsim::PhotonArray::getX"], [28, 2, 1, "_CPPv4NK6galsim11PhotonArray4getXEi", "galsim::PhotonArray::getX::i"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray9getXArrayEv", "galsim::PhotonArray::getXArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray9getXArrayEv", "galsim::PhotonArray::getXArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray4getYEi", "galsim::PhotonArray::getY"], [28, 2, 1, "_CPPv4NK6galsim11PhotonArray4getYEi", "galsim::PhotonArray::getY::i"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray9getYArrayEv", "galsim::PhotonArray::getYArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray9getYArrayEv", "galsim::PhotonArray::getYArray"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray18hasAllocatedAnglesEv", "galsim::PhotonArray::hasAllocatedAngles"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray23hasAllocatedWavelengthsEv", "galsim::PhotonArray::hasAllocatedWavelengths"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray12isCorrelatedEv", "galsim::PhotonArray::isCorrelated"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray9scaleFluxEd", "galsim::PhotonArray::scaleFlux"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray9scaleFluxEd", "galsim::PhotonArray::scaleFlux::scale"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray7scaleXYEd", "galsim::PhotonArray::scaleXY"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray7scaleXYEd", "galsim::PhotonArray::scaleXY::scale"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray13setCorrelatedEb", "galsim::PhotonArray::setCorrelated"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray13setCorrelatedEb", "galsim::PhotonArray::setCorrelated::is_corr"], [28, 1, 1, "_CPPv4I0EN6galsim11PhotonArray7setFromEiRK9BaseImageI1TEd11BaseDeviate", "galsim::PhotonArray::setFrom"], [28, 4, 1, "_CPPv4I0EN6galsim11PhotonArray7setFromEiRK9BaseImageI1TEd11BaseDeviate", "galsim::PhotonArray::setFrom::T"], [28, 2, 1, "_CPPv4I0EN6galsim11PhotonArray7setFromEiRK9BaseImageI1TEd11BaseDeviate", "galsim::PhotonArray::setFrom::image"], [28, 2, 1, "_CPPv4I0EN6galsim11PhotonArray7setFromEiRK9BaseImageI1TEd11BaseDeviate", "galsim::PhotonArray::setFrom::maxFlux"], [28, 2, 1, "_CPPv4I0EN6galsim11PhotonArray7setFromEiRK9BaseImageI1TEd11BaseDeviate", "galsim::PhotonArray::setFrom::ud"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray9setPhotonEiddd", "galsim::PhotonArray::setPhoton"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray9setPhotonEiddd", "galsim::PhotonArray::setPhoton::flux"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray9setPhotonEiddd", "galsim::PhotonArray::setPhoton::i"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray9setPhotonEiddd", "galsim::PhotonArray::setPhoton::x"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray9setPhotonEiddd", "galsim::PhotonArray::setPhoton::y"], [28, 1, 1, "_CPPv4N6galsim11PhotonArray12setTotalFluxEd", "galsim::PhotonArray::setTotalFlux"], [28, 2, 1, "_CPPv4N6galsim11PhotonArray12setTotalFluxEd", "galsim::PhotonArray::setTotalFlux::flux"], [28, 1, 1, "_CPPv4NK6galsim11PhotonArray4sizeEv", "galsim::PhotonArray::size"], [27, 3, 1, "_CPPv4N6galsim14PoissonDeviateE", "galsim::PoissonDeviate"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEPKcd", "galsim::PoissonDeviate::PoissonDeviate"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK11BaseDeviated", "galsim::PoissonDeviate::PoissonDeviate"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK14PoissonDeviate", "galsim::PoissonDeviate::PoissonDeviate"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEld", "galsim::PoissonDeviate::PoissonDeviate"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEld", "galsim::PoissonDeviate::PoissonDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEPKcd", "galsim::PoissonDeviate::PoissonDeviate::mean"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK11BaseDeviated", "galsim::PoissonDeviate::PoissonDeviate::mean"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEld", "galsim::PoissonDeviate::PoissonDeviate::mean"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK11BaseDeviated", "galsim::PoissonDeviate::PoissonDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK14PoissonDeviate", "galsim::PoissonDeviate::PoissonDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEPKcd", "galsim::PoissonDeviate::PoissonDeviate::str_c"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate10clearCacheEv", "galsim::PoissonDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate9duplicateEv", "galsim::PoissonDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate13duplicate_ptrEv", "galsim::PoissonDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate9generate1Ev", "galsim::PoissonDeviate::generate1"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate23generateFromExpectationExPd", "galsim::PoissonDeviate::generateFromExpectation"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate23generateFromExpectationExPd", "galsim::PoissonDeviate::generateFromExpectation::N"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate23generateFromExpectationExPd", "galsim::PoissonDeviate::generateFromExpectation::data"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate7getMeanEv", "galsim::PoissonDeviate::getMean"], [27, 1, 1, "_CPPv4N6galsim14PoissonDeviate7setMeanEd", "galsim::PoissonDeviate::setMean"], [27, 2, 1, "_CPPv4N6galsim14PoissonDeviate7setMeanEd", "galsim::PoissonDeviate::setMean::mean"], [22, 3, 1, "_CPPv4I0EN6galsim8PositionE", "galsim::Position"], [22, 1, 1, "_CPPv4I0EN6galsim8Position8PositionERK8PositionI2T2E", "galsim::Position::Position"], [22, 1, 1, "_CPPv4N6galsim8Position8PositionEK1TK1T", "galsim::Position::Position"], [22, 1, 1, "_CPPv4N6galsim8Position8PositionERK8PositionI1TE", "galsim::Position::Position"], [22, 1, 1, "_CPPv4N6galsim8Position8PositionEv", "galsim::Position::Position"], [22, 4, 1, "_CPPv4I0EN6galsim8Position8PositionERK8PositionI2T2E", "galsim::Position::Position::T2"], [22, 2, 1, "_CPPv4I0EN6galsim8Position8PositionERK8PositionI2T2E", "galsim::Position::Position::rhs"], [22, 2, 1, "_CPPv4N6galsim8Position8PositionERK8PositionI1TE", "galsim::Position::Position::rhs"], [22, 2, 1, "_CPPv4N6galsim8Position8PositionEK1TK1T", "galsim::Position::Position::xin"], [22, 2, 1, "_CPPv4N6galsim8Position8PositionEK1TK1T", "galsim::Position::Position::yin"], [22, 4, 1, "_CPPv4I0EN6galsim8PositionE", "galsim::Position::T"], [22, 1, 1, "_CPPv4NK6galsim8PositionneERK8PositionI1TE", "galsim::Position::operator!="], [22, 2, 1, "_CPPv4NK6galsim8PositionneERK8PositionI1TE", "galsim::Position::operator!=::rhs"], [22, 1, 1, "_CPPv4N6galsim8PositionmlEK1TRK8PositionI1TE", "galsim::Position::operator*"], [22, 1, 1, "_CPPv4NK6galsim8PositionmlEK1T", "galsim::Position::operator*"], [22, 2, 1, "_CPPv4N6galsim8PositionmlEK1TRK8PositionI1TE", "galsim::Position::operator*::lhs"], [22, 2, 1, "_CPPv4N6galsim8PositionmlEK1TRK8PositionI1TE", "galsim::Position::operator*::rhs"], [22, 2, 1, "_CPPv4NK6galsim8PositionmlEK1T", "galsim::Position::operator*::rhs"], [22, 1, 1, "_CPPv4N6galsim8PositionmLEK1T", "galsim::Position::operator*="], [22, 2, 1, "_CPPv4N6galsim8PositionmLEK1T", "galsim::Position::operator*=::rhs"], [22, 1, 1, "_CPPv4I0ENK6galsim8PositionplE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator+"], [22, 4, 1, "_CPPv4I0ENK6galsim8PositionplE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator+::T2"], [22, 2, 1, "_CPPv4I0ENK6galsim8PositionplE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator+::rhs"], [22, 1, 1, "_CPPv4I0EN6galsim8PositionpLER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator+="], [22, 4, 1, "_CPPv4I0EN6galsim8PositionpLER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator+=::T2"], [22, 2, 1, "_CPPv4I0EN6galsim8PositionpLER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator+=::rhs"], [22, 1, 1, "_CPPv4I0ENK6galsim8PositionmiE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator-"], [22, 1, 1, "_CPPv4NK6galsim8PositionmiEv", "galsim::Position::operator-"], [22, 4, 1, "_CPPv4I0ENK6galsim8PositionmiE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator-::T2"], [22, 2, 1, "_CPPv4I0ENK6galsim8PositionmiE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator-::rhs"], [22, 1, 1, "_CPPv4I0EN6galsim8PositionmIER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator-="], [22, 4, 1, "_CPPv4I0EN6galsim8PositionmIER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator-=::T2"], [22, 2, 1, "_CPPv4I0EN6galsim8PositionmIER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E", "galsim::Position::operator-=::rhs"], [22, 1, 1, "_CPPv4NK6galsim8PositiondvEK1T", "galsim::Position::operator/"], [22, 2, 1, "_CPPv4NK6galsim8PositiondvEK1T", "galsim::Position::operator/::rhs"], [22, 1, 1, "_CPPv4N6galsim8PositiondVEK1T", "galsim::Position::operator/="], [22, 2, 1, "_CPPv4N6galsim8PositiondVEK1T", "galsim::Position::operator/=::rhs"], [22, 1, 1, "_CPPv4I0EN6galsim8PositionaSER8PositionRK8PositionI2T2E", "galsim::Position::operator="], [22, 1, 1, "_CPPv4N6galsim8PositionaSERK8PositionI1TE", "galsim::Position::operator="], [22, 4, 1, "_CPPv4I0EN6galsim8PositionaSER8PositionRK8PositionI2T2E", "galsim::Position::operator=::T2"], [22, 2, 1, "_CPPv4I0EN6galsim8PositionaSER8PositionRK8PositionI2T2E", "galsim::Position::operator=::rhs"], [22, 2, 1, "_CPPv4N6galsim8PositionaSERK8PositionI1TE", "galsim::Position::operator=::rhs"], [22, 1, 1, "_CPPv4NK6galsim8PositioneqERK8PositionI1TE", "galsim::Position::operator=="], [22, 2, 1, "_CPPv4NK6galsim8PositioneqERK8PositionI1TE", "galsim::Position::operator==::rhs"], [22, 1, 1, "_CPPv4N6galsim8Position4readERNSt7istreamE", "galsim::Position::read"], [22, 2, 1, "_CPPv4N6galsim8Position4readERNSt7istreamE", "galsim::Position::read::fin"], [22, 1, 1, "_CPPv4NK6galsim8Position5writeERNSt7ostreamE", "galsim::Position::write"], [22, 2, 1, "_CPPv4NK6galsim8Position5writeERNSt7ostreamE", "galsim::Position::write::fout"], [22, 5, 1, "_CPPv4N6galsim8Position1xE", "galsim::Position::x"], [22, 5, 1, "_CPPv4N6galsim8Position1yE", "galsim::Position::y"], [25, 3, 1, "_CPPv4N6galsim7QuinticE", "galsim::Quintic"], [25, 1, 1, "_CPPv4N6galsim7Quintic7QuinticERK8GSParams", "galsim::Quintic::Quintic"], [25, 2, 1, "_CPPv4N6galsim7Quintic7QuinticERK8GSParams", "galsim::Quintic::Quintic::gsparams"], [25, 1, 1, "_CPPv4NK6galsim7Quintic15getNegativeFluxEv", "galsim::Quintic::getNegativeFlux"], [25, 1, 1, "_CPPv4NK6galsim7Quintic15getPositiveFluxEv", "galsim::Quintic::getPositiveFlux"], [25, 1, 1, "_CPPv4NK6galsim7Quintic7ixrangeEv", "galsim::Quintic::ixrange"], [25, 1, 1, "_CPPv4NK6galsim7Quintic7makeStrEv", "galsim::Quintic::makeStr"], [25, 1, 1, "_CPPv4NK6galsim7Quintic6urangeEv", "galsim::Quintic::urange"], [25, 1, 1, "_CPPv4NK6galsim7Quintic4uvalEd", "galsim::Quintic::uval"], [25, 2, 1, "_CPPv4NK6galsim7Quintic4uvalEd", "galsim::Quintic::uval::u"], [25, 1, 1, "_CPPv4NK6galsim7Quintic6xrangeEv", "galsim::Quintic::xrange"], [25, 1, 1, "_CPPv4NK6galsim7Quintic4xvalEd", "galsim::Quintic::xval"], [25, 2, 1, "_CPPv4NK6galsim7Quintic4xvalEd", "galsim::Quintic::xval::x"], [25, 1, 1, "_CPPv4N6galsim7QuinticD0Ev", "galsim::Quintic::~Quintic"], [29, 3, 1, "_CPPv4N6galsim5SBAddE", "galsim::SBAdd"], [29, 1, 1, "_CPPv4N6galsim5SBAdd5SBAddERK5SBAdd", "galsim::SBAdd::SBAdd"], [29, 1, 1, "_CPPv4N6galsim5SBAdd5SBAddERKNSt4listI9SBProfileEERK8GSParams", "galsim::SBAdd::SBAdd"], [29, 2, 1, "_CPPv4N6galsim5SBAdd5SBAddERKNSt4listI9SBProfileEERK8GSParams", "galsim::SBAdd::SBAdd::gsparams"], [29, 2, 1, "_CPPv4N6galsim5SBAdd5SBAddERK5SBAdd", "galsim::SBAdd::SBAdd::rhs"], [29, 2, 1, "_CPPv4N6galsim5SBAdd5SBAddERKNSt4listI9SBProfileEERK8GSParams", "galsim::SBAdd::SBAdd::slist"], [29, 1, 1, "_CPPv4NK6galsim5SBAdd7getObjsEv", "galsim::SBAdd::getObjs"], [29, 1, 1, "_CPPv4N6galsim5SBAddD0Ev", "galsim::SBAdd::~SBAdd"], [29, 3, 1, "_CPPv4N6galsim6SBAiryE", "galsim::SBAiry"], [29, 1, 1, "_CPPv4N6galsim6SBAiry6SBAiryERK6SBAiry", "galsim::SBAiry::SBAiry"], [29, 1, 1, "_CPPv4N6galsim6SBAiry6SBAiryEdddRK8GSParams", "galsim::SBAiry::SBAiry"], [29, 2, 1, "_CPPv4N6galsim6SBAiry6SBAiryEdddRK8GSParams", "galsim::SBAiry::SBAiry::flux"], [29, 2, 1, "_CPPv4N6galsim6SBAiry6SBAiryEdddRK8GSParams", "galsim::SBAiry::SBAiry::gsparams"], [29, 2, 1, "_CPPv4N6galsim6SBAiry6SBAiryEdddRK8GSParams", "galsim::SBAiry::SBAiry::lam_over_D"], [29, 2, 1, "_CPPv4N6galsim6SBAiry6SBAiryEdddRK8GSParams", "galsim::SBAiry::SBAiry::obscuration"], [29, 2, 1, "_CPPv4N6galsim6SBAiry6SBAiryERK6SBAiry", "galsim::SBAiry::SBAiry::rhs"], [29, 1, 1, "_CPPv4NK6galsim6SBAiry11getLamOverDEv", "galsim::SBAiry::getLamOverD"], [29, 1, 1, "_CPPv4NK6galsim6SBAiry14getObscurationEv", "galsim::SBAiry::getObscuration"], [29, 1, 1, "_CPPv4N6galsim6SBAiryD0Ev", "galsim::SBAiry::~SBAiry"], [29, 3, 1, "_CPPv4N6galsim14SBAutoConvolveE", "galsim::SBAutoConvolve"], [29, 1, 1, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK14SBAutoConvolve", "galsim::SBAutoConvolve::SBAutoConvolve"], [29, 1, 1, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK9SBProfilebRK8GSParams", "galsim::SBAutoConvolve::SBAutoConvolve"], [29, 2, 1, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK9SBProfilebRK8GSParams", "galsim::SBAutoConvolve::SBAutoConvolve::gsparams"], [29, 2, 1, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK9SBProfilebRK8GSParams", "galsim::SBAutoConvolve::SBAutoConvolve::real_space"], [29, 2, 1, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK14SBAutoConvolve", "galsim::SBAutoConvolve::SBAutoConvolve::rhs"], [29, 2, 1, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK9SBProfilebRK8GSParams", "galsim::SBAutoConvolve::SBAutoConvolve::s"], [29, 1, 1, "_CPPv4NK6galsim14SBAutoConvolve6getObjEv", "galsim::SBAutoConvolve::getObj"], [29, 1, 1, "_CPPv4NK6galsim14SBAutoConvolve11isRealSpaceEv", "galsim::SBAutoConvolve::isRealSpace"], [29, 1, 1, "_CPPv4N6galsim14SBAutoConvolveD0Ev", "galsim::SBAutoConvolve::~SBAutoConvolve"], [29, 3, 1, "_CPPv4N6galsim15SBAutoCorrelateE", "galsim::SBAutoCorrelate"], [29, 1, 1, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK15SBAutoCorrelate", "galsim::SBAutoCorrelate::SBAutoCorrelate"], [29, 1, 1, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK9SBProfilebRK8GSParams", "galsim::SBAutoCorrelate::SBAutoCorrelate"], [29, 2, 1, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK9SBProfilebRK8GSParams", "galsim::SBAutoCorrelate::SBAutoCorrelate::gsparams"], [29, 2, 1, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK9SBProfilebRK8GSParams", "galsim::SBAutoCorrelate::SBAutoCorrelate::real_space"], [29, 2, 1, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK15SBAutoCorrelate", "galsim::SBAutoCorrelate::SBAutoCorrelate::rhs"], [29, 2, 1, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK9SBProfilebRK8GSParams", "galsim::SBAutoCorrelate::SBAutoCorrelate::s"], [29, 1, 1, "_CPPv4NK6galsim15SBAutoCorrelate6getObjEv", "galsim::SBAutoCorrelate::getObj"], [29, 1, 1, "_CPPv4NK6galsim15SBAutoCorrelate11isRealSpaceEv", "galsim::SBAutoCorrelate::isRealSpace"], [29, 1, 1, "_CPPv4N6galsim15SBAutoCorrelateD0Ev", "galsim::SBAutoCorrelate::~SBAutoCorrelate"], [29, 3, 1, "_CPPv4N6galsim5SBBoxE", "galsim::SBBox"], [29, 1, 1, "_CPPv4N6galsim5SBBox5SBBoxERK5SBBox", "galsim::SBBox::SBBox"], [29, 1, 1, "_CPPv4N6galsim5SBBox5SBBoxEdddRK8GSParams", "galsim::SBBox::SBBox"], [29, 2, 1, "_CPPv4N6galsim5SBBox5SBBoxEdddRK8GSParams", "galsim::SBBox::SBBox::flux"], [29, 2, 1, "_CPPv4N6galsim5SBBox5SBBoxEdddRK8GSParams", "galsim::SBBox::SBBox::gsparams"], [29, 2, 1, "_CPPv4N6galsim5SBBox5SBBoxEdddRK8GSParams", "galsim::SBBox::SBBox::height"], [29, 2, 1, "_CPPv4N6galsim5SBBox5SBBoxERK5SBBox", "galsim::SBBox::SBBox::rhs"], [29, 2, 1, "_CPPv4N6galsim5SBBox5SBBoxEdddRK8GSParams", "galsim::SBBox::SBBox::width"], [29, 1, 1, "_CPPv4NK6galsim5SBBox9getHeightEv", "galsim::SBBox::getHeight"], [29, 1, 1, "_CPPv4NK6galsim5SBBox8getWidthEv", "galsim::SBBox::getWidth"], [29, 1, 1, "_CPPv4N6galsim5SBBoxD0Ev", "galsim::SBBox::~SBBox"], [29, 3, 1, "_CPPv4N6galsim10SBConvolveE", "galsim::SBConvolve"], [29, 1, 1, "_CPPv4N6galsim10SBConvolve10SBConvolveERK10SBConvolve", "galsim::SBConvolve::SBConvolve"], [29, 1, 1, "_CPPv4N6galsim10SBConvolve10SBConvolveERKNSt4listI9SBProfileEEbRK8GSParams", "galsim::SBConvolve::SBConvolve"], [29, 2, 1, "_CPPv4N6galsim10SBConvolve10SBConvolveERKNSt4listI9SBProfileEEbRK8GSParams", "galsim::SBConvolve::SBConvolve::gsparams"], [29, 2, 1, "_CPPv4N6galsim10SBConvolve10SBConvolveERKNSt4listI9SBProfileEEbRK8GSParams", "galsim::SBConvolve::SBConvolve::real_space"], [29, 2, 1, "_CPPv4N6galsim10SBConvolve10SBConvolveERK10SBConvolve", "galsim::SBConvolve::SBConvolve::rhs"], [29, 2, 1, "_CPPv4N6galsim10SBConvolve10SBConvolveERKNSt4listI9SBProfileEEbRK8GSParams", "galsim::SBConvolve::SBConvolve::slist"], [29, 1, 1, "_CPPv4NK6galsim10SBConvolve7getObjsEv", "galsim::SBConvolve::getObjs"], [29, 1, 1, "_CPPv4NK6galsim10SBConvolve11isRealSpaceEv", "galsim::SBConvolve::isRealSpace"], [29, 1, 1, "_CPPv4N6galsim10SBConvolveD0Ev", "galsim::SBConvolve::~SBConvolve"], [29, 3, 1, "_CPPv4N6galsim12SBDeconvolveE", "galsim::SBDeconvolve"], [29, 1, 1, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK12SBDeconvolve", "galsim::SBDeconvolve::SBDeconvolve"], [29, 1, 1, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK9SBProfileRK8GSParams", "galsim::SBDeconvolve::SBDeconvolve"], [29, 2, 1, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK9SBProfileRK8GSParams", "galsim::SBDeconvolve::SBDeconvolve::adaptee"], [29, 2, 1, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK9SBProfileRK8GSParams", "galsim::SBDeconvolve::SBDeconvolve::gsparams"], [29, 2, 1, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK12SBDeconvolve", "galsim::SBDeconvolve::SBDeconvolve::rhs"], [29, 1, 1, "_CPPv4NK6galsim12SBDeconvolve6getObjEv", "galsim::SBDeconvolve::getObj"], [29, 1, 1, "_CPPv4N6galsim12SBDeconvolveD0Ev", "galsim::SBDeconvolve::~SBDeconvolve"], [29, 3, 1, "_CPPv4N6galsim15SBDeltaFunctionE", "galsim::SBDeltaFunction"], [29, 1, 1, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionERK15SBDeltaFunction", "galsim::SBDeltaFunction::SBDeltaFunction"], [29, 1, 1, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionEdRK8GSParams", "galsim::SBDeltaFunction::SBDeltaFunction"], [29, 2, 1, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionEdRK8GSParams", "galsim::SBDeltaFunction::SBDeltaFunction::flux"], [29, 2, 1, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionEdRK8GSParams", "galsim::SBDeltaFunction::SBDeltaFunction::gsparams"], [29, 2, 1, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionERK15SBDeltaFunction", "galsim::SBDeltaFunction::SBDeltaFunction::rhs"], [29, 1, 1, "_CPPv4N6galsim15SBDeltaFunctionD0Ev", "galsim::SBDeltaFunction::~SBDeltaFunction"], [29, 3, 1, "_CPPv4N6galsim13SBExponentialE", "galsim::SBExponential"], [29, 1, 1, "_CPPv4N6galsim13SBExponential13SBExponentialERK13SBExponential", "galsim::SBExponential::SBExponential"], [29, 1, 1, "_CPPv4N6galsim13SBExponential13SBExponentialEddRK8GSParams", "galsim::SBExponential::SBExponential"], [29, 2, 1, "_CPPv4N6galsim13SBExponential13SBExponentialEddRK8GSParams", "galsim::SBExponential::SBExponential::flux"], [29, 2, 1, "_CPPv4N6galsim13SBExponential13SBExponentialEddRK8GSParams", "galsim::SBExponential::SBExponential::gsparams"], [29, 2, 1, "_CPPv4N6galsim13SBExponential13SBExponentialEddRK8GSParams", "galsim::SBExponential::SBExponential::r0"], [29, 2, 1, "_CPPv4N6galsim13SBExponential13SBExponentialERK13SBExponential", "galsim::SBExponential::SBExponential::rhs"], [29, 1, 1, "_CPPv4NK6galsim13SBExponential14getScaleRadiusEv", "galsim::SBExponential::getScaleRadius"], [29, 1, 1, "_CPPv4N6galsim13SBExponentialD0Ev", "galsim::SBExponential::~SBExponential"], [29, 3, 1, "_CPPv4N6galsim13SBFourierSqrtE", "galsim::SBFourierSqrt"], [29, 1, 1, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK13SBFourierSqrt", "galsim::SBFourierSqrt::SBFourierSqrt"], [29, 1, 1, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK9SBProfileRK8GSParams", "galsim::SBFourierSqrt::SBFourierSqrt"], [29, 2, 1, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK9SBProfileRK8GSParams", "galsim::SBFourierSqrt::SBFourierSqrt::adaptee"], [29, 2, 1, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK9SBProfileRK8GSParams", "galsim::SBFourierSqrt::SBFourierSqrt::gsparams"], [29, 2, 1, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK13SBFourierSqrt", "galsim::SBFourierSqrt::SBFourierSqrt::rhs"], [29, 1, 1, "_CPPv4NK6galsim13SBFourierSqrt6getObjEv", "galsim::SBFourierSqrt::getObj"], [29, 1, 1, "_CPPv4N6galsim13SBFourierSqrtD0Ev", "galsim::SBFourierSqrt::~SBFourierSqrt"], [29, 3, 1, "_CPPv4N6galsim10SBGaussianE", "galsim::SBGaussian"], [29, 1, 1, "_CPPv4N6galsim10SBGaussian10SBGaussianERK10SBGaussian", "galsim::SBGaussian::SBGaussian"], [29, 1, 1, "_CPPv4N6galsim10SBGaussian10SBGaussianEddRK8GSParams", "galsim::SBGaussian::SBGaussian"], [29, 2, 1, "_CPPv4N6galsim10SBGaussian10SBGaussianEddRK8GSParams", "galsim::SBGaussian::SBGaussian::flux"], [29, 2, 1, "_CPPv4N6galsim10SBGaussian10SBGaussianEddRK8GSParams", "galsim::SBGaussian::SBGaussian::gsparams"], [29, 2, 1, "_CPPv4N6galsim10SBGaussian10SBGaussianERK10SBGaussian", "galsim::SBGaussian::SBGaussian::rhs"], [29, 2, 1, "_CPPv4N6galsim10SBGaussian10SBGaussianEddRK8GSParams", "galsim::SBGaussian::SBGaussian::sigma"], [29, 1, 1, "_CPPv4NK6galsim10SBGaussian8getSigmaEv", "galsim::SBGaussian::getSigma"], [29, 1, 1, "_CPPv4N6galsim10SBGaussianD0Ev", "galsim::SBGaussian::~SBGaussian"], [29, 3, 1, "_CPPv4N6galsim21SBInclinedExponentialE", "galsim::SBInclinedExponential"], [29, 1, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialERK21SBInclinedExponential", "galsim::SBInclinedExponential::SBInclinedExponential"], [29, 1, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams", "galsim::SBInclinedExponential::SBInclinedExponential"], [29, 2, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams", "galsim::SBInclinedExponential::SBInclinedExponential::flux"], [29, 2, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams", "galsim::SBInclinedExponential::SBInclinedExponential::gsparams"], [29, 2, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams", "galsim::SBInclinedExponential::SBInclinedExponential::inclination"], [29, 2, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialERK21SBInclinedExponential", "galsim::SBInclinedExponential::SBInclinedExponential::rhs"], [29, 2, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams", "galsim::SBInclinedExponential::SBInclinedExponential::scale_height"], [29, 2, 1, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams", "galsim::SBInclinedExponential::SBInclinedExponential::scale_radius"], [29, 1, 1, "_CPPv4NK6galsim21SBInclinedExponential14getInclinationEv", "galsim::SBInclinedExponential::getInclination"], [29, 1, 1, "_CPPv4NK6galsim21SBInclinedExponential14getScaleHeightEv", "galsim::SBInclinedExponential::getScaleHeight"], [29, 1, 1, "_CPPv4NK6galsim21SBInclinedExponential14getScaleRadiusEv", "galsim::SBInclinedExponential::getScaleRadius"], [29, 1, 1, "_CPPv4N6galsim21SBInclinedExponentialD0Ev", "galsim::SBInclinedExponential::~SBInclinedExponential"], [29, 3, 1, "_CPPv4N6galsim16SBInclinedSersicE", "galsim::SBInclinedSersic"], [29, 1, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicERK16SBInclinedSersic", "galsim::SBInclinedSersic::SBInclinedSersic"], [29, 1, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::flux"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::gsparams"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::height"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::inclination"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::n"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicERK16SBInclinedSersic", "galsim::SBInclinedSersic::SBInclinedSersic::rhs"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::scale_radius"], [29, 2, 1, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams", "galsim::SBInclinedSersic::SBInclinedSersic::trunc"], [29, 1, 1, "_CPPv4NK6galsim16SBInclinedSersic18getHalfLightRadiusEv", "galsim::SBInclinedSersic::getHalfLightRadius"], [29, 1, 1, "_CPPv4NK6galsim16SBInclinedSersic14getInclinationEv", "galsim::SBInclinedSersic::getInclination"], [29, 1, 1, "_CPPv4NK6galsim16SBInclinedSersic4getNEv", "galsim::SBInclinedSersic::getN"], [29, 1, 1, "_CPPv4NK6galsim16SBInclinedSersic14getScaleHeightEv", "galsim::SBInclinedSersic::getScaleHeight"], [29, 1, 1, "_CPPv4NK6galsim16SBInclinedSersic14getScaleRadiusEv", "galsim::SBInclinedSersic::getScaleRadius"], [29, 1, 1, "_CPPv4NK6galsim16SBInclinedSersic8getTruncEv", "galsim::SBInclinedSersic::getTrunc"], [29, 1, 1, "_CPPv4N6galsim16SBInclinedSersicD0Ev", "galsim::SBInclinedSersic::~SBInclinedSersic"], [29, 3, 1, "_CPPv4N6galsim19SBInterpolatedImageE", "galsim::SBInterpolatedImage"], [29, 1, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK19SBInterpolatedImage", "galsim::SBInterpolatedImage::SBInterpolatedImage"], [29, 1, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::gsparams"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::image"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::init_bounds"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::kInterp"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::maxk"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::nonzero_bounds"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK19SBInterpolatedImage", "galsim::SBInterpolatedImage::SBInterpolatedImage::rhs"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::stepk"], [29, 2, 1, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams", "galsim::SBInterpolatedImage::SBInterpolatedImage::xInterp"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage13calculateMaxKEd", "galsim::SBInterpolatedImage::calculateMaxK"], [29, 2, 1, "_CPPv4NK6galsim19SBInterpolatedImage13calculateMaxKEd", "galsim::SBInterpolatedImage::calculateMaxK::max_maxk"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage14calculateStepKEd", "galsim::SBInterpolatedImage::calculateStepK"], [29, 2, 1, "_CPPv4NK6galsim19SBInterpolatedImage14calculateStepKEd", "galsim::SBInterpolatedImage::calculateStepK::max_stepk"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage8getImageEv", "galsim::SBInterpolatedImage::getImage"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage10getKInterpEv", "galsim::SBInterpolatedImage::getKInterp"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage15getNonZeroImageEv", "galsim::SBInterpolatedImage::getNonZeroImage"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage12getPadFactorEv", "galsim::SBInterpolatedImage::getPadFactor"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage14getPaddedImageEv", "galsim::SBInterpolatedImage::getPaddedImage"], [29, 1, 1, "_CPPv4NK6galsim19SBInterpolatedImage10getXInterpEv", "galsim::SBInterpolatedImage::getXInterp"], [29, 1, 1, "_CPPv4N6galsim19SBInterpolatedImageD0Ev", "galsim::SBInterpolatedImage::~SBInterpolatedImage"], [29, 3, 1, "_CPPv4N6galsim20SBInterpolatedKImageE", "galsim::SBInterpolatedKImage"], [29, 1, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK20SBInterpolatedKImage", "galsim::SBInterpolatedKImage::SBInterpolatedKImage"], [29, 1, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageINSt7complexIdEEEdRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage"], [29, 1, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::data"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageINSt7complexIdEEEdRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::gsparams"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::gsparams"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageINSt7complexIdEEEdRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::kInterp"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::kInterp"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageINSt7complexIdEEEdRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::kimage"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::maxk"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK20SBInterpolatedKImage", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::rhs"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageINSt7complexIdEEEdRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::stepk"], [29, 2, 1, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams", "galsim::SBInterpolatedKImage::SBInterpolatedKImage::stepk"], [29, 1, 1, "_CPPv4NK6galsim20SBInterpolatedKImage8getKDataEv", "galsim::SBInterpolatedKImage::getKData"], [29, 1, 1, "_CPPv4NK6galsim20SBInterpolatedKImage10getKInterpEv", "galsim::SBInterpolatedKImage::getKInterp"], [29, 1, 1, "_CPPv4N6galsim20SBInterpolatedKImageD0Ev", "galsim::SBInterpolatedKImage::~SBInterpolatedKImage"], [29, 3, 1, "_CPPv4N6galsim12SBKolmogorovE", "galsim::SBKolmogorov"], [29, 1, 1, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovERK12SBKolmogorov", "galsim::SBKolmogorov::SBKolmogorov"], [29, 1, 1, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovEddRK8GSParams", "galsim::SBKolmogorov::SBKolmogorov"], [29, 2, 1, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovEddRK8GSParams", "galsim::SBKolmogorov::SBKolmogorov::flux"], [29, 2, 1, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovEddRK8GSParams", "galsim::SBKolmogorov::SBKolmogorov::gsparams"], [29, 2, 1, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovEddRK8GSParams", "galsim::SBKolmogorov::SBKolmogorov::lam_over_r0"], [29, 2, 1, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovERK12SBKolmogorov", "galsim::SBKolmogorov::SBKolmogorov::rhs"], [29, 1, 1, "_CPPv4NK6galsim12SBKolmogorov12getLamOverR0Ev", "galsim::SBKolmogorov::getLamOverR0"], [29, 1, 1, "_CPPv4N6galsim12SBKolmogorovD0Ev", "galsim::SBKolmogorov::~SBKolmogorov"], [29, 3, 1, "_CPPv4N6galsim8SBMoffatE", "galsim::SBMoffat"], [29, 1, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatERK8SBMoffat", "galsim::SBMoffat::SBMoffat"], [29, 1, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams", "galsim::SBMoffat::SBMoffat"], [29, 2, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams", "galsim::SBMoffat::SBMoffat::beta"], [29, 2, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams", "galsim::SBMoffat::SBMoffat::flux"], [29, 2, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams", "galsim::SBMoffat::SBMoffat::gsparams"], [29, 2, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatERK8SBMoffat", "galsim::SBMoffat::SBMoffat::rhs"], [29, 2, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams", "galsim::SBMoffat::SBMoffat::scale_radius"], [29, 2, 1, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams", "galsim::SBMoffat::SBMoffat::trunc"], [29, 1, 1, "_CPPv4NK6galsim8SBMoffat7getBetaEv", "galsim::SBMoffat::getBeta"], [29, 1, 1, "_CPPv4NK6galsim8SBMoffat7getFWHMEv", "galsim::SBMoffat::getFWHM"], [29, 1, 1, "_CPPv4NK6galsim8SBMoffat18getHalfLightRadiusEv", "galsim::SBMoffat::getHalfLightRadius"], [29, 1, 1, "_CPPv4NK6galsim8SBMoffat14getScaleRadiusEv", "galsim::SBMoffat::getScaleRadius"], [29, 1, 1, "_CPPv4NK6galsim8SBMoffat8getTruncEv", "galsim::SBMoffat::getTrunc"], [29, 1, 1, "_CPPv4N6galsim8SBMoffatD0Ev", "galsim::SBMoffat::~SBMoffat"], [29, 3, 1, "_CPPv4N6galsim9SBProfileE", "galsim::SBProfile"], [29, 1, 1, "_CPPv4N6galsim9SBProfile9SBProfileERK9SBProfile", "galsim::SBProfile::SBProfile"], [29, 1, 1, "_CPPv4N6galsim9SBProfile9SBProfileEv", "galsim::SBProfile::SBProfile"], [29, 2, 1, "_CPPv4N6galsim9SBProfile9SBProfileERK9SBProfile", "galsim::SBProfile::SBProfile::rhs"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile8centroidEv", "galsim::SBProfile::centroid"], [29, 1, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw"], [29, 4, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::T"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::dx"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::flux_ratio"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::image"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::jac"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::xoff"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd", "galsim::SBProfile::draw::yoff"], [29, 1, 1, "_CPPv4I0ENK6galsim9SBProfile5drawKEv9ImageViewINSt7complexI1TEEEdPd", "galsim::SBProfile::drawK"], [29, 4, 1, "_CPPv4I0ENK6galsim9SBProfile5drawKEv9ImageViewINSt7complexI1TEEEdPd", "galsim::SBProfile::drawK::T"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile5drawKEv9ImageViewINSt7complexI1TEEEdPd", "galsim::SBProfile::drawK::dk"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile5drawKEv9ImageViewINSt7complexI1TEEEdPd", "galsim::SBProfile::drawK::image"], [29, 2, 1, "_CPPv4I0ENK6galsim9SBProfile5drawKEv9ImageViewINSt7complexI1TEEEdPd", "galsim::SBProfile::drawK::jac"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile6expandEd", "galsim::SBProfile::expand"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile6expandEd", "galsim::SBProfile::expand::scale"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile7getFluxEv", "galsim::SBProfile::getFlux"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile11getGSParamsEv", "galsim::SBProfile::getGSParams"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile16getGoodImageSizeEd", "galsim::SBProfile::getGoodImageSize"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile16getGoodImageSizeEd", "galsim::SBProfile::getGoodImageSize::dx"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile15getNegativeFluxEv", "galsim::SBProfile::getNegativeFlux"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile15getPositiveFluxEv", "galsim::SBProfile::getPositiveFlux"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile9getXRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getXRange"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9getXRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getXRange::splits"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9getXRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getXRange::xmax"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9getXRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getXRange::xmin"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile9getYRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRange"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9getYRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRange::splits"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9getYRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRange::ymax"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9getYRangeERdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRange::ymin"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile10getYRangeXEdRdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRangeX"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile10getYRangeXEdRdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRangeX::splits"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile10getYRangeXEdRdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRangeX::x"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile10getYRangeXEdRdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRangeX::ymax"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile10getYRangeXEdRdRdRNSt6vectorIdEE", "galsim::SBProfile::getYRangeX::ymin"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile12hasHardEdgesEv", "galsim::SBProfile::hasHardEdges"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile11isAnalyticKEv", "galsim::SBProfile::isAnalyticK"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile11isAnalyticXEv", "galsim::SBProfile::isAnalyticX"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile14isAxisymmetricEv", "galsim::SBProfile::isAxisymmetric"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile6kValueERK8PositionIdE", "galsim::SBProfile::kValue"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile6kValueERK8PositionIdE", "galsim::SBProfile::kValue::k"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile4maxKEv", "galsim::SBProfile::maxK"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile5maxSBEv", "galsim::SBProfile::maxSB"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile9nyquistDxEv", "galsim::SBProfile::nyquistDx"], [29, 1, 1, "_CPPv4N6galsim9SBProfileaSERK9SBProfile", "galsim::SBProfile::operator="], [29, 2, 1, "_CPPv4N6galsim9SBProfileaSERK9SBProfile", "galsim::SBProfile::operator=::rhs"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile6rotateEd", "galsim::SBProfile::rotate"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile6rotateEd", "galsim::SBProfile::rotate::theta"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile9scaleFluxEd", "galsim::SBProfile::scaleFlux"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9scaleFluxEd", "galsim::SBProfile::scaleFlux::fluxRatio"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile5shiftERK8PositionIdE", "galsim::SBProfile::shift"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile5shiftERK8PositionIdE", "galsim::SBProfile::shift::delta"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile5shootER11PhotonArray11BaseDeviate", "galsim::SBProfile::shoot"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile5shootER11PhotonArray11BaseDeviate", "galsim::SBProfile::shoot::photons"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile5shootER11PhotonArray11BaseDeviate", "galsim::SBProfile::shoot::rng"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile5stepKEv", "galsim::SBProfile::stepK"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile9transformEdddd", "galsim::SBProfile::transform"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9transformEdddd", "galsim::SBProfile::transform::dudx"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9transformEdddd", "galsim::SBProfile::transform::dudy"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9transformEdddd", "galsim::SBProfile::transform::dvdx"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile9transformEdddd", "galsim::SBProfile::transform::dvdy"], [29, 1, 1, "_CPPv4NK6galsim9SBProfile6xValueERK8PositionIdE", "galsim::SBProfile::xValue"], [29, 2, 1, "_CPPv4NK6galsim9SBProfile6xValueERK8PositionIdE", "galsim::SBProfile::xValue::p"], [29, 1, 1, "_CPPv4N6galsim9SBProfileD0Ev", "galsim::SBProfile::~SBProfile"], [29, 3, 1, "_CPPv4N6galsim12SBSecondKickE", "galsim::SBSecondKick"], [29, 1, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickERK12SBSecondKick", "galsim::SBSecondKick::SBSecondKick"], [29, 1, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickEdddRK11GSParamsPtr", "galsim::SBSecondKick::SBSecondKick"], [29, 2, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickEdddRK11GSParamsPtr", "galsim::SBSecondKick::SBSecondKick::flux"], [29, 2, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickEdddRK11GSParamsPtr", "galsim::SBSecondKick::SBSecondKick::gsparams"], [29, 2, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickEdddRK11GSParamsPtr", "galsim::SBSecondKick::SBSecondKick::kcrit"], [29, 2, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickEdddRK11GSParamsPtr", "galsim::SBSecondKick::SBSecondKick::lam_over_r0"], [29, 2, 1, "_CPPv4N6galsim12SBSecondKick12SBSecondKickERK12SBSecondKick", "galsim::SBSecondKick::SBSecondKick::rhs"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick8getDeltaEv", "galsim::SBSecondKick::getDelta"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick8getKCritEv", "galsim::SBSecondKick::getKCrit"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick12getLamOverR0Ev", "galsim::SBSecondKick::getLamOverR0"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick6kValueEd", "galsim::SBSecondKick::kValue"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick9kValueRawEd", "galsim::SBSecondKick::kValueRaw"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick17structureFunctionEd", "galsim::SBSecondKick::structureFunction"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick6xValueEd", "galsim::SBSecondKick::xValue"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick11xValueExactEd", "galsim::SBSecondKick::xValueExact"], [29, 1, 1, "_CPPv4NK6galsim12SBSecondKick9xValueRawEd", "galsim::SBSecondKick::xValueRaw"], [29, 1, 1, "_CPPv4N6galsim12SBSecondKickD0Ev", "galsim::SBSecondKick::~SBSecondKick"], [29, 3, 1, "_CPPv4N6galsim8SBSersicE", "galsim::SBSersic"], [29, 1, 1, "_CPPv4N6galsim8SBSersic8SBSersicERK8SBSersic", "galsim::SBSersic::SBSersic"], [29, 1, 1, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams", "galsim::SBSersic::SBSersic"], [29, 2, 1, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams", "galsim::SBSersic::SBSersic::flux"], [29, 2, 1, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams", "galsim::SBSersic::SBSersic::gsparams"], [29, 2, 1, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams", "galsim::SBSersic::SBSersic::n"], [29, 2, 1, "_CPPv4N6galsim8SBSersic8SBSersicERK8SBSersic", "galsim::SBSersic::SBSersic::rhs"], [29, 2, 1, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams", "galsim::SBSersic::SBSersic::scale_radius"], [29, 2, 1, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams", "galsim::SBSersic::SBSersic::trunc"], [29, 1, 1, "_CPPv4NK6galsim8SBSersic18getHalfLightRadiusEv", "galsim::SBSersic::getHalfLightRadius"], [29, 1, 1, "_CPPv4NK6galsim8SBSersic4getNEv", "galsim::SBSersic::getN"], [29, 1, 1, "_CPPv4NK6galsim8SBSersic14getScaleRadiusEv", "galsim::SBSersic::getScaleRadius"], [29, 1, 1, "_CPPv4NK6galsim8SBSersic8getTruncEv", "galsim::SBSersic::getTrunc"], [29, 1, 1, "_CPPv4N6galsim8SBSersicD0Ev", "galsim::SBSersic::~SBSersic"], [29, 3, 1, "_CPPv4N6galsim10SBShapeletE", "galsim::SBShapelet"], [29, 1, 1, "_CPPv4N6galsim10SBShapelet10SBShapeletERK10SBShapelet", "galsim::SBShapelet::SBShapelet"], [29, 1, 1, "_CPPv4N6galsim10SBShapelet10SBShapeletEd7LVectorRK8GSParams", "galsim::SBShapelet::SBShapelet"], [29, 2, 1, "_CPPv4N6galsim10SBShapelet10SBShapeletEd7LVectorRK8GSParams", "galsim::SBShapelet::SBShapelet::bvec"], [29, 2, 1, "_CPPv4N6galsim10SBShapelet10SBShapeletEd7LVectorRK8GSParams", "galsim::SBShapelet::SBShapelet::gsparams"], [29, 2, 1, "_CPPv4N6galsim10SBShapelet10SBShapeletERK10SBShapelet", "galsim::SBShapelet::SBShapelet::rhs"], [29, 2, 1, "_CPPv4N6galsim10SBShapelet10SBShapeletEd7LVectorRK8GSParams", "galsim::SBShapelet::SBShapelet::sigma"], [29, 1, 1, "_CPPv4NK6galsim10SBShapelet7getBVecEv", "galsim::SBShapelet::getBVec"], [29, 1, 1, "_CPPv4NK6galsim10SBShapelet8getSigmaEv", "galsim::SBShapelet::getSigma"], [29, 1, 1, "_CPPv4N6galsim10SBShapelet6rotateEd", "galsim::SBShapelet::rotate"], [29, 2, 1, "_CPPv4N6galsim10SBShapelet6rotateEd", "galsim::SBShapelet::rotate::theta"], [29, 1, 1, "_CPPv4N6galsim10SBShapeletD0Ev", "galsim::SBShapelet::~SBShapelet"], [29, 3, 1, "_CPPv4N6galsim9SBSpergelE", "galsim::SBSpergel"], [29, 1, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelERK9SBSpergel", "galsim::SBSpergel::SBSpergel"], [29, 1, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelEdddRK8GSParams", "galsim::SBSpergel::SBSpergel"], [29, 2, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelEdddRK8GSParams", "galsim::SBSpergel::SBSpergel::flux"], [29, 2, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelEdddRK8GSParams", "galsim::SBSpergel::SBSpergel::gsparams"], [29, 2, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelEdddRK8GSParams", "galsim::SBSpergel::SBSpergel::nu"], [29, 2, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelERK9SBSpergel", "galsim::SBSpergel::SBSpergel::rhs"], [29, 2, 1, "_CPPv4N6galsim9SBSpergel9SBSpergelEdddRK8GSParams", "galsim::SBSpergel::SBSpergel::scale_radius"], [29, 1, 1, "_CPPv4NK6galsim9SBSpergel19calculateFluxRadiusEd", "galsim::SBSpergel::calculateFluxRadius"], [29, 2, 1, "_CPPv4NK6galsim9SBSpergel19calculateFluxRadiusEd", "galsim::SBSpergel::calculateFluxRadius::f"], [29, 1, 1, "_CPPv4NK6galsim9SBSpergel23calculateIntegratedFluxEd", "galsim::SBSpergel::calculateIntegratedFlux"], [29, 2, 1, "_CPPv4NK6galsim9SBSpergel23calculateIntegratedFluxEd", "galsim::SBSpergel::calculateIntegratedFlux::r"], [29, 1, 1, "_CPPv4NK6galsim9SBSpergel5getNuEv", "galsim::SBSpergel::getNu"], [29, 1, 1, "_CPPv4NK6galsim9SBSpergel14getScaleRadiusEv", "galsim::SBSpergel::getScaleRadius"], [29, 1, 1, "_CPPv4N6galsim9SBSpergelD0Ev", "galsim::SBSpergel::~SBSpergel"], [29, 3, 1, "_CPPv4N6galsim8SBTopHatE", "galsim::SBTopHat"], [29, 1, 1, "_CPPv4N6galsim8SBTopHat8SBTopHatERK8SBTopHat", "galsim::SBTopHat::SBTopHat"], [29, 1, 1, "_CPPv4N6galsim8SBTopHat8SBTopHatEddRK8GSParams", "galsim::SBTopHat::SBTopHat"], [29, 2, 1, "_CPPv4N6galsim8SBTopHat8SBTopHatEddRK8GSParams", "galsim::SBTopHat::SBTopHat::flux"], [29, 2, 1, "_CPPv4N6galsim8SBTopHat8SBTopHatEddRK8GSParams", "galsim::SBTopHat::SBTopHat::gsparams"], [29, 2, 1, "_CPPv4N6galsim8SBTopHat8SBTopHatEddRK8GSParams", "galsim::SBTopHat::SBTopHat::radius"], [29, 2, 1, "_CPPv4N6galsim8SBTopHat8SBTopHatERK8SBTopHat", "galsim::SBTopHat::SBTopHat::rhs"], [29, 1, 1, "_CPPv4NK6galsim8SBTopHat9getRadiusEv", "galsim::SBTopHat::getRadius"], [29, 1, 1, "_CPPv4N6galsim8SBTopHatD0Ev", "galsim::SBTopHat::~SBTopHat"], [29, 3, 1, "_CPPv4N6galsim11SBTransformE", "galsim::SBTransform"], [29, 1, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK11SBTransform", "galsim::SBTransform::SBTransform"], [29, 1, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams", "galsim::SBTransform::SBTransform"], [29, 2, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams", "galsim::SBTransform::SBTransform::ampScaling"], [29, 2, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams", "galsim::SBTransform::SBTransform::cen"], [29, 2, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams", "galsim::SBTransform::SBTransform::gsparams"], [29, 2, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams", "galsim::SBTransform::SBTransform::jac"], [29, 2, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK11SBTransform", "galsim::SBTransform::SBTransform::rhs"], [29, 2, 1, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams", "galsim::SBTransform::SBTransform::sbin"], [29, 1, 1, "_CPPv4NK6galsim11SBTransform14getFluxScalingEv", "galsim::SBTransform::getFluxScaling"], [29, 1, 1, "_CPPv4NK6galsim11SBTransform6getJacERdRdRdRd", "galsim::SBTransform::getJac"], [29, 2, 1, "_CPPv4NK6galsim11SBTransform6getJacERdRdRdRd", "galsim::SBTransform::getJac::mA"], [29, 2, 1, "_CPPv4NK6galsim11SBTransform6getJacERdRdRdRd", "galsim::SBTransform::getJac::mB"], [29, 2, 1, "_CPPv4NK6galsim11SBTransform6getJacERdRdRdRd", "galsim::SBTransform::getJac::mC"], [29, 2, 1, "_CPPv4NK6galsim11SBTransform6getJacERdRdRdRd", "galsim::SBTransform::getJac::mD"], [29, 1, 1, "_CPPv4NK6galsim11SBTransform6getObjEv", "galsim::SBTransform::getObj"], [29, 1, 1, "_CPPv4NK6galsim11SBTransform9getOffsetEv", "galsim::SBTransform::getOffset"], [29, 1, 1, "_CPPv4N6galsim11SBTransformD0Ev", "galsim::SBTransform::~SBTransform"], [29, 3, 1, "_CPPv4N6galsim11SBVonKarmanE", "galsim::SBVonKarman"], [29, 1, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanERK11SBVonKarman", "galsim::SBVonKarman::SBVonKarman"], [29, 1, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::L0"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::doDelta"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::flux"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::force_stepk"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::gsparams"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::lam"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::r0"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanERK11SBVonKarman", "galsim::SBVonKarman::SBVonKarman::rhs"], [29, 2, 1, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd", "galsim::SBVonKarman::SBVonKarman::scale"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman8getDeltaEv", "galsim::SBVonKarman::getDelta"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman10getDoDeltaEv", "galsim::SBVonKarman::getDoDelta"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman18getHalfLightRadiusEv", "galsim::SBVonKarman::getHalfLightRadius"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman5getL0Ev", "galsim::SBVonKarman::getL0"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman6getLamEv", "galsim::SBVonKarman::getLam"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman5getR0Ev", "galsim::SBVonKarman::getR0"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman8getScaleEv", "galsim::SBVonKarman::getScale"], [29, 1, 1, "_CPPv4NK6galsim11SBVonKarman17structureFunctionEd", "galsim::SBVonKarman::structureFunction"], [29, 1, 1, "_CPPv4N6galsim11SBVonKarmanD0Ev", "galsim::SBVonKarman::~SBVonKarman"], [26, 1, 1, "_CPPv4N6galsim13SetOMPThreadsEi", "galsim::SetOMPThreads"], [26, 2, 1, "_CPPv4N6galsim13SetOMPThreadsEi", "galsim::SetOMPThreads::num_threads"], [28, 3, 1, "_CPPv4N6galsim7SiliconE", "galsim::Silicon"], [28, 1, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::abs_length_table"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::diffStep"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::numElec"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::numVertices"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::nx"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::ny"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::pixelSize"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::qDist"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::sensorThickness"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::tr_radial_table"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::transpose"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::treeRingCenter"], [28, 2, 1, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb", "galsim::Silicon::Silicon::vertex_data"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate::i1"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate::i2"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate::photons"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate::rng"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE", "galsim::Silicon::accumulate::target"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon8addDeltaEv9ImageViewI1TE", "galsim::Silicon::addDelta"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon8addDeltaEv9ImageViewI1TE", "galsim::Silicon::addDelta::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon8addDeltaEv9ImageViewI1TE", "galsim::Silicon::addDelta::target"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon22addTreeRingDistortionsEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::addTreeRingDistortions"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon22addTreeRingDistortionsEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::addTreeRingDistortions::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon22addTreeRingDistortionsEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::addTreeRingDistortions::orig_center"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon22addTreeRingDistortionsEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::addTreeRingDistortions::target"], [28, 1, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::abs_length_table_data"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::i"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::photonsDXDZ"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::photonsDYDZ"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::photonsHasAllocatedAngles"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::photonsHasAllocatedWavelengths"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::photonsWavelength"], [28, 2, 1, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid", "galsim::Silicon::calculateConversionDepth::randomNumber"], [28, 1, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion"], [28, 1, 1, "_CPPv4NK6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiER7Polygon", "galsim::Silicon::calculateTreeRingDistortion"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::i"], [28, 2, 1, "_CPPv4NK6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiER7Polygon", "galsim::Silicon::calculateTreeRingDistortion::i"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::i1"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::j"], [28, 2, 1, "_CPPv4NK6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiER7Polygon", "galsim::Silicon::calculateTreeRingDistortion::j"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::j1"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::nx"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::ny"], [28, 2, 1, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii", "galsim::Silicon::calculateTreeRingDistortion::orig_center"], [28, 2, 1, "_CPPv4NK6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiER7Polygon", "galsim::Silicon::calculateTreeRingDistortion::orig_center"], [28, 2, 1, "_CPPv4NK6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiER7Polygon", "galsim::Silicon::calculateTreeRingDistortion::poly"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon18fillWithPixelAreasEv9ImageViewI1TE8PositionIiEb", "galsim::Silicon::fillWithPixelAreas"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon18fillWithPixelAreasEv9ImageViewI1TE8PositionIiEb", "galsim::Silicon::fillWithPixelAreas::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon18fillWithPixelAreasEv9ImageViewI1TE8PositionIiEb", "galsim::Silicon::fillWithPixelAreas::orig_center"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon18fillWithPixelAreasEv9ImageViewI1TE8PositionIiEb", "galsim::Silicon::fillWithPixelAreas::target"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon18fillWithPixelAreasEv9ImageViewI1TE8PositionIiEb", "galsim::Silicon::fillWithPixelAreas::use_flux"], [28, 1, 1, "_CPPv4N6galsim7Silicon8finalizeEv", "galsim::Silicon::finalize"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon10initializeEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::initialize"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon10initializeEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::initialize::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10initializeEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::initialize::orig_center"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon10initializeEv9ImageViewI1TE8PositionIiE", "galsim::Silicon::initialize::target"], [28, 1, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::emptypolyData"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::emptypolySize"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::horizontalBoundaryPointsData"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::ix"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::iy"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::off_edge"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::pixelInnerBoundsData"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::pixelOuterBoundsData"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::targetBounds"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::verticalBoundaryPointsData"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::x"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::y"], [28, 2, 1, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE", "galsim::Silicon::insidePixel::zconv"], [28, 1, 1, "_CPPv4NK6galsim7Silicon9pixelAreaEiiii", "galsim::Silicon::pixelArea"], [28, 2, 1, "_CPPv4NK6galsim7Silicon9pixelAreaEiiii", "galsim::Silicon::pixelArea::i"], [28, 2, 1, "_CPPv4NK6galsim7Silicon9pixelAreaEiiii", "galsim::Silicon::pixelArea::j"], [28, 2, 1, "_CPPv4NK6galsim7Silicon9pixelAreaEiiii", "galsim::Silicon::pixelArea::nx"], [28, 2, 1, "_CPPv4NK6galsim7Silicon9pixelAreaEiiii", "galsim::Silicon::pixelArea::ny"], [28, 1, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::emptypoly"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::factor"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::i"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::j"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::nx"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::ny"], [28, 2, 1, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond", "galsim::Silicon::scaleBoundsToPoly::result"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon13subtractDeltaEv9ImageViewI1TE", "galsim::Silicon::subtractDelta"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon13subtractDeltaEv9ImageViewI1TE", "galsim::Silicon::subtractDelta::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon13subtractDeltaEv9ImageViewI1TE", "galsim::Silicon::subtractDelta::target"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon6updateEv9ImageViewI1TE", "galsim::Silicon::update"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon6updateEv9ImageViewI1TE", "galsim::Silicon::update::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon6updateEv9ImageViewI1TE", "galsim::Silicon::update::target"], [28, 1, 1, "_CPPv4I0EN6galsim7Silicon22updatePixelDistortionsEv9ImageViewI1TE", "galsim::Silicon::updatePixelDistortions"], [28, 4, 1, "_CPPv4I0EN6galsim7Silicon22updatePixelDistortionsEv9ImageViewI1TE", "galsim::Silicon::updatePixelDistortions::T"], [28, 2, 1, "_CPPv4I0EN6galsim7Silicon22updatePixelDistortionsEv9ImageViewI1TE", "galsim::Silicon::updatePixelDistortions::target"], [28, 1, 1, "_CPPv4N6galsim7SiliconD0Ev", "galsim::Silicon::~Silicon"], [25, 3, 1, "_CPPv4N6galsim15SincInterpolantE", "galsim::SincInterpolant"], [25, 1, 1, "_CPPv4N6galsim15SincInterpolant15SincInterpolantERK8GSParams", "galsim::SincInterpolant::SincInterpolant"], [25, 2, 1, "_CPPv4N6galsim15SincInterpolant15SincInterpolantERK8GSParams", "galsim::SincInterpolant::SincInterpolant::gsparams"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant7ixrangeEv", "galsim::SincInterpolant::ixrange"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant7makeStrEv", "galsim::SincInterpolant::makeStr"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant5shootER11PhotonArray14UniformDeviate", "galsim::SincInterpolant::shoot"], [25, 2, 1, "_CPPv4NK6galsim15SincInterpolant5shootER11PhotonArray14UniformDeviate", "galsim::SincInterpolant::shoot::photons"], [25, 2, 1, "_CPPv4NK6galsim15SincInterpolant5shootER11PhotonArray14UniformDeviate", "galsim::SincInterpolant::shoot::ud"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant6urangeEv", "galsim::SincInterpolant::urange"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant4uvalEd", "galsim::SincInterpolant::uval"], [25, 2, 1, "_CPPv4NK6galsim15SincInterpolant4uvalEd", "galsim::SincInterpolant::uval::u"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant6xrangeEv", "galsim::SincInterpolant::xrange"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant4xvalEd", "galsim::SincInterpolant::xval"], [25, 2, 1, "_CPPv4NK6galsim15SincInterpolant4xvalEd", "galsim::SincInterpolant::xval::x"], [25, 1, 1, "_CPPv4NK6galsim15SincInterpolant11xvalWrappedEdi", "galsim::SincInterpolant::xvalWrapped"], [25, 2, 1, "_CPPv4NK6galsim15SincInterpolant11xvalWrappedEdi", "galsim::SincInterpolant::xvalWrapped::N"], [25, 2, 1, "_CPPv4NK6galsim15SincInterpolant11xvalWrappedEdi", "galsim::SincInterpolant::xvalWrapped::x"], [25, 1, 1, "_CPPv4N6galsim15SincInterpolantD0Ev", "galsim::SincInterpolant::~SincInterpolant"], [26, 3, 1, "_CPPv4I00EN6galsim5SolveE", "galsim::Solve"], [26, 4, 1, "_CPPv4I00EN6galsim5SolveE", "galsim::Solve::F"], [26, 1, 1, "_CPPv4N6galsim5Solve5SolveERK1F1T1T", "galsim::Solve::Solve"], [26, 2, 1, "_CPPv4N6galsim5Solve5SolveERK1F1T1T", "galsim::Solve::Solve::func_"], [26, 2, 1, "_CPPv4N6galsim5Solve5SolveERK1F1T1T", "galsim::Solve::Solve::lb_"], [26, 2, 1, "_CPPv4N6galsim5Solve5SolveERK1F1T1T", "galsim::Solve::Solve::ub_"], [26, 4, 1, "_CPPv4I00EN6galsim5SolveE", "galsim::Solve::T"], [26, 1, 1, "_CPPv4NK6galsim5Solve6bisectEv", "galsim::Solve::bisect"], [26, 1, 1, "_CPPv4N6galsim5Solve7bracketEv", "galsim::Solve::bracket"], [26, 1, 1, "_CPPv4N6galsim5Solve8bracket1ER1TR1TR1TR1T", "galsim::Solve::bracket1"], [26, 2, 1, "_CPPv4N6galsim5Solve8bracket1ER1TR1TR1TR1T", "galsim::Solve::bracket1::a"], [26, 2, 1, "_CPPv4N6galsim5Solve8bracket1ER1TR1TR1TR1T", "galsim::Solve::bracket1::b"], [26, 2, 1, "_CPPv4N6galsim5Solve8bracket1ER1TR1TR1TR1T", "galsim::Solve::bracket1::fa"], [26, 2, 1, "_CPPv4N6galsim5Solve8bracket1ER1TR1TR1TR1T", "galsim::Solve::bracket1::fb"], [26, 1, 1, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T", "galsim::Solve::bracket1WithLimit"], [26, 2, 1, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T", "galsim::Solve::bracket1WithLimit::a"], [26, 2, 1, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T", "galsim::Solve::bracket1WithLimit::b"], [26, 2, 1, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T", "galsim::Solve::bracket1WithLimit::c"], [26, 2, 1, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T", "galsim::Solve::bracket1WithLimit::fa"], [26, 2, 1, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T", "galsim::Solve::bracket1WithLimit::fb"], [26, 1, 1, "_CPPv4N6galsim5Solve12bracketLowerEv", "galsim::Solve::bracketLower"], [26, 1, 1, "_CPPv4N6galsim5Solve21bracketLowerWithLimitE1T", "galsim::Solve::bracketLowerWithLimit"], [26, 2, 1, "_CPPv4N6galsim5Solve21bracketLowerWithLimitE1T", "galsim::Solve::bracketLowerWithLimit::lower_limit"], [26, 1, 1, "_CPPv4N6galsim5Solve12bracketUpperEv", "galsim::Solve::bracketUpper"], [26, 1, 1, "_CPPv4N6galsim5Solve21bracketUpperWithLimitE1T", "galsim::Solve::bracketUpperWithLimit"], [26, 2, 1, "_CPPv4N6galsim5Solve21bracketUpperWithLimitE1T", "galsim::Solve::bracketUpperWithLimit::upper_limit"], [26, 1, 1, "_CPPv4NK6galsim5Solve14evaluateBoundsEv", "galsim::Solve::evaluateBounds"], [26, 1, 1, "_CPPv4NK6galsim5Solve13getLowerBoundEv", "galsim::Solve::getLowerBound"], [26, 1, 1, "_CPPv4NK6galsim5Solve13getUpperBoundEv", "galsim::Solve::getUpperBound"], [26, 1, 1, "_CPPv4NK6galsim5Solve13getXToleranceEv", "galsim::Solve::getXTolerance"], [26, 1, 1, "_CPPv4NK6galsim5Solve4rootEv", "galsim::Solve::root"], [26, 1, 1, "_CPPv4N6galsim5Solve9setBoundsE1T1T", "galsim::Solve::setBounds"], [26, 2, 1, "_CPPv4N6galsim5Solve9setBoundsE1T1T", "galsim::Solve::setBounds::lb"], [26, 2, 1, "_CPPv4N6galsim5Solve9setBoundsE1T1T", "galsim::Solve::setBounds::ub"], [26, 1, 1, "_CPPv4N6galsim5Solve11setMaxStepsEi", "galsim::Solve::setMaxSteps"], [26, 2, 1, "_CPPv4N6galsim5Solve11setMaxStepsEi", "galsim::Solve::setMaxSteps::m"], [26, 1, 1, "_CPPv4N6galsim5Solve9setMethodE6Method", "galsim::Solve::setMethod"], [26, 2, 1, "_CPPv4N6galsim5Solve9setMethodE6Method", "galsim::Solve::setMethod::m_"], [26, 1, 1, "_CPPv4N6galsim5Solve13setXToleranceE1T", "galsim::Solve::setXTolerance"], [26, 2, 1, "_CPPv4N6galsim5Solve13setXToleranceE1T", "galsim::Solve::setXTolerance::tol"], [26, 1, 1, "_CPPv4NK6galsim5Solve6zbrentEv", "galsim::Solve::zbrent"], [25, 3, 1, "_CPPv4N6galsim5TableE", "galsim::Table"], [25, 3, 1, "_CPPv4N6galsim7Table2DE", "galsim::Table2D"], [25, 1, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D"], [25, 1, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D"], [25, 1, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D::Nx"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D::Nx"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::Nx"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D::Ny"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D::Ny"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::Ny"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::d2fdxdy"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::dfdx"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::dfdy"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D::gsinterp"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D::in"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D::vals"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D::vals"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::vals"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D::xargs"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D::xargs"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::xargs"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant", "galsim::Table2D::Table2D::yargs"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant", "galsim::Table2D::Table2D::yargs"], [25, 2, 1, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd", "galsim::Table2D::Table2D::yargs"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant4ceilE", "galsim::Table2D::ceil"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant5floorE", "galsim::Table2D::floor"], [25, 1, 1, "_CPPv4NK6galsim7Table2D8gradientEddRdRd", "galsim::Table2D::gradient"], [25, 2, 1, "_CPPv4NK6galsim7Table2D8gradientEddRdRd", "galsim::Table2D::gradient::dfdx"], [25, 2, 1, "_CPPv4NK6galsim7Table2D8gradientEddRdRd", "galsim::Table2D::gradient::dfdy"], [25, 2, 1, "_CPPv4NK6galsim7Table2D8gradientEddRdRd", "galsim::Table2D::gradient::x"], [25, 2, 1, "_CPPv4NK6galsim7Table2D8gradientEddRdRd", "galsim::Table2D::gradient::y"], [25, 1, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid::Nx"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid::Ny"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid::dfdxvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid::dfdyvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid::xvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii", "galsim::Table2D::gradientGrid::yvec"], [25, 1, 1, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi", "galsim::Table2D::gradientMany"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi", "galsim::Table2D::gradientMany::N"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi", "galsim::Table2D::gradientMany::dfdxvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi", "galsim::Table2D::gradientMany::dfdyvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi", "galsim::Table2D::gradientMany::xvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi", "galsim::Table2D::gradientMany::yvec"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant8gsinterpE", "galsim::Table2D::gsinterp"], [25, 1, 1, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii", "galsim::Table2D::interpGrid"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii", "galsim::Table2D::interpGrid::Nx"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii", "galsim::Table2D::interpGrid::Ny"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii", "galsim::Table2D::interpGrid::valvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii", "galsim::Table2D::interpGrid::xvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii", "galsim::Table2D::interpGrid::yvec"], [25, 1, 1, "_CPPv4NK6galsim7Table2D10interpManyEPKdPKdPdi", "galsim::Table2D::interpMany"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpManyEPKdPKdPdi", "galsim::Table2D::interpMany::N"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpManyEPKdPKdPdi", "galsim::Table2D::interpMany::valvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpManyEPKdPKdPdi", "galsim::Table2D::interpMany::xvec"], [25, 2, 1, "_CPPv4NK6galsim7Table2D10interpManyEPKdPKdPdi", "galsim::Table2D::interpMany::yvec"], [25, 7, 1, "_CPPv4N6galsim7Table2D11interpolantE", "galsim::Table2D::interpolant"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant4ceilE", "galsim::Table2D::interpolant::ceil"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant5floorE", "galsim::Table2D::interpolant::floor"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant8gsinterpE", "galsim::Table2D::interpolant::gsinterp"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant6linearE", "galsim::Table2D::interpolant::linear"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant7nearestE", "galsim::Table2D::interpolant::nearest"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant6splineE", "galsim::Table2D::interpolant::spline"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant6linearE", "galsim::Table2D::linear"], [25, 1, 1, "_CPPv4NK6galsim7Table2D6lookupEdd", "galsim::Table2D::lookup"], [25, 2, 1, "_CPPv4NK6galsim7Table2D6lookupEdd", "galsim::Table2D::lookup::x"], [25, 2, 1, "_CPPv4NK6galsim7Table2D6lookupEdd", "galsim::Table2D::lookup::y"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant7nearestE", "galsim::Table2D::nearest"], [25, 6, 1, "_CPPv4N6galsim7Table2D11interpolant6splineE", "galsim::Table2D::spline"], [25, 1, 1, "_CPPv4N6galsim5Table5TableEPKdPKdi11interpolant", "galsim::Table::Table"], [25, 1, 1, "_CPPv4N6galsim5Table5TableEPKdPKdiPK11Interpolant", "galsim::Table::Table"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdi11interpolant", "galsim::Table::Table::N"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdiPK11Interpolant", "galsim::Table::Table::N"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdi11interpolant", "galsim::Table::Table::args"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdiPK11Interpolant", "galsim::Table::Table::args"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdiPK11Interpolant", "galsim::Table::Table::gsinterp"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdi11interpolant", "galsim::Table::Table::in"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdi11interpolant", "galsim::Table::Table::vals"], [25, 2, 1, "_CPPv4N6galsim5Table5TableEPKdPKdiPK11Interpolant", "galsim::Table::Table::vals"], [25, 1, 1, "_CPPv4NK6galsim5Table6argMaxEv", "galsim::Table::argMax"], [25, 1, 1, "_CPPv4NK6galsim5Table6argMinEv", "galsim::Table::argMin"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant4ceilE", "galsim::Table::ceil"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant5floorE", "galsim::Table::floor"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant8gsinterpE", "galsim::Table::gsinterp"], [25, 1, 1, "_CPPv4NK6galsim5Table9integrateEdd", "galsim::Table::integrate"], [25, 2, 1, "_CPPv4NK6galsim5Table9integrateEdd", "galsim::Table::integrate::xmax"], [25, 2, 1, "_CPPv4NK6galsim5Table9integrateEdd", "galsim::Table::integrate::xmin"], [25, 1, 1, "_CPPv4NK6galsim5Table16integrateProductERK5Tableddd", "galsim::Table::integrateProduct"], [25, 2, 1, "_CPPv4NK6galsim5Table16integrateProductERK5Tableddd", "galsim::Table::integrateProduct::g"], [25, 2, 1, "_CPPv4NK6galsim5Table16integrateProductERK5Tableddd", "galsim::Table::integrateProduct::xfact"], [25, 2, 1, "_CPPv4NK6galsim5Table16integrateProductERK5Tableddd", "galsim::Table::integrateProduct::xmax"], [25, 2, 1, "_CPPv4NK6galsim5Table16integrateProductERK5Tableddd", "galsim::Table::integrateProduct::xmin"], [25, 1, 1, "_CPPv4NK6galsim5Table10interpManyEPKdPdi", "galsim::Table::interpMany"], [25, 2, 1, "_CPPv4NK6galsim5Table10interpManyEPKdPdi", "galsim::Table::interpMany::N"], [25, 2, 1, "_CPPv4NK6galsim5Table10interpManyEPKdPdi", "galsim::Table::interpMany::argvec"], [25, 2, 1, "_CPPv4NK6galsim5Table10interpManyEPKdPdi", "galsim::Table::interpMany::valvec"], [25, 7, 1, "_CPPv4N6galsim5Table11interpolantE", "galsim::Table::interpolant"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant4ceilE", "galsim::Table::interpolant::ceil"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant5floorE", "galsim::Table::interpolant::floor"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant8gsinterpE", "galsim::Table::interpolant::gsinterp"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant6linearE", "galsim::Table::interpolant::linear"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant7nearestE", "galsim::Table::interpolant::nearest"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant6splineE", "galsim::Table::interpolant::spline"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant6linearE", "galsim::Table::linear"], [25, 1, 1, "_CPPv4NK6galsim5Table6lookupEd", "galsim::Table::lookup"], [25, 2, 1, "_CPPv4NK6galsim5Table6lookupEd", "galsim::Table::lookup::a"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant7nearestE", "galsim::Table::nearest"], [25, 1, 1, "_CPPv4NK6galsim5TableclEd", "galsim::Table::operator()"], [25, 2, 1, "_CPPv4NK6galsim5TableclEd", "galsim::Table::operator()::a"], [25, 1, 1, "_CPPv4NK6galsim5Table4sizeEv", "galsim::Table::size"], [25, 6, 1, "_CPPv4N6galsim5Table11interpolant6splineE", "galsim::Table::spline"], [25, 3, 1, "_CPPv4N6galsim12TableBuilderE", "galsim::TableBuilder"], [25, 1, 1, "_CPPv4N6galsim12TableBuilder12TableBuilderE11interpolant", "galsim::TableBuilder::TableBuilder"], [25, 1, 1, "_CPPv4N6galsim12TableBuilder12TableBuilderEPK11Interpolant", "galsim::TableBuilder::TableBuilder"], [25, 2, 1, "_CPPv4N6galsim12TableBuilder12TableBuilderEPK11Interpolant", "galsim::TableBuilder::TableBuilder::gsinterp"], [25, 2, 1, "_CPPv4N6galsim12TableBuilder12TableBuilderE11interpolant", "galsim::TableBuilder::TableBuilder::in"], [25, 1, 1, "_CPPv4N6galsim12TableBuilder8addEntryEdd", "galsim::TableBuilder::addEntry"], [25, 2, 1, "_CPPv4N6galsim12TableBuilder8addEntryEdd", "galsim::TableBuilder::addEntry::f"], [25, 2, 1, "_CPPv4N6galsim12TableBuilder8addEntryEdd", "galsim::TableBuilder::addEntry::x"], [25, 1, 1, "_CPPv4N6galsim12TableBuilder8finalizeEv", "galsim::TableBuilder::finalize"], [25, 1, 1, "_CPPv4NK6galsim12TableBuilder9finalizedEv", "galsim::TableBuilder::finalized"], [25, 1, 1, "_CPPv4NK6galsim12TableBuilder6lookupEd", "galsim::TableBuilder::lookup"], [25, 2, 1, "_CPPv4NK6galsim12TableBuilder6lookupEd", "galsim::TableBuilder::lookup::a"], [27, 3, 1, "_CPPv4N6galsim14UniformDeviateE", "galsim::UniformDeviate"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateEPKc", "galsim::UniformDeviate::UniformDeviate"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateERK11BaseDeviate", "galsim::UniformDeviate::UniformDeviate"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateERK14UniformDeviate", "galsim::UniformDeviate::UniformDeviate"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateEl", "galsim::UniformDeviate::UniformDeviate"], [27, 2, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateEl", "galsim::UniformDeviate::UniformDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateERK11BaseDeviate", "galsim::UniformDeviate::UniformDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateERK14UniformDeviate", "galsim::UniformDeviate::UniformDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim14UniformDeviate14UniformDeviateEPKc", "galsim::UniformDeviate::UniformDeviate::str_c"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate10clearCacheEv", "galsim::UniformDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate9duplicateEv", "galsim::UniformDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate13duplicate_ptrEv", "galsim::UniformDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim14UniformDeviate9generate1Ev", "galsim::UniformDeviate::generate1"], [27, 3, 1, "_CPPv4N6galsim14WeibullDeviateE", "galsim::WeibullDeviate"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEPKcdd", "galsim::WeibullDeviate::WeibullDeviate"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK11BaseDeviatedd", "galsim::WeibullDeviate::WeibullDeviate"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK14WeibullDeviate", "galsim::WeibullDeviate::WeibullDeviate"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEldd", "galsim::WeibullDeviate::WeibullDeviate"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEPKcdd", "galsim::WeibullDeviate::WeibullDeviate::a"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK11BaseDeviatedd", "galsim::WeibullDeviate::WeibullDeviate::a"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEldd", "galsim::WeibullDeviate::WeibullDeviate::a"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEPKcdd", "galsim::WeibullDeviate::WeibullDeviate::b"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK11BaseDeviatedd", "galsim::WeibullDeviate::WeibullDeviate::b"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEldd", "galsim::WeibullDeviate::WeibullDeviate::b"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEldd", "galsim::WeibullDeviate::WeibullDeviate::lseed"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK11BaseDeviatedd", "galsim::WeibullDeviate::WeibullDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK14WeibullDeviate", "galsim::WeibullDeviate::WeibullDeviate::rhs"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEPKcdd", "galsim::WeibullDeviate::WeibullDeviate::str_c"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate10clearCacheEv", "galsim::WeibullDeviate::clearCache"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate9duplicateEv", "galsim::WeibullDeviate::duplicate"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate13duplicate_ptrEv", "galsim::WeibullDeviate::duplicate_ptr"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate9generate1Ev", "galsim::WeibullDeviate::generate1"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate4getAEv", "galsim::WeibullDeviate::getA"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate4getBEv", "galsim::WeibullDeviate::getB"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate4setAEd", "galsim::WeibullDeviate::setA"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate4setAEd", "galsim::WeibullDeviate::setA::a"], [27, 1, 1, "_CPPv4N6galsim14WeibullDeviate4setBEd", "galsim::WeibullDeviate::setB"], [27, 2, 1, "_CPPv4N6galsim14WeibullDeviate4setBEd", "galsim::WeibullDeviate::setB::b"], [27, 1, 1, "_CPPv4N6galsim25calculateCovarianceMatrixER9ImageViewIdERK9SBProfileRK6BoundsIiEd", "galsim::calculateCovarianceMatrix"], [27, 2, 1, "_CPPv4N6galsim25calculateCovarianceMatrixER9ImageViewIdERK9SBProfileRK6BoundsIiEd", "galsim::calculateCovarianceMatrix::bounds"], [27, 2, 1, "_CPPv4N6galsim25calculateCovarianceMatrixER9ImageViewIdERK9SBProfileRK6BoundsIiEd", "galsim::calculateCovarianceMatrix::cov"], [27, 2, 1, "_CPPv4N6galsim25calculateCovarianceMatrixER9ImageViewIdERK9SBProfileRK6BoundsIiEd", "galsim::calculateCovarianceMatrix::dx"], [27, 2, 1, "_CPPv4N6galsim25calculateCovarianceMatrixER9ImageViewIdERK9SBProfileRK6BoundsIiEd", "galsim::calculateCovarianceMatrix::sbp"], [24, 1, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft"], [24, 4, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft::T"], [24, 2, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft::in"], [24, 2, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft::inverse"], [24, 2, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft::out"], [24, 2, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft::shift_in"], [24, 2, 1, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb", "galsim::cfft::shift_out"], [21, 1, 1, "_CPPv4N6galsim13check_versionEv", "galsim::check_version"], [24, 1, 1, "_CPPv4N6galsim11goodFFTSizeEi", "galsim::goodFFTSize"], [24, 2, 1, "_CPPv4N6galsim11goodFFTSizeEi", "galsim::goodFFTSize::input"], [23, 1, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::PSF_image"], [23, 4, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::T"], [23, 4, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::U"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::gal_image"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::gal_mask_image"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::guess_centroid"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::guess_sig_PSF"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::guess_sig_gal"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::hsmparams"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::precision"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::recompute_flux"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::results"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::shear_est"], [23, 2, 1, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams", "galsim::hsm::EstimateShearView::sky_var"], [23, 1, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView"], [23, 4, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::T"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::guess_centroid"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::guess_sig"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::hsmparams"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::object_image"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::object_mask_image"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::precision"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::results"], [23, 2, 1, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams", "galsim::hsm::FindAdaptiveMomView::round_moments"], [23, 3, 1, "_CPPv4N6galsim3hsm8HSMErrorE", "galsim::hsm::HSMError"], [23, 1, 1, "_CPPv4N6galsim3hsm8HSMError8HSMErrorERKNSt6stringE", "galsim::hsm::HSMError::HSMError"], [23, 2, 1, "_CPPv4N6galsim3hsm8HSMError8HSMErrorERKNSt6stringE", "galsim::hsm::HSMError::HSMError::m"], [23, 3, 1, "_CPPv4N6galsim3hsm9HSMParamsE", "galsim::hsm::HSMParams"], [23, 1, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams"], [23, 1, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEv", "galsim::hsm::HSMParams::HSMParams"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_adapt_order"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_bound_correct_wt"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_convergence_threshold"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_failed_moments"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_ksb_moments_max"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_ksb_sig_factor"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_ksb_sig_weight"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_max_amoment"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_max_ashift"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_max_mom2_iter"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_max_moment_nsig2"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_nsig_rg"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_nsig_rg2"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_num_iter_default"], [23, 2, 1, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd", "galsim::hsm::HSMParams::HSMParams::_regauss_too_small"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams11adapt_orderE", "galsim::hsm::HSMParams::adapt_order"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams16bound_correct_wtE", "galsim::hsm::HSMParams::bound_correct_wt"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams21convergence_thresholdE", "galsim::hsm::HSMParams::convergence_threshold"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams14failed_momentsE", "galsim::hsm::HSMParams::failed_moments"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams15ksb_moments_maxE", "galsim::hsm::HSMParams::ksb_moments_max"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams14ksb_sig_factorE", "galsim::hsm::HSMParams::ksb_sig_factor"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams14ksb_sig_weightE", "galsim::hsm::HSMParams::ksb_sig_weight"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams11max_amomentE", "galsim::hsm::HSMParams::max_amoment"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams10max_ashiftE", "galsim::hsm::HSMParams::max_ashift"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams13max_mom2_iterE", "galsim::hsm::HSMParams::max_mom2_iter"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams16max_moment_nsig2E", "galsim::hsm::HSMParams::max_moment_nsig2"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams7nsig_rgE", "galsim::hsm::HSMParams::nsig_rg"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams8nsig_rg2E", "galsim::hsm::HSMParams::nsig_rg2"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams16num_iter_defaultE", "galsim::hsm::HSMParams::num_iter_default"], [23, 5, 1, "_CPPv4N6galsim3hsm9HSMParams17regauss_too_smallE", "galsim::hsm::HSMParams::regauss_too_small"], [23, 3, 1, "_CPPv4N6galsim3hsm10ObjectDataE", "galsim::hsm::ObjectData"], [23, 1, 1, "_CPPv4N6galsim3hsm10ObjectData10ObjectDataEv", "galsim::hsm::ObjectData::ObjectData"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData2e1E", "galsim::hsm::ObjectData::e1"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData2e2E", "galsim::hsm::ObjectData::e2"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData4fluxE", "galsim::hsm::ObjectData::flux"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData9meas_typeE", "galsim::hsm::ObjectData::meas_type"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData10resolutionE", "galsim::hsm::ObjectData::resolution"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData12responsivityE", "galsim::hsm::ObjectData::responsivity"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData5sigmaE", "galsim::hsm::ObjectData::sigma"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData2x0E", "galsim::hsm::ObjectData::x0"], [23, 5, 1, "_CPPv4N6galsim3hsm10ObjectData2y0E", "galsim::hsm::ObjectData::y0"], [23, 3, 1, "_CPPv4N6galsim3hsm9ShapeDataE", "galsim::hsm::ShapeData"], [23, 1, 1, "_CPPv4N6galsim3hsm9ShapeData9ShapeDataEv", "galsim::hsm::ShapeData::ShapeData"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData12corrected_e1E", "galsim::hsm::ShapeData::corrected_e1"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData12corrected_e2E", "galsim::hsm::ShapeData::corrected_e2"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData12corrected_g1E", "galsim::hsm::ShapeData::corrected_g1"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData12corrected_g2E", "galsim::hsm::ShapeData::corrected_g2"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData19corrected_shape_errE", "galsim::hsm::ShapeData::corrected_shape_err"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData17correction_methodE", "galsim::hsm::ShapeData::correction_method"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData17correction_statusE", "galsim::hsm::ShapeData::correction_status"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData13error_messageE", "galsim::hsm::ShapeData::error_message"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData12image_boundsE", "galsim::hsm::ShapeData::image_bounds"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData9meas_typeE", "galsim::hsm::ShapeData::meas_type"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData11moments_ampE", "galsim::hsm::ShapeData::moments_amp"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData16moments_centroidE", "galsim::hsm::ShapeData::moments_centroid"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData14moments_n_iterE", "galsim::hsm::ShapeData::moments_n_iter"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData12moments_rho4E", "galsim::hsm::ShapeData::moments_rho4"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData13moments_sigmaE", "galsim::hsm::ShapeData::moments_sigma"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData14moments_statusE", "galsim::hsm::ShapeData::moments_status"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData11observed_e1E", "galsim::hsm::ShapeData::observed_e1"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData11observed_e2E", "galsim::hsm::ShapeData::observed_e2"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData6psf_e1E", "galsim::hsm::ShapeData::psf_e1"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData6psf_e2E", "galsim::hsm::ShapeData::psf_e2"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData9psf_sigmaE", "galsim::hsm::ShapeData::psf_sigma"], [23, 5, 1, "_CPPv4N6galsim3hsm9ShapeData17resolution_factorE", "galsim::hsm::ShapeData::resolution_factor"], [23, 1, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::A"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::Mxx"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::Mxy"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::Myy"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::convergence_threshold"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::data"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::hsmparams"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::num_iter"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::rho4"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::x0"], [23, 2, 1, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams", "galsim::hsm::find_ellipmom_2::y0"], [23, 1, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::PSF_data"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::PSF_image"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::flags"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::gal_data"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::gal_image"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::hsmparams"], [23, 2, 1, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams", "galsim::hsm::general_shear_estimator::shear_est"], [26, 3, 1, "_CPPv4I0EN6galsim5integ9IntRegionE", "galsim::integ::IntRegion"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion9IntRegionEK1TK1TPNSt7ostreamEPNSt3mapI1T1TEE", "galsim::integ::IntRegion::IntRegion"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion9IntRegionEK1TK1TPNSt7ostreamEPNSt3mapI1T1TEE", "galsim::integ::IntRegion::IntRegion::a"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion9IntRegionEK1TK1TPNSt7ostreamEPNSt3mapI1T1TEE", "galsim::integ::IntRegion::IntRegion::b"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion9IntRegionEK1TK1TPNSt7ostreamEPNSt3mapI1T1TEE", "galsim::integ::IntRegion::IntRegion::dbgout_"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion9IntRegionEK1TK1TPNSt7ostreamEPNSt3mapI1T1TEE", "galsim::integ::IntRegion::IntRegion::fxmap_"], [26, 4, 1, "_CPPv4I0EN6galsim5integ9IntRegionE", "galsim::integ::IntRegion::T"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion8addSplitEK1T", "galsim::integ::IntRegion::addSplit"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion8addSplitEK1T", "galsim::integ::IntRegion::addSplit::x"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion6bisectEv", "galsim::integ::IntRegion::bisect"], [26, 5, 1, "_CPPv4N6galsim5integ9IntRegion6dbgoutE", "galsim::integ::IntRegion::dbgout"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion17findZeroCrossingsEv", "galsim::integ::IntRegion::findZeroCrossings"], [26, 5, 1, "_CPPv4N6galsim5integ9IntRegion5fxmapE", "galsim::integ::IntRegion::fxmap"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegion7getAreaEv", "galsim::integ::IntRegion::getArea"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegion6getErrEv", "galsim::integ::IntRegion::getErr"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegion9getNSplitEv", "galsim::integ::IntRegion::getNSplit"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegion4leftEv", "galsim::integ::IntRegion::left"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegionltERK9IntRegionI1TE", "galsim::integ::IntRegion::operator<"], [26, 2, 1, "_CPPv4NK6galsim5integ9IntRegionltERK9IntRegionI1TE", "galsim::integ::IntRegion::operator<::r2"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegiongtERK9IntRegionI1TE", "galsim::integ::IntRegion::operator>"], [26, 2, 1, "_CPPv4NK6galsim5integ9IntRegiongtERK9IntRegionI1TE", "galsim::integ::IntRegion::operator>::r2"], [26, 1, 1, "_CPPv4NK6galsim5integ9IntRegion5rightEv", "galsim::integ::IntRegion::right"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion7setAreaERK1TRK1T", "galsim::integ::IntRegion::setArea"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion7setAreaERK1TRK1T", "galsim::integ::IntRegion::setArea::a"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion7setAreaERK1TRK1T", "galsim::integ::IntRegion::setArea::e"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion9subDivideERNSt6vectorI9IntRegionI1TEEE", "galsim::integ::IntRegion::subDivide"], [26, 2, 1, "_CPPv4N6galsim5integ9IntRegion9subDivideERNSt6vectorI9IntRegionI1TEEE", "galsim::integ::IntRegion::subDivide::children"], [26, 1, 1, "_CPPv4N6galsim5integ9IntRegion8useFXMapEv", "galsim::integ::IntRegion::useFXMap"], [26, 1, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd", "galsim::integ::int1d"], [26, 1, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d"], [26, 4, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd", "galsim::integ::int1d::UF"], [26, 4, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d::UF"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd", "galsim::integ::int1d::abserr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d::abserr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd", "galsim::integ::int1d::func"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d::func"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d::max"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d::min"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd", "galsim::integ::int1d::reg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd", "galsim::integ::int1d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd", "galsim::integ::int1d::relerr"], [26, 1, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d"], [26, 1, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d"], [26, 1, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d"], [26, 4, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::BF"], [26, 4, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d::BF"], [26, 4, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::BF"], [26, 4, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::YREG"], [26, 2, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::abserr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d::abserr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::abserr"], [26, 2, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::func"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d::func"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::func"], [26, 2, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::reg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d::reg"], [26, 2, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::xmax"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::xmin"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::ymax"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd", "galsim::integ::int2d::ymin"], [26, 2, 1, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd", "galsim::integ::int2d::yreg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int2d::yreg"], [26, 1, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d"], [26, 1, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d"], [26, 1, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d"], [26, 4, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::TF"], [26, 4, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::TF"], [26, 4, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::TF"], [26, 4, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::YREG"], [26, 4, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::ZREG"], [26, 2, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::abserr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::abserr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::abserr"], [26, 2, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::func"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::func"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::func"], [26, 2, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::reg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::reg"], [26, 2, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::relerr"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::xmax"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::xmin"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::ymax"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::ymin"], [26, 2, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::yreg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::yreg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::zmax"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd", "galsim::integ::int3d::zmin"], [26, 2, 1, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd", "galsim::integ::int3d::zreg"], [26, 2, 1, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd", "galsim::integ::int3d::zreg"], [24, 1, 1, "_CPPv4I0EN6galsim11invertImageEv9ImageViewI1TE", "galsim::invertImage"], [24, 4, 1, "_CPPv4I0EN6galsim11invertImageEv9ImageViewI1TE", "galsim::invertImage::T"], [24, 2, 1, "_CPPv4I0EN6galsim11invertImageEv9ImageViewI1TE", "galsim::invertImage::im"], [24, 1, 1, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb", "galsim::irfft"], [24, 4, 1, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb", "galsim::irfft::T"], [24, 2, 1, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb", "galsim::irfft::in"], [24, 2, 1, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb", "galsim::irfft::out"], [24, 2, 1, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb", "galsim::irfft::shift_in"], [24, 2, 1, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb", "galsim::irfft::shift_out"], [21, 1, 1, "_CPPv4N6galsim13major_versionEv", "galsim::major_version"], [26, 1, 1, "_CPPv4N6galsim4math2CiEd", "galsim::math::Ci"], [26, 2, 1, "_CPPv4N6galsim4math2CiEd", "galsim::math::Ci::x"], [26, 1, 1, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd", "galsim::math::Horner"], [26, 1, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::coef"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::ncx"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::ncy"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::nx"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::result"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::temp"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::x"], [26, 2, 1, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd", "galsim::math::Horner2D::y"], [26, 2, 1, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd", "galsim::math::Horner::coef"], [26, 2, 1, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd", "galsim::math::Horner::nc"], [26, 2, 1, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd", "galsim::math::Horner::nx"], [26, 2, 1, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd", "galsim::math::Horner::result"], [26, 2, 1, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd", "galsim::math::Horner::x"], [26, 1, 1, "_CPPv4N6galsim4math2SiEd", "galsim::math::Si"], [26, 2, 1, "_CPPv4N6galsim4math2SiEd", "galsim::math::Si::x"], [26, 1, 1, "_CPPv4N6galsim4math12cyl_bessel_iEdd", "galsim::math::cyl_bessel_i"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_iEdd", "galsim::math::cyl_bessel_i::nu"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_iEdd", "galsim::math::cyl_bessel_i::x"], [26, 1, 1, "_CPPv4N6galsim4math12cyl_bessel_jEdd", "galsim::math::cyl_bessel_j"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_jEdd", "galsim::math::cyl_bessel_j::nu"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_jEdd", "galsim::math::cyl_bessel_j::x"], [26, 1, 1, "_CPPv4N6galsim4math12cyl_bessel_kEdd", "galsim::math::cyl_bessel_k"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_kEdd", "galsim::math::cyl_bessel_k::nu"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_kEdd", "galsim::math::cyl_bessel_k::x"], [26, 1, 1, "_CPPv4N6galsim4math12cyl_bessel_yEdd", "galsim::math::cyl_bessel_y"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_yEdd", "galsim::math::cyl_bessel_y::nu"], [26, 2, 1, "_CPPv4N6galsim4math12cyl_bessel_yEdd", "galsim::math::cyl_bessel_y::x"], [26, 1, 1, "_CPPv4N6galsim4math7gamma_pEdd", "galsim::math::gamma_p"], [26, 2, 1, "_CPPv4N6galsim4math7gamma_pEdd", "galsim::math::gamma_p::a"], [26, 2, 1, "_CPPv4N6galsim4math7gamma_pEdd", "galsim::math::gamma_p::x"], [26, 1, 1, "_CPPv4N6galsim4math13getBesselRootEdi", "galsim::math::getBesselRoot"], [26, 1, 1, "_CPPv4N6galsim4math14getBesselRoot0Ei", "galsim::math::getBesselRoot0"], [26, 2, 1, "_CPPv4N6galsim4math14getBesselRoot0Ei", "galsim::math::getBesselRoot0::s"], [26, 2, 1, "_CPPv4N6galsim4math13getBesselRootEdi", "galsim::math::getBesselRoot::nu"], [26, 2, 1, "_CPPv4N6galsim4math13getBesselRootEdi", "galsim::math::getBesselRoot::s"], [26, 1, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf"], [26, 2, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf::abserr"], [26, 2, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf::f"], [26, 2, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf::k"], [26, 2, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf::nu"], [26, 2, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf::nzeros"], [26, 2, 1, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi", "galsim::math::hankel_inf::relerr"], [26, 1, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::abserr"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::f"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::k"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::maxr"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::nu"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::nzeros"], [26, 2, 1, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi", "galsim::math::hankel_trunc::relerr"], [26, 1, 1, "_CPPv4I0EN6galsim4math5isNanEb1T", "galsim::math::isNan"], [26, 4, 1, "_CPPv4I0EN6galsim4math5isNanEb1T", "galsim::math::isNan::T"], [26, 2, 1, "_CPPv4I0EN6galsim4math5isNanEb1T", "galsim::math::isNan::x"], [26, 1, 1, "_CPPv4N6galsim4math2j0Ed", "galsim::math::j0"], [26, 2, 1, "_CPPv4N6galsim4math2j0Ed", "galsim::math::j0::x"], [26, 1, 1, "_CPPv4N6galsim4math2j1Ed", "galsim::math::j1"], [26, 2, 1, "_CPPv4N6galsim4math2j1Ed", "galsim::math::j1::x"], [26, 1, 1, "_CPPv4N6galsim4math4sincEd", "galsim::math::sinc"], [26, 2, 1, "_CPPv4N6galsim4math4sincEd", "galsim::math::sinc::x"], [26, 1, 1, "_CPPv4N6galsim4math6sincosEdRdRd", "galsim::math::sincos"], [26, 2, 1, "_CPPv4N6galsim4math6sincosEdRdRd", "galsim::math::sincos::cost"], [26, 2, 1, "_CPPv4N6galsim4math6sincosEdRdRd", "galsim::math::sincos::sint"], [26, 2, 1, "_CPPv4N6galsim4math6sincosEdRdRd", "galsim::math::sincos::theta"], [21, 1, 1, "_CPPv4N6galsim13minor_versionEv", "galsim::minor_version"], [21, 1, 1, "_CPPv4N6galsim8revisionEv", "galsim::revision"], [24, 1, 1, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb", "galsim::rfft"], [24, 4, 1, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb", "galsim::rfft::T"], [24, 2, 1, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb", "galsim::rfft::in"], [24, 2, 1, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb", "galsim::rfft::out"], [24, 2, 1, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb", "galsim::rfft::shift_in"], [24, 2, 1, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb", "galsim::rfft::shift_out"], [21, 1, 1, "_CPPv4N6galsim7versionEv", "galsim::version"], [24, 1, 1, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb", "galsim::wrapImage"], [24, 4, 1, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb", "galsim::wrapImage::T"], [24, 2, 1, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb", "galsim::wrapImage::bounds"], [24, 2, 1, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb", "galsim::wrapImage::hermx"], [24, 2, 1, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb", "galsim::wrapImage::hermy"], [24, 2, 1, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb", "galsim::wrapImage::im"]], "galsim": [[8, 8, 1, "", "Add"], [77, 9, 1, "", "AffineTransform"], [61, 9, 1, "", "Airy"], [75, 9, 1, "", "Angle"], [75, 9, 1, "", "AngleUnit"], [54, 9, 1, "", "Aperture"], [77, 9, 1, "", "AstropyWCS"], [54, 8, 1, "", "Atmosphere"], [54, 9, 1, "", "AtmosphericScreen"], [8, 9, 1, "", "AutoConvolution"], [8, 8, 1, "", "AutoConvolve"], [8, 8, 1, "", "AutoCorrelate"], [8, 9, 1, "", "AutoCorrelation"], [1, 9, 1, "", "Bandpass"], [20, 9, 1, "", "BaseCorrelatedNoise"], [32, 9, 1, "", "BaseDeviate"], [51, 9, 1, "", "BaseNoise"], [77, 9, 1, "", "BaseWCS"], [32, 9, 1, "", "BinomialDeviate"], [3, 9, 1, "", "Bounds"], [3, 9, 1, "", "BoundsD"], [3, 9, 1, "", "BoundsI"], [70, 9, 1, "", "Box"], [51, 9, 1, "", "CCDNoise"], [63, 9, 1, "", "COSMOSCatalog"], [4, 9, 1, "", "Catalog"], [77, 9, 1, "", "CelestialCoord"], [32, 9, 1, "", "Chi2Deviate"], [7, 9, 1, "", "ChromaticAiry"], [7, 9, 1, "", "ChromaticAtmosphere"], [7, 9, 1, "", "ChromaticAutoConvolution"], [7, 9, 1, "", "ChromaticAutoCorrelation"], [7, 9, 1, "", "ChromaticConvolution"], [7, 9, 1, "", "ChromaticDeconvolution"], [7, 9, 1, "", "ChromaticFourierSqrtProfile"], [7, 9, 1, "", "ChromaticObject"], [7, 9, 1, "", "ChromaticOpticalPSF"], [7, 9, 1, "", "ChromaticRealGalaxy"], [7, 9, 1, "", "ChromaticSum"], [7, 9, 1, "", "ChromaticTransformation"], [8, 9, 1, "", "Convolution"], [8, 8, 1, "", "Convolve"], [20, 9, 1, "", "CorrelatedNoise"], [50, 9, 1, "", "Cosmology"], [71, 9, 1, "", "CovarianceSpectrum"], [48, 9, 1, "", "Cubic"], [36, 9, 1, "", "DeVaucouleurs"], [73, 9, 1, "", "Deconvolution"], [73, 8, 1, "", "Deconvolve"], [48, 9, 1, "", "Delta"], [70, 9, 1, "", "DeltaFunction"], [51, 9, 1, "", "DeviateNoise"], [4, 9, 1, "", "Dict"], [32, 9, 1, "", "DistDeviate"], [66, 9, 1, "", "EmissionLine"], [36, 9, 1, "", "Exponential"], [57, 9, 1, "", "FRatioAngles"], [77, 8, 1, "", "FitsWCS"], [77, 8, 1, "", "FittedSIPWCS"], [57, 9, 1, "", "FocusDepth"], [73, 8, 1, "", "FourierSqrt"], [73, 9, 1, "", "FourierSqrtProfile"], [77, 9, 1, "", "GSFitsWCS"], [37, 9, 1, "", "GSObject"], [38, 9, 1, "", "GSParams"], [33, 9, 1, "", "GalSimBoundsError"], [33, 9, 1, "", "GalSimConfigError"], [33, 9, 1, "", "GalSimConfigValueError"], [33, 9, 1, "", "GalSimDeprecationWarning"], [33, 9, 1, "", "GalSimError"], [33, 9, 1, "", "GalSimFFTSizeError"], [33, 9, 1, "", "GalSimHSMError"], [33, 9, 1, "", "GalSimImmutableError"], [33, 9, 1, "", "GalSimIncompatibleValuesError"], [33, 9, 1, "", "GalSimIndexError"], [33, 9, 1, "", "GalSimKeyError"], [33, 9, 1, "", "GalSimNotImplementedError"], [33, 9, 1, "", "GalSimRangeError"], [33, 9, 1, "", "GalSimSEDError"], [33, 9, 1, "", "GalSimUndefinedBoundsError"], [33, 9, 1, "", "GalSimValueError"], [33, 9, 1, "", "GalSimWarning"], [63, 9, 1, "", "GalaxySample"], [32, 9, 1, "", "GammaDeviate"], [70, 9, 1, "", "Gaussian"], [32, 9, 1, "", "GaussianDeviate"], [51, 9, 1, "", "GaussianNoise"], [42, 9, 1, "", "Image"], [42, 8, 1, "", "ImageCD"], [42, 8, 1, "", "ImageCF"], [42, 8, 1, "", "ImageD"], [42, 8, 1, "", "ImageF"], [42, 8, 1, "", "ImageI"], [42, 8, 1, "", "ImageS"], [42, 8, 1, "", "ImageUI"], [42, 8, 1, "", "ImageUS"], [36, 9, 1, "", "InclinedExponential"], [36, 9, 1, "", "InclinedSersic"], [48, 9, 1, "", "Interpolant"], [7, 9, 1, "", "InterpolatedChromaticObject"], [0, 9, 1, "", "InterpolatedImage"], [0, 9, 1, "", "InterpolatedKImage"], [77, 9, 1, "", "JacobianWCS"], [61, 9, 1, "", "Kolmogorov"], [48, 9, 1, "", "Lanczos"], [48, 9, 1, "", "Linear"], [72, 9, 1, "", "LookupTable"], [72, 9, 1, "", "LookupTable2D"], [61, 9, 1, "", "Moffat"], [50, 9, 1, "", "NFWHalo"], [48, 9, 1, "", "Nearest"], [77, 9, 1, "", "OffsetShearWCS"], [77, 9, 1, "", "OffsetWCS"], [61, 9, 1, "", "OpticalPSF"], [54, 9, 1, "", "OpticalScreen"], [4, 9, 1, "", "OutputCatalog"], [54, 9, 1, "", "PhaseScreenList"], [54, 9, 1, "", "PhaseScreenPSF"], [56, 9, 1, "", "PhotonArray"], [57, 9, 1, "", "PhotonDCR"], [57, 9, 1, "", "PhotonOp"], [70, 9, 1, "", "Pixel"], [77, 9, 1, "", "PixelScale"], [32, 9, 1, "", "PoissonDeviate"], [51, 9, 1, "", "PoissonNoise"], [58, 9, 1, "", "Position"], [58, 9, 1, "", "PositionD"], [58, 9, 1, "", "PositionI"], [59, 9, 1, "", "PowerSpectrum"], [57, 9, 1, "", "PupilAnnulusSampler"], [57, 9, 1, "", "PupilImageSampler"], [77, 9, 1, "", "PyAstWCS"], [48, 9, 1, "", "Quintic"], [77, 9, 1, "", "RaDecFunction"], [36, 9, 1, "", "RandomKnots"], [63, 9, 1, "", "RealGalaxy"], [63, 9, 1, "", "RealGalaxyCatalog"], [57, 9, 1, "", "Refraction"], [66, 9, 1, "", "SED"], [54, 9, 1, "", "SecondKick"], [67, 9, 1, "", "Sensor"], [36, 9, 1, "", "Sersic"], [0, 9, 1, "", "Shapelet"], [69, 9, 1, "", "Shear"], [77, 9, 1, "", "ShearWCS"], [67, 9, 1, "", "SiliconSensor"], [48, 9, 1, "", "SincInterpolant"], [36, 9, 1, "", "Spergel"], [8, 9, 1, "", "Sum"], [77, 8, 1, "", "TanWCS"], [57, 9, 1, "", "TimeSampler"], [70, 9, 1, "", "TopHat"], [73, 8, 1, "", "Transform"], [73, 9, 1, "", "Transformation"], [77, 9, 1, "", "UVFunction"], [20, 9, 1, "", "UncorrelatedNoise"], [32, 9, 1, "", "UniformDeviate"], [54, 9, 1, "", "UserScreen"], [51, 9, 1, "", "VariableGaussianNoise"], [61, 9, 1, "", "VonKarman"], [57, 9, 1, "", "WavelengthSampler"], [77, 9, 1, "", "WcsToolsWCS"], [32, 9, 1, "", "WeibullDeviate"], [75, 8, 1, "", "_Angle"], [3, 8, 1, "", "_BoundsD"], [3, 8, 1, "", "_BoundsI"], [42, 8, 1, "", "_Image"], [0, 8, 1, "", "_InterpolatedImage"], [0, 8, 1, "", "_InterpolatedKImage"], [69, 8, 1, "", "_Shear"], [73, 8, 1, "", "_Transform"], [20, 8, 1, "", "getCOSMOSNoise"], [72, 8, 1, "", "trapz"]], "galsim.AffineTransform": [[77, 10, 1, "", "dudx"], [77, 10, 1, "", "dudy"], [77, 10, 1, "", "dvdx"], [77, 10, 1, "", "dvdy"], [77, 10, 1, "", "origin"], [77, 10, 1, "", "world_origin"]], "galsim.Airy": [[61, 10, 1, "", "fwhm"], [61, 10, 1, "", "half_light_radius"], [61, 10, 1, "", "lam_over_diam"], [61, 10, 1, "", "obscuration"], [61, 11, 1, "", "withFlux"]], "galsim.Angle": [[75, 11, 1, "", "cos"], [75, 10, 1, "", "deg"], [75, 11, 1, "", "dms"], [75, 11, 1, "", "from_dms"], [75, 11, 1, "", "from_hms"], [75, 11, 1, "", "hms"], [75, 10, 1, "", "rad"], [75, 11, 1, "", "sin"], [75, 11, 1, "", "sincos"], [75, 11, 1, "", "tan"], [75, 11, 1, "", "wrap"]], "galsim.AngleUnit": [[75, 11, 1, "", "from_name"], [75, 10, 1, "", "value"]], "galsim.Aperture": [[54, 10, 1, "", "diam"], [54, 10, 1, "", "gsparams"], [54, 10, 1, "", "illuminated"], [54, 10, 1, "", "npix"], [54, 10, 1, "", "obscuration"], [54, 10, 1, "", "pupil_plane_scale"], [54, 10, 1, "", "pupil_plane_size"], [54, 11, 1, "", "samplePupil"], [54, 10, 1, "", "u"], [54, 10, 1, "", "v"], [54, 11, 1, "", "withGSParams"]], "galsim.AstropyWCS": [[77, 10, 1, "", "origin"], [77, 10, 1, "", "wcs"]], "galsim.AtmosphericScreen": [[54, 10, 1, "", "altitude"], [54, 11, 1, "", "instantiate"], [54, 10, 1, "", "kmax"], [54, 10, 1, "", "kmin"], [54, 11, 1, "", "wavefront"], [54, 11, 1, "", "wavefront_gradient"]], "galsim.AutoConvolution": [[8, 10, 1, "", "orig_obj"], [8, 10, 1, "", "real_space"], [8, 11, 1, "", "withGSParams"]], "galsim.AutoCorrelation": [[8, 10, 1, "", "orig_obj"], [8, 10, 1, "", "real_space"], [8, 11, 1, "", "withGSParams"]], "galsim.Bandpass": [[1, 11, 1, "", "__call__"], [1, 11, 1, "", "calculateEffectiveWavelength"], [1, 10, 1, "", "effective_wavelength"], [1, 11, 1, "", "thin"], [1, 11, 1, "", "truncate"], [1, 11, 1, "", "withZeropoint"]], "galsim.BaseCorrelatedNoise": [[20, 11, 1, "", "applyTo"], [20, 11, 1, "", "convolvedWith"], [20, 11, 1, "", "copy"], [20, 11, 1, "", "dilate"], [20, 11, 1, "", "drawImage"], [20, 11, 1, "", "drawKImage"], [20, 11, 1, "", "expand"], [20, 11, 1, "", "from_file"], [20, 11, 1, "", "getVariance"], [20, 10, 1, "", "gsparams"], [20, 11, 1, "", "lens"], [20, 11, 1, "", "magnify"], [20, 10, 1, "", "rng"], [20, 11, 1, "", "rotate"], [20, 11, 1, "", "shear"], [20, 11, 1, "", "symmetrizeImage"], [20, 11, 1, "", "transform"], [20, 11, 1, "", "whitenImage"], [20, 11, 1, "", "withGSParams"], [20, 11, 1, "", "withScaledVariance"], [20, 11, 1, "", "withVariance"]], "galsim.BaseDeviate": [[32, 11, 1, "", "_reset"], [32, 11, 1, "", "_seed"], [32, 11, 1, "", "add_generate"], [32, 11, 1, "", "as_numpy_generator"], [32, 11, 1, "", "clearCache"], [32, 11, 1, "", "discard"], [32, 11, 1, "", "duplicate"], [32, 11, 1, "", "generate"], [32, 10, 1, "", "generates_in_pairs"], [32, 10, 1, "", "has_reliable_discard"], [32, 10, 1, "", "np"], [32, 11, 1, "", "raw"], [32, 11, 1, "", "reset"], [32, 11, 1, "", "seed"]], "galsim.BaseNoise": [[51, 11, 1, "", "__div__"], [51, 11, 1, "", "__mul__"], [51, 11, 1, "", "applyTo"], [51, 11, 1, "", "getVariance"], [51, 10, 1, "", "rng"], [51, 11, 1, "", "withScaledVariance"], [51, 11, 1, "", "withVariance"]], "galsim.BaseWCS": [[77, 11, 1, "", "affine"], [77, 11, 1, "", "fixColor"], [77, 11, 1, "", "isCelestial"], [77, 11, 1, "", "isLocal"], [77, 11, 1, "", "isPixelScale"], [77, 11, 1, "", "isUniform"], [77, 11, 1, "", "jacobian"], [77, 11, 1, "", "local"], [77, 11, 1, "", "makeSkyImage"], [77, 11, 1, "", "maxLinearScale"], [77, 11, 1, "", "minLinearScale"], [77, 11, 1, "", "pixelArea"], [77, 11, 1, "", "posToImage"], [77, 11, 1, "", "posToWorld"], [77, 11, 1, "", "profileToImage"], [77, 11, 1, "", "profileToWorld"], [77, 11, 1, "", "shearToImage"], [77, 11, 1, "", "shearToWorld"], [77, 11, 1, "", "shiftOrigin"], [77, 11, 1, "", "toImage"], [77, 11, 1, "", "toWorld"], [77, 11, 1, "", "writeToFitsHeader"]], "galsim.BinomialDeviate": [[32, 11, 1, "", "__call__"], [32, 10, 1, "", "n"], [32, 10, 1, "", "p"]], "galsim.Bounds": [[3, 11, 1, "", "area"], [3, 10, 1, "", "center"], [3, 11, 1, "", "expand"], [3, 11, 1, "", "getXMax"], [3, 11, 1, "", "getXMin"], [3, 11, 1, "", "getYMax"], [3, 11, 1, "", "getYMin"], [3, 11, 1, "", "includes"], [3, 11, 1, "", "isDefined"], [3, 10, 1, "", "origin"], [3, 11, 1, "", "shift"], [3, 10, 1, "", "true_center"], [3, 11, 1, "", "withBorder"]], "galsim.BoundsI": [[3, 11, 1, "", "numpyShape"]], "galsim.Box": [[70, 10, 1, "", "height"], [70, 10, 1, "", "width"], [70, 11, 1, "", "withFlux"]], "galsim.CCDNoise": [[51, 11, 1, "", "copy"], [51, 10, 1, "", "gain"], [51, 10, 1, "", "read_noise"], [51, 10, 1, "", "sky_level"]], "galsim.Catalog": [[4, 11, 1, "", "get"], [4, 11, 1, "", "getFloat"], [4, 11, 1, "", "getInt"], [4, 11, 1, "", "readAscii"], [4, 11, 1, "", "readFits"]], "galsim.CelestialCoord": [[77, 11, 1, "", "angleBetween"], [77, 11, 1, "", "area"], [77, 10, 1, "", "dec"], [77, 11, 1, "", "deproject"], [77, 11, 1, "", "deproject_rad"], [77, 11, 1, "", "distanceTo"], [77, 11, 1, "", "ecliptic"], [77, 11, 1, "", "from_ecliptic"], [77, 11, 1, "", "from_galactic"], [77, 11, 1, "", "from_xyz"], [77, 11, 1, "", "galactic"], [77, 11, 1, "", "get_xyz"], [77, 11, 1, "", "greatCirclePoint"], [77, 11, 1, "", "jac_deproject"], [77, 11, 1, "", "jac_deproject_rad"], [77, 11, 1, "", "normal"], [77, 11, 1, "", "precess"], [77, 11, 1, "", "project"], [77, 11, 1, "", "project_rad"], [77, 10, 1, "", "ra"], [77, 10, 1, "", "rad"], [77, 11, 1, "", "radec_to_xyz"], [77, 11, 1, "", "xyz_to_radec"]], "galsim.Chi2Deviate": [[32, 11, 1, "", "__call__"], [32, 10, 1, "", "has_reliable_discard"], [32, 10, 1, "", "n"]], "galsim.ChromaticAiry": [[7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticAtmosphere": [[7, 11, 1, "", "build_obj"], [7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticAutoConvolution": [[7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticAutoCorrelation": [[7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticConvolution": [[7, 11, 1, "", "drawImage"], [7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 10, 1, "", "obj_list"], [7, 11, 1, "", "resize_effective_prof_cache"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticDeconvolution": [[7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticFourierSqrtProfile": [[7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticObject": [[7, 11, 1, "", "__mul__"], [7, 11, 1, "", "applyTo"], [7, 11, 1, "", "atRedshift"], [7, 11, 1, "", "calculateCentroid"], [7, 11, 1, "", "calculateFlux"], [7, 11, 1, "", "calculateMagnitude"], [7, 11, 1, "", "dilate"], [7, 10, 1, "", "dimensionless"], [7, 11, 1, "", "drawImage"], [7, 11, 1, "", "drawKImage"], [7, 11, 1, "", "evaluateAtWavelength"], [7, 11, 1, "", "expand"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "interpolate"], [7, 11, 1, "", "lens"], [7, 11, 1, "", "magnify"], [7, 10, 1, "", "redshift"], [7, 11, 1, "", "resize_multiplier_cache"], [7, 11, 1, "", "rotate"], [7, 11, 1, "", "shear"], [7, 11, 1, "", "shift"], [7, 10, 1, "", "spectral"], [7, 11, 1, "", "transform"], [7, 11, 1, "", "withFlux"], [7, 11, 1, "", "withFluxDensity"], [7, 11, 1, "", "withGSParams"], [7, 11, 1, "", "withMagnitude"], [7, 11, 1, "", "withScaledFlux"]], "galsim.ChromaticOpticalPSF": [[7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.ChromaticRealGalaxy": [[7, 11, 1, "", "makeFromImages"]], "galsim.ChromaticSum": [[7, 11, 1, "", "drawImage"], [7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 10, 1, "", "obj_list"], [7, 11, 1, "", "withGSParams"], [7, 11, 1, "", "withScaledFlux"]], "galsim.ChromaticTransformation": [[7, 11, 1, "", "drawImage"], [7, 11, 1, "", "evaluateAtWavelength"], [7, 10, 1, "", "gsparams"], [7, 10, 1, "", "original"], [7, 11, 1, "", "withGSParams"]], "galsim.Convolution": [[8, 10, 1, "", "obj_list"], [8, 10, 1, "", "real_space"], [8, 11, 1, "", "withGSParams"]], "galsim.Cosmology": [[50, 11, 1, "", "Da"], [50, 11, 1, "", "E"], [50, 11, 1, "", "a"]], "galsim.CovarianceSpectrum": [[71, 11, 1, "", "toNoise"]], "galsim.Cubic": [[48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 11, 1, "", "unit_integrals"], [48, 10, 1, "", "xrange"]], "galsim.DeVaucouleurs": [[36, 11, 1, "", "withFlux"]], "galsim.Deconvolution": [[73, 10, 1, "", "orig_obj"], [73, 11, 1, "", "withGSParams"]], "galsim.Delta": [[48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 11, 1, "", "unit_integrals"], [48, 10, 1, "", "xrange"]], "galsim.DeltaFunction": [[70, 11, 1, "", "withFlux"]], "galsim.DeviateNoise": [[51, 11, 1, "", "copy"]], "galsim.DistDeviate": [[32, 11, 1, "", "__call__"], [32, 11, 1, "", "add_generate"], [32, 11, 1, "", "generate"], [32, 11, 1, "", "val"]], "galsim.EmissionLine": [[66, 11, 1, "", "atRedshift"]], "galsim.Exponential": [[36, 10, 1, "", "half_light_radius"], [36, 10, 1, "", "scale_radius"], [36, 11, 1, "", "withFlux"]], "galsim.FRatioAngles": [[57, 11, 1, "", "applyTo"]], "galsim.FocusDepth": [[57, 11, 1, "", "applyTo"]], "galsim.FourierSqrtProfile": [[73, 10, 1, "", "orig_obj"], [73, 11, 1, "", "withGSParams"]], "galsim.GSFitsWCS": [[77, 10, 1, "", "origin"]], "galsim.GSObject": [[37, 11, 1, "", "__add__"], [37, 11, 1, "", "__div__"], [37, 11, 1, "", "__mul__"], [37, 11, 1, "", "__rmul__"], [37, 11, 1, "", "__sub__"], [37, 11, 1, "", "_calculate_nphotons"], [37, 11, 1, "", "_drawKImage"], [37, 11, 1, "", "_drawReal"], [37, 11, 1, "", "_kValue"], [37, 11, 1, "", "_shear"], [37, 11, 1, "", "_shift"], [37, 11, 1, "", "_shoot"], [37, 11, 1, "", "_xValue"], [37, 11, 1, "", "applyTo"], [37, 11, 1, "", "atRedshift"], [37, 11, 1, "", "calculateFWHM"], [37, 11, 1, "", "calculateHLR"], [37, 11, 1, "", "calculateMomentRadius"], [37, 10, 1, "", "centroid"], [37, 11, 1, "", "dilate"], [37, 11, 1, "", "drawFFT"], [37, 11, 1, "", "drawFFT_finish"], [37, 11, 1, "", "drawFFT_makeKImage"], [37, 11, 1, "", "drawImage"], [37, 11, 1, "", "drawKImage"], [37, 11, 1, "", "drawPhot"], [37, 11, 1, "", "drawReal"], [37, 11, 1, "", "expand"], [37, 10, 1, "", "flux"], [37, 11, 1, "", "getGoodImageSize"], [37, 10, 1, "", "gsparams"], [37, 10, 1, "", "has_hard_edges"], [37, 10, 1, "", "is_analytic_k"], [37, 10, 1, "", "is_analytic_x"], [37, 10, 1, "", "is_axisymmetric"], [37, 11, 1, "", "kValue"], [37, 11, 1, "", "lens"], [37, 11, 1, "", "magnify"], [37, 11, 1, "", "makePhot"], [37, 10, 1, "", "max_sb"], [37, 10, 1, "", "maxk"], [37, 10, 1, "", "negative_flux"], [37, 10, 1, "", "noise"], [37, 10, 1, "", "nyquist_scale"], [37, 10, 1, "", "positive_flux"], [37, 11, 1, "", "rotate"], [37, 11, 1, "", "shear"], [37, 11, 1, "", "shift"], [37, 11, 1, "", "shoot"], [37, 10, 1, "", "stepk"], [37, 11, 1, "", "transform"], [37, 11, 1, "", "withFlux"], [37, 11, 1, "", "withGSParams"], [37, 11, 1, "", "withScaledFlux"], [37, 11, 1, "", "xValue"]], "galsim.GSParams": [[38, 11, 1, "", "check"], [38, 11, 1, "", "combine"], [38, 11, 1, "", "withParams"]], "galsim.GalaxySample": [[63, 11, 1, "", "canMakeReal"], [63, 11, 1, "", "getParametricRecord"], [63, 11, 1, "", "getRealParams"], [63, 11, 1, "", "getValue"], [63, 11, 1, "", "makeGalaxy"], [63, 11, 1, "", "selectRandomIndex"]], "galsim.GammaDeviate": [[32, 11, 1, "", "__call__"], [32, 10, 1, "", "has_reliable_discard"], [32, 10, 1, "", "k"], [32, 10, 1, "", "theta"]], "galsim.Gaussian": [[70, 10, 1, "", "fwhm"], [70, 10, 1, "", "half_light_radius"], [70, 10, 1, "", "sigma"], [70, 11, 1, "", "withFlux"]], "galsim.GaussianDeviate": [[32, 11, 1, "", "__call__"], [32, 11, 1, "", "generate_from_variance"], [32, 10, 1, "", "generates_in_pairs"], [32, 10, 1, "", "mean"], [32, 10, 1, "", "sigma"]], "galsim.GaussianNoise": [[51, 11, 1, "", "copy"], [51, 10, 1, "", "sigma"]], "galsim.Image": [[42, 11, 1, "", "FindAdaptiveMom"], [42, 11, 1, "", "__call__"], [42, 11, 1, "", "__getitem__"], [42, 11, 1, "", "__setitem__"], [42, 11, 1, "", "_addValue"], [42, 11, 1, "", "_fill"], [42, 11, 1, "", "_getValue"], [42, 11, 1, "", "_invertSelf"], [42, 11, 1, "", "_setValue"], [42, 11, 1, "", "_shift"], [42, 11, 1, "", "_view"], [42, 11, 1, "", "_wrap"], [42, 11, 1, "", "addNoise"], [42, 11, 1, "", "addNoiseSNR"], [42, 11, 1, "", "addReciprocityFailure"], [42, 11, 1, "", "addValue"], [42, 11, 1, "", "applyIPC"], [42, 11, 1, "", "applyNonlinearity"], [42, 11, 1, "", "applyPersistence"], [42, 10, 1, "", "array"], [42, 11, 1, "", "bin"], [42, 10, 1, "", "bounds"], [42, 11, 1, "", "calculateFWHM"], [42, 11, 1, "", "calculateHLR"], [42, 11, 1, "", "calculateMomentRadius"], [42, 11, 1, "", "calculate_fft"], [42, 11, 1, "", "calculate_inverse_fft"], [42, 10, 1, "", "center"], [42, 11, 1, "", "clear_depixelize_cache"], [42, 10, 1, "", "conjugate"], [42, 11, 1, "", "copy"], [42, 11, 1, "", "copyFrom"], [42, 11, 1, "", "depixelize"], [42, 10, 1, "", "dtype"], [42, 11, 1, "", "fill"], [42, 11, 1, "", "flip_lr"], [42, 11, 1, "", "flip_ud"], [42, 11, 1, "", "getValue"], [42, 11, 1, "", "get_pixel_centers"], [42, 11, 1, "", "good_fft_size"], [42, 10, 1, "", "imag"], [42, 11, 1, "", "invertSelf"], [42, 10, 1, "", "iscomplex"], [42, 10, 1, "", "isconst"], [42, 10, 1, "", "iscontiguous"], [42, 10, 1, "", "isinteger"], [42, 10, 1, "", "ncol"], [42, 10, 1, "", "nrow"], [42, 10, 1, "", "origin"], [42, 10, 1, "", "outer_bounds"], [42, 11, 1, "", "quantize"], [42, 10, 1, "", "real"], [42, 11, 1, "", "replaceNegative"], [42, 11, 1, "", "resize"], [42, 11, 1, "", "rot_180"], [42, 11, 1, "", "rot_ccw"], [42, 11, 1, "", "rot_cw"], [42, 10, 1, "", "scale"], [42, 11, 1, "", "setCenter"], [42, 11, 1, "", "setOrigin"], [42, 11, 1, "", "setSubImage"], [42, 11, 1, "", "setValue"], [42, 11, 1, "", "setZero"], [42, 11, 1, "", "shift"], [42, 11, 1, "", "subImage"], [42, 11, 1, "", "subsample"], [42, 11, 1, "", "symmetrizeNoise"], [42, 11, 1, "", "transpose"], [42, 10, 1, "", "true_center"], [42, 11, 1, "", "view"], [42, 11, 1, "", "whitenNoise"], [42, 11, 1, "", "wrap"], [42, 11, 1, "", "write"], [42, 10, 1, "", "xmax"], [42, 10, 1, "", "xmin"], [42, 10, 1, "", "ymax"], [42, 10, 1, "", "ymin"]], "galsim.InclinedExponential": [[36, 10, 1, "", "disk_half_light_radius"], [36, 10, 1, "", "inclination"], [36, 10, 1, "", "scale_h_over_r"], [36, 10, 1, "", "scale_height"], [36, 10, 1, "", "scale_radius"], [36, 11, 1, "", "withFlux"]], "galsim.InclinedSersic": [[36, 10, 1, "", "disk_half_light_radius"], [36, 10, 1, "", "inclination"], [36, 10, 1, "", "n"], [36, 10, 1, "", "scale_h_over_r"], [36, 10, 1, "", "scale_height"], [36, 10, 1, "", "scale_radius"], [36, 10, 1, "", "trunc"], [36, 11, 1, "", "withFlux"]], "galsim.Interpolant": [[48, 11, 1, "", "from_name"], [48, 10, 1, "", "gsparams"], [48, 11, 1, "", "kval"], [48, 10, 1, "", "negative_flux"], [48, 10, 1, "", "positive_flux"], [48, 11, 1, "", "unit_integrals"], [48, 11, 1, "", "withGSParams"], [48, 11, 1, "", "xval"]], "galsim.InterpolatedChromaticObject": [[7, 11, 1, "", "drawImage"], [7, 11, 1, "", "evaluateAtWavelength"], [7, 11, 1, "", "from_images"], [7, 10, 1, "", "gsparams"], [7, 11, 1, "", "withGSParams"]], "galsim.InterpolatedImage": [[0, 10, 1, "", "image"], [0, 10, 1, "", "k_interpolant"], [0, 11, 1, "", "withGSParams"], [0, 10, 1, "", "x_interpolant"]], "galsim.InterpolatedKImage": [[0, 10, 1, "", "k_interpolant"], [0, 10, 1, "", "kimage"], [0, 11, 1, "", "withGSParams"]], "galsim.JacobianWCS": [[77, 10, 1, "", "dudx"], [77, 10, 1, "", "dudy"], [77, 10, 1, "", "dvdx"], [77, 10, 1, "", "dvdy"], [77, 11, 1, "", "getDecomposition"], [77, 11, 1, "", "getMatrix"]], "galsim.Kolmogorov": [[61, 10, 1, "", "fwhm"], [61, 10, 1, "", "half_light_radius"], [61, 10, 1, "", "lam_over_r0"], [61, 11, 1, "", "withFlux"]], "galsim.Lanczos": [[48, 10, 1, "", "conserve_dc"], [48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 10, 1, "", "n"], [48, 10, 1, "", "xrange"]], "galsim.Linear": [[48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 11, 1, "", "unit_integrals"], [48, 10, 1, "", "xrange"]], "galsim.LookupTable": [[72, 11, 1, "", "__call__"], [72, 11, 1, "", "from_file"], [72, 11, 1, "", "from_func"], [72, 11, 1, "", "integrate"], [72, 11, 1, "", "integrate_product"], [72, 10, 1, "", "x_max"], [72, 10, 1, "", "x_min"]], "galsim.LookupTable2D": [[72, 11, 1, "", "__call__"], [72, 11, 1, "", "gradient"]], "galsim.Moffat": [[61, 10, 1, "", "beta"], [61, 10, 1, "", "half_light_radius"], [61, 10, 1, "", "scale_radius"], [61, 10, 1, "", "trunc"], [61, 11, 1, "", "withFlux"]], "galsim.NFWHalo": [[50, 11, 1, "", "_getConvergence"], [50, 11, 1, "", "_getLensing"], [50, 11, 1, "", "_getMagnification"], [50, 11, 1, "", "_getShear"], [50, 11, 1, "", "getConvergence"], [50, 11, 1, "", "getLensing"], [50, 11, 1, "", "getMagnification"], [50, 11, 1, "", "getShear"]], "galsim.Nearest": [[48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 11, 1, "", "unit_integrals"], [48, 10, 1, "", "xrange"]], "galsim.OffsetShearWCS": [[77, 10, 1, "", "origin"], [77, 10, 1, "", "scale"], [77, 10, 1, "", "shear"], [77, 10, 1, "", "world_origin"]], "galsim.OffsetWCS": [[77, 10, 1, "", "origin"], [77, 10, 1, "", "scale"], [77, 10, 1, "", "world_origin"]], "galsim.OpticalPSF": [[61, 11, 1, "", "withFlux"]], "galsim.OpticalScreen": [[54, 11, 1, "", "wavefront"], [54, 11, 1, "", "wavefront_gradient"]], "galsim.OutputCatalog": [[4, 11, 1, "", "addRow"], [4, 11, 1, "", "getNCols"], [4, 11, 1, "", "getNObjects"], [4, 11, 1, "", "getNames"], [4, 11, 1, "", "getTypes"], [4, 11, 1, "", "makeData"], [4, 10, 1, "", "ncols"], [4, 10, 1, "", "nobjects"], [4, 11, 1, "", "setTypes"], [4, 11, 1, "", "write"], [4, 11, 1, "", "writeAscii"], [4, 11, 1, "", "writeFits"], [4, 11, 1, "", "writeFitsHdu"]], "galsim.PhaseScreenList": [[54, 11, 1, "", "instantiate"], [54, 11, 1, "", "makePSF"], [54, 11, 1, "", "wavefront"], [54, 11, 1, "", "wavefront_gradient"]], "galsim.PhaseScreenPSF": [[54, 10, 1, "", "fft_sign"], [54, 10, 1, "", "flux"], [54, 10, 1, "", "kcrit"], [54, 10, 1, "", "screen_list"], [54, 11, 1, "", "withFlux"], [54, 11, 1, "", "withGSParams"]], "galsim.PhotonArray": [[56, 11, 1, "", "addTo"], [56, 11, 1, "", "allocateAngles"], [56, 11, 1, "", "allocatePupil"], [56, 11, 1, "", "allocateTimes"], [56, 11, 1, "", "allocateWavelengths"], [56, 11, 1, "", "assignAt"], [56, 11, 1, "", "concatenate"], [56, 11, 1, "", "convolve"], [56, 11, 1, "", "copyFrom"], [56, 10, 1, "", "dxdz"], [56, 10, 1, "", "dydz"], [56, 10, 1, "", "flux"], [56, 11, 1, "", "fromArrays"], [56, 11, 1, "", "getTotalFlux"], [56, 11, 1, "", "hasAllocatedAngles"], [56, 11, 1, "", "hasAllocatedPupil"], [56, 11, 1, "", "hasAllocatedTimes"], [56, 11, 1, "", "hasAllocatedWavelengths"], [56, 11, 1, "", "isCorrelated"], [56, 11, 1, "", "makeFromImage"], [56, 10, 1, "", "pupil_u"], [56, 10, 1, "", "pupil_v"], [56, 11, 1, "", "read"], [56, 11, 1, "", "scaleFlux"], [56, 11, 1, "", "scaleXY"], [56, 11, 1, "", "setCorrelated"], [56, 11, 1, "", "setTotalFlux"], [56, 11, 1, "", "size"], [56, 10, 1, "", "time"], [56, 10, 1, "", "wavelength"], [56, 11, 1, "", "write"], [56, 10, 1, "", "x"], [56, 10, 1, "", "y"]], "galsim.PhotonDCR": [[57, 11, 1, "", "applyTo"]], "galsim.PhotonOp": [[57, 11, 1, "", "applyTo"]], "galsim.Pixel": [[70, 10, 1, "", "scale"], [70, 11, 1, "", "withFlux"]], "galsim.PixelScale": [[77, 10, 1, "", "scale"]], "galsim.PoissonDeviate": [[32, 11, 1, "", "__call__"], [32, 11, 1, "", "generate_from_expectation"], [32, 10, 1, "", "has_reliable_discard"], [32, 10, 1, "", "mean"]], "galsim.PoissonNoise": [[51, 11, 1, "", "copy"], [51, 10, 1, "", "sky_level"]], "galsim.Position": [[58, 11, 1, "", "shear"]], "galsim.PositionD": [[58, 11, 1, "", "round"]], "galsim.PowerSpectrum": [[59, 11, 1, "", "_getConvergence"], [59, 11, 1, "", "_getLensing"], [59, 11, 1, "", "_getMagnification"], [59, 11, 1, "", "_getShear"], [59, 11, 1, "", "buildGrid"], [59, 11, 1, "", "calculateXi"], [59, 11, 1, "", "getConvergence"], [59, 11, 1, "", "getLensing"], [59, 11, 1, "", "getMagnification"], [59, 11, 1, "", "getShear"], [59, 11, 1, "", "nRandCallsForBuildGrid"]], "galsim.PupilAnnulusSampler": [[57, 11, 1, "", "applyTo"]], "galsim.PupilImageSampler": [[57, 11, 1, "", "applyTo"]], "galsim.PyAstWCS": [[77, 10, 1, "", "origin"], [77, 10, 1, "", "wcsinfo"]], "galsim.Quintic": [[48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 10, 1, "", "xrange"]], "galsim.RaDecFunction": [[77, 10, 1, "", "origin"], [77, 10, 1, "", "radec_func"]], "galsim.RandomKnots": [[36, 11, 1, "", "calculateHLR"], [36, 11, 1, "", "dilate"], [36, 11, 1, "", "expand"], [36, 10, 1, "", "input_half_light_radius"], [36, 10, 1, "", "npoints"], [36, 11, 1, "", "rotate"], [36, 11, 1, "", "shear"], [36, 11, 1, "", "shift"], [36, 11, 1, "", "transform"], [36, 11, 1, "", "withFlux"], [36, 11, 1, "", "withScaledFlux"]], "galsim.RealGalaxy": [[63, 11, 1, "", "makeFromImage"], [63, 11, 1, "", "withGSParams"]], "galsim.RealGalaxyCatalog": [[63, 11, 1, "", "getBandpass"], [63, 11, 1, "", "getGalImage"], [63, 11, 1, "", "getIndexForID"], [63, 11, 1, "", "getNoise"], [63, 11, 1, "", "getNoiseProperties"], [63, 11, 1, "", "getPSF"], [63, 11, 1, "", "getPSFImage"], [63, 11, 1, "", "preload"]], "galsim.Refraction": [[57, 11, 1, "", "applyTo"]], "galsim.SED": [[66, 11, 1, "", "__call__"], [66, 11, 1, "", "__mul__"], [66, 11, 1, "", "_mul_bandpass"], [66, 11, 1, "", "_mul_scalar"], [66, 11, 1, "", "_mul_sed"], [66, 11, 1, "", "atRedshift"], [66, 11, 1, "", "calculateDCRMomentShifts"], [66, 11, 1, "", "calculateFlux"], [66, 11, 1, "", "calculateMagnitude"], [66, 11, 1, "", "calculateSeeingMomentRatio"], [66, 11, 1, "", "check_dimensionless"], [66, 11, 1, "", "check_spectral"], [66, 10, 1, "", "dimensionless"], [66, 11, 1, "", "sampleWavelength"], [66, 11, 1, "", "thin"], [66, 11, 1, "", "withFlux"], [66, 11, 1, "", "withFluxDensity"], [66, 11, 1, "", "withMagnitude"]], "galsim.SecondKick": [[54, 10, 1, "", "diam"], [54, 10, 1, "", "kcrit"], [54, 10, 1, "", "lam"], [54, 10, 1, "", "obscuration"], [54, 10, 1, "", "r0"], [54, 10, 1, "", "scale_unit"], [54, 11, 1, "", "withFlux"]], "galsim.Sensor": [[67, 11, 1, "", "accumulate"], [67, 11, 1, "", "calculate_pixel_areas"]], "galsim.Sersic": [[36, 11, 1, "", "calculateHLRFactor"], [36, 11, 1, "", "calculateIntegratedFlux"], [36, 10, 1, "", "half_light_radius"], [36, 10, 1, "", "n"], [36, 10, 1, "", "scale_radius"], [36, 10, 1, "", "trunc"], [36, 11, 1, "", "withFlux"]], "galsim.Shapelet": [[0, 10, 1, "", "bvec"], [0, 11, 1, "", "fit"], [0, 11, 1, "", "getNM"], [0, 11, 1, "", "getPQ"], [0, 10, 1, "", "order"], [0, 10, 1, "", "sigma"], [0, 11, 1, "", "size"]], "galsim.Shear": [[69, 10, 1, "", "beta"], [69, 10, 1, "", "e"], [69, 10, 1, "", "e1"], [69, 10, 1, "", "e2"], [69, 10, 1, "", "esq"], [69, 10, 1, "", "eta"], [69, 10, 1, "", "eta1"], [69, 10, 1, "", "eta2"], [69, 10, 1, "", "g"], [69, 10, 1, "", "g1"], [69, 10, 1, "", "g2"], [69, 11, 1, "", "getMatrix"], [69, 10, 1, "", "q"], [69, 11, 1, "", "rotationWith"], [69, 10, 1, "", "shear"]], "galsim.ShearWCS": [[77, 10, 1, "", "scale"], [77, 10, 1, "", "shear"]], "galsim.SiliconSensor": [[67, 11, 1, "", "accumulate"], [67, 11, 1, "", "calculate_pixel_areas"], [67, 11, 1, "", "simple_treerings"]], "galsim.SincInterpolant": [[48, 10, 1, "", "ixrange"], [48, 10, 1, "", "krange"], [48, 11, 1, "", "unit_integrals"], [48, 10, 1, "", "xrange"]], "galsim.Spergel": [[36, 11, 1, "", "calculateFluxRadius"], [36, 11, 1, "", "calculateIntegratedFlux"], [36, 10, 1, "", "half_light_radius"], [36, 10, 1, "", "nu"], [36, 10, 1, "", "scale_radius"], [36, 11, 1, "", "withFlux"]], "galsim.Sum": [[8, 10, 1, "", "obj_list"], [8, 11, 1, "", "withGSParams"]], "galsim.TimeSampler": [[57, 11, 1, "", "applyTo"]], "galsim.TopHat": [[70, 10, 1, "", "radius"], [70, 11, 1, "", "withFlux"]], "galsim.Transformation": [[73, 10, 1, "", "flux_ratio"], [73, 10, 1, "", "jac"], [73, 10, 1, "", "offset"], [73, 10, 1, "", "original"], [73, 11, 1, "", "withGSParams"]], "galsim.UVFunction": [[77, 10, 1, "", "origin"], [77, 10, 1, "", "ufunc"], [77, 10, 1, "", "vfunc"], [77, 10, 1, "", "world_origin"], [77, 10, 1, "", "xfunc"], [77, 10, 1, "", "yfunc"]], "galsim.UncorrelatedNoise": [[20, 11, 1, "", "withGSParams"]], "galsim.UniformDeviate": [[32, 11, 1, "", "__call__"]], "galsim.UserScreen": [[54, 11, 1, "", "wavefront"], [54, 11, 1, "", "wavefront_gradient"]], "galsim.VariableGaussianNoise": [[51, 11, 1, "", "applyTo"], [51, 11, 1, "", "copy"], [51, 10, 1, "", "var_image"]], "galsim.VonKarman": [[61, 10, 1, "", "L0"], [61, 10, 1, "", "delta_amplitude"], [61, 10, 1, "", "do_delta"], [61, 10, 1, "", "force_stepk"], [61, 10, 1, "", "half_light_radius"], [61, 10, 1, "", "lam"], [61, 10, 1, "", "r0"], [61, 10, 1, "", "r0_500"], [61, 10, 1, "", "scale_unit"], [61, 11, 1, "", "withFlux"]], "galsim.WavelengthSampler": [[57, 11, 1, "", "applyTo"]], "galsim.WcsToolsWCS": [[77, 10, 1, "", "file_name"], [77, 10, 1, "", "origin"]], "galsim.WeibullDeviate": [[32, 11, 1, "", "__call__"], [32, 10, 1, "", "a"], [32, 10, 1, "", "b"]], "galsim.bessel": [[2, 8, 1, "", "ci"], [2, 8, 1, "", "gammainc"], [2, 8, 1, "", "iv"], [2, 8, 1, "", "j0"], [2, 8, 1, "", "j0_root"], [2, 8, 1, "", "j1"], [2, 8, 1, "", "jn"], [2, 8, 1, "", "jv"], [2, 8, 1, "", "jv_root"], [2, 8, 1, "", "kn"], [2, 8, 1, "", "kv"], [2, 8, 1, "", "si"], [2, 8, 1, "", "sinc"], [2, 8, 1, "", "yn"], [2, 8, 1, "", "yv"]], "galsim.cdmodel": [[5, 9, 1, "", "BaseCDModel"], [5, 9, 1, "", "PowerLawCD"]], "galsim.cdmodel.BaseCDModel": [[5, 11, 1, "", "__init__"], [5, 11, 1, "", "applyBackward"], [5, 11, 1, "", "applyForward"]], "galsim.cdmodel.PowerLawCD": [[5, 11, 1, "", "__init__"]], "galsim.config": [[15, 8, 1, "", "AddExtraOutputHDUs"], [15, 8, 1, "", "AddNoise"], [15, 8, 1, "", "AddNoiseVariance"], [15, 8, 1, "", "AddSky"], [11, 9, 1, "", "BandpassBuilder"], [15, 8, 1, "", "BuildFile"], [15, 8, 1, "", "BuildFiles"], [15, 8, 1, "", "BuildGSObject"], [15, 8, 1, "", "BuildImage"], [15, 8, 1, "", "BuildImages"], [15, 8, 1, "", "BuildStamp"], [15, 8, 1, "", "BuildStamps"], [15, 8, 1, "", "BuildWCS"], [11, 9, 1, "", "CCDNoiseBuilder"], [11, 9, 1, "", "COSMOSNoiseBuilder"], [15, 8, 1, "", "CalculateNoiseVariance"], [15, 8, 1, "", "CheckAllParams"], [15, 8, 1, "", "CheckNoExtraOutputHDUs"], [15, 8, 1, "", "CleanConfig"], [15, 8, 1, "", "ConvertNones"], [15, 8, 1, "", "CopyConfig"], [15, 8, 1, "", "DrawBasic"], [15, 8, 1, "", "EvaluateCurrentValue"], [14, 9, 1, "", "ExtraOutputBuilder"], [15, 8, 1, "", "FlattenNoiseVariance"], [11, 9, 1, "", "GaussianNoiseBuilder"], [15, 8, 1, "", "GetAllParams"], [15, 8, 1, "", "GetCurrentValue"], [15, 8, 1, "", "GetFinalExtraOutput"], [15, 8, 1, "", "GetFromConfig"], [15, 8, 1, "", "GetIndex"], [15, 8, 1, "", "GetInputObj"], [15, 8, 1, "", "GetLoggerProxy"], [15, 8, 1, "", "GetNFiles"], [15, 8, 1, "", "GetNImagesForFile"], [15, 8, 1, "", "GetNObjForFile"], [15, 8, 1, "", "GetNObjForImage"], [15, 8, 1, "", "GetRNG"], [15, 8, 1, "", "GetSky"], [11, 9, 1, "", "ImageBuilder"], [15, 8, 1, "", "ImportModules"], [12, 9, 1, "", "InputLoader"], [11, 9, 1, "", "ListWCSBuilder"], [15, 9, 1, "", "LoggerWrapper"], [15, 8, 1, "", "MakeImageTasks"], [15, 8, 1, "", "MakeStampTasks"], [15, 8, 1, "", "MergeConfig"], [15, 8, 1, "", "MultiProcess"], [11, 9, 1, "", "NoiseBuilder"], [11, 9, 1, "", "OriginWCSBuilder"], [14, 9, 1, "", "OutputBuilder"], [15, 8, 1, "", "ParseExtendedKey"], [15, 8, 1, "", "ParseRandomSeed"], [15, 8, 1, "", "ParseValue"], [15, 8, 1, "", "ParseWorldPos"], [17, 9, 1, "", "PhotonOpBuilder"], [11, 9, 1, "", "PoissonNoiseBuilder"], [15, 8, 1, "", "Process"], [15, 8, 1, "", "ProcessAllTemplates"], [15, 8, 1, "", "ProcessExtraOutputsForImage"], [15, 8, 1, "", "ProcessExtraOutputsForStamp"], [15, 8, 1, "", "ProcessInput"], [15, 8, 1, "", "ProcessInputNObjects"], [15, 8, 1, "", "ProcessTemplate"], [15, 8, 1, "", "PropagateIndexKeyRNGNum"], [15, 8, 1, "", "ReadConfig"], [15, 8, 1, "", "ReadJson"], [15, 8, 1, "", "ReadYaml"], [11, 8, 1, "", "RegisterBandpassType"], [14, 8, 1, "", "RegisterExtraOutput"], [11, 8, 1, "", "RegisterImageType"], [15, 8, 1, "", "RegisterInputConnectedType"], [12, 8, 1, "", "RegisterInputType"], [11, 8, 1, "", "RegisterNoiseType"], [13, 8, 1, "", "RegisterObjectType"], [14, 8, 1, "", "RegisterOutputType"], [17, 8, 1, "", "RegisterPhotonOpType"], [13, 8, 1, "", "RegisterSEDType"], [11, 8, 1, "", "RegisterSensorType"], [16, 8, 1, "", "RegisterTemplate"], [19, 8, 1, "", "RegisterValueType"], [11, 8, 1, "", "RegisterWCSType"], [15, 8, 1, "", "RemoveCurrent"], [15, 8, 1, "", "RetryIO"], [13, 9, 1, "", "SEDBuilder"], [11, 9, 1, "", "SensorBuilder"], [15, 8, 1, "", "SetDefaultExt"], [15, 8, 1, "", "SetDefaultIndex"], [15, 8, 1, "", "SetInConfig"], [15, 8, 1, "", "SetupConfigFileNum"], [15, 8, 1, "", "SetupConfigImageNum"], [15, 8, 1, "", "SetupConfigImageSize"], [15, 8, 1, "", "SetupConfigObjNum"], [15, 8, 1, "", "SetupConfigRNG"], [15, 8, 1, "", "SetupConfigStampSize"], [15, 8, 1, "", "SetupExtraOutput"], [15, 8, 1, "", "SetupExtraOutputsForImage"], [15, 8, 1, "", "SetupInput"], [15, 8, 1, "", "SetupInputsForImage"], [11, 9, 1, "", "SimpleWCSBuilder"], [15, 9, 1, "", "SkipThisObject"], [17, 9, 1, "", "StampBuilder"], [11, 9, 1, "", "TanWCSBuilder"], [15, 8, 1, "", "TransformObject"], [15, 8, 1, "", "UpdateConfig"], [15, 8, 1, "", "UpdateGSParams"], [15, 8, 1, "", "UpdateNProc"], [11, 9, 1, "", "WCSBuilder"], [15, 8, 1, "", "WriteExtraOutputs"]], "galsim.config.BandpassBuilder": [[11, 11, 1, "", "buildBandpass"]], "galsim.config.ExtraOutputBuilder": [[14, 11, 1, "", "ensureFinalized"], [14, 11, 1, "", "finalize"], [14, 11, 1, "", "initialize"], [14, 11, 1, "", "processImage"], [14, 11, 1, "", "processSkippedStamp"], [14, 11, 1, "", "processStamp"], [14, 11, 1, "", "setupImage"], [14, 11, 1, "", "writeFile"], [14, 11, 1, "", "writeHdu"]], "galsim.config.ImageBuilder": [[11, 11, 1, "", "addNoise"], [11, 11, 1, "", "buildBandpass"], [11, 11, 1, "", "buildImage"], [11, 11, 1, "", "buildSensor"], [11, 11, 1, "", "getNObj"], [11, 11, 1, "", "makeTasks"], [11, 11, 1, "", "setup"]], "galsim.config.InputLoader": [[12, 11, 1, "", "getKwargs"], [12, 11, 1, "", "initialize"], [12, 11, 1, "", "setupImage"], [12, 11, 1, "", "useProxy"]], "galsim.config.NoiseBuilder": [[11, 11, 1, "", "addNoise"], [11, 11, 1, "", "addNoiseVariance"], [11, 11, 1, "", "getNoiseVariance"]], "galsim.config.OutputBuilder": [[14, 11, 1, "", "addExtraOutputHDUs"], [14, 11, 1, "", "buildImages"], [14, 11, 1, "", "canAddHdus"], [14, 11, 1, "", "getFilename"], [14, 11, 1, "", "getNFiles"], [14, 11, 1, "", "getNImages"], [14, 11, 1, "", "getNObjPerImage"], [14, 11, 1, "", "setup"], [14, 11, 1, "", "writeExtraOutputs"], [14, 11, 1, "", "writeFile"]], "galsim.config.PhotonOpBuilder": [[17, 11, 1, "", "buildPhotonOp"]], "galsim.config.SEDBuilder": [[13, 11, 1, "", "buildSED"]], "galsim.config.SensorBuilder": [[11, 11, 1, "", "buildSensor"]], "galsim.config.StampBuilder": [[17, 11, 1, "", "addNoise"], [17, 11, 1, "", "applySNRScale"], [17, 11, 1, "", "buildPSF"], [17, 11, 1, "", "buildProfile"], [17, 11, 1, "", "draw"], [17, 11, 1, "", "getDrawMethod"], [17, 11, 1, "", "getOffset"], [17, 11, 1, "", "getSNRScale"], [17, 11, 1, "", "getSkip"], [17, 11, 1, "", "locateStamp"], [17, 11, 1, "", "makeStamp"], [17, 11, 1, "", "makeTasks"], [17, 11, 1, "", "quickSkip"], [17, 11, 1, "", "reject"], [17, 11, 1, "", "reset"], [17, 11, 1, "", "setup"], [17, 11, 1, "", "setupRNG"], [17, 11, 1, "", "updateOrigin"], [17, 11, 1, "", "updateSkip"], [17, 11, 1, "", "whiten"]], "galsim.config.WCSBuilder": [[11, 11, 1, "", "buildWCS"]], "galsim.config.extra_badpix": [[14, 9, 1, "", "BadPixBuilder"]], "galsim.config.extra_psf": [[14, 9, 1, "", "ExtraPSFBuilder"]], "galsim.config.extra_truth": [[14, 9, 1, "", "TruthBuilder"]], "galsim.config.extra_weight": [[14, 9, 1, "", "WeightBuilder"]], "galsim.config.gsobject": [[13, 8, 1, "", "_BuildAdd"], [13, 8, 1, "", "_BuildConvolve"], [13, 8, 1, "", "_BuildList"], [13, 8, 1, "", "_BuildOpticalPSF"]], "galsim.config.image_scattered": [[11, 9, 1, "", "ScatteredImageBuilder"]], "galsim.config.image_tiled": [[11, 9, 1, "", "TiledImageBuilder"]], "galsim.config.input_cosmos": [[12, 9, 1, "", "SampleLoader"], [12, 8, 1, "", "_BuildCOSMOSGalaxy"]], "galsim.config.input_nfw": [[12, 9, 1, "", "NFWLoader"], [12, 8, 1, "", "_GenerateFromNFWHaloMagnification"], [12, 8, 1, "", "_GenerateFromNFWHaloShear"]], "galsim.config.input_powerspectrum": [[12, 9, 1, "", "PowerSpectrumLoader"], [12, 8, 1, "", "_GenerateFromPowerSpectrumMagnification"], [12, 8, 1, "", "_GenerateFromPowerSpectrumShear"]], "galsim.config.input_real": [[12, 8, 1, "", "_BuildRealGalaxy"], [12, 8, 1, "", "_BuildRealGalaxyOriginal"]], "galsim.config.output_datacube": [[14, 9, 1, "", "DataCubeBuilder"]], "galsim.config.output_multifits": [[14, 9, 1, "", "MultiFitsBuilder"]], "galsim.config.stamp_ring": [[17, 9, 1, "", "RingBuilder"]], "galsim.dcr": [[30, 8, 1, "", "air_refractive_index_minus_one"], [30, 8, 1, "", "get_refraction"], [30, 8, 1, "", "parse_dcr_angles"], [30, 8, 1, "", "zenith_parallactic_angles"]], "galsim.des": [[31, 9, 1, "", "DES_PSFEx"], [31, 9, 1, "", "DES_Shapelet"], [31, 9, 1, "", "MEDSBuilder"], [31, 9, 1, "", "MultiExposureObject"], [31, 9, 1, "", "OffsetBuilder"], [31, 8, 1, "", "WriteMEDS"]], "galsim.des.DES_PSFEx": [[31, 11, 1, "", "getLocalWCS"], [31, 11, 1, "", "getPSF"], [31, 11, 1, "", "getPSFArray"]], "galsim.des.DES_Shapelet": [[31, 11, 1, "", "getB"], [31, 11, 1, "", "getPSF"], [31, 11, 1, "", "read_fits"]], "galsim.des.MEDSBuilder": [[31, 11, 1, "", "buildImages"], [31, 11, 1, "", "getNImages"], [31, 11, 1, "", "writeFile"]], "galsim.des.OffsetBuilder": [[31, 11, 1, "", "finalize"], [31, 11, 1, "", "processStamp"]], "galsim.fft": [[34, 8, 1, "", "fft2"], [34, 8, 1, "", "ifft2"], [34, 8, 1, "", "irfft2"], [34, 8, 1, "", "rfft2"]], "galsim.fits": [[35, 9, 1, "", "FitsHeader"], [35, 8, 1, "", "closeHDUList"], [35, 8, 1, "", "read"], [35, 8, 1, "", "readCube"], [35, 8, 1, "", "readFile"], [35, 8, 1, "", "readMulti"], [35, 8, 1, "", "write"], [35, 8, 1, "", "writeCube"], [35, 8, 1, "", "writeFile"], [35, 8, 1, "", "writeMulti"]], "galsim.fits.FitsHeader": [[35, 11, 1, "", "append"], [35, 11, 1, "", "clear"], [35, 11, 1, "", "comment"], [35, 11, 1, "", "extend"], [35, 11, 1, "", "get"], [35, 11, 1, "", "items"], [35, 11, 1, "", "iteritems"], [35, 11, 1, "", "iterkeys"], [35, 11, 1, "", "itervalues"], [35, 11, 1, "", "keys"], [35, 11, 1, "", "pop"], [35, 11, 1, "", "update"], [35, 11, 1, "", "values"]], "galsim.hsm": [[40, 8, 1, "", "EstimateShear"], [40, 8, 1, "", "FindAdaptiveMom"], [40, 9, 1, "", "HSMParams"], [40, 9, 1, "", "ShapeData"]], "galsim.hsm.HSMParams": [[40, 11, 1, "", "check"]], "galsim.hsm.ShapeData": [[40, 11, 1, "", "applyWCS"]], "galsim.integ": [[47, 9, 1, "", "ContinuousIntegrator"], [47, 9, 1, "", "ImageIntegrator"], [47, 9, 1, "", "IntegrationRule"], [47, 9, 1, "", "MidptRule"], [47, 9, 1, "", "QuadRule"], [47, 9, 1, "", "SampleIntegrator"], [47, 9, 1, "", "TrapzRule"], [47, 8, 1, "", "hankel"], [47, 8, 1, "", "int1d"], [47, 12, 1, "", "midptRule"], [47, 12, 1, "", "quadRule"], [47, 12, 1, "", "trapzRule"]], "galsim.integ.ImageIntegrator": [[47, 11, 1, "", "__call__"]], "galsim.integ.MidptRule": [[47, 11, 1, "", "calculateWeights"]], "galsim.integ.QuadRule": [[47, 11, 1, "", "calculateWeights"]], "galsim.integ.TrapzRule": [[47, 11, 1, "", "calculateWeights"]], "galsim.lensing_ps": [[59, 9, 1, "", "PowerSpectrumRealizer"], [59, 8, 1, "", "theoryToObserved"]], "galsim.lensing_ps.PowerSpectrumRealizer": [[59, 11, 1, "", "__call__"]], "galsim.meta_data": [[68, 12, 1, "", "share_dir"]], "galsim.phase_screens": [[54, 8, 1, "", "initWorker"], [54, 8, 1, "", "initWorkerArgs"], [54, 8, 1, "", "reset_shared_screens"]], "galsim.pse": [[60, 9, 1, "", "PowerSpectrumEstimator"]], "galsim.pse.PowerSpectrumEstimator": [[60, 11, 1, "", "estimate"]], "galsim.roman": [[64, 8, 1, "", "addReciprocityFailure"], [64, 8, 1, "", "allDetectorEffects"], [64, 8, 1, "", "allowedPos"], [64, 8, 1, "", "applyIPC"], [64, 8, 1, "", "applyNonlinearity"], [64, 8, 1, "", "applyPersistence"], [64, 8, 1, "", "bestPA"], [64, 8, 1, "", "convertCenter"], [64, 8, 1, "", "findSCA"], [64, 8, 1, "", "getBandpasses"], [64, 8, 1, "", "getPSF"], [64, 8, 1, "", "getSkyLevel"], [64, 8, 1, "", "getWCS"]], "galsim.table": [[72, 8, 1, "", "_LookupTable2D"]], "galsim.utilities": [[49, 9, 1, "", "CaptureLog"], [49, 9, 1, "", "LRU_Cache"], [49, 9, 1, "", "OrderedWeakRef"], [49, 9, 1, "", "Profile"], [49, 9, 1, "", "SimpleGenerator"], [49, 9, 1, "", "WeakMethod"], [49, 8, 1, "", "_horner"], [49, 8, 1, "", "_horner2d"], [49, 8, 1, "", "binomial"], [49, 8, 1, "", "check_all_diff"], [49, 8, 1, "", "check_pickle"], [49, 8, 1, "", "check_share_file"], [49, 8, 1, "", "combine_wave_list"], [49, 8, 1, "", "convert_interpolant"], [49, 8, 1, "", "deInterleaveImage"], [49, 9, 1, "", "doc_inherit"], [49, 8, 1, "", "dol_to_lod"], [49, 8, 1, "", "ensure_dir"], [49, 8, 1, "", "find_out_of_bounds_position"], [49, 8, 1, "", "functionize"], [49, 8, 1, "", "g1g2_to_e1e2"], [49, 8, 1, "", "get_omp_threads"], [49, 8, 1, "", "horner"], [49, 8, 1, "", "horner2d"], [49, 8, 1, "", "interleaveImages"], [49, 8, 1, "", "isinteger"], [49, 8, 1, "", "kxky"], [49, 9, 1, "", "lazy_property"], [49, 8, 1, "", "listify"], [49, 8, 1, "", "math_eval"], [49, 8, 1, "", "merge_sorted"], [49, 8, 1, "", "nCr"], [49, 8, 1, "", "old_thin_tabulated_values"], [49, 8, 1, "", "parse_pos_args"], [49, 8, 1, "", "pickle_shared"], [49, 8, 1, "", "printoptions"], [49, 8, 1, "", "rand_arr"], [49, 8, 1, "", "rand_with_replacement"], [49, 8, 1, "", "roll2d"], [49, 8, 1, "", "rotate_xy"], [49, 8, 1, "", "set_omp_threads"], [49, 9, 1, "", "single_threaded"], [49, 8, 1, "", "structure_function"], [49, 8, 1, "", "thin_tabulated_values"], [49, 9, 1, "", "timer"], [49, 8, 1, "", "unweighted_moments"], [49, 8, 1, "", "unweighted_shape"]], "galsim.utilities.LRU_Cache": [[49, 11, 1, "", "resize"]], "galsim.wcs": [[77, 9, 1, "", "CelestialWCS"], [77, 9, 1, "", "EuclideanWCS"], [77, 9, 1, "", "LocalWCS"], [77, 9, 1, "", "UniformWCS"], [77, 8, 1, "", "compatible"], [77, 8, 1, "", "readFromFitsHeader"]], "galsim.wcs.CelestialWCS": [[77, 11, 1, "", "radecToxy"], [77, 10, 1, "", "x0"], [77, 11, 1, "", "xyToradec"], [77, 10, 1, "", "y0"]], "galsim.wcs.EuclideanWCS": [[77, 10, 1, "", "u0"], [77, 11, 1, "", "uvToxy"], [77, 10, 1, "", "v0"], [77, 10, 1, "", "x0"], [77, 11, 1, "", "xyTouv"], [77, 10, 1, "", "y0"]], "galsim.wcs.LocalWCS": [[77, 10, 1, "", "origin"], [77, 11, 1, "", "withOrigin"], [77, 10, 1, "", "world_origin"]], "galsim.wcs.UniformWCS": [[77, 11, 1, "", "inverse"]], "galsim.zernike": [[79, 9, 1, "", "DoubleZernike"], [79, 9, 1, "", "Zernike"], [79, 8, 1, "", "describe_zernike"], [79, 8, 1, "", "doubleZernikeBasis"], [79, 8, 1, "", "noll_to_zern"], [79, 8, 1, "", "zernikeBasis"], [79, 8, 1, "", "zernikeGradBases"], [79, 8, 1, "", "zernikeRotMatrix"]], "galsim.zernike.Zernike": [[79, 11, 1, "", "__add__"], [79, 11, 1, "", "__call__"], [79, 11, 1, "", "__mul__"], [79, 11, 1, "", "__neg__"], [79, 11, 1, "", "__rmul__"], [79, 11, 1, "", "__sub__"], [79, 11, 1, "", "evalCartesian"], [79, 11, 1, "", "evalCartesianGrad"], [79, 11, 1, "", "evalPolar"], [79, 11, 1, "", "rotate"]]}, "objtypes": {"0": "c:macro", "1": "cpp:function", "2": "cpp:functionParam", "3": "cpp:class", "4": "cpp:templateParam", "5": "cpp:member", "6": "cpp:enumerator", "7": "cpp:enum", "8": "py:function", "9": "py:class", "10": "py:property", "11": "py:method", "12": "py:data"}, "objnames": {"0": ["c", "macro", "C macro"], "1": ["cpp", "function", "C++ function"], "2": ["cpp", "functionParam", "C++ function parameter"], "3": ["cpp", "class", "C++ class"], "4": ["cpp", "templateParam", "C++ template parameter"], "5": ["cpp", "member", "C++ member"], "6": ["cpp", "enumerator", "C++ enumerator"], "7": ["cpp", "enum", "C++ enum"], "8": ["py", "function", "Python function"], "9": ["py", "class", "Python class"], "10": ["py", "property", "Python property"], "11": ["py", "method", "Python method"], "12": ["py", "data", "Python data"]}, "titleterms": {"arbitrari": [0, 29], "profil": [0, 6, 7, 8, 29, 36, 61, 65, 70, 73], "interpol": [0, 25, 48], "imag": [0, 11, 15, 24, 41, 42], "fourier": [0, 34, 73], "space": [0, 64, 73], "shapelet": 0, "decomposit": 0, "bandpass": [1, 11, 68], "filter": 1, "bessel": [2, 26], "function": [2, 23, 24, 26, 27, 40, 49, 61, 64, 70, 76, 79], "bound": [3, 22], "box": [3, 70], "catalog": [4, 63], "input": [4, 12, 15], "dictionari": 4, "charg": [5, 28], "deflect": [5, 28], "model": [5, 31, 67, 68], "wavelength": 6, "depend": [6, 39, 46], "chromat": [7, 30], "composit": [8, 29], "sum": 8, "gsobject": [8, 37, 73], "convolut": [8, 73], "The": [9, 10, 19, 31, 37, 38, 40, 42, 64, 69], "config": [9, 10, 11, 12, 13, 14, 15, 17, 19, 39], "modul": [9, 16, 19, 31, 40, 64], "galsim": [10, 49, 53], "execut": 10, "chang": [10, 39], "ad": [10, 14], "paramet": [10, 40], "split": 10, "up": [10, 53], "job": 10, "other": [10, 13, 26, 49], "command": 10, "line": 10, "option": 10, "field": [11, 12, 13, 14, 15, 16, 17, 18, 19], "attribut": [11, 13, 14, 17, 64], "type": [11, 12, 13, 14, 17, 19], "custom": [11, 12, 13, 14, 17, 19], "nois": [11, 20, 27, 51, 62, 68, 71], "wc": [11, 77], "sensor": [11, 28, 67, 68], "object": [13, 15], "psf": [13, 29, 31, 54, 61], "galaxi": [13, 29, 36, 63], "gener": [13, 15, 51], "sed": [13, 68, 75], "output": [14, 15, 40], "extra": [14, 15], "your": [14, 21], "own": 14, "process": 15, "from": [15, 39], "python": [15, 46], "run": [15, 44], "whole": 15, "script": [15, 44, 53], "build": 15, "file": [15, 31, 35, 68], "stamp": [15, 17, 63], "valu": [15, 19, 62], "us": [15, 46, 49], "util": [15, 26, 49, 77], "special": 16, "eval_vari": 16, "templat": 16, "specif": [16, 49], "index_kei": 16, "rng_index_kei": 16, "rng_num": 16, "photon": [17, 28, 55, 56, 57], "oper": [17, 57], "list": 17, "top": 18, "level": [18, 64], "float_valu": 19, "int_valu": 19, "bool_valu": 19, "str_valu": 19, "angle_valu": 19, "shear_valu": 19, "pos_valu": 19, "sky_valu": 19, "table_valu": 19, "quantity_valu": 19, "unit_valu": 19, "eval": 19, "preset": 19, "variabl": 19, "avail": 19, "user": 19, "defin": 19, "shorthand": 19, "notat": 19, "correl": [20, 27, 71], "c": [21, 24, 25, 26, 27, 28, 29, 46], "layer": 21, "link": 21, "code": 21, "version": [21, 39], "control": 21, "posit": [22, 58], "hsm": [23, 40], "implement": 23, "primari": 23, "interfac": [23, 35], "helper": [23, 49, 76], "class": [24, 29, 37, 38, 42, 49, 69, 76, 77], "relat": [24, 26, 27, 41, 49], "tool": 25, "lookup": [25, 72], "tabl": [25, 43, 72], "math": [26, 49], "nonlinear": 26, "solver": 26, "mathemat": 26, "horner": 26, "": [26, 77], "method": 26, "polynomi": 26, "evalu": 26, "integr": [26, 47], "misc": 26, "random": [27, 32, 62], "deviat": [27, 32], "effect": 28, "arrai": [28, 56], "silicon": 28, "correct": 28, "surfac": [29, 65], "bright": [29, 65], "sbprofil": 29, "base": [29, 37, 77], "simpl": [29, 70], "transform": [29, 34, 73], "gsparam": [29, 38], "differenti": 30, "refract": 30, "de": [31, 36, 73, 74], "write": [31, 35, 49], "med": 31, "error": 33, "warn": 33, "fit": 35, "read": 35, "header": 35, "exponenati": 36, "vaucouleur": 36, "sersic": 36, "inclin": 36, "exponenti": 36, "spergel": 36, "knot": 36, "star": 36, "format": 36, "revis": 39, "histori": 39, "v2": [39, 52], "5": [39, 52, 74], "6": [39, 52, 74], "api": 39, "updat": 39, "new": 39, "featur": 39, "bug": 39, "fix": 39, "older": 39, "shape": 40, "measur": 40, "concept": 41, "indic": 43, "instal": [44, 45, 46, 53], "instruct": 44, "test": [44, 49], "exampl": 44, "With": [45, 46], "conda": [45, 46], "pip": 46, "overal": 46, "summari": [46, 53], "fftw": 46, "yourself": 46, "an": 46, "exist": 46, "apt": 46, "get": 46, "fink": 46, "macport": 46, "eigen": 46, "share": [46, 68], "librari": 46, "miscellan": 49, "decor": 49, "openmp": 49, "utilti": 49, "lru": 49, "cach": 49, "context": 49, "manag": 49, "atmosphericscreen": 49, "pickl": 49, "possibli": 49, "calcul": 49, "numpi": 49, "suit": 49, "nfw": 50, "halo": 50, "shear": [50, 59, 69], "4": [52, 74], "3": [52, 74], "2": [52, 74], "1": [52, 74], "0": 52, "v1": 52, "v0": 52, "overview": 53, "basic": 53, "sourc": 53, "distribut": [53, 66], "keep": 53, "date": 53, "how": 53, "commun": 53, "develop": 53, "demonstr": 53, "current": 53, "capabl": 53, "plan": 53, "futur": 53, "phase": 54, "screen": 54, "shoot": 55, "power": [59, 60], "spectrum": [59, 60], "estim": 60, "point": 61, "spread": 61, "airi": 61, "moffat": 61, "kolmogorov": 61, "von": 61, "karman": 61, "optic": 61, "real": 63, "individu": 63, "realist": 63, "scene": 63, "download": 63, "cosmo": [63, 68], "hsc": 63, "postag": 63, "data": [63, 68], "roman": [64, 68], "telescop": 64, "spectral": [66, 71], "energi": 66, "hst": 68, "st": 68, "gaussian": 70, "pixel": 70, "tophat": 70, "delta": 70, "affin": 73, "squar": 73, "root": 73, "tutori": 74, "demo": 74, "7": 74, "8": 74, "9": 74, "10": 74, "11": 74, "12": 74, "13": 74, "advanc": 74, "simul": 74, "great3": 74, "unit": 75, "size": 75, "flux": 75, "angl": 75, "world": 77, "coordin": 77, "system": 77, "euclidean": 77, "celesti": 77, "weak": 78, "lens": 78, "zernik": 79}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.viewcode": 1, "sphinx.ext.intersphinx": 1, "sphinx": 57}, "alltitles": {"Arbitrary Profiles": [[0, "arbitrary-profiles"]], "Interpolated Images": [[0, "interpolated-images"]], "Interpolated Fourier-space Images": [[0, "interpolated-fourier-space-images"]], "Shapelet Decomposition": [[0, "shapelet-decomposition"]], "Bandpass Filters": [[1, "bandpass-filters"]], "Bessel Functions": [[2, "bessel-functions"]], "Bounding boxes": [[3, "bounding-boxes"]], "Catalogs and Input Dictionaries": [[4, "catalogs-and-input-dictionaries"]], "Charge Deflection Model": [[5, "charge-deflection-model"]], "Wavelength-dependent Profiles": [[6, "wavelength-dependent-profiles"]], "Chromatic Profiles": [[7, "chromatic-profiles"]], "Composite Profiles": [[8, "composite-profiles"]], "Sums of GSObjects": [[8, "sums-of-gsobjects"]], "Convolutions of GSObjects": [[8, "convolutions-of-gsobjects"]], "The Config Module": [[9, "the-config-module"]], "The galsim Executable": [[10, "the-galsim-executable"]], "Changing or adding parameters": [[10, "changing-or-adding-parameters"]], "Splitting up a config job": [[10, "splitting-up-a-config-job"]], "Other command line options": [[10, "other-command-line-options"]], "Config Image Field": [[11, "config-image-field"]], "Image Field Attributes": [[11, "image-field-attributes"]], "Image Types": [[11, "image-types"]], "Custom Image Types": [[11, "custom-image-types"]], "Noise": [[11, "noise"]], "WCS Field": [[11, "wcs-field"]], "Bandpass Field": [[11, "bandpass-field"]], "Sensor Field": [[11, "sensor-field"]], "Config Input Field": [[12, "config-input-field"]], "Input Types": [[12, "input-types"]], "Custom Input Types": [[12, "custom-input-types"]], "Config Objects": [[13, "config-objects"]], "PSF Types": [[13, "psf-types"]], "Galaxy Types": [[13, "galaxy-types"]], "Generic Types": [[13, "generic-types"]], "Other Attributes": [[13, "other-attributes"]], "Custom Object Types": [[13, "custom-object-types"]], "SED Field": [[13, "sed-field"]], "Config Output Field": [[14, "config-output-field"]], "Output Field Attributes": [[14, "output-field-attributes"]], "Output Types": [[14, "output-types"]], "Custom Output Types": [[14, "custom-output-types"]], "Extra Outputs": [[14, "extra-outputs"]], "Adding your own Extra Output Type": [[14, "adding-your-own-extra-output-type"]], "Config Processing From Python": [[15, "config-processing-from-python"]], "Running the Whole Script": [[15, "running-the-whole-script"]], "Building Files": [[15, "building-files"]], "Building Images": [[15, "building-images"]], "Building Stamps": [[15, "building-stamps"]], "Building Objects": [[15, "building-objects"]], "Generating Values": [[15, "generating-values"]], "Using Input Fields": [[15, "using-input-fields"]], "Processing Extra Outputs": [[15, "processing-extra-outputs"]], "Config Utilities": [[15, "config-utilities"]], "Special Fields": [[16, "special-fields"]], "modules": [[16, "modules"]], "eval_variables": [[16, "eval-variables"]], "template": [[16, "template"]], "Special Specifications": [[16, "special-specifications"]], "index_key": [[16, "index-key"]], "rng_index_key": [[16, "rng-index-key"]], "rng_num": [[16, "rng-num"]], "Config Stamp Field": [[17, "config-stamp-field"]], "Stamp Field Attributes": [[17, "stamp-field-attributes"]], "Stamp Types": [[17, "stamp-types"]], "Custom Stamp Types": [[17, "custom-stamp-types"]], "Photon Operators List": [[17, "photon-operators-list"]], "Top Level Fields": [[18, "top-level-fields"]], "Config Values": [[19, "config-values"]], "float_value": [[19, "float-value"]], "int_value": [[19, "int-value"]], "bool_value": [[19, "bool-value"]], "str_value": [[19, "str-value"]], "angle_value": [[19, "angle-value"]], "shear_value": [[19, "shear-value"]], "pos_value": [[19, "pos-value"]], "sky_value": [[19, "sky-value"]], "table_value": [[19, "table-value"]], "quantity_value": [[19, "quantity-value"]], "unit_value": [[19, "unit-value"]], "Eval type": [[19, "eval-type"]], "Preset variables": [[19, "preset-variables"]], "Available Modules": [[19, "available-modules"]], "User-defined variables": [[19, "user-defined-variables"]], "The eval-variables field": [[19, "the-eval-variables-field"]], "Module-defined variables": [[19, "module-defined-variables"]], "Shorthand notation": [[19, "shorthand-notation"]], "Custom Value Types": [[19, "custom-value-types"]], "Correlated Noise": [[20, "correlated-noise"]], "C++ Layer": [[21, "c-layer"]], "Linking Your Code": [[21, "linking-your-code"]], "Version control": [[21, "version-control"]], "Positions and Bounds": [[22, "positions-and-bounds"]], "HSM Implementation": [[23, "hsm-implementation"]], "Primary Interface": [[23, "primary-interface"]], "Helper Functions": [[23, "helper-functions"]], "C++ Images": [[24, "c-images"]], "Image Classes": [[24, "image-classes"]], "Image-related Functionality": [[24, "image-related-functionality"]], "Interpolation Tools": [[25, "interpolation-tools"]], "C++ Interpolants": [[25, "c-interpolants"]], "C++ Lookup Tables": [[25, "c-lookup-tables"]], "Math": [[26, "math"]], "Nonlinear solver": [[26, "nonlinear-solver"]], "Bessel and Related Functions": [[26, "bessel-and-related-functions"]], "Other mathematical functions": [[26, "other-mathematical-functions"]], "Horner\u2019s method for polynomial evaluation": [[26, "horner-s-method-for-polynomial-evaluation"]], "C++ Integration Functions": [[26, "c-integration-functions"]], "Misc Utilities": [[26, "misc-utilities"]], "Noise-related Functionality": [[27, "noise-related-functionality"]], "C++ Random Deviates": [[27, "c-random-deviates"]], "C++ Correlated Noise": [[27, "c-correlated-noise"]], "Photons and Sensor Effects": [[28, "photons-and-sensor-effects"]], "C++ Photon Array": [[28, "c-photon-array"]], "Silicon Sensor": [[28, "silicon-sensor"]], "Charge Deflection Correction": [[28, "charge-deflection-correction"]], "C++ Surface Brightness Profiles": [[29, "c-surface-brightness-profiles"]], "SBProfile Base Class": [[29, "sbprofile-base-class"]], "Simple C++ Profiles": [[29, "simple-c-profiles"]], "PSF C++ Profiles": [[29, "psf-c-profiles"]], "Galaxy C++ Profiles": [[29, "galaxy-c-profiles"]], "Arbitrary C++ Profiles": [[29, "arbitrary-c-profiles"]], "Composite C++ Profiles": [[29, "composite-c-profiles"]], "Transformed C++ Profiles": [[29, "transformed-c-profiles"]], "C++ GSParams": [[29, "c-gsparams"]], "Differential Chromatic Refraction": [[30, "differential-chromatic-refraction"]], "The DES Module": [[31, "the-des-module"]], "DES PSF models": [[31, "des-psf-models"]], "Writing to MEDS Files": [[31, "writing-to-meds-files"]], "Random Deviates": [[32, "random-deviates"]], "Errors and Warnings": [[33, "errors-and-warnings"]], "Fourier Transforms": [[34, "fourier-transforms"]], "Interfacing with FITS Files": [[35, "interfacing-with-fits-files"]], "Reading FITS Files": [[35, "reading-fits-files"]], "Writing FITS Files": [[35, "writing-fits-files"]], "FITS Headers": [[35, "fits-headers"]], "Galaxies": [[36, "galaxies"]], "Exponenatial Profile": [[36, "exponenatial-profile"]], "De Vaucouleurs Profile": [[36, "de-vaucouleurs-profile"]], "Sersic Profile": [[36, "sersic-profile"]], "Inclined Exponential Profile": [[36, "inclined-exponential-profile"]], "Inclined Sersic Profile": [[36, "inclined-sersic-profile"]], "Spergel Profile": [[36, "spergel-profile"]], "Knots of Star Formation": [[36, "knots-of-star-formation"]], "The GSObject base class": [[37, "the-gsobject-base-class"]], "The GSParams class": [[38, "the-gsparams-class"]], "Revision History": [[39, "revision-history"]], "Changes from v2.5 to v2.6": [[39, "changes-from-v2-5-to-v2-6"]], "Dependency Changes": [[39, "dependency-changes"]], "API Changes": [[39, "api-changes"]], "Config Updates": [[39, "config-updates"]], "New Features": [[39, "new-features"]], "Bug Fixes": [[39, "bug-fixes"]], "Older Versions": [[39, "older-versions"]], "The HSM Module": [[40, "the-hsm-module"]], "Shape Measurement Functions": [[40, "shape-measurement-functions"]], "HSM output": [[40, "hsm-output"]], "HSM parameters": [[40, "hsm-parameters"]], "Images and Related Concepts": [[41, "images-and-related-concepts"]], "The Image class": [[42, "the-image-class"]], "Indices and tables": [[43, "indices-and-tables"]], "Installation Instructions": [[44, "installation-instructions"]], "Running tests": [[44, "running-tests"]], "Running example scripts": [[44, "running-example-scripts"]], "Installing With Conda": [[45, "installing-with-conda"]], "Installing With Pip": [[46, "installing-with-pip"]], "Overall summary": [[46, "overall-summary"]], "Installing Python Dependencies": [[46, "installing-python-dependencies"]], "Installing FFTW": [[46, "installing-fftw"]], "Installing FFTW yourself": [[46, "installing-fftw-yourself"]], "Using an existing installation of FFTW": [[46, "using-an-existing-installation-of-fftw"]], "Installing FFTW with conda": [[46, "installing-fftw-with-conda"]], "Installing FFTW with apt-get": [[46, "installing-fftw-with-apt-get"]], "Installing FFTW with fink": [[46, "installing-fftw-with-fink"]], "Installing FFTW with MacPorts": [[46, "installing-fftw-with-macports"]], "Installing Eigen": [[46, "installing-eigen"]], "Installing Eigen yourself": [[46, "installing-eigen-yourself"]], "Using an existing installation of Eigen": [[46, "using-an-existing-installation-of-eigen"]], "Installing Eigen with conda": [[46, "installing-eigen-with-conda"]], "Installing Eigen with apt-get": [[46, "installing-eigen-with-apt-get"]], "Installing Eigen with fink": [[46, "installing-eigen-with-fink"]], "Installing Eigen with MacPorts": [[46, "installing-eigen-with-macports"]], "Installing the C++ Shared Library": [[46, "installing-the-c-shared-library"]], "Integration": [[47, "integration"]], "Interpolants": [[48, "interpolants"]], "Miscellaneous Utilities": [[49, "miscellaneous-utilities"]], "Decorators": [[49, "decorators"]], "OpenMP Utilties": [[49, "openmp-utilties"]], "LRU Cache": [[49, "lru-cache"]], "Context Manager for writing AtmosphericScreen pickles": [[49, "context-manager-for-writing-atmosphericscreen-pickles"]], "Other Possibly Useful Classes": [[49, "other-possibly-useful-classes"]], "Math Calculations": [[49, "math-calculations"]], "Utilities Related to NumPy Functions": [[49, "utilities-related-to-numpy-functions"]], "Test Suite Helper Functions and Contexts": [[49, "test-suite-helper-functions-and-contexts"]], "Other Helper Functions": [[49, "other-helper-functions"]], "GalSim-specific Helper Functions": [[49, "galsim-specific-helper-functions"]], "NFW Halo Shears": [[50, "nfw-halo-shears"]], "Noise Generators": [[51, "noise-generators"]], "v2.5": [[52, "v2-5"]], "v2.4": [[52, "v2-4"]], "v2.3": [[52, "v2-3"]], "v2.2": [[52, "v2-2"]], "v2.1": [[52, "v2-1"]], "v2.0": [[52, "v2-0"]], "v1.6": [[52, "v1-6"]], "v1.5": [[52, "v1-5"]], "v1.4": [[52, "v1-4"]], "v1.3": [[52, "v1-3"]], "v1.2": [[52, "v1-2"]], "v1.1": [[52, "v1-1"]], "v1.0": [[52, "v1-0"]], "v0.5": [[52, "v0-5"]], "v0.4": [[52, "v0-4"]], "v0.3": [[52, "v0-3"]], "v0.2": [[52, "v0-2"]], "v0.1": [[52, "v0-1"]], "Overview": [[53, "overview"]], "Basic Installation": [[53, "basic-installation"]], "Source Distribution": [[53, "source-distribution"]], "Keeping up-to-date with GalSim": [[53, "keeping-up-to-date-with-galsim"]], "How to communicate with the GalSim developers": [[53, "how-to-communicate-with-the-galsim-developers"]], "Demonstration scripts": [[53, "demonstration-scripts"]], "Summary of current capabilities": [[53, "summary-of-current-capabilities"]], "Planned future development": [[53, "planned-future-development"]], "Phase-screen PSFs": [[54, "phase-screen-psfs"]], "Photon Shooting": [[55, "photon-shooting"]], "Photon Arrays": [[56, "photon-arrays"]], "Photon Operators": [[57, "photon-operators"]], "Positions": [[58, "positions"]], "Power Spectrum Shears": [[59, "power-spectrum-shears"]], "Power Spectrum Estimation": [[60, "power-spectrum-estimation"]], "Point-spread functions": [[61, "point-spread-functions"]], "Airy Profile": [[61, "airy-profile"]], "Moffat Profile": [[61, "moffat-profile"]], "Kolmogorov Profile": [[61, "kolmogorov-profile"]], "Von Karman Profile": [[61, "von-karman-profile"]], "Optical PSF": [[61, "optical-psf"]], "Noise and Random Values": [[62, "noise-and-random-values"]], "\u201cReal\u201d Galaxies": [[63, "real-galaxies"]], "Individual Real Galaxies": [[63, "individual-real-galaxies"]], "Realistic Scene": [[63, "realistic-scene"]], "Downloading the COSMOS Catalog": [[63, "downloading-the-cosmos-catalog"]], "HSC Postage Stamp Data": [[63, "hsc-postage-stamp-data"]], "The Roman Space Telescope Module": [[64, "the-roman-space-telescope-module"]], "Module-level Attributes": [[64, "module-level-attributes"]], "Roman Functions": [[64, "roman-functions"]], "Surface Brightness Profiles": [[65, "surface-brightness-profiles"]], "Spectral Energy Distributions": [[66, "spectral-energy-distributions"]], "Sensor Models": [[67, "sensor-models"]], "Shared Data": [[68, "shared-data"]], "Shared SED files": [[68, "shared-sed-files"]], "Shared Bandpass files": [[68, "shared-bandpass-files"]], "Shared Sensor models": [[68, "shared-sensor-models"]], "Shared HST noise model": [[68, "shared-hst-noise-model"]], "Shared Roman ST files": [[68, "shared-roman-st-files"]], "Shared COSMOS files": [[68, "shared-cosmos-files"]], "The Shear class": [[69, "the-shear-class"]], "Simple Profiles": [[70, "simple-profiles"]], "Gaussian Profile": [[70, "gaussian-profile"]], "Pixel Profile": [[70, "pixel-profile"]], "Box Profile": [[70, "box-profile"]], "TopHat Profile": [[70, "tophat-profile"]], "Delta Function": [[70, "delta-function"]], "Spectral Correlated Noise": [[71, "spectral-correlated-noise"]], "Lookup Tables": [[72, "lookup-tables"]], "Transformed Profiles": [[73, "transformed-profiles"]], "Affine Transformations": [[73, "affine-transformations"]], "De-convolution of a GSObject": [[73, "de-convolution-of-a-gsobject"]], "Fourier-space Square Root": [[73, "fourier-space-square-root"]], "Tutorials": [[74, "tutorials"]], "Demo 1": [[74, "demo-1"]], "Demo 2": [[74, "demo-2"]], "Demo 3": [[74, "demo-3"]], "Demo 4": [[74, "demo-4"]], "Demo 5": [[74, "demo-5"]], "Demo 6": [[74, "demo-6"]], "Demo 7": [[74, "demo-7"]], "Demo 8": [[74, "demo-8"]], "Demo 9": [[74, "demo-9"]], "Demo 10": [[74, "demo-10"]], "Demo 11": [[74, "demo-11"]], "Demo 12": [[74, "demo-12"]], "Demo 13": [[74, "demo-13"]], "Advanced Simulations": [[74, "advanced-simulations"]], "Great3 Simulations": [[74, "great3-simulations"]], "DES Simulations": [[74, "des-simulations"]], "Units": [[75, "units"]], "Size Units": [[75, "size-units"]], "Flux Units": [[75, "flux-units"]], "SED Units": [[75, "sed-units"]], "Angles": [[75, "angles"]], "Helper Functions and Classes": [[76, "helper-functions-and-classes"]], "World Coordinate Systems": [[77, "world-coordinate-systems"]], "WCS Base Classes": [[77, "wcs-base-classes"]], "Euclidean WCS\u2019s": [[77, "euclidean-wcs-s"]], "Celestial WCS\u2019s": [[77, "celestial-wcs-s"]], "Celestial Coordinates": [[77, "celestial-coordinates"]], "WCS Utilities": [[77, "wcs-utilities"]], "Weak Lensing": [[78, "weak-lensing"]], "Zernike Functions": [[79, "zernike-functions"]]}, "indexentries": {"interpolatedimage (class in galsim)": [[0, "galsim.InterpolatedImage"]], "interpolatedkimage (class in galsim)": [[0, "galsim.InterpolatedKImage"]], "shapelet (class in galsim)": [[0, "galsim.Shapelet"]], "_interpolatedimage() (in module galsim)": [[0, "galsim._InterpolatedImage"]], "_interpolatedkimage() (in module galsim)": [[0, "galsim._InterpolatedKImage"]], "bvec (galsim.shapelet property)": [[0, "galsim.Shapelet.bvec"]], "fit() (galsim.shapelet class method)": [[0, "galsim.Shapelet.fit"]], "getnm() (galsim.shapelet method)": [[0, "galsim.Shapelet.getNM"]], "getpq() (galsim.shapelet method)": [[0, "galsim.Shapelet.getPQ"]], "image (galsim.interpolatedimage property)": [[0, "galsim.InterpolatedImage.image"]], "k_interpolant (galsim.interpolatedimage property)": [[0, "galsim.InterpolatedImage.k_interpolant"]], "k_interpolant (galsim.interpolatedkimage property)": [[0, "galsim.InterpolatedKImage.k_interpolant"]], "kimage (galsim.interpolatedkimage property)": [[0, "galsim.InterpolatedKImage.kimage"]], "order (galsim.shapelet property)": [[0, "galsim.Shapelet.order"]], "sigma (galsim.shapelet property)": [[0, "galsim.Shapelet.sigma"]], "size() (galsim.shapelet class method)": [[0, "galsim.Shapelet.size"]], "withgsparams() (galsim.interpolatedimage method)": [[0, "galsim.InterpolatedImage.withGSParams"]], "withgsparams() (galsim.interpolatedkimage method)": [[0, "galsim.InterpolatedKImage.withGSParams"]], "x_interpolant (galsim.interpolatedimage property)": [[0, "galsim.InterpolatedImage.x_interpolant"]], "bandpass (class in galsim)": [[1, "galsim.Bandpass"]], "__call__() (galsim.bandpass method)": [[1, "galsim.Bandpass.__call__"]], "calculateeffectivewavelength() (galsim.bandpass method)": [[1, "galsim.Bandpass.calculateEffectiveWavelength"]], "effective_wavelength (galsim.bandpass property)": [[1, "galsim.Bandpass.effective_wavelength"]], "thin() (galsim.bandpass method)": [[1, "galsim.Bandpass.thin"]], "truncate() (galsim.bandpass method)": [[1, "galsim.Bandpass.truncate"]], "withzeropoint() (galsim.bandpass method)": [[1, "galsim.Bandpass.withZeropoint"]], "ci() (in module galsim.bessel)": [[2, "galsim.bessel.ci"]], "gammainc() (in module galsim.bessel)": [[2, "galsim.bessel.gammainc"]], "iv() (in module galsim.bessel)": [[2, "galsim.bessel.iv"]], "j0() (in module galsim.bessel)": [[2, "galsim.bessel.j0"]], "j0_root() (in module galsim.bessel)": [[2, "galsim.bessel.j0_root"]], "j1() (in module galsim.bessel)": [[2, "galsim.bessel.j1"]], "jn() (in module galsim.bessel)": [[2, "galsim.bessel.jn"]], "jv() (in module galsim.bessel)": [[2, "galsim.bessel.jv"]], "jv_root() (in module galsim.bessel)": [[2, "galsim.bessel.jv_root"]], "kn() (in module galsim.bessel)": [[2, "galsim.bessel.kn"]], "kv() (in module galsim.bessel)": [[2, "galsim.bessel.kv"]], "si() (in module galsim.bessel)": [[2, "galsim.bessel.si"]], "sinc() (in module galsim.bessel)": [[2, "galsim.bessel.sinc"]], "yn() (in module galsim.bessel)": [[2, "galsim.bessel.yn"]], "yv() (in module galsim.bessel)": [[2, "galsim.bessel.yv"]], "bounds (class in galsim)": [[3, "galsim.Bounds"]], "boundsd (class in galsim)": [[3, "galsim.BoundsD"]], "boundsi (class in galsim)": [[3, "galsim.BoundsI"]], "_boundsd() (in module galsim)": [[3, "galsim._BoundsD"]], "_boundsi() (in module galsim)": [[3, "galsim._BoundsI"]], "area() (galsim.bounds method)": [[3, "galsim.Bounds.area"]], "center (galsim.bounds property)": [[3, "galsim.Bounds.center"]], "expand() (galsim.bounds method)": [[3, "galsim.Bounds.expand"]], "getxmax() (galsim.bounds method)": [[3, "galsim.Bounds.getXMax"]], "getxmin() (galsim.bounds method)": [[3, "galsim.Bounds.getXMin"]], "getymax() (galsim.bounds method)": [[3, "galsim.Bounds.getYMax"]], "getymin() (galsim.bounds method)": [[3, "galsim.Bounds.getYMin"]], "includes() (galsim.bounds method)": [[3, "galsim.Bounds.includes"]], "isdefined() (galsim.bounds method)": [[3, "galsim.Bounds.isDefined"]], "numpyshape() (galsim.boundsi method)": [[3, "galsim.BoundsI.numpyShape"]], "origin (galsim.bounds property)": [[3, "galsim.Bounds.origin"]], "shift() (galsim.bounds method)": [[3, "galsim.Bounds.shift"]], "true_center (galsim.bounds property)": [[3, "galsim.Bounds.true_center"]], "withborder() (galsim.bounds method)": [[3, "galsim.Bounds.withBorder"]], "catalog (class in galsim)": [[4, "galsim.Catalog"]], "dict (class in galsim)": [[4, "galsim.Dict"]], "outputcatalog (class in galsim)": [[4, "galsim.OutputCatalog"]], "addrow() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.addRow"]], "get() (galsim.catalog method)": [[4, "galsim.Catalog.get"]], "getfloat() (galsim.catalog method)": [[4, "galsim.Catalog.getFloat"]], "getint() (galsim.catalog method)": [[4, "galsim.Catalog.getInt"]], "getncols() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.getNCols"]], "getnobjects() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.getNObjects"]], "getnames() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.getNames"]], "gettypes() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.getTypes"]], "makedata() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.makeData"]], "ncols (galsim.outputcatalog property)": [[4, "galsim.OutputCatalog.ncols"]], "nobjects (galsim.outputcatalog property)": [[4, "galsim.OutputCatalog.nobjects"]], "readascii() (galsim.catalog method)": [[4, "galsim.Catalog.readAscii"]], "readfits() (galsim.catalog method)": [[4, "galsim.Catalog.readFits"]], "settypes() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.setTypes"]], "write() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.write"]], "writeascii() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.writeAscii"]], "writefits() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.writeFits"]], "writefitshdu() (galsim.outputcatalog method)": [[4, "galsim.OutputCatalog.writeFitsHdu"]], "basecdmodel (class in galsim.cdmodel)": [[5, "galsim.cdmodel.BaseCDModel"]], "powerlawcd (class in galsim.cdmodel)": [[5, "galsim.cdmodel.PowerLawCD"]], "__init__() (galsim.cdmodel.basecdmodel method)": [[5, "galsim.cdmodel.BaseCDModel.__init__"]], "__init__() (galsim.cdmodel.powerlawcd method)": [[5, "galsim.cdmodel.PowerLawCD.__init__"]], "applybackward() (galsim.cdmodel.basecdmodel method)": [[5, "galsim.cdmodel.BaseCDModel.applyBackward"]], "applyforward() (galsim.cdmodel.basecdmodel method)": [[5, "galsim.cdmodel.BaseCDModel.applyForward"]], "chromaticairy (class in galsim)": [[7, "galsim.ChromaticAiry"]], "chromaticatmosphere (class in galsim)": [[7, "galsim.ChromaticAtmosphere"]], "chromaticautoconvolution (class in galsim)": [[7, "galsim.ChromaticAutoConvolution"]], "chromaticautocorrelation (class in galsim)": [[7, "galsim.ChromaticAutoCorrelation"]], "chromaticconvolution (class in galsim)": [[7, "galsim.ChromaticConvolution"]], "chromaticdeconvolution (class in galsim)": [[7, "galsim.ChromaticDeconvolution"]], "chromaticfouriersqrtprofile (class in galsim)": [[7, "galsim.ChromaticFourierSqrtProfile"]], "chromaticobject (class in galsim)": [[7, "galsim.ChromaticObject"]], "chromaticopticalpsf (class in galsim)": [[7, "galsim.ChromaticOpticalPSF"]], "chromaticrealgalaxy (class in galsim)": [[7, "galsim.ChromaticRealGalaxy"]], "chromaticsum (class in galsim)": [[7, "galsim.ChromaticSum"]], "chromatictransformation (class in galsim)": [[7, "galsim.ChromaticTransformation"]], "interpolatedchromaticobject (class in galsim)": [[7, "galsim.InterpolatedChromaticObject"]], "__mul__() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.__mul__"]], "applyto() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.applyTo"]], "atredshift() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.atRedshift"]], "build_obj() (galsim.chromaticatmosphere method)": [[7, "galsim.ChromaticAtmosphere.build_obj"]], "calculatecentroid() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.calculateCentroid"]], "calculateflux() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.calculateFlux"]], "calculatemagnitude() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.calculateMagnitude"]], "dilate() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.dilate"]], "dimensionless (galsim.chromaticobject property)": [[7, "galsim.ChromaticObject.dimensionless"]], "drawimage() (galsim.chromaticconvolution method)": [[7, "galsim.ChromaticConvolution.drawImage"]], "drawimage() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.drawImage"]], "drawimage() (galsim.chromaticsum method)": [[7, "galsim.ChromaticSum.drawImage"]], "drawimage() (galsim.chromatictransformation method)": [[7, "galsim.ChromaticTransformation.drawImage"]], "drawimage() (galsim.interpolatedchromaticobject method)": [[7, "galsim.InterpolatedChromaticObject.drawImage"]], "drawkimage() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.drawKImage"]], "evaluateatwavelength() (galsim.chromaticairy method)": [[7, "galsim.ChromaticAiry.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticatmosphere method)": [[7, "galsim.ChromaticAtmosphere.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticautoconvolution method)": [[7, "galsim.ChromaticAutoConvolution.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticautocorrelation method)": [[7, "galsim.ChromaticAutoCorrelation.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticconvolution method)": [[7, "galsim.ChromaticConvolution.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticdeconvolution method)": [[7, "galsim.ChromaticDeconvolution.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticfouriersqrtprofile method)": [[7, "galsim.ChromaticFourierSqrtProfile.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticopticalpsf method)": [[7, "galsim.ChromaticOpticalPSF.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromaticsum method)": [[7, "galsim.ChromaticSum.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.chromatictransformation method)": [[7, "galsim.ChromaticTransformation.evaluateAtWavelength"]], "evaluateatwavelength() (galsim.interpolatedchromaticobject method)": [[7, "galsim.InterpolatedChromaticObject.evaluateAtWavelength"]], "expand() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.expand"]], "from_images() (galsim.interpolatedchromaticobject class method)": [[7, "galsim.InterpolatedChromaticObject.from_images"]], "gsparams (galsim.chromaticairy property)": [[7, "galsim.ChromaticAiry.gsparams"]], "gsparams (galsim.chromaticatmosphere property)": [[7, "galsim.ChromaticAtmosphere.gsparams"]], "gsparams (galsim.chromaticautoconvolution property)": [[7, "galsim.ChromaticAutoConvolution.gsparams"]], "gsparams (galsim.chromaticautocorrelation property)": [[7, "galsim.ChromaticAutoCorrelation.gsparams"]], "gsparams (galsim.chromaticconvolution property)": [[7, "galsim.ChromaticConvolution.gsparams"]], "gsparams (galsim.chromaticdeconvolution property)": [[7, "galsim.ChromaticDeconvolution.gsparams"]], "gsparams (galsim.chromaticfouriersqrtprofile property)": [[7, "galsim.ChromaticFourierSqrtProfile.gsparams"]], "gsparams (galsim.chromaticobject property)": [[7, "galsim.ChromaticObject.gsparams"]], "gsparams (galsim.chromaticopticalpsf property)": [[7, "galsim.ChromaticOpticalPSF.gsparams"]], "gsparams (galsim.chromaticsum property)": [[7, "galsim.ChromaticSum.gsparams"]], "gsparams (galsim.chromatictransformation property)": [[7, "galsim.ChromaticTransformation.gsparams"]], "gsparams (galsim.interpolatedchromaticobject property)": [[7, "galsim.InterpolatedChromaticObject.gsparams"]], "interpolate() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.interpolate"]], "lens() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.lens"]], "magnify() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.magnify"]], "makefromimages() (galsim.chromaticrealgalaxy class method)": [[7, "galsim.ChromaticRealGalaxy.makeFromImages"]], "obj_list (galsim.chromaticconvolution property)": [[7, "galsim.ChromaticConvolution.obj_list"]], "obj_list (galsim.chromaticsum property)": [[7, "galsim.ChromaticSum.obj_list"]], "original (galsim.chromatictransformation property)": [[7, "galsim.ChromaticTransformation.original"]], "redshift (galsim.chromaticobject property)": [[7, "galsim.ChromaticObject.redshift"]], "resize_effective_prof_cache() (galsim.chromaticconvolution static method)": [[7, "galsim.ChromaticConvolution.resize_effective_prof_cache"]], "resize_multiplier_cache() (galsim.chromaticobject static method)": [[7, "galsim.ChromaticObject.resize_multiplier_cache"]], "rotate() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.rotate"]], "shear() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.shear"]], "shift() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.shift"]], "spectral (galsim.chromaticobject property)": [[7, "galsim.ChromaticObject.spectral"]], "transform() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.transform"]], "withflux() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.withFlux"]], "withfluxdensity() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.withFluxDensity"]], "withgsparams() (galsim.chromaticairy method)": [[7, "galsim.ChromaticAiry.withGSParams"]], "withgsparams() (galsim.chromaticatmosphere method)": [[7, "galsim.ChromaticAtmosphere.withGSParams"]], "withgsparams() (galsim.chromaticautoconvolution method)": [[7, "galsim.ChromaticAutoConvolution.withGSParams"]], "withgsparams() (galsim.chromaticautocorrelation method)": [[7, "galsim.ChromaticAutoCorrelation.withGSParams"]], "withgsparams() (galsim.chromaticconvolution method)": [[7, "galsim.ChromaticConvolution.withGSParams"]], "withgsparams() (galsim.chromaticdeconvolution method)": [[7, "galsim.ChromaticDeconvolution.withGSParams"]], "withgsparams() (galsim.chromaticfouriersqrtprofile method)": [[7, "galsim.ChromaticFourierSqrtProfile.withGSParams"]], "withgsparams() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.withGSParams"]], "withgsparams() (galsim.chromaticopticalpsf method)": [[7, "galsim.ChromaticOpticalPSF.withGSParams"]], "withgsparams() (galsim.chromaticsum method)": [[7, "galsim.ChromaticSum.withGSParams"]], "withgsparams() (galsim.chromatictransformation method)": [[7, "galsim.ChromaticTransformation.withGSParams"]], "withgsparams() (galsim.interpolatedchromaticobject method)": [[7, "galsim.InterpolatedChromaticObject.withGSParams"]], "withmagnitude() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.withMagnitude"]], "withscaledflux() (galsim.chromaticobject method)": [[7, "galsim.ChromaticObject.withScaledFlux"]], "withscaledflux() (galsim.chromaticsum method)": [[7, "galsim.ChromaticSum.withScaledFlux"]], "add() (in module galsim)": [[8, "galsim.Add"]], "autoconvolution (class in galsim)": [[8, "galsim.AutoConvolution"]], "autoconvolve() (in module galsim)": [[8, "galsim.AutoConvolve"]], "autocorrelate() (in module galsim)": [[8, "galsim.AutoCorrelate"]], "autocorrelation (class in galsim)": [[8, "galsim.AutoCorrelation"]], "convolution (class in galsim)": [[8, "galsim.Convolution"]], "convolve() (in module galsim)": [[8, "galsim.Convolve"]], "sum (class in galsim)": [[8, "galsim.Sum"]], "obj_list (galsim.convolution property)": [[8, "galsim.Convolution.obj_list"]], "obj_list (galsim.sum property)": [[8, "galsim.Sum.obj_list"]], "orig_obj (galsim.autoconvolution property)": [[8, "galsim.AutoConvolution.orig_obj"]], "orig_obj (galsim.autocorrelation property)": [[8, "galsim.AutoCorrelation.orig_obj"]], "real_space (galsim.autoconvolution property)": [[8, "galsim.AutoConvolution.real_space"]], "real_space (galsim.autocorrelation property)": [[8, "galsim.AutoCorrelation.real_space"]], "real_space (galsim.convolution property)": [[8, "galsim.Convolution.real_space"]], "withgsparams() (galsim.autoconvolution method)": [[8, "galsim.AutoConvolution.withGSParams"]], "withgsparams() (galsim.autocorrelation method)": [[8, "galsim.AutoCorrelation.withGSParams"]], "withgsparams() (galsim.convolution method)": [[8, "galsim.Convolution.withGSParams"]], "withgsparams() (galsim.sum method)": [[8, "galsim.Sum.withGSParams"]], "bandpassbuilder (class in galsim.config)": [[11, "galsim.config.BandpassBuilder"]], "ccdnoisebuilder (class in galsim.config)": [[11, "galsim.config.CCDNoiseBuilder"]], "cosmosnoisebuilder (class in galsim.config)": [[11, "galsim.config.COSMOSNoiseBuilder"]], "gaussiannoisebuilder (class in galsim.config)": [[11, "galsim.config.GaussianNoiseBuilder"]], "imagebuilder (class in galsim.config)": [[11, "galsim.config.ImageBuilder"]], "listwcsbuilder (class in galsim.config)": [[11, "galsim.config.ListWCSBuilder"]], "noisebuilder (class in galsim.config)": [[11, "galsim.config.NoiseBuilder"]], "originwcsbuilder (class in galsim.config)": [[11, "galsim.config.OriginWCSBuilder"]], "poissonnoisebuilder (class in galsim.config)": [[11, "galsim.config.PoissonNoiseBuilder"]], "registerbandpasstype() (in module galsim.config)": [[11, "galsim.config.RegisterBandpassType"]], "registerimagetype() (in module galsim.config)": [[11, "galsim.config.RegisterImageType"]], "registernoisetype() (in module galsim.config)": [[11, "galsim.config.RegisterNoiseType"]], "registersensortype() (in module galsim.config)": [[11, "galsim.config.RegisterSensorType"]], "registerwcstype() (in module galsim.config)": [[11, "galsim.config.RegisterWCSType"]], "scatteredimagebuilder (class in galsim.config.image_scattered)": [[11, "galsim.config.image_scattered.ScatteredImageBuilder"]], "sensorbuilder (class in galsim.config)": [[11, "galsim.config.SensorBuilder"]], "simplewcsbuilder (class in galsim.config)": [[11, "galsim.config.SimpleWCSBuilder"]], "tanwcsbuilder (class in galsim.config)": [[11, "galsim.config.TanWCSBuilder"]], "tiledimagebuilder (class in galsim.config.image_tiled)": [[11, "galsim.config.image_tiled.TiledImageBuilder"]], "wcsbuilder (class in galsim.config)": [[11, "galsim.config.WCSBuilder"]], "addnoise() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.addNoise"]], "addnoise() (galsim.config.noisebuilder method)": [[11, "galsim.config.NoiseBuilder.addNoise"]], "addnoisevariance() (galsim.config.noisebuilder method)": [[11, "galsim.config.NoiseBuilder.addNoiseVariance"]], "buildbandpass() (galsim.config.bandpassbuilder method)": [[11, "galsim.config.BandpassBuilder.buildBandpass"]], "buildbandpass() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.buildBandpass"]], "buildimage() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.buildImage"]], "buildsensor() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.buildSensor"]], "buildsensor() (galsim.config.sensorbuilder method)": [[11, "galsim.config.SensorBuilder.buildSensor"]], "buildwcs() (galsim.config.wcsbuilder method)": [[11, "galsim.config.WCSBuilder.buildWCS"]], "getnobj() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.getNObj"]], "getnoisevariance() (galsim.config.noisebuilder method)": [[11, "galsim.config.NoiseBuilder.getNoiseVariance"]], "maketasks() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.makeTasks"]], "setup() (galsim.config.imagebuilder method)": [[11, "galsim.config.ImageBuilder.setup"]], "inputloader (class in galsim.config)": [[12, "galsim.config.InputLoader"]], "nfwloader (class in galsim.config.input_nfw)": [[12, "galsim.config.input_nfw.NFWLoader"]], "powerspectrumloader (class in galsim.config.input_powerspectrum)": [[12, "galsim.config.input_powerspectrum.PowerSpectrumLoader"]], "registerinputtype() (in module galsim.config)": [[12, "galsim.config.RegisterInputType"]], "sampleloader (class in galsim.config.input_cosmos)": [[12, "galsim.config.input_cosmos.SampleLoader"]], "_buildcosmosgalaxy() (in module galsim.config.input_cosmos)": [[12, "galsim.config.input_cosmos._BuildCOSMOSGalaxy"]], "_buildrealgalaxy() (in module galsim.config.input_real)": [[12, "galsim.config.input_real._BuildRealGalaxy"]], "_buildrealgalaxyoriginal() (in module galsim.config.input_real)": [[12, "galsim.config.input_real._BuildRealGalaxyOriginal"]], "_generatefromnfwhalomagnification() (in module galsim.config.input_nfw)": [[12, "galsim.config.input_nfw._GenerateFromNFWHaloMagnification"]], "_generatefromnfwhaloshear() (in module galsim.config.input_nfw)": [[12, "galsim.config.input_nfw._GenerateFromNFWHaloShear"]], "_generatefrompowerspectrummagnification() (in module galsim.config.input_powerspectrum)": [[12, "galsim.config.input_powerspectrum._GenerateFromPowerSpectrumMagnification"]], "_generatefrompowerspectrumshear() (in module galsim.config.input_powerspectrum)": [[12, "galsim.config.input_powerspectrum._GenerateFromPowerSpectrumShear"]], "getkwargs() (galsim.config.inputloader method)": [[12, "galsim.config.InputLoader.getKwargs"]], "initialize() (galsim.config.inputloader method)": [[12, "galsim.config.InputLoader.initialize"]], "setupimage() (galsim.config.inputloader method)": [[12, "galsim.config.InputLoader.setupImage"]], "useproxy() (galsim.config.inputloader method)": [[12, "galsim.config.InputLoader.useProxy"]], "registerobjecttype() (in module galsim.config)": [[13, "galsim.config.RegisterObjectType"]], "registersedtype() (in module galsim.config)": [[13, "galsim.config.RegisterSEDType"]], "sedbuilder (class in galsim.config)": [[13, "galsim.config.SEDBuilder"]], "_buildadd() (in module galsim.config.gsobject)": [[13, "galsim.config.gsobject._BuildAdd"]], "_buildconvolve() (in module galsim.config.gsobject)": [[13, "galsim.config.gsobject._BuildConvolve"]], "_buildlist() (in module galsim.config.gsobject)": [[13, "galsim.config.gsobject._BuildList"]], "_buildopticalpsf() (in module galsim.config.gsobject)": [[13, "galsim.config.gsobject._BuildOpticalPSF"]], "buildsed() (galsim.config.sedbuilder method)": [[13, "galsim.config.SEDBuilder.buildSED"]], "badpixbuilder (class in galsim.config.extra_badpix)": [[14, "galsim.config.extra_badpix.BadPixBuilder"]], "datacubebuilder (class in galsim.config.output_datacube)": [[14, "galsim.config.output_datacube.DataCubeBuilder"]], "extraoutputbuilder (class in galsim.config)": [[14, "galsim.config.ExtraOutputBuilder"]], "extrapsfbuilder (class in galsim.config.extra_psf)": [[14, "galsim.config.extra_psf.ExtraPSFBuilder"]], "multifitsbuilder (class in galsim.config.output_multifits)": [[14, "galsim.config.output_multifits.MultiFitsBuilder"]], "outputbuilder (class in galsim.config)": [[14, "galsim.config.OutputBuilder"]], "registerextraoutput() (in module galsim.config)": [[14, "galsim.config.RegisterExtraOutput"]], "registeroutputtype() (in module galsim.config)": [[14, "galsim.config.RegisterOutputType"]], "truthbuilder (class in galsim.config.extra_truth)": [[14, "galsim.config.extra_truth.TruthBuilder"]], "weightbuilder (class in galsim.config.extra_weight)": [[14, "galsim.config.extra_weight.WeightBuilder"]], "addextraoutputhdus() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.addExtraOutputHDUs"]], "buildimages() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.buildImages"]], "canaddhdus() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.canAddHdus"]], "ensurefinalized() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.ensureFinalized"]], "finalize() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.finalize"]], "getfilename() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.getFilename"]], "getnfiles() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.getNFiles"]], "getnimages() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.getNImages"]], "getnobjperimage() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.getNObjPerImage"]], "initialize() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.initialize"]], "processimage() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.processImage"]], "processskippedstamp() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.processSkippedStamp"]], "processstamp() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.processStamp"]], "setup() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.setup"]], "setupimage() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.setupImage"]], "writeextraoutputs() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.writeExtraOutputs"]], "writefile() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.writeFile"]], "writefile() (galsim.config.outputbuilder method)": [[14, "galsim.config.OutputBuilder.writeFile"]], "writehdu() (galsim.config.extraoutputbuilder method)": [[14, "galsim.config.ExtraOutputBuilder.writeHdu"]], "addextraoutputhdus() (in module galsim.config)": [[15, "galsim.config.AddExtraOutputHDUs"]], "addnoise() (in module galsim.config)": [[15, "galsim.config.AddNoise"]], "addnoisevariance() (in module galsim.config)": [[15, "galsim.config.AddNoiseVariance"]], "addsky() (in module galsim.config)": [[15, "galsim.config.AddSky"]], "buildfile() (in module galsim.config)": [[15, "galsim.config.BuildFile"]], "buildfiles() (in module galsim.config)": [[15, "galsim.config.BuildFiles"]], "buildgsobject() (in module galsim.config)": [[15, "galsim.config.BuildGSObject"]], "buildimage() (in module galsim.config)": [[15, "galsim.config.BuildImage"]], "buildimages() (in module galsim.config)": [[15, "galsim.config.BuildImages"]], "buildstamp() (in module galsim.config)": [[15, "galsim.config.BuildStamp"]], "buildstamps() (in module galsim.config)": [[15, "galsim.config.BuildStamps"]], "buildwcs() (in module galsim.config)": [[15, "galsim.config.BuildWCS"]], "calculatenoisevariance() (in module galsim.config)": [[15, "galsim.config.CalculateNoiseVariance"]], "checkallparams() (in module galsim.config)": [[15, "galsim.config.CheckAllParams"]], "checknoextraoutputhdus() (in module galsim.config)": [[15, "galsim.config.CheckNoExtraOutputHDUs"]], "cleanconfig() (in module galsim.config)": [[15, "galsim.config.CleanConfig"]], "convertnones() (in module galsim.config)": [[15, "galsim.config.ConvertNones"]], "copyconfig() (in module galsim.config)": [[15, "galsim.config.CopyConfig"]], "drawbasic() (in module galsim.config)": [[15, "galsim.config.DrawBasic"]], "evaluatecurrentvalue() (in module galsim.config)": [[15, "galsim.config.EvaluateCurrentValue"]], "flattennoisevariance() (in module galsim.config)": [[15, "galsim.config.FlattenNoiseVariance"]], "getallparams() (in module galsim.config)": [[15, "galsim.config.GetAllParams"]], "getcurrentvalue() (in module galsim.config)": [[15, "galsim.config.GetCurrentValue"]], "getfinalextraoutput() (in module galsim.config)": [[15, "galsim.config.GetFinalExtraOutput"]], "getfromconfig() (in module galsim.config)": [[15, "galsim.config.GetFromConfig"]], "getindex() (in module galsim.config)": [[15, "galsim.config.GetIndex"]], "getinputobj() (in module galsim.config)": [[15, "galsim.config.GetInputObj"]], "getloggerproxy() (in module galsim.config)": [[15, "galsim.config.GetLoggerProxy"]], "getnfiles() (in module galsim.config)": [[15, "galsim.config.GetNFiles"]], "getnimagesforfile() (in module galsim.config)": [[15, "galsim.config.GetNImagesForFile"]], "getnobjforfile() (in module galsim.config)": [[15, "galsim.config.GetNObjForFile"]], "getnobjforimage() (in module galsim.config)": [[15, "galsim.config.GetNObjForImage"]], "getrng() (in module galsim.config)": [[15, "galsim.config.GetRNG"]], "getsky() (in module galsim.config)": [[15, "galsim.config.GetSky"]], "importmodules() (in module galsim.config)": [[15, "galsim.config.ImportModules"]], "loggerwrapper (class in galsim.config)": [[15, "galsim.config.LoggerWrapper"]], "makeimagetasks() (in module galsim.config)": [[15, "galsim.config.MakeImageTasks"]], "makestamptasks() (in module galsim.config)": [[15, "galsim.config.MakeStampTasks"]], "mergeconfig() (in module galsim.config)": [[15, "galsim.config.MergeConfig"]], "multiprocess() (in module galsim.config)": [[15, "galsim.config.MultiProcess"]], "parseextendedkey() (in module galsim.config)": [[15, "galsim.config.ParseExtendedKey"]], "parserandomseed() (in module galsim.config)": [[15, "galsim.config.ParseRandomSeed"]], "parsevalue() (in module galsim.config)": [[15, "galsim.config.ParseValue"]], "parseworldpos() (in module galsim.config)": [[15, "galsim.config.ParseWorldPos"]], "process() (in module galsim.config)": [[15, "galsim.config.Process"]], "processalltemplates() (in module galsim.config)": [[15, "galsim.config.ProcessAllTemplates"]], "processextraoutputsforimage() (in module galsim.config)": [[15, "galsim.config.ProcessExtraOutputsForImage"]], "processextraoutputsforstamp() (in module galsim.config)": [[15, "galsim.config.ProcessExtraOutputsForStamp"]], "processinput() (in module galsim.config)": [[15, "galsim.config.ProcessInput"]], "processinputnobjects() (in module galsim.config)": [[15, "galsim.config.ProcessInputNObjects"]], "processtemplate() (in module galsim.config)": [[15, "galsim.config.ProcessTemplate"]], "propagateindexkeyrngnum() (in module galsim.config)": [[15, "galsim.config.PropagateIndexKeyRNGNum"]], "readconfig() (in module galsim.config)": [[15, "galsim.config.ReadConfig"]], "readjson() (in module galsim.config)": [[15, "galsim.config.ReadJson"]], "readyaml() (in module galsim.config)": [[15, "galsim.config.ReadYaml"]], "registerinputconnectedtype() (in module galsim.config)": [[15, "galsim.config.RegisterInputConnectedType"]], "removecurrent() (in module galsim.config)": [[15, "galsim.config.RemoveCurrent"]], "retryio() (in module galsim.config)": [[15, "galsim.config.RetryIO"]], "setdefaultext() (in module galsim.config)": [[15, "galsim.config.SetDefaultExt"]], "setdefaultindex() (in module galsim.config)": [[15, "galsim.config.SetDefaultIndex"]], "setinconfig() (in module galsim.config)": [[15, "galsim.config.SetInConfig"]], "setupconfigfilenum() (in module galsim.config)": [[15, "galsim.config.SetupConfigFileNum"]], "setupconfigimagenum() (in module galsim.config)": [[15, "galsim.config.SetupConfigImageNum"]], "setupconfigimagesize() (in module galsim.config)": [[15, "galsim.config.SetupConfigImageSize"]], "setupconfigobjnum() (in module galsim.config)": [[15, "galsim.config.SetupConfigObjNum"]], "setupconfigrng() (in module galsim.config)": [[15, "galsim.config.SetupConfigRNG"]], "setupconfigstampsize() (in module galsim.config)": [[15, "galsim.config.SetupConfigStampSize"]], "setupextraoutput() (in module galsim.config)": [[15, "galsim.config.SetupExtraOutput"]], "setupextraoutputsforimage() (in module galsim.config)": [[15, "galsim.config.SetupExtraOutputsForImage"]], "setupinput() (in module galsim.config)": [[15, "galsim.config.SetupInput"]], "setupinputsforimage() (in module galsim.config)": [[15, "galsim.config.SetupInputsForImage"]], "skipthisobject (class in galsim.config)": [[15, "galsim.config.SkipThisObject"]], "transformobject() (in module galsim.config)": [[15, "galsim.config.TransformObject"]], "updateconfig() (in module galsim.config)": [[15, "galsim.config.UpdateConfig"]], "updategsparams() (in module galsim.config)": [[15, "galsim.config.UpdateGSParams"]], "updatenproc() (in module galsim.config)": [[15, "galsim.config.UpdateNProc"]], "writeextraoutputs() (in module galsim.config)": [[15, "galsim.config.WriteExtraOutputs"]], "registertemplate() (in module galsim.config)": [[16, "galsim.config.RegisterTemplate"]], "photonopbuilder (class in galsim.config)": [[17, "galsim.config.PhotonOpBuilder"]], "registerphotonoptype() (in module galsim.config)": [[17, "galsim.config.RegisterPhotonOpType"]], "ringbuilder (class in galsim.config.stamp_ring)": [[17, "galsim.config.stamp_ring.RingBuilder"]], "stampbuilder (class in galsim.config)": [[17, "galsim.config.StampBuilder"]], "addnoise() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.addNoise"]], "applysnrscale() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.applySNRScale"]], "buildpsf() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.buildPSF"]], "buildphotonop() (galsim.config.photonopbuilder method)": [[17, "galsim.config.PhotonOpBuilder.buildPhotonOp"]], "buildprofile() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.buildProfile"]], "draw() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.draw"]], "getdrawmethod() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.getDrawMethod"]], "getoffset() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.getOffset"]], "getsnrscale() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.getSNRScale"]], "getskip() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.getSkip"]], "locatestamp() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.locateStamp"]], "makestamp() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.makeStamp"]], "maketasks() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.makeTasks"]], "quickskip() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.quickSkip"]], "reject() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.reject"]], "reset() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.reset"]], "setup() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.setup"]], "setuprng() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.setupRNG"]], "updateorigin() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.updateOrigin"]], "updateskip() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.updateSkip"]], "whiten() (galsim.config.stampbuilder method)": [[17, "galsim.config.StampBuilder.whiten"]], "registervaluetype() (in module galsim.config)": [[19, "galsim.config.RegisterValueType"]], "basecorrelatednoise (class in galsim)": [[20, "galsim.BaseCorrelatedNoise"]], "correlatednoise (class in galsim)": [[20, "galsim.CorrelatedNoise"]], "uncorrelatednoise (class in galsim)": [[20, "galsim.UncorrelatedNoise"]], "applyto() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.applyTo"]], "convolvedwith() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.convolvedWith"]], "copy() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.copy"]], "dilate() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.dilate"]], "drawimage() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.drawImage"]], "drawkimage() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.drawKImage"]], "expand() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.expand"]], "from_file() (galsim.basecorrelatednoise class method)": [[20, "galsim.BaseCorrelatedNoise.from_file"]], "getcosmosnoise() (in module galsim)": [[20, "galsim.getCOSMOSNoise"]], "getvariance() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.getVariance"]], "gsparams (galsim.basecorrelatednoise property)": [[20, "galsim.BaseCorrelatedNoise.gsparams"]], "lens() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.lens"]], "magnify() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.magnify"]], "rng (galsim.basecorrelatednoise property)": [[20, "galsim.BaseCorrelatedNoise.rng"]], "rotate() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.rotate"]], "shear() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.shear"]], "symmetrizeimage() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.symmetrizeImage"]], "transform() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.transform"]], "whitenimage() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.whitenImage"]], "withgsparams() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.withGSParams"]], "withgsparams() (galsim.uncorrelatednoise method)": [[20, "galsim.UncorrelatedNoise.withGSParams"]], "withscaledvariance() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.withScaledVariance"]], "withvariance() (galsim.basecorrelatednoise method)": [[20, "galsim.BaseCorrelatedNoise.withVariance"]], "galsim_major (c macro)": [[21, "c.GALSIM_MAJOR"]], "galsim_minor (c macro)": [[21, "c.GALSIM_MINOR"]], "galsim_revision (c macro)": [[21, "c.GALSIM_REVISION"]], "galsim::check_version (c++ function)": [[21, "_CPPv4N6galsim13check_versionEv"]], "galsim::major_version (c++ function)": [[21, "_CPPv4N6galsim13major_versionEv"]], "galsim::minor_version (c++ function)": [[21, "_CPPv4N6galsim13minor_versionEv"]], "galsim::revision (c++ function)": [[21, "_CPPv4N6galsim8revisionEv"]], "galsim::version (c++ function)": [[21, "_CPPv4N6galsim7versionEv"]], "galsim::bounds (c++ class)": [[22, "_CPPv4I0EN6galsim6BoundsE"]], "galsim::bounds::bounds (c++ function)": [[22, "_CPPv4N6galsim6Bounds6BoundsEK1TK1TK1TK1T"], [22, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TE"], [22, "_CPPv4N6galsim6Bounds6BoundsERK8PositionI1TERK8PositionI1TE"], [22, "_CPPv4N6galsim6Bounds6BoundsEv"]], "galsim::bounds::addborder (c++ function)": [[22, "_CPPv4N6galsim6Bounds9addBorderEK1T"]], "galsim::bounds::area (c++ function)": [[22, "_CPPv4NK6galsim6Bounds4areaEv"]], "galsim::bounds::center (c++ function)": [[22, "_CPPv4NK6galsim6Bounds6centerEv"]], "galsim::bounds::copy (c++ function)": [[22, "_CPPv4NK6galsim6Bounds4copyEv"]], "galsim::bounds::divide (c++ function)": [[22, "_CPPv4NK6galsim6Bounds6divideEii"]], "galsim::bounds::expand (c++ function)": [[22, "_CPPv4N6galsim6Bounds6expandEKd"]], "galsim::bounds::getxmax (c++ function)": [[22, "_CPPv4NK6galsim6Bounds7getXMaxEv"]], "galsim::bounds::getxmin (c++ function)": [[22, "_CPPv4NK6galsim6Bounds7getXMinEv"]], "galsim::bounds::getymax (c++ function)": [[22, "_CPPv4NK6galsim6Bounds7getYMaxEv"]], "galsim::bounds::getymin (c++ function)": [[22, "_CPPv4NK6galsim6Bounds7getYMinEv"]], "galsim::bounds::includes (c++ function)": [[22, "_CPPv4I0ENK6galsim6Bounds8includesEbK2T2K2T2"], [22, "_CPPv4I0ENK6galsim6Bounds8includesEbRK8PositionI2T2E"], [22, "_CPPv4NK6galsim6Bounds8includesERK6BoundsI1TE"]], "galsim::bounds::isdefined (c++ function)": [[22, "_CPPv4NK6galsim6Bounds9isDefinedEv"]], "galsim::bounds::issameshapeas (c++ function)": [[22, "_CPPv4NK6galsim6Bounds13isSameShapeAsERK6BoundsI1TE"]], "galsim::bounds::makeexpanded (c++ function)": [[22, "_CPPv4NK6galsim6Bounds12makeExpandedEKd"]], "galsim::bounds::makeshifted (c++ function)": [[22, "_CPPv4NK6galsim6Bounds11makeShiftedEK1TK1T"], [22, "_CPPv4NK6galsim6Bounds11makeShiftedERK8PositionI1TE"]], "galsim::bounds::operator!= (c++ function)": [[22, "_CPPv4NK6galsim6BoundsneERK6BoundsI1TE"]], "galsim::bounds::operator& (c++ function)": [[22, "_CPPv4NK6galsim6BoundsanERK6BoundsI1TE"]], "galsim::bounds::operator+ (c++ function)": [[22, "_CPPv4NK6galsim6BoundsplEK1T"], [22, "_CPPv4NK6galsim6BoundsplERK6BoundsI1TE"], [22, "_CPPv4NK6galsim6BoundsplERK8PositionI1TE"]], "galsim::bounds::operator+= (c++ function)": [[22, "_CPPv4N6galsim6BoundspLEK1T"], [22, "_CPPv4N6galsim6BoundspLERK6BoundsI1TE"], [22, "_CPPv4N6galsim6BoundspLERK8PositionI1TE"]], "galsim::bounds::operator== (c++ function)": [[22, "_CPPv4NK6galsim6BoundseqERK6BoundsI1TE"]], "galsim::bounds::origin (c++ function)": [[22, "_CPPv4NK6galsim6Bounds6originEv"]], "galsim::bounds::read (c++ function)": [[22, "_CPPv4N6galsim6Bounds4readERNSt7istreamE"]], "galsim::bounds::setxmax (c++ function)": [[22, "_CPPv4N6galsim6Bounds7setXMaxEK1T"]], "galsim::bounds::setxmin (c++ function)": [[22, "_CPPv4N6galsim6Bounds7setXMinEK1T"]], "galsim::bounds::setymax (c++ function)": [[22, "_CPPv4N6galsim6Bounds7setYMaxEK1T"]], "galsim::bounds::setymin (c++ function)": [[22, "_CPPv4N6galsim6Bounds7setYMinEK1T"]], "galsim::bounds::shift (c++ function)": [[22, "_CPPv4N6galsim6Bounds5shiftEK1TK1T"], [22, "_CPPv4N6galsim6Bounds5shiftERK8PositionI1TE"]], "galsim::bounds::truecenter (c++ function)": [[22, "_CPPv4NK6galsim6Bounds10trueCenterEv"]], "galsim::bounds::withborder (c++ function)": [[22, "_CPPv4NK6galsim6Bounds10withBorderEK1T"]], "galsim::bounds::write (c++ function)": [[22, "_CPPv4NK6galsim6Bounds5writeERNSt7ostreamE"]], "galsim::bounds::~bounds (c++ function)": [[22, "_CPPv4N6galsim6BoundsD0Ev"]], "galsim::position (c++ class)": [[22, "_CPPv4I0EN6galsim8PositionE"]], "galsim::position::position (c++ function)": [[22, "_CPPv4I0EN6galsim8Position8PositionERK8PositionI2T2E"], [22, "_CPPv4N6galsim8Position8PositionEK1TK1T"], [22, "_CPPv4N6galsim8Position8PositionERK8PositionI1TE"], [22, "_CPPv4N6galsim8Position8PositionEv"]], "galsim::position::operator!= (c++ function)": [[22, "_CPPv4NK6galsim8PositionneERK8PositionI1TE"]], "galsim::position::operator* (c++ function)": [[22, "_CPPv4N6galsim8PositionmlEK1TRK8PositionI1TE"], [22, "_CPPv4NK6galsim8PositionmlEK1T"]], "galsim::position::operator*= (c++ function)": [[22, "_CPPv4N6galsim8PositionmLEK1T"]], "galsim::position::operator+ (c++ function)": [[22, "_CPPv4I0ENK6galsim8PositionplE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E"]], "galsim::position::operator+= (c++ function)": [[22, "_CPPv4I0EN6galsim8PositionpLER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E"]], "galsim::position::operator- (c++ function)": [[22, "_CPPv4I0ENK6galsim8PositionmiE8PositionIN9PromotionI1T2T2E4typeEERK8PositionI2T2E"], [22, "_CPPv4NK6galsim8PositionmiEv"]], "galsim::position::operator-= (c++ function)": [[22, "_CPPv4I0EN6galsim8PositionmIER8PositionIN13SelfPromotionI1T2T2E4typeEERK8PositionI2T2E"]], "galsim::position::operator/ (c++ function)": [[22, "_CPPv4NK6galsim8PositiondvEK1T"]], "galsim::position::operator/= (c++ function)": [[22, "_CPPv4N6galsim8PositiondVEK1T"]], "galsim::position::operator= (c++ function)": [[22, "_CPPv4I0EN6galsim8PositionaSER8PositionRK8PositionI2T2E"], [22, "_CPPv4N6galsim8PositionaSERK8PositionI1TE"]], "galsim::position::operator== (c++ function)": [[22, "_CPPv4NK6galsim8PositioneqERK8PositionI1TE"]], "galsim::position::read (c++ function)": [[22, "_CPPv4N6galsim8Position4readERNSt7istreamE"]], "galsim::position::write (c++ function)": [[22, "_CPPv4NK6galsim8Position5writeERNSt7ostreamE"]], "galsim::position::x (c++ member)": [[22, "_CPPv4N6galsim8Position1xE"]], "galsim::position::y (c++ member)": [[22, "_CPPv4N6galsim8Position1yE"]], "galsim::hsm::estimateshearview (c++ function)": [[23, "_CPPv4I00EN6galsim3hsm17EstimateShearViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageI1UERK9BaseImageIiEfPKcPKcdddN6galsim8PositionIdEERK9HSMParams"]], "galsim::hsm::findadaptivemomview (c++ function)": [[23, "_CPPv4I0EN6galsim3hsm19FindAdaptiveMomViewEvR9ShapeDataRK9BaseImageI1TERK9BaseImageIiEddN6galsim8PositionIdEEbRK9HSMParams"]], "galsim::hsm::hsmerror (c++ class)": [[23, "_CPPv4N6galsim3hsm8HSMErrorE"]], "galsim::hsm::hsmerror::hsmerror (c++ function)": [[23, "_CPPv4N6galsim3hsm8HSMError8HSMErrorERKNSt6stringE"]], "galsim::hsm::hsmparams (c++ struct)": [[23, "_CPPv4N6galsim3hsm9HSMParamsE"]], "galsim::hsm::hsmparams::hsmparams (c++ function)": [[23, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEdddiidlldddiddd"], [23, "_CPPv4N6galsim3hsm9HSMParams9HSMParamsEv"]], "galsim::hsm::hsmparams::adapt_order (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams11adapt_orderE"]], "galsim::hsm::hsmparams::bound_correct_wt (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams16bound_correct_wtE"]], "galsim::hsm::hsmparams::convergence_threshold (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams21convergence_thresholdE"]], "galsim::hsm::hsmparams::failed_moments (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams14failed_momentsE"]], "galsim::hsm::hsmparams::ksb_moments_max (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams15ksb_moments_maxE"]], "galsim::hsm::hsmparams::ksb_sig_factor (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams14ksb_sig_factorE"]], "galsim::hsm::hsmparams::ksb_sig_weight (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams14ksb_sig_weightE"]], "galsim::hsm::hsmparams::max_amoment (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams11max_amomentE"]], "galsim::hsm::hsmparams::max_ashift (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams10max_ashiftE"]], "galsim::hsm::hsmparams::max_mom2_iter (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams13max_mom2_iterE"]], "galsim::hsm::hsmparams::max_moment_nsig2 (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams16max_moment_nsig2E"]], "galsim::hsm::hsmparams::nsig_rg (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams7nsig_rgE"]], "galsim::hsm::hsmparams::nsig_rg2 (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams8nsig_rg2E"]], "galsim::hsm::hsmparams::num_iter_default (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams16num_iter_defaultE"]], "galsim::hsm::hsmparams::regauss_too_small (c++ member)": [[23, "_CPPv4N6galsim3hsm9HSMParams17regauss_too_smallE"]], "galsim::hsm::objectdata (c++ struct)": [[23, "_CPPv4N6galsim3hsm10ObjectDataE"]], "galsim::hsm::objectdata::objectdata (c++ function)": [[23, "_CPPv4N6galsim3hsm10ObjectData10ObjectDataEv"]], "galsim::hsm::objectdata::e1 (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData2e1E"]], "galsim::hsm::objectdata::e2 (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData2e2E"]], "galsim::hsm::objectdata::flux (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData4fluxE"]], "galsim::hsm::objectdata::meas_type (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData9meas_typeE"]], "galsim::hsm::objectdata::resolution (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData10resolutionE"]], "galsim::hsm::objectdata::responsivity (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData12responsivityE"]], "galsim::hsm::objectdata::sigma (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData5sigmaE"]], "galsim::hsm::objectdata::x0 (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData2x0E"]], "galsim::hsm::objectdata::y0 (c++ member)": [[23, "_CPPv4N6galsim3hsm10ObjectData2y0E"]], "galsim::hsm::shapedata (c++ struct)": [[23, "_CPPv4N6galsim3hsm9ShapeDataE"]], "galsim::hsm::shapedata::shapedata (c++ function)": [[23, "_CPPv4N6galsim3hsm9ShapeData9ShapeDataEv"]], "galsim::hsm::shapedata::corrected_e1 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData12corrected_e1E"]], "galsim::hsm::shapedata::corrected_e2 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData12corrected_e2E"]], "galsim::hsm::shapedata::corrected_g1 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData12corrected_g1E"]], "galsim::hsm::shapedata::corrected_g2 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData12corrected_g2E"]], "galsim::hsm::shapedata::corrected_shape_err (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData19corrected_shape_errE"]], "galsim::hsm::shapedata::correction_method (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData17correction_methodE"]], "galsim::hsm::shapedata::correction_status (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData17correction_statusE"]], "galsim::hsm::shapedata::error_message (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData13error_messageE"]], "galsim::hsm::shapedata::image_bounds (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData12image_boundsE"]], "galsim::hsm::shapedata::meas_type (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData9meas_typeE"]], "galsim::hsm::shapedata::moments_amp (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData11moments_ampE"]], "galsim::hsm::shapedata::moments_centroid (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData16moments_centroidE"]], "galsim::hsm::shapedata::moments_n_iter (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData14moments_n_iterE"]], "galsim::hsm::shapedata::moments_rho4 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData12moments_rho4E"]], "galsim::hsm::shapedata::moments_sigma (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData13moments_sigmaE"]], "galsim::hsm::shapedata::moments_status (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData14moments_statusE"]], "galsim::hsm::shapedata::observed_e1 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData11observed_e1E"]], "galsim::hsm::shapedata::observed_e2 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData11observed_e2E"]], "galsim::hsm::shapedata::psf_e1 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData6psf_e1E"]], "galsim::hsm::shapedata::psf_e2 (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData6psf_e2E"]], "galsim::hsm::shapedata::psf_sigma (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData9psf_sigmaE"]], "galsim::hsm::shapedata::resolution_factor (c++ member)": [[23, "_CPPv4N6galsim3hsm9ShapeData17resolution_factorE"]], "galsim::hsm::find_ellipmom_2 (c++ function)": [[23, "_CPPv4N6galsim3hsm15find_ellipmom_2E14ConstImageViewIdERdRdRdRdRdRdRddRiRK9HSMParams"]], "galsim::hsm::general_shear_estimator (c++ function)": [[23, "_CPPv4N6galsim3hsm23general_shear_estimatorE14ConstImageViewIdE14ConstImageViewIdER10ObjectDataR10ObjectDataPKcmRK9HSMParams"]], "galsim::assignabletoimage (c++ class)": [[24, "_CPPv4I0EN6galsim17AssignableToImageE"]], "galsim::assignabletoimage::assignto (c++ function)": [[24, "_CPPv4NK6galsim17AssignableToImage8assignToE9ImageViewI1TE"]], "galsim::assignabletoimage::getbounds (c++ function)": [[24, "_CPPv4NK6galsim17AssignableToImage9getBoundsEv"]], "galsim::assignabletoimage::~assignabletoimage (c++ function)": [[24, "_CPPv4N6galsim17AssignableToImageD0Ev"]], "galsim::baseimage (c++ class)": [[24, "_CPPv4I0EN6galsim9BaseImageE"]], "galsim::baseimage::assignto (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage8assignToE9ImageViewI1TE"]], "galsim::baseimage::at (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage2atERK8PositionIiE"], [24, "_CPPv4NK6galsim9BaseImage2atEii"]], "galsim::baseimage::copy (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage4copyEv"]], "galsim::baseimage::getdata (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getDataEv"]], "galsim::baseimage::getmaxptr (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage9getMaxPtrEv"]], "galsim::baseimage::getncol (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getNColEv"]], "galsim::baseimage::getnelements (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage12getNElementsEv"]], "galsim::baseimage::getnrow (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getNRowEv"]], "galsim::baseimage::getnskip (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage8getNSkipEv"]], "galsim::baseimage::getowner (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage8getOwnerEv"]], "galsim::baseimage::getptr (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage6getPtrERK8PositionIiE"], [24, "_CPPv4NK6galsim9BaseImage6getPtrEii"]], "galsim::baseimage::getstep (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getStepEv"]], "galsim::baseimage::getstride (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage9getStrideEv"]], "galsim::baseimage::getxmax (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getXMaxEv"]], "galsim::baseimage::getxmin (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getXMinEv"]], "galsim::baseimage::getymax (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getYMaxEv"]], "galsim::baseimage::getymin (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage7getYMinEv"]], "galsim::baseimage::iscontiguous (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage12isContiguousEv"]], "galsim::baseimage::maxabselement (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage13maxAbsElementEv"]], "galsim::baseimage::nonzerobounds (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage13nonZeroBoundsEv"]], "galsim::baseimage::ok_ptr (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage6ok_ptrEPK1T"]], "galsim::baseimage::operator() (c++ function)": [[24, "_CPPv4NK6galsim9BaseImageclERK8PositionIiE"], [24, "_CPPv4NK6galsim9BaseImageclEii"]], "galsim::baseimage::operator[] (c++ function)": [[24, "_CPPv4NK6galsim9BaseImageixERK6BoundsIiE"]], "galsim::baseimage::shift (c++ function)": [[24, "_CPPv4N6galsim9BaseImage5shiftERK8PositionIiE"]], "galsim::baseimage::subimage (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage8subImageERK6BoundsIiE"]], "galsim::baseimage::sumelements (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage11sumElementsEv"]], "galsim::baseimage::view (c++ function)": [[24, "_CPPv4NK6galsim9BaseImage4viewEv"]], "galsim::baseimage::~baseimage (c++ function)": [[24, "_CPPv4N6galsim9BaseImageD0Ev"]], "galsim::cleardepixelizecache (c++ function)": [[24, "_CPPv4N6galsim20ClearDepixelizeCacheEv"]], "galsim::constimageview (c++ class)": [[24, "_CPPv4I0EN6galsim14ConstImageViewE"]], "galsim::constimageview::constimageview (c++ function)": [[24, "_CPPv4N6galsim14ConstImageView14ConstImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE"], [24, "_CPPv4N6galsim14ConstImageView14ConstImageViewERK14ConstImageViewI1TE"], [24, "_CPPv4N6galsim14ConstImageView14ConstImageViewERK9BaseImageI1TE"]], "galsim::constimageview::view (c++ function)": [[24, "_CPPv4NK6galsim14ConstImageView4viewEv"]], "galsim::imagealloc (c++ class)": [[24, "_CPPv4I0EN6galsim10ImageAllocE"]], "galsim::imagealloc::imagealloc (c++ function)": [[24, "_CPPv4I0EN6galsim10ImageAlloc10ImageAllocERK9BaseImageI1UE"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK10ImageAllocI1TE"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK17AssignableToImageI1TE"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocERK6BoundsIiE1T"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocEii1T"], [24, "_CPPv4N6galsim10ImageAlloc10ImageAllocEv"]], "galsim::imagealloc::at (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc2atERK8PositionIiE"], [24, "_CPPv4N6galsim10ImageAlloc2atEii"], [24, "_CPPv4NK6galsim10ImageAlloc2atERK8PositionIiE"], [24, "_CPPv4NK6galsim10ImageAlloc2atEii"]], "galsim::imagealloc::copyfrom (c++ function)": [[24, "_CPPv4I0EN6galsim10ImageAlloc8copyFromEvRK9BaseImageI1UE"]], "galsim::imagealloc::fill (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc4fillE1T"]], "galsim::imagealloc::getdata (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc7getDataEv"], [24, "_CPPv4NK6galsim10ImageAlloc7getDataEv"]], "galsim::imagealloc::getmaxptr (c++ function)": [[24, "_CPPv4NK6galsim10ImageAlloc9getMaxPtrEv"]], "galsim::imagealloc::invertself (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc10invertSelfEv"]], "galsim::imagealloc::operator() (c++ function)": [[24, "_CPPv4N6galsim10ImageAllocclERK8PositionIiE"], [24, "_CPPv4N6galsim10ImageAllocclEii"], [24, "_CPPv4NK6galsim10ImageAllocclERK8PositionIiE"], [24, "_CPPv4NK6galsim10ImageAllocclEii"]], "galsim::imagealloc::operator= (c++ function)": [[24, "_CPPv4I0EN6galsim10ImageAllocaSER10ImageAllocI1TERK9BaseImageI1UE"], [24, "_CPPv4N6galsim10ImageAllocaSE1T"], [24, "_CPPv4N6galsim10ImageAllocaSERK10ImageAllocI1TE"], [24, "_CPPv4N6galsim10ImageAllocaSERK17AssignableToImageI1TE"]], "galsim::imagealloc::operator[] (c++ function)": [[24, "_CPPv4N6galsim10ImageAllocixERK6BoundsIiE"], [24, "_CPPv4NK6galsim10ImageAllocixERK6BoundsIiE"]], "galsim::imagealloc::resize (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc6resizeERK6BoundsIiEb"]], "galsim::imagealloc::setvalue (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc8setValueEii1T"]], "galsim::imagealloc::setzero (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc7setZeroEv"]], "galsim::imagealloc::subimage (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc8subImageERK6BoundsIiE"], [24, "_CPPv4NK6galsim10ImageAlloc8subImageERK6BoundsIiE"]], "galsim::imagealloc::view (c++ function)": [[24, "_CPPv4N6galsim10ImageAlloc4viewEv"], [24, "_CPPv4NK6galsim10ImageAlloc4viewEv"]], "galsim::imageboundserror (c++ class)": [[24, "_CPPv4N6galsim16ImageBoundsErrorE"]], "galsim::imageboundserror::imageboundserror (c++ function)": [[24, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringE"], [24, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorERKNSt6stringEiii"], [24, "_CPPv4N6galsim16ImageBoundsError16ImageBoundsErrorEiiK6BoundsIiE"]], "galsim::imageerror (c++ class)": [[24, "_CPPv4N6galsim10ImageErrorE"]], "galsim::imageerror::imageerror (c++ function)": [[24, "_CPPv4N6galsim10ImageError10ImageErrorERKNSt6stringE"]], "galsim::imageview (c++ class)": [[24, "_CPPv4I0EN6galsim9ImageViewE"]], "galsim::imageview::imageview (c++ function)": [[24, "_CPPv4N6galsim9ImageView9ImageViewEP1TPK1T9ptrdiff_tRK10shared_ptrI1TEiiRK6BoundsIiE"], [24, "_CPPv4N6galsim9ImageView9ImageViewER10ImageAllocI1TE"], [24, "_CPPv4N6galsim9ImageView9ImageViewERK9ImageViewI1TE"]], "galsim::imageview::at (c++ function)": [[24, "_CPPv4N6galsim9ImageView2atERK8PositionIiE"], [24, "_CPPv4N6galsim9ImageView2atEii"]], "galsim::imageview::copyfrom (c++ function)": [[24, "_CPPv4I0EN6galsim9ImageView8copyFromEvRK9BaseImageI1UE"], [24, "_CPPv4N6galsim9ImageView8copyFromERK9BaseImageI1TE"]], "galsim::imageview::depixelizeself (c++ function)": [[24, "_CPPv4N6galsim9ImageView14depixelizeSelfEPKdKi"]], "galsim::imageview::fill (c++ function)": [[24, "_CPPv4N6galsim9ImageView4fillE1T"]], "galsim::imageview::getdata (c++ function)": [[24, "_CPPv4N6galsim9ImageView7getDataEv"]], "galsim::imageview::getmaxptr (c++ function)": [[24, "_CPPv4N6galsim9ImageView9getMaxPtrEv"]], "galsim::imageview::invertself (c++ function)": [[24, "_CPPv4N6galsim9ImageView10invertSelfEv"]], "galsim::imageview::operator() (c++ function)": [[24, "_CPPv4N6galsim9ImageViewclERK8PositionIiE"], [24, "_CPPv4N6galsim9ImageViewclEii"]], "galsim::imageview::operator= (c++ function)": [[24, "_CPPv4I0EN6galsim9ImageViewaSER9ImageViewI1TERK9BaseImageI1UE"], [24, "_CPPv4N6galsim9ImageViewaSE1T"], [24, "_CPPv4N6galsim9ImageViewaSERK17AssignableToImageI1TE"], [24, "_CPPv4N6galsim9ImageViewaSERK9ImageViewI1TE"]], "galsim::imageview::operator[] (c++ function)": [[24, "_CPPv4N6galsim9ImageViewixERK6BoundsIiE"]], "galsim::imageview::setvalue (c++ function)": [[24, "_CPPv4N6galsim9ImageView8setValueEii1T"]], "galsim::imageview::setzero (c++ function)": [[24, "_CPPv4N6galsim9ImageView7setZeroEv"]], "galsim::imageview::subimage (c++ function)": [[24, "_CPPv4N6galsim9ImageView8subImageERK6BoundsIiE"]], "galsim::imageview::view (c++ function)": [[24, "_CPPv4N6galsim9ImageView4viewEv"]], "galsim::cfft (c++ function)": [[24, "_CPPv4I0EN6galsim4cfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbbb"]], "galsim::goodfftsize (c++ function)": [[24, "_CPPv4N6galsim11goodFFTSizeEi"]], "galsim::invertimage (c++ function)": [[24, "_CPPv4I0EN6galsim11invertImageEv9ImageViewI1TE"]], "galsim::irfft (c++ function)": [[24, "_CPPv4I0EN6galsim5irfftEvRK9BaseImageI1TE9ImageViewIdEbb"]], "galsim::rfft (c++ function)": [[24, "_CPPv4I0EN6galsim4rfftEvRK9BaseImageI1TE9ImageViewINSt7complexIdEEEbb"]], "galsim::wrapimage (c++ function)": [[24, "_CPPv4I0EN6galsim9wrapImageEv9ImageViewI1TERK6BoundsIiEbb"]], "galsim::cubic (c++ class)": [[25, "_CPPv4N6galsim5CubicE"]], "galsim::cubic::cubic (c++ function)": [[25, "_CPPv4N6galsim5Cubic5CubicERK8GSParams"]], "galsim::cubic::getnegativeflux (c++ function)": [[25, "_CPPv4NK6galsim5Cubic15getNegativeFluxEv"]], "galsim::cubic::getpositiveflux (c++ function)": [[25, "_CPPv4NK6galsim5Cubic15getPositiveFluxEv"]], "galsim::cubic::ixrange (c++ function)": [[25, "_CPPv4NK6galsim5Cubic7ixrangeEv"]], "galsim::cubic::makestr (c++ function)": [[25, "_CPPv4NK6galsim5Cubic7makeStrEv"]], "galsim::cubic::urange (c++ function)": [[25, "_CPPv4NK6galsim5Cubic6urangeEv"]], "galsim::cubic::uval (c++ function)": [[25, "_CPPv4NK6galsim5Cubic4uvalEd"]], "galsim::cubic::xrange (c++ function)": [[25, "_CPPv4NK6galsim5Cubic6xrangeEv"]], "galsim::cubic::xval (c++ function)": [[25, "_CPPv4NK6galsim5Cubic4xvalEd"]], "galsim::cubic::~cubic (c++ function)": [[25, "_CPPv4N6galsim5CubicD0Ev"]], "galsim::delta (c++ class)": [[25, "_CPPv4N6galsim5DeltaE"]], "galsim::delta::delta (c++ function)": [[25, "_CPPv4N6galsim5Delta5DeltaERK8GSParams"]], "galsim::delta::getnegativeflux (c++ function)": [[25, "_CPPv4NK6galsim5Delta15getNegativeFluxEv"]], "galsim::delta::getpositiveflux (c++ function)": [[25, "_CPPv4NK6galsim5Delta15getPositiveFluxEv"]], "galsim::delta::ixrange (c++ function)": [[25, "_CPPv4NK6galsim5Delta7ixrangeEv"]], "galsim::delta::makestr (c++ function)": [[25, "_CPPv4NK6galsim5Delta7makeStrEv"]], "galsim::delta::shoot (c++ function)": [[25, "_CPPv4NK6galsim5Delta5shootER11PhotonArray14UniformDeviate"]], "galsim::delta::urange (c++ function)": [[25, "_CPPv4NK6galsim5Delta6urangeEv"]], "galsim::delta::uval (c++ function)": [[25, "_CPPv4NK6galsim5Delta4uvalEd"]], "galsim::delta::xrange (c++ function)": [[25, "_CPPv4NK6galsim5Delta6xrangeEv"]], "galsim::delta::xval (c++ function)": [[25, "_CPPv4NK6galsim5Delta4xvalEd"]], "galsim::delta::~delta (c++ function)": [[25, "_CPPv4N6galsim5DeltaD0Ev"]], "galsim::interpolant (c++ class)": [[25, "_CPPv4N6galsim11InterpolantE"]], "galsim::interpolant::interpolant (c++ function)": [[25, "_CPPv4N6galsim11Interpolant11InterpolantERK11Interpolant"], [25, "_CPPv4N6galsim11Interpolant11InterpolantERK8GSParams"]], "galsim::interpolant::getnegativeflux (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant15getNegativeFluxEv"]], "galsim::interpolant::getnegativeflux2d (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant17getNegativeFlux2dEv"]], "galsim::interpolant::getpositiveflux (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant15getPositiveFluxEv"]], "galsim::interpolant::getpositiveflux2d (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant17getPositiveFlux2dEv"]], "galsim::interpolant::isexactatnodes (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant14isExactAtNodesEv"]], "galsim::interpolant::ixrange (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant7ixrangeEv"]], "galsim::interpolant::makestr (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant7makeStrEv"]], "galsim::interpolant::shoot (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant5shootER11PhotonArray14UniformDeviate"]], "galsim::interpolant::urange (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant6urangeEv"]], "galsim::interpolant::uval (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant4uvalEd"]], "galsim::interpolant::uvalmany (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant8uvalManyEPdi"]], "galsim::interpolant::xrange (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant6xrangeEv"]], "galsim::interpolant::xval (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant4xvalEd"]], "galsim::interpolant::xvalmany (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant8xvalManyEPdi"]], "galsim::interpolant::xvalwrapped (c++ function)": [[25, "_CPPv4NK6galsim11Interpolant11xvalWrappedEdi"]], "galsim::interpolant::~interpolant (c++ function)": [[25, "_CPPv4N6galsim11InterpolantD0Ev"]], "galsim::lanczos (c++ class)": [[25, "_CPPv4N6galsim7LanczosE"]], "galsim::lanczos::lanczos (c++ function)": [[25, "_CPPv4N6galsim7Lanczos7LanczosEibRK8GSParams"]], "galsim::lanczos::conservesdc (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos11conservesDCEv"]], "galsim::lanczos::getn (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos4getNEv"]], "galsim::lanczos::ixrange (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos7ixrangeEv"]], "galsim::lanczos::makestr (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos7makeStrEv"]], "galsim::lanczos::urange (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos6urangeEv"]], "galsim::lanczos::uval (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos4uvalEd"]], "galsim::lanczos::xrange (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos6xrangeEv"]], "galsim::lanczos::xval (c++ function)": [[25, "_CPPv4NK6galsim7Lanczos4xvalEd"]], "galsim::lanczos::~lanczos (c++ function)": [[25, "_CPPv4N6galsim7LanczosD0Ev"]], "galsim::linear (c++ class)": [[25, "_CPPv4N6galsim6LinearE"]], "galsim::linear::linear (c++ function)": [[25, "_CPPv4N6galsim6Linear6LinearERK8GSParams"]], "galsim::linear::getnegativeflux (c++ function)": [[25, "_CPPv4NK6galsim6Linear15getNegativeFluxEv"]], "galsim::linear::getpositiveflux (c++ function)": [[25, "_CPPv4NK6galsim6Linear15getPositiveFluxEv"]], "galsim::linear::ixrange (c++ function)": [[25, "_CPPv4NK6galsim6Linear7ixrangeEv"]], "galsim::linear::makestr (c++ function)": [[25, "_CPPv4NK6galsim6Linear7makeStrEv"]], "galsim::linear::shoot (c++ function)": [[25, "_CPPv4NK6galsim6Linear5shootER11PhotonArray14UniformDeviate"]], "galsim::linear::urange (c++ function)": [[25, "_CPPv4NK6galsim6Linear6urangeEv"]], "galsim::linear::uval (c++ function)": [[25, "_CPPv4NK6galsim6Linear4uvalEd"]], "galsim::linear::xrange (c++ function)": [[25, "_CPPv4NK6galsim6Linear6xrangeEv"]], "galsim::linear::xval (c++ function)": [[25, "_CPPv4NK6galsim6Linear4xvalEd"]], "galsim::linear::~linear (c++ function)": [[25, "_CPPv4N6galsim6LinearD0Ev"]], "galsim::nearest (c++ class)": [[25, "_CPPv4N6galsim7NearestE"]], "galsim::nearest::nearest (c++ function)": [[25, "_CPPv4N6galsim7Nearest7NearestERK8GSParams"]], "galsim::nearest::getnegativeflux (c++ function)": [[25, "_CPPv4NK6galsim7Nearest15getNegativeFluxEv"]], "galsim::nearest::getpositiveflux (c++ function)": [[25, "_CPPv4NK6galsim7Nearest15getPositiveFluxEv"]], "galsim::nearest::ixrange (c++ function)": [[25, "_CPPv4NK6galsim7Nearest7ixrangeEv"]], "galsim::nearest::makestr (c++ function)": [[25, "_CPPv4NK6galsim7Nearest7makeStrEv"]], "galsim::nearest::shoot (c++ function)": [[25, "_CPPv4NK6galsim7Nearest5shootER11PhotonArray14UniformDeviate"]], "galsim::nearest::urange (c++ function)": [[25, "_CPPv4NK6galsim7Nearest6urangeEv"]], "galsim::nearest::uval (c++ function)": [[25, "_CPPv4NK6galsim7Nearest4uvalEd"]], "galsim::nearest::xrange (c++ function)": [[25, "_CPPv4NK6galsim7Nearest6xrangeEv"]], "galsim::nearest::xval (c++ function)": [[25, "_CPPv4NK6galsim7Nearest4xvalEd"]], "galsim::nearest::~nearest (c++ function)": [[25, "_CPPv4N6galsim7NearestD0Ev"]], "galsim::quintic (c++ class)": [[25, "_CPPv4N6galsim7QuinticE"]], "galsim::quintic::quintic (c++ function)": [[25, "_CPPv4N6galsim7Quintic7QuinticERK8GSParams"]], "galsim::quintic::getnegativeflux (c++ function)": [[25, "_CPPv4NK6galsim7Quintic15getNegativeFluxEv"]], "galsim::quintic::getpositiveflux (c++ function)": [[25, "_CPPv4NK6galsim7Quintic15getPositiveFluxEv"]], "galsim::quintic::ixrange (c++ function)": [[25, "_CPPv4NK6galsim7Quintic7ixrangeEv"]], "galsim::quintic::makestr (c++ function)": [[25, "_CPPv4NK6galsim7Quintic7makeStrEv"]], "galsim::quintic::urange (c++ function)": [[25, "_CPPv4NK6galsim7Quintic6urangeEv"]], "galsim::quintic::uval (c++ function)": [[25, "_CPPv4NK6galsim7Quintic4uvalEd"]], "galsim::quintic::xrange (c++ function)": [[25, "_CPPv4NK6galsim7Quintic6xrangeEv"]], "galsim::quintic::xval (c++ function)": [[25, "_CPPv4NK6galsim7Quintic4xvalEd"]], "galsim::quintic::~quintic (c++ function)": [[25, "_CPPv4N6galsim7QuinticD0Ev"]], "galsim::sincinterpolant (c++ class)": [[25, "_CPPv4N6galsim15SincInterpolantE"]], "galsim::sincinterpolant::sincinterpolant (c++ function)": [[25, "_CPPv4N6galsim15SincInterpolant15SincInterpolantERK8GSParams"]], "galsim::sincinterpolant::ixrange (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant7ixrangeEv"]], "galsim::sincinterpolant::makestr (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant7makeStrEv"]], "galsim::sincinterpolant::shoot (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant5shootER11PhotonArray14UniformDeviate"]], "galsim::sincinterpolant::urange (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant6urangeEv"]], "galsim::sincinterpolant::uval (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant4uvalEd"]], "galsim::sincinterpolant::xrange (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant6xrangeEv"]], "galsim::sincinterpolant::xval (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant4xvalEd"]], "galsim::sincinterpolant::xvalwrapped (c++ function)": [[25, "_CPPv4NK6galsim15SincInterpolant11xvalWrappedEdi"]], "galsim::sincinterpolant::~sincinterpolant (c++ function)": [[25, "_CPPv4N6galsim15SincInterpolantD0Ev"]], "galsim::table (c++ class)": [[25, "_CPPv4N6galsim5TableE"]], "galsim::table2d (c++ class)": [[25, "_CPPv4N6galsim7Table2DE"]], "galsim::table2d::table2d (c++ function)": [[25, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdii11interpolant"], [25, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPK11Interpolant"], [25, "_CPPv4N6galsim7Table2D7Table2DEPKdPKdPKdiiPKdPKdPKd"]], "galsim::table2d::gradient (c++ function)": [[25, "_CPPv4NK6galsim7Table2D8gradientEddRdRd"]], "galsim::table2d::gradientgrid (c++ function)": [[25, "_CPPv4NK6galsim7Table2D12gradientGridEPKdPKdPdPdii"]], "galsim::table2d::gradientmany (c++ function)": [[25, "_CPPv4NK6galsim7Table2D12gradientManyEPKdPKdPdPdi"]], "galsim::table2d::interpgrid (c++ function)": [[25, "_CPPv4NK6galsim7Table2D10interpGridEPKdPKdPdii"]], "galsim::table2d::interpmany (c++ function)": [[25, "_CPPv4NK6galsim7Table2D10interpManyEPKdPKdPdi"]], "galsim::table2d::interpolant (c++ enum)": [[25, "_CPPv4N6galsim7Table2D11interpolantE"]], "galsim::table2d::interpolant::ceil (c++ enumerator)": [[25, "_CPPv4N6galsim7Table2D11interpolant4ceilE"]], "galsim::table2d::interpolant::floor (c++ enumerator)": [[25, "_CPPv4N6galsim7Table2D11interpolant5floorE"]], "galsim::table2d::interpolant::gsinterp (c++ enumerator)": [[25, "_CPPv4N6galsim7Table2D11interpolant8gsinterpE"]], "galsim::table2d::interpolant::linear (c++ enumerator)": [[25, "_CPPv4N6galsim7Table2D11interpolant6linearE"]], "galsim::table2d::interpolant::nearest (c++ enumerator)": [[25, "_CPPv4N6galsim7Table2D11interpolant7nearestE"]], "galsim::table2d::interpolant::spline (c++ enumerator)": [[25, "_CPPv4N6galsim7Table2D11interpolant6splineE"]], "galsim::table2d::lookup (c++ function)": [[25, "_CPPv4NK6galsim7Table2D6lookupEdd"]], "galsim::table::table (c++ function)": [[25, "_CPPv4N6galsim5Table5TableEPKdPKdi11interpolant"], [25, "_CPPv4N6galsim5Table5TableEPKdPKdiPK11Interpolant"]], "galsim::table::argmax (c++ function)": [[25, "_CPPv4NK6galsim5Table6argMaxEv"]], "galsim::table::argmin (c++ function)": [[25, "_CPPv4NK6galsim5Table6argMinEv"]], "galsim::table::integrate (c++ function)": [[25, "_CPPv4NK6galsim5Table9integrateEdd"]], "galsim::table::integrateproduct (c++ function)": [[25, "_CPPv4NK6galsim5Table16integrateProductERK5Tableddd"]], "galsim::table::interpmany (c++ function)": [[25, "_CPPv4NK6galsim5Table10interpManyEPKdPdi"]], "galsim::table::interpolant (c++ enum)": [[25, "_CPPv4N6galsim5Table11interpolantE"]], "galsim::table::interpolant::ceil (c++ enumerator)": [[25, "_CPPv4N6galsim5Table11interpolant4ceilE"]], "galsim::table::interpolant::floor (c++ enumerator)": [[25, "_CPPv4N6galsim5Table11interpolant5floorE"]], "galsim::table::interpolant::gsinterp (c++ enumerator)": [[25, "_CPPv4N6galsim5Table11interpolant8gsinterpE"]], "galsim::table::interpolant::linear (c++ enumerator)": [[25, "_CPPv4N6galsim5Table11interpolant6linearE"]], "galsim::table::interpolant::nearest (c++ enumerator)": [[25, "_CPPv4N6galsim5Table11interpolant7nearestE"]], "galsim::table::interpolant::spline (c++ enumerator)": [[25, "_CPPv4N6galsim5Table11interpolant6splineE"]], "galsim::table::lookup (c++ function)": [[25, "_CPPv4NK6galsim5Table6lookupEd"]], "galsim::table::operator() (c++ function)": [[25, "_CPPv4NK6galsim5TableclEd"]], "galsim::table::size (c++ function)": [[25, "_CPPv4NK6galsim5Table4sizeEv"]], "galsim::tablebuilder (c++ class)": [[25, "_CPPv4N6galsim12TableBuilderE"]], "galsim::tablebuilder::tablebuilder (c++ function)": [[25, "_CPPv4N6galsim12TableBuilder12TableBuilderE11interpolant"], [25, "_CPPv4N6galsim12TableBuilder12TableBuilderEPK11Interpolant"]], "galsim::tablebuilder::addentry (c++ function)": [[25, "_CPPv4N6galsim12TableBuilder8addEntryEdd"]], "galsim::tablebuilder::finalize (c++ function)": [[25, "_CPPv4N6galsim12TableBuilder8finalizeEv"]], "galsim::tablebuilder::finalized (c++ function)": [[25, "_CPPv4NK6galsim12TableBuilder9finalizedEv"]], "galsim::tablebuilder::lookup (c++ function)": [[25, "_CPPv4NK6galsim12TableBuilder6lookupEd"]], "galsim::getompthreads (c++ function)": [[26, "_CPPv4N6galsim13GetOMPThreadsEv"]], "galsim::setompthreads (c++ function)": [[26, "_CPPv4N6galsim13SetOMPThreadsEi"]], "galsim::solve (c++ class)": [[26, "_CPPv4I00EN6galsim5SolveE"]], "galsim::solve::solve (c++ function)": [[26, "_CPPv4N6galsim5Solve5SolveERK1F1T1T"]], "galsim::solve::bisect (c++ function)": [[26, "_CPPv4NK6galsim5Solve6bisectEv"]], "galsim::solve::bracket (c++ function)": [[26, "_CPPv4N6galsim5Solve7bracketEv"]], "galsim::solve::bracket1 (c++ function)": [[26, "_CPPv4N6galsim5Solve8bracket1ER1TR1TR1TR1T"]], "galsim::solve::bracket1withlimit (c++ function)": [[26, "_CPPv4N6galsim5Solve17bracket1WithLimitER1TR1TR1TR1TR1T"]], "galsim::solve::bracketlower (c++ function)": [[26, "_CPPv4N6galsim5Solve12bracketLowerEv"]], "galsim::solve::bracketlowerwithlimit (c++ function)": [[26, "_CPPv4N6galsim5Solve21bracketLowerWithLimitE1T"]], "galsim::solve::bracketupper (c++ function)": [[26, "_CPPv4N6galsim5Solve12bracketUpperEv"]], "galsim::solve::bracketupperwithlimit (c++ function)": [[26, "_CPPv4N6galsim5Solve21bracketUpperWithLimitE1T"]], "galsim::solve::evaluatebounds (c++ function)": [[26, "_CPPv4NK6galsim5Solve14evaluateBoundsEv"]], "galsim::solve::getlowerbound (c++ function)": [[26, "_CPPv4NK6galsim5Solve13getLowerBoundEv"]], "galsim::solve::getupperbound (c++ function)": [[26, "_CPPv4NK6galsim5Solve13getUpperBoundEv"]], "galsim::solve::getxtolerance (c++ function)": [[26, "_CPPv4NK6galsim5Solve13getXToleranceEv"]], "galsim::solve::root (c++ function)": [[26, "_CPPv4NK6galsim5Solve4rootEv"]], "galsim::solve::setbounds (c++ function)": [[26, "_CPPv4N6galsim5Solve9setBoundsE1T1T"]], "galsim::solve::setmaxsteps (c++ function)": [[26, "_CPPv4N6galsim5Solve11setMaxStepsEi"]], "galsim::solve::setmethod (c++ function)": [[26, "_CPPv4N6galsim5Solve9setMethodE6Method"]], "galsim::solve::setxtolerance (c++ function)": [[26, "_CPPv4N6galsim5Solve13setXToleranceE1T"]], "galsim::solve::zbrent (c++ function)": [[26, "_CPPv4NK6galsim5Solve6zbrentEv"]], "galsim::integ::intregion (c++ struct)": [[26, "_CPPv4I0EN6galsim5integ9IntRegionE"]], "galsim::integ::intregion::intregion (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion9IntRegionEK1TK1TPNSt7ostreamEPNSt3mapI1T1TEE"]], "galsim::integ::intregion::addsplit (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion8addSplitEK1T"]], "galsim::integ::intregion::bisect (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion6bisectEv"]], "galsim::integ::intregion::dbgout (c++ member)": [[26, "_CPPv4N6galsim5integ9IntRegion6dbgoutE"]], "galsim::integ::intregion::findzerocrossings (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion17findZeroCrossingsEv"]], "galsim::integ::intregion::fxmap (c++ member)": [[26, "_CPPv4N6galsim5integ9IntRegion5fxmapE"]], "galsim::integ::intregion::getarea (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegion7getAreaEv"]], "galsim::integ::intregion::geterr (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegion6getErrEv"]], "galsim::integ::intregion::getnsplit (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegion9getNSplitEv"]], "galsim::integ::intregion::left (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegion4leftEv"]], "galsim::integ::intregion::operator< (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegionltERK9IntRegionI1TE"]], "galsim::integ::intregion::operator> (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegiongtERK9IntRegionI1TE"]], "galsim::integ::intregion::right (c++ function)": [[26, "_CPPv4NK6galsim5integ9IntRegion5rightEv"]], "galsim::integ::intregion::setarea (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion7setAreaERK1TRK1T"]], "galsim::integ::intregion::subdivide (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion9subDivideERNSt6vectorI9IntRegionI1TEEE"]], "galsim::integ::intregion::usefxmap (c++ function)": [[26, "_CPPv4N6galsim5integ9IntRegion8useFXMapEv"]], "galsim::integ::int1d (c++ function)": [[26, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFR9IntRegionIdEKdKd"], [26, "_CPPv4I0EN6galsim5integ5int1dEdRK2UFddKdKd"]], "galsim::integ::int2d (c++ function)": [[26, "_CPPv4I00EN6galsim5integ5int2dEdRK2BFR9IntRegionIdERK4YREGKdKd"], [26, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFR9IntRegionIdER9IntRegionIdEKdKd"], [26, "_CPPv4I0EN6galsim5integ5int2dEdRK2BFddddKdKd"]], "galsim::integ::int3d (c++ function)": [[26, "_CPPv4I000EN6galsim5integ5int3dEdRK2TFR9IntRegionIdERK4YREGRK4ZREGKdKd"], [26, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFR9IntRegionIdER9IntRegionIdER9IntRegionIdEKdKd"], [26, "_CPPv4I0EN6galsim5integ5int3dEdRK2TFddddddKdKd"]], "galsim::math::ci (c++ function)": [[26, "_CPPv4N6galsim4math2CiEd"]], "galsim::math::horner (c++ function)": [[26, "_CPPv4N6galsim4math6HornerEPKdKiPKdKiPd"]], "galsim::math::horner2d (c++ function)": [[26, "_CPPv4N6galsim4math8Horner2DEPKdPKdKiPKdKiKiPdPd"]], "galsim::math::si (c++ function)": [[26, "_CPPv4N6galsim4math2SiEd"]], "galsim::math::cyl_bessel_i (c++ function)": [[26, "_CPPv4N6galsim4math12cyl_bessel_iEdd"]], "galsim::math::cyl_bessel_j (c++ function)": [[26, "_CPPv4N6galsim4math12cyl_bessel_jEdd"]], "galsim::math::cyl_bessel_k (c++ function)": [[26, "_CPPv4N6galsim4math12cyl_bessel_kEdd"]], "galsim::math::cyl_bessel_y (c++ function)": [[26, "_CPPv4N6galsim4math12cyl_bessel_yEdd"]], "galsim::math::gamma_p (c++ function)": [[26, "_CPPv4N6galsim4math7gamma_pEdd"]], "galsim::math::getbesselroot (c++ function)": [[26, "_CPPv4N6galsim4math13getBesselRootEdi"]], "galsim::math::getbesselroot0 (c++ function)": [[26, "_CPPv4N6galsim4math14getBesselRoot0Ei"]], "galsim::math::hankel_inf (c++ function)": [[26, "_CPPv4N6galsim4math10hankel_infEKNSt8functionIFddEEEddddi"]], "galsim::math::hankel_trunc (c++ function)": [[26, "_CPPv4N6galsim4math12hankel_truncEKNSt8functionIFddEEEdddddi"]], "galsim::math::isnan (c++ function)": [[26, "_CPPv4I0EN6galsim4math5isNanEb1T"]], "galsim::math::j0 (c++ function)": [[26, "_CPPv4N6galsim4math2j0Ed"]], "galsim::math::j1 (c++ function)": [[26, "_CPPv4N6galsim4math2j1Ed"]], "galsim::math::sinc (c++ function)": [[26, "_CPPv4N6galsim4math4sincEd"]], "galsim::math::sincos (c++ function)": [[26, "_CPPv4N6galsim4math6sincosEdRdRd"]], "galsim::basedeviate (c++ class)": [[27, "_CPPv4N6galsim11BaseDeviateE"]], "galsim::basedeviate::basedeviate (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate11BaseDeviateEPKc"], [27, "_CPPv4N6galsim11BaseDeviate11BaseDeviateERK11BaseDeviate"], [27, "_CPPv4N6galsim11BaseDeviate11BaseDeviateEl"]], "galsim::basedeviate::addgenerate (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate11addGenerateExPd"]], "galsim::basedeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate10clearCacheEv"]], "galsim::basedeviate::discard (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate7discardEi"]], "galsim::basedeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate9duplicateEv"]], "galsim::basedeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate13duplicate_ptrEv"]], "galsim::basedeviate::generate (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate8generateExPd"]], "galsim::basedeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate9generate1Ev"]], "galsim::basedeviate::operator() (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviateclEv"]], "galsim::basedeviate::raw (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate3rawEv"]], "galsim::basedeviate::repr (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate4reprEv"]], "galsim::basedeviate::reset (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate5resetERK11BaseDeviate"], [27, "_CPPv4N6galsim11BaseDeviate5resetEl"]], "galsim::basedeviate::seed (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate4seedEl"]], "galsim::basedeviate::serialize (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate9serializeEv"]], "galsim::basedeviate::str (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviate3strEv"]], "galsim::basedeviate::~basedeviate (c++ function)": [[27, "_CPPv4N6galsim11BaseDeviateD0Ev"]], "galsim::binomialdeviate (c++ class)": [[27, "_CPPv4N6galsim15BinomialDeviateE"]], "galsim::binomialdeviate::binomialdeviate (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateEPKcid"], [27, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK11BaseDeviateid"], [27, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateERK15BinomialDeviate"], [27, "_CPPv4N6galsim15BinomialDeviate15BinomialDeviateElid"]], "galsim::binomialdeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate10clearCacheEv"]], "galsim::binomialdeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate9duplicateEv"]], "galsim::binomialdeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate13duplicate_ptrEv"]], "galsim::binomialdeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate9generate1Ev"]], "galsim::binomialdeviate::getn (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate4getNEv"]], "galsim::binomialdeviate::getp (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate4getPEv"]], "galsim::binomialdeviate::setn (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate4setNEi"]], "galsim::binomialdeviate::setp (c++ function)": [[27, "_CPPv4N6galsim15BinomialDeviate4setPEd"]], "galsim::chi2deviate (c++ class)": [[27, "_CPPv4N6galsim11Chi2DeviateE"]], "galsim::chi2deviate::chi2deviate (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEPKcd"], [27, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11BaseDeviated"], [27, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateERK11Chi2Deviate"], [27, "_CPPv4N6galsim11Chi2Deviate11Chi2DeviateEld"]], "galsim::chi2deviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate10clearCacheEv"]], "galsim::chi2deviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate9duplicateEv"]], "galsim::chi2deviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate13duplicate_ptrEv"]], "galsim::chi2deviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate9generate1Ev"]], "galsim::chi2deviate::getn (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate4getNEv"]], "galsim::chi2deviate::setn (c++ function)": [[27, "_CPPv4N6galsim11Chi2Deviate4setNEd"]], "galsim::gammadeviate (c++ class)": [[27, "_CPPv4N6galsim12GammaDeviateE"]], "galsim::gammadeviate::gammadeviate (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEPKcdd"], [27, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK11BaseDeviatedd"], [27, "_CPPv4N6galsim12GammaDeviate12GammaDeviateERK12GammaDeviate"], [27, "_CPPv4N6galsim12GammaDeviate12GammaDeviateEldd"]], "galsim::gammadeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate10clearCacheEv"]], "galsim::gammadeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate9duplicateEv"]], "galsim::gammadeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate13duplicate_ptrEv"]], "galsim::gammadeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate9generate1Ev"]], "galsim::gammadeviate::getk (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate4getKEv"]], "galsim::gammadeviate::gettheta (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate8getThetaEv"]], "galsim::gammadeviate::setk (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate4setKEd"]], "galsim::gammadeviate::settheta (c++ function)": [[27, "_CPPv4N6galsim12GammaDeviate8setThetaEd"]], "galsim::gaussiandeviate (c++ class)": [[27, "_CPPv4N6galsim15GaussianDeviateE"]], "galsim::gaussiandeviate::gaussiandeviate (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEPKcdd"], [27, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK11BaseDeviatedd"], [27, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateERK15GaussianDeviate"], [27, "_CPPv4N6galsim15GaussianDeviate15GaussianDeviateEldd"]], "galsim::gaussiandeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate10clearCacheEv"]], "galsim::gaussiandeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate9duplicateEv"]], "galsim::gaussiandeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate13duplicate_ptrEv"]], "galsim::gaussiandeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate9generate1Ev"]], "galsim::gaussiandeviate::generatefromvariance (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate20generateFromVarianceExPd"]], "galsim::gaussiandeviate::getmean (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate7getMeanEv"]], "galsim::gaussiandeviate::getsigma (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate8getSigmaEv"]], "galsim::gaussiandeviate::setmean (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate7setMeanEd"]], "galsim::gaussiandeviate::setsigma (c++ function)": [[27, "_CPPv4N6galsim15GaussianDeviate8setSigmaEd"]], "galsim::poissondeviate (c++ class)": [[27, "_CPPv4N6galsim14PoissonDeviateE"]], "galsim::poissondeviate::poissondeviate (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEPKcd"], [27, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK11BaseDeviated"], [27, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateERK14PoissonDeviate"], [27, "_CPPv4N6galsim14PoissonDeviate14PoissonDeviateEld"]], "galsim::poissondeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate10clearCacheEv"]], "galsim::poissondeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate9duplicateEv"]], "galsim::poissondeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate13duplicate_ptrEv"]], "galsim::poissondeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate9generate1Ev"]], "galsim::poissondeviate::generatefromexpectation (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate23generateFromExpectationExPd"]], "galsim::poissondeviate::getmean (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate7getMeanEv"]], "galsim::poissondeviate::setmean (c++ function)": [[27, "_CPPv4N6galsim14PoissonDeviate7setMeanEd"]], "galsim::uniformdeviate (c++ class)": [[27, "_CPPv4N6galsim14UniformDeviateE"]], "galsim::uniformdeviate::uniformdeviate (c++ function)": [[27, "_CPPv4N6galsim14UniformDeviate14UniformDeviateEPKc"], [27, "_CPPv4N6galsim14UniformDeviate14UniformDeviateERK11BaseDeviate"], [27, "_CPPv4N6galsim14UniformDeviate14UniformDeviateERK14UniformDeviate"], [27, "_CPPv4N6galsim14UniformDeviate14UniformDeviateEl"]], "galsim::uniformdeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim14UniformDeviate10clearCacheEv"]], "galsim::uniformdeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim14UniformDeviate9duplicateEv"]], "galsim::uniformdeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim14UniformDeviate13duplicate_ptrEv"]], "galsim::uniformdeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim14UniformDeviate9generate1Ev"]], "galsim::weibulldeviate (c++ class)": [[27, "_CPPv4N6galsim14WeibullDeviateE"]], "galsim::weibulldeviate::weibulldeviate (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEPKcdd"], [27, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK11BaseDeviatedd"], [27, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateERK14WeibullDeviate"], [27, "_CPPv4N6galsim14WeibullDeviate14WeibullDeviateEldd"]], "galsim::weibulldeviate::clearcache (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate10clearCacheEv"]], "galsim::weibulldeviate::duplicate (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate9duplicateEv"]], "galsim::weibulldeviate::duplicate_ptr (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate13duplicate_ptrEv"]], "galsim::weibulldeviate::generate1 (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate9generate1Ev"]], "galsim::weibulldeviate::geta (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate4getAEv"]], "galsim::weibulldeviate::getb (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate4getBEv"]], "galsim::weibulldeviate::seta (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate4setAEd"]], "galsim::weibulldeviate::setb (c++ function)": [[27, "_CPPv4N6galsim14WeibullDeviate4setBEd"]], "galsim::calculatecovariancematrix (c++ function)": [[27, "_CPPv4N6galsim25calculateCovarianceMatrixER9ImageViewIdERK9SBProfileRK6BoundsIiEd"]], "galsim::applycd (c++ function)": [[28, "_CPPv4N6galsim7ApplyCDEiPdPdPKd"]], "galsim::photonarray (c++ class)": [[28, "_CPPv4N6galsim11PhotonArrayE"]], "galsim::photonarray::photonarray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray11PhotonArrayE6size_tPdPdPdPdPdPdb"], [28, "_CPPv4N6galsim11PhotonArray11PhotonArrayEi"]], "galsim::photonarray::addto (c++ function)": [[28, "_CPPv4I0ENK6galsim11PhotonArray5addToEd9ImageViewI1TE"]], "galsim::photonarray::assignat (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray8assignAtEiRK11PhotonArray"]], "galsim::photonarray::convolve (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray8convolveERK11PhotonArray11BaseDeviate"]], "galsim::photonarray::convolveshuffle (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray15convolveShuffleERK11PhotonArray11BaseDeviate"]], "galsim::photonarray::getdxdz (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray7getDXDZEi"]], "galsim::photonarray::getdxdzarray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray12getDXDZArrayEv"], [28, "_CPPv4NK6galsim11PhotonArray12getDXDZArrayEv"]], "galsim::photonarray::getdydz (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray7getDYDZEi"]], "galsim::photonarray::getdydzarray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray12getDYDZArrayEv"], [28, "_CPPv4NK6galsim11PhotonArray12getDYDZArrayEv"]], "galsim::photonarray::getflux (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray7getFluxEi"]], "galsim::photonarray::getfluxarray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray12getFluxArrayEv"], [28, "_CPPv4NK6galsim11PhotonArray12getFluxArrayEv"]], "galsim::photonarray::gettotalflux (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray12getTotalFluxEv"]], "galsim::photonarray::getwavelength (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray13getWavelengthEi"]], "galsim::photonarray::getwavelengtharray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray18getWavelengthArrayEv"], [28, "_CPPv4NK6galsim11PhotonArray18getWavelengthArrayEv"]], "galsim::photonarray::getx (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray4getXEi"]], "galsim::photonarray::getxarray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray9getXArrayEv"], [28, "_CPPv4NK6galsim11PhotonArray9getXArrayEv"]], "galsim::photonarray::gety (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray4getYEi"]], "galsim::photonarray::getyarray (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray9getYArrayEv"], [28, "_CPPv4NK6galsim11PhotonArray9getYArrayEv"]], "galsim::photonarray::hasallocatedangles (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray18hasAllocatedAnglesEv"]], "galsim::photonarray::hasallocatedwavelengths (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray23hasAllocatedWavelengthsEv"]], "galsim::photonarray::iscorrelated (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray12isCorrelatedEv"]], "galsim::photonarray::scaleflux (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray9scaleFluxEd"]], "galsim::photonarray::scalexy (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray7scaleXYEd"]], "galsim::photonarray::setcorrelated (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray13setCorrelatedEb"]], "galsim::photonarray::setfrom (c++ function)": [[28, "_CPPv4I0EN6galsim11PhotonArray7setFromEiRK9BaseImageI1TEd11BaseDeviate"]], "galsim::photonarray::setphoton (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray9setPhotonEiddd"]], "galsim::photonarray::settotalflux (c++ function)": [[28, "_CPPv4N6galsim11PhotonArray12setTotalFluxEd"]], "galsim::photonarray::size (c++ function)": [[28, "_CPPv4NK6galsim11PhotonArray4sizeEv"]], "galsim::silicon (c++ class)": [[28, "_CPPv4N6galsim7SiliconE"]], "galsim::silicon::silicon (c++ function)": [[28, "_CPPv4N6galsim7Silicon7SiliconEidiiidddPdRK5Table8PositionIdERK5Tableb"]], "galsim::silicon::accumulate (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon10accumulateEdRK11PhotonArrayii11BaseDeviate9ImageViewI1TE"]], "galsim::silicon::adddelta (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon8addDeltaEv9ImageViewI1TE"]], "galsim::silicon::addtreeringdistortions (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon22addTreeRingDistortionsEv9ImageViewI1TE8PositionIiE"]], "galsim::silicon::calculateconversiondepth (c++ function)": [[28, "_CPPv4NK6galsim7Silicon24calculateConversionDepthEbPKdPKdbPKdPKdid"]], "galsim::silicon::calculatetreeringdistortion (c++ function)": [[28, "_CPPv4N6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiEiiii"], [28, "_CPPv4NK6galsim7Silicon27calculateTreeRingDistortionEii8PositionIiER7Polygon"]], "galsim::silicon::fillwithpixelareas (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon18fillWithPixelAreasEv9ImageViewI1TE8PositionIiEb"]], "galsim::silicon::finalize (c++ function)": [[28, "_CPPv4N6galsim7Silicon8finalizeEv"]], "galsim::silicon::initialize (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon10initializeEv9ImageViewI1TE8PositionIiE"]], "galsim::silicon::insidepixel (c++ function)": [[28, "_CPPv4NK6galsim7Silicon11insidePixelEiidddR6BoundsIiEPbiP6BoundsIdEP6BoundsIdEP8PositionIfEP8PositionIfEP8PositionIdE"]], "galsim::silicon::pixelarea (c++ function)": [[28, "_CPPv4NK6galsim7Silicon9pixelAreaEiiii"]], "galsim::silicon::scaleboundstopoly (c++ function)": [[28, "_CPPv4NK6galsim7Silicon17scaleBoundsToPolyEiiiiRK7PolygonR7Polygond"]], "galsim::silicon::subtractdelta (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon13subtractDeltaEv9ImageViewI1TE"]], "galsim::silicon::update (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon6updateEv9ImageViewI1TE"]], "galsim::silicon::updatepixeldistortions (c++ function)": [[28, "_CPPv4I0EN6galsim7Silicon22updatePixelDistortionsEv9ImageViewI1TE"]], "galsim::silicon::~silicon (c++ function)": [[28, "_CPPv4N6galsim7SiliconD0Ev"]], "galsim::gsparams (c++ struct)": [[29, "_CPPv4N6galsim8GSParamsE"]], "galsim::gsparams::gsparams (c++ function)": [[29, "_CPPv4N6galsim8GSParams8GSParamsEiiddddddddddd"], [29, "_CPPv4N6galsim8GSParams8GSParamsEv"]], "galsim::gsparams::folding_threshold (c++ member)": [[29, "_CPPv4N6galsim8GSParams17folding_thresholdE"]], "galsim::gsparams::integration_abserr (c++ member)": [[29, "_CPPv4N6galsim8GSParams18integration_abserrE"]], "galsim::gsparams::integration_relerr (c++ member)": [[29, "_CPPv4N6galsim8GSParams18integration_relerrE"]], "galsim::gsparams::kvalue_accuracy (c++ member)": [[29, "_CPPv4N6galsim8GSParams15kvalue_accuracyE"]], "galsim::gsparams::maximum_fft_size (c++ member)": [[29, "_CPPv4N6galsim8GSParams16maximum_fft_sizeE"]], "galsim::gsparams::maxk_threshold (c++ member)": [[29, "_CPPv4N6galsim8GSParams14maxk_thresholdE"]], "galsim::gsparams::minimum_fft_size (c++ member)": [[29, "_CPPv4N6galsim8GSParams16minimum_fft_sizeE"]], "galsim::gsparams::operator< (c++ function)": [[29, "_CPPv4NK6galsim8GSParamsltERK8GSParams"]], "galsim::gsparams::operator== (c++ function)": [[29, "_CPPv4NK6galsim8GSParamseqERK8GSParams"]], "galsim::gsparams::realspace_abserr (c++ member)": [[29, "_CPPv4N6galsim8GSParams16realspace_abserrE"]], "galsim::gsparams::realspace_relerr (c++ member)": [[29, "_CPPv4N6galsim8GSParams16realspace_relerrE"]], "galsim::gsparams::shoot_accuracy (c++ member)": [[29, "_CPPv4N6galsim8GSParams14shoot_accuracyE"]], "galsim::gsparams::stepk_minimum_hlr (c++ member)": [[29, "_CPPv4N6galsim8GSParams17stepk_minimum_hlrE"]], "galsim::gsparams::table_spacing (c++ member)": [[29, "_CPPv4N6galsim8GSParams13table_spacingE"]], "galsim::gsparams::xvalue_accuracy (c++ member)": [[29, "_CPPv4N6galsim8GSParams15xvalue_accuracyE"]], "galsim::sbadd (c++ class)": [[29, "_CPPv4N6galsim5SBAddE"]], "galsim::sbadd::sbadd (c++ function)": [[29, "_CPPv4N6galsim5SBAdd5SBAddERK5SBAdd"], [29, "_CPPv4N6galsim5SBAdd5SBAddERKNSt4listI9SBProfileEERK8GSParams"]], "galsim::sbadd::getobjs (c++ function)": [[29, "_CPPv4NK6galsim5SBAdd7getObjsEv"]], "galsim::sbadd::~sbadd (c++ function)": [[29, "_CPPv4N6galsim5SBAddD0Ev"]], "galsim::sbairy (c++ class)": [[29, "_CPPv4N6galsim6SBAiryE"]], "galsim::sbairy::sbairy (c++ function)": [[29, "_CPPv4N6galsim6SBAiry6SBAiryERK6SBAiry"], [29, "_CPPv4N6galsim6SBAiry6SBAiryEdddRK8GSParams"]], "galsim::sbairy::getlamoverd (c++ function)": [[29, "_CPPv4NK6galsim6SBAiry11getLamOverDEv"]], "galsim::sbairy::getobscuration (c++ function)": [[29, "_CPPv4NK6galsim6SBAiry14getObscurationEv"]], "galsim::sbairy::~sbairy (c++ function)": [[29, "_CPPv4N6galsim6SBAiryD0Ev"]], "galsim::sbautoconvolve (c++ class)": [[29, "_CPPv4N6galsim14SBAutoConvolveE"]], "galsim::sbautoconvolve::sbautoconvolve (c++ function)": [[29, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK14SBAutoConvolve"], [29, "_CPPv4N6galsim14SBAutoConvolve14SBAutoConvolveERK9SBProfilebRK8GSParams"]], "galsim::sbautoconvolve::getobj (c++ function)": [[29, "_CPPv4NK6galsim14SBAutoConvolve6getObjEv"]], "galsim::sbautoconvolve::isrealspace (c++ function)": [[29, "_CPPv4NK6galsim14SBAutoConvolve11isRealSpaceEv"]], "galsim::sbautoconvolve::~sbautoconvolve (c++ function)": [[29, "_CPPv4N6galsim14SBAutoConvolveD0Ev"]], "galsim::sbautocorrelate (c++ class)": [[29, "_CPPv4N6galsim15SBAutoCorrelateE"]], "galsim::sbautocorrelate::sbautocorrelate (c++ function)": [[29, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK15SBAutoCorrelate"], [29, "_CPPv4N6galsim15SBAutoCorrelate15SBAutoCorrelateERK9SBProfilebRK8GSParams"]], "galsim::sbautocorrelate::getobj (c++ function)": [[29, "_CPPv4NK6galsim15SBAutoCorrelate6getObjEv"]], "galsim::sbautocorrelate::isrealspace (c++ function)": [[29, "_CPPv4NK6galsim15SBAutoCorrelate11isRealSpaceEv"]], "galsim::sbautocorrelate::~sbautocorrelate (c++ function)": [[29, "_CPPv4N6galsim15SBAutoCorrelateD0Ev"]], "galsim::sbbox (c++ class)": [[29, "_CPPv4N6galsim5SBBoxE"]], "galsim::sbbox::sbbox (c++ function)": [[29, "_CPPv4N6galsim5SBBox5SBBoxERK5SBBox"], [29, "_CPPv4N6galsim5SBBox5SBBoxEdddRK8GSParams"]], "galsim::sbbox::getheight (c++ function)": [[29, "_CPPv4NK6galsim5SBBox9getHeightEv"]], "galsim::sbbox::getwidth (c++ function)": [[29, "_CPPv4NK6galsim5SBBox8getWidthEv"]], "galsim::sbbox::~sbbox (c++ function)": [[29, "_CPPv4N6galsim5SBBoxD0Ev"]], "galsim::sbconvolve (c++ class)": [[29, "_CPPv4N6galsim10SBConvolveE"]], "galsim::sbconvolve::sbconvolve (c++ function)": [[29, "_CPPv4N6galsim10SBConvolve10SBConvolveERK10SBConvolve"], [29, "_CPPv4N6galsim10SBConvolve10SBConvolveERKNSt4listI9SBProfileEEbRK8GSParams"]], "galsim::sbconvolve::getobjs (c++ function)": [[29, "_CPPv4NK6galsim10SBConvolve7getObjsEv"]], "galsim::sbconvolve::isrealspace (c++ function)": [[29, "_CPPv4NK6galsim10SBConvolve11isRealSpaceEv"]], "galsim::sbconvolve::~sbconvolve (c++ function)": [[29, "_CPPv4N6galsim10SBConvolveD0Ev"]], "galsim::sbdeconvolve (c++ class)": [[29, "_CPPv4N6galsim12SBDeconvolveE"]], "galsim::sbdeconvolve::sbdeconvolve (c++ function)": [[29, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK12SBDeconvolve"], [29, "_CPPv4N6galsim12SBDeconvolve12SBDeconvolveERK9SBProfileRK8GSParams"]], "galsim::sbdeconvolve::getobj (c++ function)": [[29, "_CPPv4NK6galsim12SBDeconvolve6getObjEv"]], "galsim::sbdeconvolve::~sbdeconvolve (c++ function)": [[29, "_CPPv4N6galsim12SBDeconvolveD0Ev"]], "galsim::sbdeltafunction (c++ class)": [[29, "_CPPv4N6galsim15SBDeltaFunctionE"]], "galsim::sbdeltafunction::sbdeltafunction (c++ function)": [[29, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionERK15SBDeltaFunction"], [29, "_CPPv4N6galsim15SBDeltaFunction15SBDeltaFunctionEdRK8GSParams"]], "galsim::sbdeltafunction::~sbdeltafunction (c++ function)": [[29, "_CPPv4N6galsim15SBDeltaFunctionD0Ev"]], "galsim::sbexponential (c++ class)": [[29, "_CPPv4N6galsim13SBExponentialE"]], "galsim::sbexponential::sbexponential (c++ function)": [[29, "_CPPv4N6galsim13SBExponential13SBExponentialERK13SBExponential"], [29, "_CPPv4N6galsim13SBExponential13SBExponentialEddRK8GSParams"]], "galsim::sbexponential::getscaleradius (c++ function)": [[29, "_CPPv4NK6galsim13SBExponential14getScaleRadiusEv"]], "galsim::sbexponential::~sbexponential (c++ function)": [[29, "_CPPv4N6galsim13SBExponentialD0Ev"]], "galsim::sbfouriersqrt (c++ class)": [[29, "_CPPv4N6galsim13SBFourierSqrtE"]], "galsim::sbfouriersqrt::sbfouriersqrt (c++ function)": [[29, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK13SBFourierSqrt"], [29, "_CPPv4N6galsim13SBFourierSqrt13SBFourierSqrtERK9SBProfileRK8GSParams"]], "galsim::sbfouriersqrt::getobj (c++ function)": [[29, "_CPPv4NK6galsim13SBFourierSqrt6getObjEv"]], "galsim::sbfouriersqrt::~sbfouriersqrt (c++ function)": [[29, "_CPPv4N6galsim13SBFourierSqrtD0Ev"]], "galsim::sbgaussian (c++ class)": [[29, "_CPPv4N6galsim10SBGaussianE"]], "galsim::sbgaussian::sbgaussian (c++ function)": [[29, "_CPPv4N6galsim10SBGaussian10SBGaussianERK10SBGaussian"], [29, "_CPPv4N6galsim10SBGaussian10SBGaussianEddRK8GSParams"]], "galsim::sbgaussian::getsigma (c++ function)": [[29, "_CPPv4NK6galsim10SBGaussian8getSigmaEv"]], "galsim::sbgaussian::~sbgaussian (c++ function)": [[29, "_CPPv4N6galsim10SBGaussianD0Ev"]], "galsim::sbinclinedexponential (c++ class)": [[29, "_CPPv4N6galsim21SBInclinedExponentialE"]], "galsim::sbinclinedexponential::sbinclinedexponential (c++ function)": [[29, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialERK21SBInclinedExponential"], [29, "_CPPv4N6galsim21SBInclinedExponential21SBInclinedExponentialEddddRK8GSParams"]], "galsim::sbinclinedexponential::getinclination (c++ function)": [[29, "_CPPv4NK6galsim21SBInclinedExponential14getInclinationEv"]], "galsim::sbinclinedexponential::getscaleheight (c++ function)": [[29, "_CPPv4NK6galsim21SBInclinedExponential14getScaleHeightEv"]], "galsim::sbinclinedexponential::getscaleradius (c++ function)": [[29, "_CPPv4NK6galsim21SBInclinedExponential14getScaleRadiusEv"]], "galsim::sbinclinedexponential::~sbinclinedexponential (c++ function)": [[29, "_CPPv4N6galsim21SBInclinedExponentialD0Ev"]], "galsim::sbinclinedsersic (c++ class)": [[29, "_CPPv4N6galsim16SBInclinedSersicE"]], "galsim::sbinclinedsersic::sbinclinedsersic (c++ function)": [[29, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicERK16SBInclinedSersic"], [29, "_CPPv4N6galsim16SBInclinedSersic16SBInclinedSersicEddddddRK8GSParams"]], "galsim::sbinclinedsersic::gethalflightradius (c++ function)": [[29, "_CPPv4NK6galsim16SBInclinedSersic18getHalfLightRadiusEv"]], "galsim::sbinclinedsersic::getinclination (c++ function)": [[29, "_CPPv4NK6galsim16SBInclinedSersic14getInclinationEv"]], "galsim::sbinclinedsersic::getn (c++ function)": [[29, "_CPPv4NK6galsim16SBInclinedSersic4getNEv"]], "galsim::sbinclinedsersic::getscaleheight (c++ function)": [[29, "_CPPv4NK6galsim16SBInclinedSersic14getScaleHeightEv"]], "galsim::sbinclinedsersic::getscaleradius (c++ function)": [[29, "_CPPv4NK6galsim16SBInclinedSersic14getScaleRadiusEv"]], "galsim::sbinclinedsersic::gettrunc (c++ function)": [[29, "_CPPv4NK6galsim16SBInclinedSersic8getTruncEv"]], "galsim::sbinclinedsersic::~sbinclinedsersic (c++ function)": [[29, "_CPPv4N6galsim16SBInclinedSersicD0Ev"]], "galsim::sbinterpolatedimage (c++ class)": [[29, "_CPPv4N6galsim19SBInterpolatedImageE"]], "galsim::sbinterpolatedimage::sbinterpolatedimage (c++ function)": [[29, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK19SBInterpolatedImage"], [29, "_CPPv4N6galsim19SBInterpolatedImage19SBInterpolatedImageERK9BaseImageIdERK6BoundsIiERK6BoundsIiERK11InterpolantRK11InterpolantddRK8GSParams"]], "galsim::sbinterpolatedimage::calculatemaxk (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage13calculateMaxKEd"]], "galsim::sbinterpolatedimage::calculatestepk (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage14calculateStepKEd"]], "galsim::sbinterpolatedimage::getimage (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage8getImageEv"]], "galsim::sbinterpolatedimage::getkinterp (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage10getKInterpEv"]], "galsim::sbinterpolatedimage::getnonzeroimage (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage15getNonZeroImageEv"]], "galsim::sbinterpolatedimage::getpadfactor (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage12getPadFactorEv"]], "galsim::sbinterpolatedimage::getpaddedimage (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage14getPaddedImageEv"]], "galsim::sbinterpolatedimage::getxinterp (c++ function)": [[29, "_CPPv4NK6galsim19SBInterpolatedImage10getXInterpEv"]], "galsim::sbinterpolatedimage::~sbinterpolatedimage (c++ function)": [[29, "_CPPv4N6galsim19SBInterpolatedImageD0Ev"]], "galsim::sbinterpolatedkimage (c++ class)": [[29, "_CPPv4N6galsim20SBInterpolatedKImageE"]], "galsim::sbinterpolatedkimage::sbinterpolatedkimage (c++ function)": [[29, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK20SBInterpolatedKImage"], [29, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageINSt7complexIdEEEdRK11InterpolantRK8GSParams"], [29, "_CPPv4N6galsim20SBInterpolatedKImage20SBInterpolatedKImageERK9BaseImageIdEddRK11InterpolantRK8GSParams"]], "galsim::sbinterpolatedkimage::getkdata (c++ function)": [[29, "_CPPv4NK6galsim20SBInterpolatedKImage8getKDataEv"]], "galsim::sbinterpolatedkimage::getkinterp (c++ function)": [[29, "_CPPv4NK6galsim20SBInterpolatedKImage10getKInterpEv"]], "galsim::sbinterpolatedkimage::~sbinterpolatedkimage (c++ function)": [[29, "_CPPv4N6galsim20SBInterpolatedKImageD0Ev"]], "galsim::sbkolmogorov (c++ class)": [[29, "_CPPv4N6galsim12SBKolmogorovE"]], "galsim::sbkolmogorov::sbkolmogorov (c++ function)": [[29, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovERK12SBKolmogorov"], [29, "_CPPv4N6galsim12SBKolmogorov12SBKolmogorovEddRK8GSParams"]], "galsim::sbkolmogorov::getlamoverr0 (c++ function)": [[29, "_CPPv4NK6galsim12SBKolmogorov12getLamOverR0Ev"]], "galsim::sbkolmogorov::~sbkolmogorov (c++ function)": [[29, "_CPPv4N6galsim12SBKolmogorovD0Ev"]], "galsim::sbmoffat (c++ class)": [[29, "_CPPv4N6galsim8SBMoffatE"]], "galsim::sbmoffat::sbmoffat (c++ function)": [[29, "_CPPv4N6galsim8SBMoffat8SBMoffatERK8SBMoffat"], [29, "_CPPv4N6galsim8SBMoffat8SBMoffatEddddRK8GSParams"]], "galsim::sbmoffat::getbeta (c++ function)": [[29, "_CPPv4NK6galsim8SBMoffat7getBetaEv"]], "galsim::sbmoffat::getfwhm (c++ function)": [[29, "_CPPv4NK6galsim8SBMoffat7getFWHMEv"]], "galsim::sbmoffat::gethalflightradius (c++ function)": [[29, "_CPPv4NK6galsim8SBMoffat18getHalfLightRadiusEv"]], "galsim::sbmoffat::getscaleradius (c++ function)": [[29, "_CPPv4NK6galsim8SBMoffat14getScaleRadiusEv"]], "galsim::sbmoffat::gettrunc (c++ function)": [[29, "_CPPv4NK6galsim8SBMoffat8getTruncEv"]], "galsim::sbmoffat::~sbmoffat (c++ function)": [[29, "_CPPv4N6galsim8SBMoffatD0Ev"]], "galsim::sbprofile (c++ class)": [[29, "_CPPv4N6galsim9SBProfileE"]], "galsim::sbprofile::sbprofile (c++ function)": [[29, "_CPPv4N6galsim9SBProfile9SBProfileERK9SBProfile"], [29, "_CPPv4N6galsim9SBProfile9SBProfileEv"]], "galsim::sbprofile::centroid (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile8centroidEv"]], "galsim::sbprofile::draw (c++ function)": [[29, "_CPPv4I0ENK6galsim9SBProfile4drawEv9ImageViewI1TEdPdddd"]], "galsim::sbprofile::drawk (c++ function)": [[29, "_CPPv4I0ENK6galsim9SBProfile5drawKEv9ImageViewINSt7complexI1TEEEdPd"]], "galsim::sbprofile::expand (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile6expandEd"]], "galsim::sbprofile::getflux (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile7getFluxEv"]], "galsim::sbprofile::getgsparams (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile11getGSParamsEv"]], "galsim::sbprofile::getgoodimagesize (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile16getGoodImageSizeEd"]], "galsim::sbprofile::getnegativeflux (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile15getNegativeFluxEv"]], "galsim::sbprofile::getpositiveflux (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile15getPositiveFluxEv"]], "galsim::sbprofile::getxrange (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile9getXRangeERdRdRNSt6vectorIdEE"]], "galsim::sbprofile::getyrange (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile9getYRangeERdRdRNSt6vectorIdEE"]], "galsim::sbprofile::getyrangex (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile10getYRangeXEdRdRdRNSt6vectorIdEE"]], "galsim::sbprofile::hashardedges (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile12hasHardEdgesEv"]], "galsim::sbprofile::isanalytick (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile11isAnalyticKEv"]], "galsim::sbprofile::isanalyticx (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile11isAnalyticXEv"]], "galsim::sbprofile::isaxisymmetric (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile14isAxisymmetricEv"]], "galsim::sbprofile::kvalue (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile6kValueERK8PositionIdE"]], "galsim::sbprofile::maxk (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile4maxKEv"]], "galsim::sbprofile::maxsb (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile5maxSBEv"]], "galsim::sbprofile::nyquistdx (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile9nyquistDxEv"]], "galsim::sbprofile::operator= (c++ function)": [[29, "_CPPv4N6galsim9SBProfileaSERK9SBProfile"]], "galsim::sbprofile::rotate (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile6rotateEd"]], "galsim::sbprofile::scaleflux (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile9scaleFluxEd"]], "galsim::sbprofile::shift (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile5shiftERK8PositionIdE"]], "galsim::sbprofile::shoot (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile5shootER11PhotonArray11BaseDeviate"]], "galsim::sbprofile::stepk (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile5stepKEv"]], "galsim::sbprofile::transform (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile9transformEdddd"]], "galsim::sbprofile::xvalue (c++ function)": [[29, "_CPPv4NK6galsim9SBProfile6xValueERK8PositionIdE"]], "galsim::sbprofile::~sbprofile (c++ function)": [[29, "_CPPv4N6galsim9SBProfileD0Ev"]], "galsim::sbsecondkick (c++ class)": [[29, "_CPPv4N6galsim12SBSecondKickE"]], "galsim::sbsecondkick::sbsecondkick (c++ function)": [[29, "_CPPv4N6galsim12SBSecondKick12SBSecondKickERK12SBSecondKick"], [29, "_CPPv4N6galsim12SBSecondKick12SBSecondKickEdddRK11GSParamsPtr"]], "galsim::sbsecondkick::getdelta (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick8getDeltaEv"]], "galsim::sbsecondkick::getkcrit (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick8getKCritEv"]], "galsim::sbsecondkick::getlamoverr0 (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick12getLamOverR0Ev"]], "galsim::sbsecondkick::kvalue (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick6kValueEd"]], "galsim::sbsecondkick::kvalueraw (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick9kValueRawEd"]], "galsim::sbsecondkick::structurefunction (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick17structureFunctionEd"]], "galsim::sbsecondkick::xvalue (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick6xValueEd"]], "galsim::sbsecondkick::xvalueexact (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick11xValueExactEd"]], "galsim::sbsecondkick::xvalueraw (c++ function)": [[29, "_CPPv4NK6galsim12SBSecondKick9xValueRawEd"]], "galsim::sbsecondkick::~sbsecondkick (c++ function)": [[29, "_CPPv4N6galsim12SBSecondKickD0Ev"]], "galsim::sbsersic (c++ class)": [[29, "_CPPv4N6galsim8SBSersicE"]], "galsim::sbsersic::sbsersic (c++ function)": [[29, "_CPPv4N6galsim8SBSersic8SBSersicERK8SBSersic"], [29, "_CPPv4N6galsim8SBSersic8SBSersicEddddRK8GSParams"]], "galsim::sbsersic::gethalflightradius (c++ function)": [[29, "_CPPv4NK6galsim8SBSersic18getHalfLightRadiusEv"]], "galsim::sbsersic::getn (c++ function)": [[29, "_CPPv4NK6galsim8SBSersic4getNEv"]], "galsim::sbsersic::getscaleradius (c++ function)": [[29, "_CPPv4NK6galsim8SBSersic14getScaleRadiusEv"]], "galsim::sbsersic::gettrunc (c++ function)": [[29, "_CPPv4NK6galsim8SBSersic8getTruncEv"]], "galsim::sbsersic::~sbsersic (c++ function)": [[29, "_CPPv4N6galsim8SBSersicD0Ev"]], "galsim::sbshapelet (c++ class)": [[29, "_CPPv4N6galsim10SBShapeletE"]], "galsim::sbshapelet::sbshapelet (c++ function)": [[29, "_CPPv4N6galsim10SBShapelet10SBShapeletERK10SBShapelet"], [29, "_CPPv4N6galsim10SBShapelet10SBShapeletEd7LVectorRK8GSParams"]], "galsim::sbshapelet::getbvec (c++ function)": [[29, "_CPPv4NK6galsim10SBShapelet7getBVecEv"]], "galsim::sbshapelet::getsigma (c++ function)": [[29, "_CPPv4NK6galsim10SBShapelet8getSigmaEv"]], "galsim::sbshapelet::rotate (c++ function)": [[29, "_CPPv4N6galsim10SBShapelet6rotateEd"]], "galsim::sbshapelet::~sbshapelet (c++ function)": [[29, "_CPPv4N6galsim10SBShapeletD0Ev"]], "galsim::sbspergel (c++ class)": [[29, "_CPPv4N6galsim9SBSpergelE"]], "galsim::sbspergel::sbspergel (c++ function)": [[29, "_CPPv4N6galsim9SBSpergel9SBSpergelERK9SBSpergel"], [29, "_CPPv4N6galsim9SBSpergel9SBSpergelEdddRK8GSParams"]], "galsim::sbspergel::calculatefluxradius (c++ function)": [[29, "_CPPv4NK6galsim9SBSpergel19calculateFluxRadiusEd"]], "galsim::sbspergel::calculateintegratedflux (c++ function)": [[29, "_CPPv4NK6galsim9SBSpergel23calculateIntegratedFluxEd"]], "galsim::sbspergel::getnu (c++ function)": [[29, "_CPPv4NK6galsim9SBSpergel5getNuEv"]], "galsim::sbspergel::getscaleradius (c++ function)": [[29, "_CPPv4NK6galsim9SBSpergel14getScaleRadiusEv"]], "galsim::sbspergel::~sbspergel (c++ function)": [[29, "_CPPv4N6galsim9SBSpergelD0Ev"]], "galsim::sbtophat (c++ class)": [[29, "_CPPv4N6galsim8SBTopHatE"]], "galsim::sbtophat::sbtophat (c++ function)": [[29, "_CPPv4N6galsim8SBTopHat8SBTopHatERK8SBTopHat"], [29, "_CPPv4N6galsim8SBTopHat8SBTopHatEddRK8GSParams"]], "galsim::sbtophat::getradius (c++ function)": [[29, "_CPPv4NK6galsim8SBTopHat9getRadiusEv"]], "galsim::sbtophat::~sbtophat (c++ function)": [[29, "_CPPv4N6galsim8SBTopHatD0Ev"]], "galsim::sbtransform (c++ class)": [[29, "_CPPv4N6galsim11SBTransformE"]], "galsim::sbtransform::sbtransform (c++ function)": [[29, "_CPPv4N6galsim11SBTransform11SBTransformERK11SBTransform"], [29, "_CPPv4N6galsim11SBTransform11SBTransformERK9SBProfilePKdRK8PositionIdEdRK8GSParams"]], "galsim::sbtransform::getfluxscaling (c++ function)": [[29, "_CPPv4NK6galsim11SBTransform14getFluxScalingEv"]], "galsim::sbtransform::getjac (c++ function)": [[29, "_CPPv4NK6galsim11SBTransform6getJacERdRdRdRd"]], "galsim::sbtransform::getobj (c++ function)": [[29, "_CPPv4NK6galsim11SBTransform6getObjEv"]], "galsim::sbtransform::getoffset (c++ function)": [[29, "_CPPv4NK6galsim11SBTransform9getOffsetEv"]], "galsim::sbtransform::~sbtransform (c++ function)": [[29, "_CPPv4N6galsim11SBTransformD0Ev"]], "galsim::sbvonkarman (c++ class)": [[29, "_CPPv4N6galsim11SBVonKarmanE"]], "galsim::sbvonkarman::sbvonkarman (c++ function)": [[29, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanERK11SBVonKarman"], [29, "_CPPv4N6galsim11SBVonKarman11SBVonKarmanEdddddbRK8GSParamsd"]], "galsim::sbvonkarman::getdelta (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman8getDeltaEv"]], "galsim::sbvonkarman::getdodelta (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman10getDoDeltaEv"]], "galsim::sbvonkarman::gethalflightradius (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman18getHalfLightRadiusEv"]], "galsim::sbvonkarman::getl0 (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman5getL0Ev"]], "galsim::sbvonkarman::getlam (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman6getLamEv"]], "galsim::sbvonkarman::getr0 (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman5getR0Ev"]], "galsim::sbvonkarman::getscale (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman8getScaleEv"]], "galsim::sbvonkarman::structurefunction (c++ function)": [[29, "_CPPv4NK6galsim11SBVonKarman17structureFunctionEd"]], "galsim::sbvonkarman::~sbvonkarman (c++ function)": [[29, "_CPPv4N6galsim11SBVonKarmanD0Ev"]], "air_refractive_index_minus_one() (in module galsim.dcr)": [[30, "galsim.dcr.air_refractive_index_minus_one"]], "get_refraction() (in module galsim.dcr)": [[30, "galsim.dcr.get_refraction"]], "parse_dcr_angles() (in module galsim.dcr)": [[30, "galsim.dcr.parse_dcr_angles"]], "zenith_parallactic_angles() (in module galsim.dcr)": [[30, "galsim.dcr.zenith_parallactic_angles"]], "des_psfex (class in galsim.des)": [[31, "galsim.des.DES_PSFEx"]], "des_shapelet (class in galsim.des)": [[31, "galsim.des.DES_Shapelet"]], "medsbuilder (class in galsim.des)": [[31, "galsim.des.MEDSBuilder"]], "multiexposureobject (class in galsim.des)": [[31, "galsim.des.MultiExposureObject"]], "offsetbuilder (class in galsim.des)": [[31, "galsim.des.OffsetBuilder"]], "writemeds() (in module galsim.des)": [[31, "galsim.des.WriteMEDS"]], "buildimages() (galsim.des.medsbuilder method)": [[31, "galsim.des.MEDSBuilder.buildImages"]], "finalize() (galsim.des.offsetbuilder method)": [[31, "galsim.des.OffsetBuilder.finalize"]], "getb() (galsim.des.des_shapelet method)": [[31, "galsim.des.DES_Shapelet.getB"]], "getlocalwcs() (galsim.des.des_psfex method)": [[31, "galsim.des.DES_PSFEx.getLocalWCS"]], "getnimages() (galsim.des.medsbuilder method)": [[31, "galsim.des.MEDSBuilder.getNImages"]], "getpsf() (galsim.des.des_psfex method)": [[31, "galsim.des.DES_PSFEx.getPSF"]], "getpsf() (galsim.des.des_shapelet method)": [[31, "galsim.des.DES_Shapelet.getPSF"]], "getpsfarray() (galsim.des.des_psfex method)": [[31, "galsim.des.DES_PSFEx.getPSFArray"]], "processstamp() (galsim.des.offsetbuilder method)": [[31, "galsim.des.OffsetBuilder.processStamp"]], "read_fits() (galsim.des.des_shapelet method)": [[31, "galsim.des.DES_Shapelet.read_fits"]], "writefile() (galsim.des.medsbuilder method)": [[31, "galsim.des.MEDSBuilder.writeFile"]], "basedeviate (class in galsim)": [[32, "galsim.BaseDeviate"]], "binomialdeviate (class in galsim)": [[32, "galsim.BinomialDeviate"]], "chi2deviate (class in galsim)": [[32, "galsim.Chi2Deviate"]], "distdeviate (class in galsim)": [[32, "galsim.DistDeviate"]], "gammadeviate (class in galsim)": [[32, "galsim.GammaDeviate"]], "gaussiandeviate (class in galsim)": [[32, "galsim.GaussianDeviate"]], "poissondeviate (class in galsim)": [[32, "galsim.PoissonDeviate"]], "uniformdeviate (class in galsim)": [[32, "galsim.UniformDeviate"]], "weibulldeviate (class in galsim)": [[32, "galsim.WeibullDeviate"]], "__call__() (galsim.binomialdeviate method)": [[32, "galsim.BinomialDeviate.__call__"]], "__call__() (galsim.chi2deviate method)": [[32, "galsim.Chi2Deviate.__call__"]], "__call__() (galsim.distdeviate method)": [[32, "galsim.DistDeviate.__call__"]], "__call__() (galsim.gammadeviate method)": [[32, "galsim.GammaDeviate.__call__"]], "__call__() (galsim.gaussiandeviate method)": [[32, "galsim.GaussianDeviate.__call__"]], "__call__() (galsim.poissondeviate method)": [[32, "galsim.PoissonDeviate.__call__"]], "__call__() (galsim.uniformdeviate method)": [[32, "galsim.UniformDeviate.__call__"]], "__call__() (galsim.weibulldeviate method)": [[32, "galsim.WeibullDeviate.__call__"]], "_reset() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate._reset"]], "_seed() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate._seed"]], "a (galsim.weibulldeviate property)": [[32, "galsim.WeibullDeviate.a"]], "add_generate() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.add_generate"]], "add_generate() (galsim.distdeviate method)": [[32, "galsim.DistDeviate.add_generate"]], "as_numpy_generator() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.as_numpy_generator"]], "b (galsim.weibulldeviate property)": [[32, "galsim.WeibullDeviate.b"]], "clearcache() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.clearCache"]], "discard() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.discard"]], "duplicate() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.duplicate"]], "generate() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.generate"]], "generate() (galsim.distdeviate method)": [[32, "galsim.DistDeviate.generate"]], "generate_from_expectation() (galsim.poissondeviate method)": [[32, "galsim.PoissonDeviate.generate_from_expectation"]], "generate_from_variance() (galsim.gaussiandeviate method)": [[32, "galsim.GaussianDeviate.generate_from_variance"]], "generates_in_pairs (galsim.basedeviate property)": [[32, "galsim.BaseDeviate.generates_in_pairs"]], "generates_in_pairs (galsim.gaussiandeviate property)": [[32, "galsim.GaussianDeviate.generates_in_pairs"]], "has_reliable_discard (galsim.basedeviate property)": [[32, "galsim.BaseDeviate.has_reliable_discard"]], "has_reliable_discard (galsim.chi2deviate property)": [[32, "galsim.Chi2Deviate.has_reliable_discard"]], "has_reliable_discard (galsim.gammadeviate property)": [[32, "galsim.GammaDeviate.has_reliable_discard"]], "has_reliable_discard (galsim.poissondeviate property)": [[32, "galsim.PoissonDeviate.has_reliable_discard"]], "k (galsim.gammadeviate property)": [[32, "galsim.GammaDeviate.k"]], "mean (galsim.gaussiandeviate property)": [[32, "galsim.GaussianDeviate.mean"]], "mean (galsim.poissondeviate property)": [[32, "galsim.PoissonDeviate.mean"]], "n (galsim.binomialdeviate property)": [[32, "galsim.BinomialDeviate.n"]], "n (galsim.chi2deviate property)": [[32, "galsim.Chi2Deviate.n"]], "np (galsim.basedeviate property)": [[32, "galsim.BaseDeviate.np"]], "p (galsim.binomialdeviate property)": [[32, "galsim.BinomialDeviate.p"]], "raw() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.raw"]], "reset() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.reset"]], "seed() (galsim.basedeviate method)": [[32, "galsim.BaseDeviate.seed"]], "sigma (galsim.gaussiandeviate property)": [[32, "galsim.GaussianDeviate.sigma"]], "theta (galsim.gammadeviate property)": [[32, "galsim.GammaDeviate.theta"]], "val() (galsim.distdeviate method)": [[32, "galsim.DistDeviate.val"]], "galsimboundserror (class in galsim)": [[33, "galsim.GalSimBoundsError"]], "galsimconfigerror (class in galsim)": [[33, "galsim.GalSimConfigError"]], "galsimconfigvalueerror (class in galsim)": [[33, "galsim.GalSimConfigValueError"]], "galsimdeprecationwarning (class in galsim)": [[33, "galsim.GalSimDeprecationWarning"]], "galsimerror (class in galsim)": [[33, "galsim.GalSimError"]], "galsimfftsizeerror (class in galsim)": [[33, "galsim.GalSimFFTSizeError"]], "galsimhsmerror (class in galsim)": [[33, "galsim.GalSimHSMError"]], "galsimimmutableerror (class in galsim)": [[33, "galsim.GalSimImmutableError"]], "galsimincompatiblevalueserror (class in galsim)": [[33, "galsim.GalSimIncompatibleValuesError"]], "galsimindexerror (class in galsim)": [[33, "galsim.GalSimIndexError"]], "galsimkeyerror (class in galsim)": [[33, "galsim.GalSimKeyError"]], "galsimnotimplementederror (class in galsim)": [[33, "galsim.GalSimNotImplementedError"]], "galsimrangeerror (class in galsim)": [[33, "galsim.GalSimRangeError"]], "galsimsederror (class in galsim)": [[33, "galsim.GalSimSEDError"]], "galsimundefinedboundserror (class in galsim)": [[33, "galsim.GalSimUndefinedBoundsError"]], "galsimvalueerror (class in galsim)": [[33, "galsim.GalSimValueError"]], "galsimwarning (class in galsim)": [[33, "galsim.GalSimWarning"]], "fft2() (in module galsim.fft)": [[34, "galsim.fft.fft2"]], "ifft2() (in module galsim.fft)": [[34, "galsim.fft.ifft2"]], "irfft2() (in module galsim.fft)": [[34, "galsim.fft.irfft2"]], "rfft2() (in module galsim.fft)": [[34, "galsim.fft.rfft2"]], "fitsheader (class in galsim.fits)": [[35, "galsim.fits.FitsHeader"]], "append() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.append"]], "clear() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.clear"]], "closehdulist() (in module galsim.fits)": [[35, "galsim.fits.closeHDUList"]], "comment() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.comment"]], "extend() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.extend"]], "get() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.get"]], "items() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.items"]], "iteritems() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.iteritems"]], "iterkeys() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.iterkeys"]], "itervalues() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.itervalues"]], "keys() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.keys"]], "pop() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.pop"]], "read() (in module galsim.fits)": [[35, "galsim.fits.read"]], "readcube() (in module galsim.fits)": [[35, "galsim.fits.readCube"]], "readfile() (in module galsim.fits)": [[35, "galsim.fits.readFile"]], "readmulti() (in module galsim.fits)": [[35, "galsim.fits.readMulti"]], "update() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.update"]], "values() (galsim.fits.fitsheader method)": [[35, "galsim.fits.FitsHeader.values"]], "write() (in module galsim.fits)": [[35, "galsim.fits.write"]], "writecube() (in module galsim.fits)": [[35, "galsim.fits.writeCube"]], "writefile() (in module galsim.fits)": [[35, "galsim.fits.writeFile"]], "writemulti() (in module galsim.fits)": [[35, "galsim.fits.writeMulti"]], "devaucouleurs (class in galsim)": [[36, "galsim.DeVaucouleurs"]], "exponential (class in galsim)": [[36, "galsim.Exponential"]], "inclinedexponential (class in galsim)": [[36, "galsim.InclinedExponential"]], "inclinedsersic (class in galsim)": [[36, "galsim.InclinedSersic"]], "randomknots (class in galsim)": [[36, "galsim.RandomKnots"]], "sersic (class in galsim)": [[36, "galsim.Sersic"]], "spergel (class in galsim)": [[36, "galsim.Spergel"]], "calculatefluxradius() (galsim.spergel method)": [[36, "galsim.Spergel.calculateFluxRadius"]], "calculatehlr() (galsim.randomknots method)": [[36, "galsim.RandomKnots.calculateHLR"]], "calculatehlrfactor() (galsim.sersic method)": [[36, "galsim.Sersic.calculateHLRFactor"]], "calculateintegratedflux() (galsim.sersic method)": [[36, "galsim.Sersic.calculateIntegratedFlux"]], "calculateintegratedflux() (galsim.spergel method)": [[36, "galsim.Spergel.calculateIntegratedFlux"]], "dilate() (galsim.randomknots method)": [[36, "galsim.RandomKnots.dilate"]], "disk_half_light_radius (galsim.inclinedexponential property)": [[36, "galsim.InclinedExponential.disk_half_light_radius"]], "disk_half_light_radius (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.disk_half_light_radius"]], "expand() (galsim.randomknots method)": [[36, "galsim.RandomKnots.expand"]], "half_light_radius (galsim.exponential property)": [[36, "galsim.Exponential.half_light_radius"]], "half_light_radius (galsim.sersic property)": [[36, "galsim.Sersic.half_light_radius"]], "half_light_radius (galsim.spergel property)": [[36, "galsim.Spergel.half_light_radius"]], "inclination (galsim.inclinedexponential property)": [[36, "galsim.InclinedExponential.inclination"]], "inclination (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.inclination"]], "input_half_light_radius (galsim.randomknots property)": [[36, "galsim.RandomKnots.input_half_light_radius"]], "n (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.n"]], "n (galsim.sersic property)": [[36, "galsim.Sersic.n"]], "npoints (galsim.randomknots property)": [[36, "galsim.RandomKnots.npoints"]], "nu (galsim.spergel property)": [[36, "galsim.Spergel.nu"]], "rotate() (galsim.randomknots method)": [[36, "galsim.RandomKnots.rotate"]], "scale_h_over_r (galsim.inclinedexponential property)": [[36, "galsim.InclinedExponential.scale_h_over_r"]], "scale_h_over_r (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.scale_h_over_r"]], "scale_height (galsim.inclinedexponential property)": [[36, "galsim.InclinedExponential.scale_height"]], "scale_height (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.scale_height"]], "scale_radius (galsim.exponential property)": [[36, "galsim.Exponential.scale_radius"]], "scale_radius (galsim.inclinedexponential property)": [[36, "galsim.InclinedExponential.scale_radius"]], "scale_radius (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.scale_radius"]], "scale_radius (galsim.sersic property)": [[36, "galsim.Sersic.scale_radius"]], "scale_radius (galsim.spergel property)": [[36, "galsim.Spergel.scale_radius"]], "shear() (galsim.randomknots method)": [[36, "galsim.RandomKnots.shear"]], "shift() (galsim.randomknots method)": [[36, "galsim.RandomKnots.shift"]], "transform() (galsim.randomknots method)": [[36, "galsim.RandomKnots.transform"]], "trunc (galsim.inclinedsersic property)": [[36, "galsim.InclinedSersic.trunc"]], "trunc (galsim.sersic property)": [[36, "galsim.Sersic.trunc"]], "withflux() (galsim.devaucouleurs method)": [[36, "galsim.DeVaucouleurs.withFlux"]], "withflux() (galsim.exponential method)": [[36, "galsim.Exponential.withFlux"]], "withflux() (galsim.inclinedexponential method)": [[36, "galsim.InclinedExponential.withFlux"]], "withflux() (galsim.inclinedsersic method)": [[36, "galsim.InclinedSersic.withFlux"]], "withflux() (galsim.randomknots method)": [[36, "galsim.RandomKnots.withFlux"]], "withflux() (galsim.sersic method)": [[36, "galsim.Sersic.withFlux"]], "withflux() (galsim.spergel method)": [[36, "galsim.Spergel.withFlux"]], "withscaledflux() (galsim.randomknots method)": [[36, "galsim.RandomKnots.withScaledFlux"]], "gsobject (class in galsim)": [[37, "galsim.GSObject"]], "__add__() (galsim.gsobject method)": [[37, "galsim.GSObject.__add__"]], "__div__() (galsim.gsobject method)": [[37, "galsim.GSObject.__div__"]], "__mul__() (galsim.gsobject method)": [[37, "galsim.GSObject.__mul__"]], "__rmul__() (galsim.gsobject method)": [[37, "galsim.GSObject.__rmul__"]], "__sub__() (galsim.gsobject method)": [[37, "galsim.GSObject.__sub__"]], "_calculate_nphotons() (galsim.gsobject method)": [[37, "galsim.GSObject._calculate_nphotons"]], "_drawkimage() (galsim.gsobject method)": [[37, "galsim.GSObject._drawKImage"]], "_drawreal() (galsim.gsobject method)": [[37, "galsim.GSObject._drawReal"]], "_kvalue() (galsim.gsobject method)": [[37, "galsim.GSObject._kValue"]], "_shear() (galsim.gsobject method)": [[37, "galsim.GSObject._shear"]], "_shift() (galsim.gsobject method)": [[37, "galsim.GSObject._shift"]], "_shoot() (galsim.gsobject method)": [[37, "galsim.GSObject._shoot"]], "_xvalue() (galsim.gsobject method)": [[37, "galsim.GSObject._xValue"]], "applyto() (galsim.gsobject method)": [[37, "galsim.GSObject.applyTo"]], "atredshift() (galsim.gsobject method)": [[37, "galsim.GSObject.atRedshift"]], "calculatefwhm() (galsim.gsobject method)": [[37, "galsim.GSObject.calculateFWHM"]], "calculatehlr() (galsim.gsobject method)": [[37, "galsim.GSObject.calculateHLR"]], "calculatemomentradius() (galsim.gsobject method)": [[37, "galsim.GSObject.calculateMomentRadius"]], "centroid (galsim.gsobject property)": [[37, "galsim.GSObject.centroid"]], "dilate() (galsim.gsobject method)": [[37, "galsim.GSObject.dilate"]], "drawfft() (galsim.gsobject method)": [[37, "galsim.GSObject.drawFFT"]], "drawfft_finish() (galsim.gsobject method)": [[37, "galsim.GSObject.drawFFT_finish"]], "drawfft_makekimage() (galsim.gsobject method)": [[37, "galsim.GSObject.drawFFT_makeKImage"]], "drawimage() (galsim.gsobject method)": [[37, "galsim.GSObject.drawImage"]], "drawkimage() (galsim.gsobject method)": [[37, "galsim.GSObject.drawKImage"]], "drawphot() (galsim.gsobject method)": [[37, "galsim.GSObject.drawPhot"]], "drawreal() (galsim.gsobject method)": [[37, "galsim.GSObject.drawReal"]], "expand() (galsim.gsobject method)": [[37, "galsim.GSObject.expand"]], "flux (galsim.gsobject property)": [[37, "galsim.GSObject.flux"]], "getgoodimagesize() (galsim.gsobject method)": [[37, "galsim.GSObject.getGoodImageSize"]], "gsparams (galsim.gsobject property)": [[37, "galsim.GSObject.gsparams"]], "has_hard_edges (galsim.gsobject property)": [[37, "galsim.GSObject.has_hard_edges"]], "is_analytic_k (galsim.gsobject property)": [[37, "galsim.GSObject.is_analytic_k"]], "is_analytic_x (galsim.gsobject property)": [[37, "galsim.GSObject.is_analytic_x"]], "is_axisymmetric (galsim.gsobject property)": [[37, "galsim.GSObject.is_axisymmetric"]], "kvalue() (galsim.gsobject method)": [[37, "galsim.GSObject.kValue"]], "lens() (galsim.gsobject method)": [[37, "galsim.GSObject.lens"]], "magnify() (galsim.gsobject method)": [[37, "galsim.GSObject.magnify"]], "makephot() (galsim.gsobject method)": [[37, "galsim.GSObject.makePhot"]], "max_sb (galsim.gsobject property)": [[37, "galsim.GSObject.max_sb"]], "maxk (galsim.gsobject property)": [[37, "galsim.GSObject.maxk"]], "negative_flux (galsim.gsobject property)": [[37, "galsim.GSObject.negative_flux"]], "noise (galsim.gsobject property)": [[37, "galsim.GSObject.noise"]], "nyquist_scale (galsim.gsobject property)": [[37, "galsim.GSObject.nyquist_scale"]], "positive_flux (galsim.gsobject property)": [[37, "galsim.GSObject.positive_flux"]], "rotate() (galsim.gsobject method)": [[37, "galsim.GSObject.rotate"]], "shear() (galsim.gsobject method)": [[37, "galsim.GSObject.shear"]], "shift() (galsim.gsobject method)": [[37, "galsim.GSObject.shift"]], "shoot() (galsim.gsobject method)": [[37, "galsim.GSObject.shoot"]], "stepk (galsim.gsobject property)": [[37, "galsim.GSObject.stepk"]], "transform() (galsim.gsobject method)": [[37, "galsim.GSObject.transform"]], "withflux() (galsim.gsobject method)": [[37, "galsim.GSObject.withFlux"]], "withgsparams() (galsim.gsobject method)": [[37, "galsim.GSObject.withGSParams"]], "withscaledflux() (galsim.gsobject method)": [[37, "galsim.GSObject.withScaledFlux"]], "xvalue() (galsim.gsobject method)": [[37, "galsim.GSObject.xValue"]], "gsparams (class in galsim)": [[38, "galsim.GSParams"]], "check() (galsim.gsparams static method)": [[38, "galsim.GSParams.check"]], "combine() (galsim.gsparams static method)": [[38, "galsim.GSParams.combine"]], "withparams() (galsim.gsparams method)": [[38, "galsim.GSParams.withParams"]], "estimateshear() (in module galsim.hsm)": [[40, "galsim.hsm.EstimateShear"]], "findadaptivemom() (in module galsim.hsm)": [[40, "galsim.hsm.FindAdaptiveMom"]], "hsmparams (class in galsim.hsm)": [[40, "galsim.hsm.HSMParams"]], "shapedata (class in galsim.hsm)": [[40, "galsim.hsm.ShapeData"]], "applywcs() (galsim.hsm.shapedata method)": [[40, "galsim.hsm.ShapeData.applyWCS"]], "check() (galsim.hsm.hsmparams static method)": [[40, "galsim.hsm.HSMParams.check"]], "findadaptivemom() (galsim.image method)": [[42, "galsim.Image.FindAdaptiveMom"]], "image (class in galsim)": [[42, "galsim.Image"]], "imagecd() (in module galsim)": [[42, "galsim.ImageCD"]], "imagecf() (in module galsim)": [[42, "galsim.ImageCF"]], "imaged() (in module galsim)": [[42, "galsim.ImageD"]], "imagef() (in module galsim)": [[42, "galsim.ImageF"]], "imagei() (in module galsim)": [[42, "galsim.ImageI"]], "images() (in module galsim)": [[42, "galsim.ImageS"]], "imageui() (in module galsim)": [[42, "galsim.ImageUI"]], "imageus() (in module galsim)": [[42, "galsim.ImageUS"]], "_image() (in module galsim)": [[42, "galsim._Image"]], "__call__() (galsim.image method)": [[42, "galsim.Image.__call__"]], "__getitem__() (galsim.image method)": [[42, "galsim.Image.__getitem__"]], "__setitem__() (galsim.image method)": [[42, "galsim.Image.__setitem__"]], "_addvalue() (galsim.image method)": [[42, "galsim.Image._addValue"]], "_fill() (galsim.image method)": [[42, "galsim.Image._fill"]], "_getvalue() (galsim.image method)": [[42, "galsim.Image._getValue"]], "_invertself() (galsim.image method)": [[42, "galsim.Image._invertSelf"]], "_setvalue() (galsim.image method)": [[42, "galsim.Image._setValue"]], "_shift() (galsim.image method)": [[42, "galsim.Image._shift"]], "_view() (galsim.image method)": [[42, "galsim.Image._view"]], "_wrap() (galsim.image method)": [[42, "galsim.Image._wrap"]], "addnoise() (galsim.image method)": [[42, "galsim.Image.addNoise"]], "addnoisesnr() (galsim.image method)": [[42, "galsim.Image.addNoiseSNR"]], "addreciprocityfailure() (galsim.image method)": [[42, "galsim.Image.addReciprocityFailure"]], "addvalue() (galsim.image method)": [[42, "galsim.Image.addValue"]], "applyipc() (galsim.image method)": [[42, "galsim.Image.applyIPC"]], "applynonlinearity() (galsim.image method)": [[42, "galsim.Image.applyNonlinearity"]], "applypersistence() (galsim.image method)": [[42, "galsim.Image.applyPersistence"]], "array (galsim.image property)": [[42, "galsim.Image.array"]], "bin() (galsim.image method)": [[42, "galsim.Image.bin"]], "bounds (galsim.image property)": [[42, "galsim.Image.bounds"]], "calculatefwhm() (galsim.image method)": [[42, "galsim.Image.calculateFWHM"]], "calculatehlr() (galsim.image method)": [[42, "galsim.Image.calculateHLR"]], "calculatemomentradius() (galsim.image method)": [[42, "galsim.Image.calculateMomentRadius"]], "calculate_fft() (galsim.image method)": [[42, "galsim.Image.calculate_fft"]], "calculate_inverse_fft() (galsim.image method)": [[42, "galsim.Image.calculate_inverse_fft"]], "center (galsim.image property)": [[42, "galsim.Image.center"]], "clear_depixelize_cache() (galsim.image static method)": [[42, "galsim.Image.clear_depixelize_cache"]], "conjugate (galsim.image property)": [[42, "galsim.Image.conjugate"]], "copy() (galsim.image method)": [[42, "galsim.Image.copy"]], "copyfrom() (galsim.image method)": [[42, "galsim.Image.copyFrom"]], "depixelize() (galsim.image method)": [[42, "galsim.Image.depixelize"]], "dtype (galsim.image property)": [[42, "galsim.Image.dtype"]], "fill() (galsim.image method)": [[42, "galsim.Image.fill"]], "flip_lr() (galsim.image method)": [[42, "galsim.Image.flip_lr"]], "flip_ud() (galsim.image method)": [[42, "galsim.Image.flip_ud"]], "getvalue() (galsim.image method)": [[42, "galsim.Image.getValue"]], "get_pixel_centers() (galsim.image method)": [[42, "galsim.Image.get_pixel_centers"]], "good_fft_size() (galsim.image class method)": [[42, "galsim.Image.good_fft_size"]], "imag (galsim.image property)": [[42, "galsim.Image.imag"]], "invertself() (galsim.image method)": [[42, "galsim.Image.invertSelf"]], "iscomplex (galsim.image property)": [[42, "galsim.Image.iscomplex"]], "isconst (galsim.image property)": [[42, "galsim.Image.isconst"]], "iscontiguous (galsim.image property)": [[42, "galsim.Image.iscontiguous"]], "isinteger (galsim.image property)": [[42, "galsim.Image.isinteger"]], "ncol (galsim.image property)": [[42, "galsim.Image.ncol"]], "nrow (galsim.image property)": [[42, "galsim.Image.nrow"]], "origin (galsim.image property)": [[42, "galsim.Image.origin"]], "outer_bounds (galsim.image property)": [[42, "galsim.Image.outer_bounds"]], "quantize() (galsim.image method)": [[42, "galsim.Image.quantize"]], "real (galsim.image property)": [[42, "galsim.Image.real"]], "replacenegative() (galsim.image method)": [[42, "galsim.Image.replaceNegative"]], "resize() (galsim.image method)": [[42, "galsim.Image.resize"]], "rot_180() (galsim.image method)": [[42, "galsim.Image.rot_180"]], "rot_ccw() (galsim.image method)": [[42, "galsim.Image.rot_ccw"]], "rot_cw() (galsim.image method)": [[42, "galsim.Image.rot_cw"]], "scale (galsim.image property)": [[42, "galsim.Image.scale"]], "setcenter() (galsim.image method)": [[42, "galsim.Image.setCenter"]], "setorigin() (galsim.image method)": [[42, "galsim.Image.setOrigin"]], "setsubimage() (galsim.image method)": [[42, "galsim.Image.setSubImage"]], "setvalue() (galsim.image method)": [[42, "galsim.Image.setValue"]], "setzero() (galsim.image method)": [[42, "galsim.Image.setZero"]], "shift() (galsim.image method)": [[42, "galsim.Image.shift"]], "subimage() (galsim.image method)": [[42, "galsim.Image.subImage"]], "subsample() (galsim.image method)": [[42, "galsim.Image.subsample"]], "symmetrizenoise() (galsim.image method)": [[42, "galsim.Image.symmetrizeNoise"]], "transpose() (galsim.image method)": [[42, "galsim.Image.transpose"]], "true_center (galsim.image property)": [[42, "galsim.Image.true_center"]], "view() (galsim.image method)": [[42, "galsim.Image.view"]], "whitennoise() (galsim.image method)": [[42, "galsim.Image.whitenNoise"]], "wrap() (galsim.image method)": [[42, "galsim.Image.wrap"]], "write() (galsim.image method)": [[42, "galsim.Image.write"]], "xmax (galsim.image property)": [[42, "galsim.Image.xmax"]], "xmin (galsim.image property)": [[42, "galsim.Image.xmin"]], "ymax (galsim.image property)": [[42, "galsim.Image.ymax"]], "ymin (galsim.image property)": [[42, "galsim.Image.ymin"]], "continuousintegrator (class in galsim.integ)": [[47, "galsim.integ.ContinuousIntegrator"]], "imageintegrator (class in galsim.integ)": [[47, "galsim.integ.ImageIntegrator"]], "integrationrule (class in galsim.integ)": [[47, "galsim.integ.IntegrationRule"]], "midptrule (class in galsim.integ)": [[47, "galsim.integ.MidptRule"]], "quadrule (class in galsim.integ)": [[47, "galsim.integ.QuadRule"]], "sampleintegrator (class in galsim.integ)": [[47, "galsim.integ.SampleIntegrator"]], "trapzrule (class in galsim.integ)": [[47, "galsim.integ.TrapzRule"]], "__call__() (galsim.integ.imageintegrator method)": [[47, "galsim.integ.ImageIntegrator.__call__"]], "calculateweights() (galsim.integ.midptrule method)": [[47, "galsim.integ.MidptRule.calculateWeights"]], "calculateweights() (galsim.integ.quadrule method)": [[47, "galsim.integ.QuadRule.calculateWeights"]], "calculateweights() (galsim.integ.trapzrule method)": [[47, "galsim.integ.TrapzRule.calculateWeights"]], "hankel() (in module galsim.integ)": [[47, "galsim.integ.hankel"]], "int1d() (in module galsim.integ)": [[47, "galsim.integ.int1d"]], "midptrule (in module galsim.integ)": [[47, "galsim.integ.midptRule"]], "quadrule (in module galsim.integ)": [[47, "galsim.integ.quadRule"]], "trapzrule (in module galsim.integ)": [[47, "galsim.integ.trapzRule"]], "cubic (class in galsim)": [[48, "galsim.Cubic"]], "delta (class in galsim)": [[48, "galsim.Delta"]], "interpolant (class in galsim)": [[48, "galsim.Interpolant"]], "lanczos (class in galsim)": [[48, "galsim.Lanczos"]], "linear (class in galsim)": [[48, "galsim.Linear"]], "nearest (class in galsim)": [[48, "galsim.Nearest"]], "quintic (class in galsim)": [[48, "galsim.Quintic"]], "sincinterpolant (class in galsim)": [[48, "galsim.SincInterpolant"]], "conserve_dc (galsim.lanczos property)": [[48, "galsim.Lanczos.conserve_dc"]], "from_name() (galsim.interpolant static method)": [[48, "galsim.Interpolant.from_name"]], "gsparams (galsim.interpolant property)": [[48, "galsim.Interpolant.gsparams"]], "ixrange (galsim.cubic property)": [[48, "galsim.Cubic.ixrange"]], "ixrange (galsim.delta property)": [[48, "galsim.Delta.ixrange"]], "ixrange (galsim.lanczos property)": [[48, "galsim.Lanczos.ixrange"]], "ixrange (galsim.linear property)": [[48, "galsim.Linear.ixrange"]], "ixrange (galsim.nearest property)": [[48, "galsim.Nearest.ixrange"]], "ixrange (galsim.quintic property)": [[48, "galsim.Quintic.ixrange"]], "ixrange (galsim.sincinterpolant property)": [[48, "galsim.SincInterpolant.ixrange"]], "krange (galsim.cubic property)": [[48, "galsim.Cubic.krange"]], "krange (galsim.delta property)": [[48, "galsim.Delta.krange"]], "krange (galsim.lanczos property)": [[48, "galsim.Lanczos.krange"]], "krange (galsim.linear property)": [[48, "galsim.Linear.krange"]], "krange (galsim.nearest property)": [[48, "galsim.Nearest.krange"]], "krange (galsim.quintic property)": [[48, "galsim.Quintic.krange"]], "krange (galsim.sincinterpolant property)": [[48, "galsim.SincInterpolant.krange"]], "kval() (galsim.interpolant method)": [[48, "galsim.Interpolant.kval"]], "n (galsim.lanczos property)": [[48, "galsim.Lanczos.n"]], "negative_flux (galsim.interpolant property)": [[48, "galsim.Interpolant.negative_flux"]], "positive_flux (galsim.interpolant property)": [[48, "galsim.Interpolant.positive_flux"]], "unit_integrals() (galsim.cubic method)": [[48, "galsim.Cubic.unit_integrals"]], "unit_integrals() (galsim.delta method)": [[48, "galsim.Delta.unit_integrals"]], "unit_integrals() (galsim.interpolant method)": [[48, "galsim.Interpolant.unit_integrals"]], "unit_integrals() (galsim.linear method)": [[48, "galsim.Linear.unit_integrals"]], "unit_integrals() (galsim.nearest method)": [[48, "galsim.Nearest.unit_integrals"]], "unit_integrals() (galsim.sincinterpolant method)": [[48, "galsim.SincInterpolant.unit_integrals"]], "withgsparams() (galsim.interpolant method)": [[48, "galsim.Interpolant.withGSParams"]], "xrange (galsim.cubic property)": [[48, "galsim.Cubic.xrange"]], "xrange (galsim.delta property)": [[48, "galsim.Delta.xrange"]], "xrange (galsim.lanczos property)": [[48, "galsim.Lanczos.xrange"]], "xrange (galsim.linear property)": [[48, "galsim.Linear.xrange"]], "xrange (galsim.nearest property)": [[48, "galsim.Nearest.xrange"]], "xrange (galsim.quintic property)": [[48, "galsim.Quintic.xrange"]], "xrange (galsim.sincinterpolant property)": [[48, "galsim.SincInterpolant.xrange"]], "xval() (galsim.interpolant method)": [[48, "galsim.Interpolant.xval"]], "capturelog (class in galsim.utilities)": [[49, "galsim.utilities.CaptureLog"]], "lru_cache (class in galsim.utilities)": [[49, "galsim.utilities.LRU_Cache"]], "orderedweakref (class in galsim.utilities)": [[49, "galsim.utilities.OrderedWeakRef"]], "profile (class in galsim.utilities)": [[49, "galsim.utilities.Profile"]], "simplegenerator (class in galsim.utilities)": [[49, "galsim.utilities.SimpleGenerator"]], "weakmethod (class in galsim.utilities)": [[49, "galsim.utilities.WeakMethod"]], "_horner() (in module galsim.utilities)": [[49, "galsim.utilities._horner"]], "_horner2d() (in module galsim.utilities)": [[49, "galsim.utilities._horner2d"]], "binomial() (in module galsim.utilities)": [[49, "galsim.utilities.binomial"]], "check_all_diff() (in module galsim.utilities)": [[49, "galsim.utilities.check_all_diff"]], "check_pickle() (in module galsim.utilities)": [[49, "galsim.utilities.check_pickle"]], "check_share_file() (in module galsim.utilities)": [[49, "galsim.utilities.check_share_file"]], "combine_wave_list() (in module galsim.utilities)": [[49, "galsim.utilities.combine_wave_list"]], "convert_interpolant() (in module galsim.utilities)": [[49, "galsim.utilities.convert_interpolant"]], "deinterleaveimage() (in module galsim.utilities)": [[49, "galsim.utilities.deInterleaveImage"]], "doc_inherit (class in galsim.utilities)": [[49, "galsim.utilities.doc_inherit"]], "dol_to_lod() (in module galsim.utilities)": [[49, "galsim.utilities.dol_to_lod"]], "ensure_dir() (in module galsim.utilities)": [[49, "galsim.utilities.ensure_dir"]], "find_out_of_bounds_position() (in module galsim.utilities)": [[49, "galsim.utilities.find_out_of_bounds_position"]], "functionize() (in module galsim.utilities)": [[49, "galsim.utilities.functionize"]], "g1g2_to_e1e2() (in module galsim.utilities)": [[49, "galsim.utilities.g1g2_to_e1e2"]], "get_omp_threads() (in module galsim.utilities)": [[49, "galsim.utilities.get_omp_threads"]], "horner() (in module galsim.utilities)": [[49, "galsim.utilities.horner"]], "horner2d() (in module galsim.utilities)": [[49, "galsim.utilities.horner2d"]], "interleaveimages() (in module galsim.utilities)": [[49, "galsim.utilities.interleaveImages"]], "isinteger() (in module galsim.utilities)": [[49, "galsim.utilities.isinteger"]], "kxky() (in module galsim.utilities)": [[49, "galsim.utilities.kxky"]], "lazy_property (class in galsim.utilities)": [[49, "galsim.utilities.lazy_property"]], "listify() (in module galsim.utilities)": [[49, "galsim.utilities.listify"]], "math_eval() (in module galsim.utilities)": [[49, "galsim.utilities.math_eval"]], "merge_sorted() (in module galsim.utilities)": [[49, "galsim.utilities.merge_sorted"]], "ncr() (in module galsim.utilities)": [[49, "galsim.utilities.nCr"]], "old_thin_tabulated_values() (in module galsim.utilities)": [[49, "galsim.utilities.old_thin_tabulated_values"]], "parse_pos_args() (in module galsim.utilities)": [[49, "galsim.utilities.parse_pos_args"]], "pickle_shared() (in module galsim.utilities)": [[49, "galsim.utilities.pickle_shared"]], "printoptions() (in module galsim.utilities)": [[49, "galsim.utilities.printoptions"]], "rand_arr() (in module galsim.utilities)": [[49, "galsim.utilities.rand_arr"]], "rand_with_replacement() (in module galsim.utilities)": [[49, "galsim.utilities.rand_with_replacement"]], "resize() (galsim.utilities.lru_cache method)": [[49, "galsim.utilities.LRU_Cache.resize"]], "roll2d() (in module galsim.utilities)": [[49, "galsim.utilities.roll2d"]], "rotate_xy() (in module galsim.utilities)": [[49, "galsim.utilities.rotate_xy"]], "set_omp_threads() (in module galsim.utilities)": [[49, "galsim.utilities.set_omp_threads"]], "single_threaded (class in galsim.utilities)": [[49, "galsim.utilities.single_threaded"]], "structure_function() (in module galsim.utilities)": [[49, "galsim.utilities.structure_function"]], "thin_tabulated_values() (in module galsim.utilities)": [[49, "galsim.utilities.thin_tabulated_values"]], "timer (class in galsim.utilities)": [[49, "galsim.utilities.timer"]], "unweighted_moments() (in module galsim.utilities)": [[49, "galsim.utilities.unweighted_moments"]], "unweighted_shape() (in module galsim.utilities)": [[49, "galsim.utilities.unweighted_shape"]], "cosmology (class in galsim)": [[50, "galsim.Cosmology"]], "da() (galsim.cosmology method)": [[50, "galsim.Cosmology.Da"]], "e() (galsim.cosmology method)": [[50, "galsim.Cosmology.E"]], "nfwhalo (class in galsim)": [[50, "galsim.NFWHalo"]], "_getconvergence() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo._getConvergence"]], "_getlensing() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo._getLensing"]], "_getmagnification() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo._getMagnification"]], "_getshear() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo._getShear"]], "a() (galsim.cosmology method)": [[50, "galsim.Cosmology.a"]], "getconvergence() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo.getConvergence"]], "getlensing() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo.getLensing"]], "getmagnification() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo.getMagnification"]], "getshear() (galsim.nfwhalo method)": [[50, "galsim.NFWHalo.getShear"]], "basenoise (class in galsim)": [[51, "galsim.BaseNoise"]], "ccdnoise (class in galsim)": [[51, "galsim.CCDNoise"]], "deviatenoise (class in galsim)": [[51, "galsim.DeviateNoise"]], "gaussiannoise (class in galsim)": [[51, "galsim.GaussianNoise"]], "poissonnoise (class in galsim)": [[51, "galsim.PoissonNoise"]], "variablegaussiannoise (class in galsim)": [[51, "galsim.VariableGaussianNoise"]], "__div__() (galsim.basenoise method)": [[51, "galsim.BaseNoise.__div__"]], "__mul__() (galsim.basenoise method)": [[51, "galsim.BaseNoise.__mul__"]], "applyto() (galsim.basenoise method)": [[51, "galsim.BaseNoise.applyTo"]], "applyto() (galsim.variablegaussiannoise method)": [[51, "galsim.VariableGaussianNoise.applyTo"]], "copy() (galsim.ccdnoise method)": [[51, "galsim.CCDNoise.copy"]], "copy() (galsim.deviatenoise method)": [[51, "galsim.DeviateNoise.copy"]], "copy() (galsim.gaussiannoise method)": [[51, "galsim.GaussianNoise.copy"]], "copy() (galsim.poissonnoise method)": [[51, "galsim.PoissonNoise.copy"]], "copy() (galsim.variablegaussiannoise method)": [[51, "galsim.VariableGaussianNoise.copy"]], "gain (galsim.ccdnoise property)": [[51, "galsim.CCDNoise.gain"]], "getvariance() (galsim.basenoise method)": [[51, "galsim.BaseNoise.getVariance"]], "read_noise (galsim.ccdnoise property)": [[51, "galsim.CCDNoise.read_noise"]], "rng (galsim.basenoise property)": [[51, "galsim.BaseNoise.rng"]], "sigma (galsim.gaussiannoise property)": [[51, "galsim.GaussianNoise.sigma"]], "sky_level (galsim.ccdnoise property)": [[51, "galsim.CCDNoise.sky_level"]], "sky_level (galsim.poissonnoise property)": [[51, "galsim.PoissonNoise.sky_level"]], "var_image (galsim.variablegaussiannoise property)": [[51, "galsim.VariableGaussianNoise.var_image"]], "withscaledvariance() (galsim.basenoise method)": [[51, "galsim.BaseNoise.withScaledVariance"]], "withvariance() (galsim.basenoise method)": [[51, "galsim.BaseNoise.withVariance"]], "aperture (class in galsim)": [[54, "galsim.Aperture"]], "atmosphere() (in module galsim)": [[54, "galsim.Atmosphere"]], "atmosphericscreen (class in galsim)": [[54, "galsim.AtmosphericScreen"]], "opticalscreen (class in galsim)": [[54, "galsim.OpticalScreen"]], "phasescreenlist (class in galsim)": [[54, "galsim.PhaseScreenList"]], "phasescreenpsf (class in galsim)": [[54, "galsim.PhaseScreenPSF"]], "secondkick (class in galsim)": [[54, "galsim.SecondKick"]], "userscreen (class in galsim)": [[54, "galsim.UserScreen"]], "altitude (galsim.atmosphericscreen property)": [[54, "galsim.AtmosphericScreen.altitude"]], "diam (galsim.aperture property)": [[54, "galsim.Aperture.diam"]], "diam (galsim.secondkick property)": [[54, "galsim.SecondKick.diam"]], "fft_sign (galsim.phasescreenpsf property)": [[54, "galsim.PhaseScreenPSF.fft_sign"]], "flux (galsim.phasescreenpsf property)": [[54, "galsim.PhaseScreenPSF.flux"]], "gsparams (galsim.aperture property)": [[54, "galsim.Aperture.gsparams"]], "illuminated (galsim.aperture property)": [[54, "galsim.Aperture.illuminated"]], "initworker() (in module galsim.phase_screens)": [[54, "galsim.phase_screens.initWorker"]], "initworkerargs() (in module galsim.phase_screens)": [[54, "galsim.phase_screens.initWorkerArgs"]], "instantiate() (galsim.atmosphericscreen method)": [[54, "galsim.AtmosphericScreen.instantiate"]], "instantiate() (galsim.phasescreenlist method)": [[54, "galsim.PhaseScreenList.instantiate"]], "kcrit (galsim.phasescreenpsf property)": [[54, "galsim.PhaseScreenPSF.kcrit"]], "kcrit (galsim.secondkick property)": [[54, "galsim.SecondKick.kcrit"]], "kmax (galsim.atmosphericscreen property)": [[54, "galsim.AtmosphericScreen.kmax"]], "kmin (galsim.atmosphericscreen property)": [[54, "galsim.AtmosphericScreen.kmin"]], "lam (galsim.secondkick property)": [[54, "galsim.SecondKick.lam"]], "makepsf() (galsim.phasescreenlist method)": [[54, "galsim.PhaseScreenList.makePSF"]], "npix (galsim.aperture property)": [[54, "galsim.Aperture.npix"]], "obscuration (galsim.aperture property)": [[54, "galsim.Aperture.obscuration"]], "obscuration (galsim.secondkick property)": [[54, "galsim.SecondKick.obscuration"]], "pupil_plane_scale (galsim.aperture property)": [[54, "galsim.Aperture.pupil_plane_scale"]], "pupil_plane_size (galsim.aperture property)": [[54, "galsim.Aperture.pupil_plane_size"]], "r0 (galsim.secondkick property)": [[54, "galsim.SecondKick.r0"]], "reset_shared_screens() (in module galsim.phase_screens)": [[54, "galsim.phase_screens.reset_shared_screens"]], "samplepupil() (galsim.aperture method)": [[54, "galsim.Aperture.samplePupil"]], "scale_unit (galsim.secondkick property)": [[54, "galsim.SecondKick.scale_unit"]], "screen_list (galsim.phasescreenpsf property)": [[54, "galsim.PhaseScreenPSF.screen_list"]], "u (galsim.aperture property)": [[54, "galsim.Aperture.u"]], "v (galsim.aperture property)": [[54, "galsim.Aperture.v"]], "wavefront() (galsim.atmosphericscreen method)": [[54, "galsim.AtmosphericScreen.wavefront"]], "wavefront() (galsim.opticalscreen method)": [[54, "galsim.OpticalScreen.wavefront"]], "wavefront() (galsim.phasescreenlist method)": [[54, "galsim.PhaseScreenList.wavefront"]], "wavefront() (galsim.userscreen method)": [[54, "galsim.UserScreen.wavefront"]], "wavefront_gradient() (galsim.atmosphericscreen method)": [[54, "galsim.AtmosphericScreen.wavefront_gradient"]], "wavefront_gradient() (galsim.opticalscreen method)": [[54, "galsim.OpticalScreen.wavefront_gradient"]], "wavefront_gradient() (galsim.phasescreenlist method)": [[54, "galsim.PhaseScreenList.wavefront_gradient"]], "wavefront_gradient() (galsim.userscreen method)": [[54, "galsim.UserScreen.wavefront_gradient"]], "withflux() (galsim.phasescreenpsf method)": [[54, "galsim.PhaseScreenPSF.withFlux"]], "withflux() (galsim.secondkick method)": [[54, "galsim.SecondKick.withFlux"]], "withgsparams() (galsim.aperture method)": [[54, "galsim.Aperture.withGSParams"]], "withgsparams() (galsim.phasescreenpsf method)": [[54, "galsim.PhaseScreenPSF.withGSParams"]], "photonarray (class in galsim)": [[56, "galsim.PhotonArray"]], "addto() (galsim.photonarray method)": [[56, "galsim.PhotonArray.addTo"]], "allocateangles() (galsim.photonarray method)": [[56, "galsim.PhotonArray.allocateAngles"]], "allocatepupil() (galsim.photonarray method)": [[56, "galsim.PhotonArray.allocatePupil"]], "allocatetimes() (galsim.photonarray method)": [[56, "galsim.PhotonArray.allocateTimes"]], "allocatewavelengths() (galsim.photonarray method)": [[56, "galsim.PhotonArray.allocateWavelengths"]], "assignat() (galsim.photonarray method)": [[56, "galsim.PhotonArray.assignAt"]], "concatenate() (galsim.photonarray class method)": [[56, "galsim.PhotonArray.concatenate"]], "convolve() (galsim.photonarray method)": [[56, "galsim.PhotonArray.convolve"]], "copyfrom() (galsim.photonarray method)": [[56, "galsim.PhotonArray.copyFrom"]], "dxdz (galsim.photonarray property)": [[56, "galsim.PhotonArray.dxdz"]], "dydz (galsim.photonarray property)": [[56, "galsim.PhotonArray.dydz"]], "flux (galsim.photonarray property)": [[56, "galsim.PhotonArray.flux"]], "fromarrays() (galsim.photonarray class method)": [[56, "galsim.PhotonArray.fromArrays"]], "gettotalflux() (galsim.photonarray method)": [[56, "galsim.PhotonArray.getTotalFlux"]], "hasallocatedangles() (galsim.photonarray method)": [[56, "galsim.PhotonArray.hasAllocatedAngles"]], "hasallocatedpupil() (galsim.photonarray method)": [[56, "galsim.PhotonArray.hasAllocatedPupil"]], "hasallocatedtimes() (galsim.photonarray method)": [[56, "galsim.PhotonArray.hasAllocatedTimes"]], "hasallocatedwavelengths() (galsim.photonarray method)": [[56, "galsim.PhotonArray.hasAllocatedWavelengths"]], "iscorrelated() (galsim.photonarray method)": [[56, "galsim.PhotonArray.isCorrelated"]], "makefromimage() (galsim.photonarray class method)": [[56, "galsim.PhotonArray.makeFromImage"]], "pupil_u (galsim.photonarray property)": [[56, "galsim.PhotonArray.pupil_u"]], "pupil_v (galsim.photonarray property)": [[56, "galsim.PhotonArray.pupil_v"]], "read() (galsim.photonarray class method)": [[56, "galsim.PhotonArray.read"]], "scaleflux() (galsim.photonarray method)": [[56, "galsim.PhotonArray.scaleFlux"]], "scalexy() (galsim.photonarray method)": [[56, "galsim.PhotonArray.scaleXY"]], "setcorrelated() (galsim.photonarray method)": [[56, "galsim.PhotonArray.setCorrelated"]], "settotalflux() (galsim.photonarray method)": [[56, "galsim.PhotonArray.setTotalFlux"]], "size() (galsim.photonarray method)": [[56, "galsim.PhotonArray.size"]], "time (galsim.photonarray property)": [[56, "galsim.PhotonArray.time"]], "wavelength (galsim.photonarray property)": [[56, "galsim.PhotonArray.wavelength"]], "write() (galsim.photonarray method)": [[56, "galsim.PhotonArray.write"]], "x (galsim.photonarray property)": [[56, "galsim.PhotonArray.x"]], "y (galsim.photonarray property)": [[56, "galsim.PhotonArray.y"]], "fratioangles (class in galsim)": [[57, "galsim.FRatioAngles"]], "focusdepth (class in galsim)": [[57, "galsim.FocusDepth"]], "photondcr (class in galsim)": [[57, "galsim.PhotonDCR"]], "photonop (class in galsim)": [[57, "galsim.PhotonOp"]], "pupilannulussampler (class in galsim)": [[57, "galsim.PupilAnnulusSampler"]], "pupilimagesampler (class in galsim)": [[57, "galsim.PupilImageSampler"]], "refraction (class in galsim)": [[57, "galsim.Refraction"]], "timesampler (class in galsim)": [[57, "galsim.TimeSampler"]], "wavelengthsampler (class in galsim)": [[57, "galsim.WavelengthSampler"]], "applyto() (galsim.fratioangles method)": [[57, "galsim.FRatioAngles.applyTo"]], "applyto() (galsim.focusdepth method)": [[57, "galsim.FocusDepth.applyTo"]], "applyto() (galsim.photondcr method)": [[57, "galsim.PhotonDCR.applyTo"]], "applyto() (galsim.photonop method)": [[57, "galsim.PhotonOp.applyTo"]], "applyto() (galsim.pupilannulussampler method)": [[57, "galsim.PupilAnnulusSampler.applyTo"]], "applyto() (galsim.pupilimagesampler method)": [[57, "galsim.PupilImageSampler.applyTo"]], "applyto() (galsim.refraction method)": [[57, "galsim.Refraction.applyTo"]], "applyto() (galsim.timesampler method)": [[57, "galsim.TimeSampler.applyTo"]], "applyto() (galsim.wavelengthsampler method)": [[57, "galsim.WavelengthSampler.applyTo"]], "position (class in galsim)": [[58, "galsim.Position"]], "positiond (class in galsim)": [[58, "galsim.PositionD"]], "positioni (class in galsim)": [[58, "galsim.PositionI"]], "round() (galsim.positiond method)": [[58, "galsim.PositionD.round"]], "shear() (galsim.position method)": [[58, "galsim.Position.shear"]], "powerspectrum (class in galsim)": [[59, "galsim.PowerSpectrum"]], "powerspectrumrealizer (class in galsim.lensing_ps)": [[59, "galsim.lensing_ps.PowerSpectrumRealizer"]], "__call__() (galsim.lensing_ps.powerspectrumrealizer method)": [[59, "galsim.lensing_ps.PowerSpectrumRealizer.__call__"]], "_getconvergence() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum._getConvergence"]], "_getlensing() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum._getLensing"]], "_getmagnification() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum._getMagnification"]], "_getshear() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum._getShear"]], "buildgrid() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.buildGrid"]], "calculatexi() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.calculateXi"]], "getconvergence() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.getConvergence"]], "getlensing() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.getLensing"]], "getmagnification() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.getMagnification"]], "getshear() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.getShear"]], "nrandcallsforbuildgrid() (galsim.powerspectrum method)": [[59, "galsim.PowerSpectrum.nRandCallsForBuildGrid"]], "theorytoobserved() (in module galsim.lensing_ps)": [[59, "galsim.lensing_ps.theoryToObserved"]], "powerspectrumestimator (class in galsim.pse)": [[60, "galsim.pse.PowerSpectrumEstimator"]], "estimate() (galsim.pse.powerspectrumestimator method)": [[60, "galsim.pse.PowerSpectrumEstimator.estimate"]], "airy (class in galsim)": [[61, "galsim.Airy"]], "kolmogorov (class in galsim)": [[61, "galsim.Kolmogorov"]], "l0 (galsim.vonkarman property)": [[61, "galsim.VonKarman.L0"]], "moffat (class in galsim)": [[61, "galsim.Moffat"]], "opticalpsf (class in galsim)": [[61, "galsim.OpticalPSF"]], "vonkarman (class in galsim)": [[61, "galsim.VonKarman"]], "beta (galsim.moffat property)": [[61, "galsim.Moffat.beta"]], "delta_amplitude (galsim.vonkarman property)": [[61, "galsim.VonKarman.delta_amplitude"]], "do_delta (galsim.vonkarman property)": [[61, "galsim.VonKarman.do_delta"]], "force_stepk (galsim.vonkarman property)": [[61, "galsim.VonKarman.force_stepk"]], "fwhm (galsim.airy property)": [[61, "galsim.Airy.fwhm"]], "fwhm (galsim.kolmogorov property)": [[61, "galsim.Kolmogorov.fwhm"]], "half_light_radius (galsim.airy property)": [[61, "galsim.Airy.half_light_radius"]], "half_light_radius (galsim.kolmogorov property)": [[61, "galsim.Kolmogorov.half_light_radius"]], "half_light_radius (galsim.moffat property)": [[61, "galsim.Moffat.half_light_radius"]], "half_light_radius (galsim.vonkarman property)": [[61, "galsim.VonKarman.half_light_radius"]], "lam (galsim.vonkarman property)": [[61, "galsim.VonKarman.lam"]], "lam_over_diam (galsim.airy property)": [[61, "galsim.Airy.lam_over_diam"]], "lam_over_r0 (galsim.kolmogorov property)": [[61, "galsim.Kolmogorov.lam_over_r0"]], "obscuration (galsim.airy property)": [[61, "galsim.Airy.obscuration"]], "r0 (galsim.vonkarman property)": [[61, "galsim.VonKarman.r0"]], "r0_500 (galsim.vonkarman property)": [[61, "galsim.VonKarman.r0_500"]], "scale_radius (galsim.moffat property)": [[61, "galsim.Moffat.scale_radius"]], "scale_unit (galsim.vonkarman property)": [[61, "galsim.VonKarman.scale_unit"]], "trunc (galsim.moffat property)": [[61, "galsim.Moffat.trunc"]], "withflux() (galsim.airy method)": [[61, "galsim.Airy.withFlux"]], "withflux() (galsim.kolmogorov method)": [[61, "galsim.Kolmogorov.withFlux"]], "withflux() (galsim.moffat method)": [[61, "galsim.Moffat.withFlux"]], "withflux() (galsim.opticalpsf method)": [[61, "galsim.OpticalPSF.withFlux"]], "withflux() (galsim.vonkarman method)": [[61, "galsim.VonKarman.withFlux"]], "cosmoscatalog (class in galsim)": [[63, "galsim.COSMOSCatalog"]], "galaxysample (class in galsim)": [[63, "galsim.GalaxySample"]], "realgalaxy (class in galsim)": [[63, "galsim.RealGalaxy"]], "realgalaxycatalog (class in galsim)": [[63, "galsim.RealGalaxyCatalog"]], "canmakereal() (galsim.galaxysample method)": [[63, "galsim.GalaxySample.canMakeReal"]], "getbandpass() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getBandpass"]], "getgalimage() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getGalImage"]], "getindexforid() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getIndexForID"]], "getnoise() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getNoise"]], "getnoiseproperties() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getNoiseProperties"]], "getpsf() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getPSF"]], "getpsfimage() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.getPSFImage"]], "getparametricrecord() (galsim.galaxysample method)": [[63, "galsim.GalaxySample.getParametricRecord"]], "getrealparams() (galsim.galaxysample method)": [[63, "galsim.GalaxySample.getRealParams"]], "getvalue() (galsim.galaxysample method)": [[63, "galsim.GalaxySample.getValue"]], "makefromimage() (galsim.realgalaxy class method)": [[63, "galsim.RealGalaxy.makeFromImage"]], "makegalaxy() (galsim.galaxysample method)": [[63, "galsim.GalaxySample.makeGalaxy"]], "preload() (galsim.realgalaxycatalog method)": [[63, "galsim.RealGalaxyCatalog.preload"]], "selectrandomindex() (galsim.galaxysample method)": [[63, "galsim.GalaxySample.selectRandomIndex"]], "withgsparams() (galsim.realgalaxy method)": [[63, "galsim.RealGalaxy.withGSParams"]], "addreciprocityfailure() (in module galsim.roman)": [[64, "galsim.roman.addReciprocityFailure"]], "alldetectoreffects() (in module galsim.roman)": [[64, "galsim.roman.allDetectorEffects"]], "allowedpos() (in module galsim.roman)": [[64, "galsim.roman.allowedPos"]], "applyipc() (in module galsim.roman)": [[64, "galsim.roman.applyIPC"]], "applynonlinearity() (in module galsim.roman)": [[64, "galsim.roman.applyNonlinearity"]], "applypersistence() (in module galsim.roman)": [[64, "galsim.roman.applyPersistence"]], "bestpa() (in module galsim.roman)": [[64, "galsim.roman.bestPA"]], "convertcenter() (in module galsim.roman)": [[64, "galsim.roman.convertCenter"]], "findsca() (in module galsim.roman)": [[64, "galsim.roman.findSCA"]], "getbandpasses() (in module galsim.roman)": [[64, "galsim.roman.getBandpasses"]], "getpsf() (in module galsim.roman)": [[64, "galsim.roman.getPSF"]], "getskylevel() (in module galsim.roman)": [[64, "galsim.roman.getSkyLevel"]], "getwcs() (in module galsim.roman)": [[64, "galsim.roman.getWCS"]], "emissionline (class in galsim)": [[66, "galsim.EmissionLine"]], "sed (class in galsim)": [[66, "galsim.SED"]], "__call__() (galsim.sed method)": [[66, "galsim.SED.__call__"]], "__mul__() (galsim.sed method)": [[66, "galsim.SED.__mul__"]], "_mul_bandpass() (galsim.sed method)": [[66, "galsim.SED._mul_bandpass"]], "_mul_scalar() (galsim.sed method)": [[66, "galsim.SED._mul_scalar"]], "_mul_sed() (galsim.sed method)": [[66, "galsim.SED._mul_sed"]], "atredshift() (galsim.emissionline method)": [[66, "galsim.EmissionLine.atRedshift"]], "atredshift() (galsim.sed method)": [[66, "galsim.SED.atRedshift"]], "calculatedcrmomentshifts() (galsim.sed method)": [[66, "galsim.SED.calculateDCRMomentShifts"]], "calculateflux() (galsim.sed method)": [[66, "galsim.SED.calculateFlux"]], "calculatemagnitude() (galsim.sed method)": [[66, "galsim.SED.calculateMagnitude"]], "calculateseeingmomentratio() (galsim.sed method)": [[66, "galsim.SED.calculateSeeingMomentRatio"]], "check_dimensionless() (galsim.sed method)": [[66, "galsim.SED.check_dimensionless"]], "check_spectral() (galsim.sed method)": [[66, "galsim.SED.check_spectral"]], "dimensionless (galsim.sed property)": [[66, "galsim.SED.dimensionless"]], "samplewavelength() (galsim.sed method)": [[66, "galsim.SED.sampleWavelength"]], "thin() (galsim.sed method)": [[66, "galsim.SED.thin"]], "withflux() (galsim.sed method)": [[66, "galsim.SED.withFlux"]], "withfluxdensity() (galsim.sed method)": [[66, "galsim.SED.withFluxDensity"]], "withmagnitude() (galsim.sed method)": [[66, "galsim.SED.withMagnitude"]], "sensor (class in galsim)": [[67, "galsim.Sensor"]], "siliconsensor (class in galsim)": [[67, "galsim.SiliconSensor"]], "accumulate() (galsim.sensor method)": [[67, "galsim.Sensor.accumulate"]], "accumulate() (galsim.siliconsensor method)": [[67, "galsim.SiliconSensor.accumulate"]], "calculate_pixel_areas() (galsim.sensor method)": [[67, "galsim.Sensor.calculate_pixel_areas"]], "calculate_pixel_areas() (galsim.siliconsensor method)": [[67, "galsim.SiliconSensor.calculate_pixel_areas"]], "simple_treerings() (galsim.siliconsensor class method)": [[67, "galsim.SiliconSensor.simple_treerings"]], "galsim.meta_data.share_dir (built-in variable)": [[68, "galsim.meta_data.share_dir"]], "shear (class in galsim)": [[69, "galsim.Shear"]], "_shear() (in module galsim)": [[69, "galsim._Shear"]], "beta (galsim.shear property)": [[69, "galsim.Shear.beta"]], "e (galsim.shear property)": [[69, "galsim.Shear.e"]], "e1 (galsim.shear property)": [[69, "galsim.Shear.e1"]], "e2 (galsim.shear property)": [[69, "galsim.Shear.e2"]], "esq (galsim.shear property)": [[69, "galsim.Shear.esq"]], "eta (galsim.shear property)": [[69, "galsim.Shear.eta"]], "eta1 (galsim.shear property)": [[69, "galsim.Shear.eta1"]], "eta2 (galsim.shear property)": [[69, "galsim.Shear.eta2"]], "g (galsim.shear property)": [[69, "galsim.Shear.g"]], "g1 (galsim.shear property)": [[69, "galsim.Shear.g1"]], "g2 (galsim.shear property)": [[69, "galsim.Shear.g2"]], "getmatrix() (galsim.shear method)": [[69, "galsim.Shear.getMatrix"]], "q (galsim.shear property)": [[69, "galsim.Shear.q"]], "rotationwith() (galsim.shear method)": [[69, "galsim.Shear.rotationWith"]], "shear (galsim.shear property)": [[69, "galsim.Shear.shear"]], "box (class in galsim)": [[70, "galsim.Box"]], "deltafunction (class in galsim)": [[70, "galsim.DeltaFunction"]], "gaussian (class in galsim)": [[70, "galsim.Gaussian"]], "pixel (class in galsim)": [[70, "galsim.Pixel"]], "tophat (class in galsim)": [[70, "galsim.TopHat"]], "fwhm (galsim.gaussian property)": [[70, "galsim.Gaussian.fwhm"]], "half_light_radius (galsim.gaussian property)": [[70, "galsim.Gaussian.half_light_radius"]], "height (galsim.box property)": [[70, "galsim.Box.height"]], "radius (galsim.tophat property)": [[70, "galsim.TopHat.radius"]], "scale (galsim.pixel property)": [[70, "galsim.Pixel.scale"]], "sigma (galsim.gaussian property)": [[70, "galsim.Gaussian.sigma"]], "width (galsim.box property)": [[70, "galsim.Box.width"]], "withflux() (galsim.box method)": [[70, "galsim.Box.withFlux"]], "withflux() (galsim.deltafunction method)": [[70, "galsim.DeltaFunction.withFlux"]], "withflux() (galsim.gaussian method)": [[70, "galsim.Gaussian.withFlux"]], "withflux() (galsim.pixel method)": [[70, "galsim.Pixel.withFlux"]], "withflux() (galsim.tophat method)": [[70, "galsim.TopHat.withFlux"]], "covariancespectrum (class in galsim)": [[71, "galsim.CovarianceSpectrum"]], "tonoise() (galsim.covariancespectrum method)": [[71, "galsim.CovarianceSpectrum.toNoise"]], "lookuptable (class in galsim)": [[72, "galsim.LookupTable"]], "lookuptable2d (class in galsim)": [[72, "galsim.LookupTable2D"]], "_lookuptable2d() (in module galsim.table)": [[72, "galsim.table._LookupTable2D"]], "__call__() (galsim.lookuptable method)": [[72, "galsim.LookupTable.__call__"]], "__call__() (galsim.lookuptable2d method)": [[72, "galsim.LookupTable2D.__call__"]], "from_file() (galsim.lookuptable class method)": [[72, "galsim.LookupTable.from_file"]], "from_func() (galsim.lookuptable class method)": [[72, "galsim.LookupTable.from_func"]], "gradient() (galsim.lookuptable2d method)": [[72, "galsim.LookupTable2D.gradient"]], "integrate() (galsim.lookuptable method)": [[72, "galsim.LookupTable.integrate"]], "integrate_product() (galsim.lookuptable method)": [[72, "galsim.LookupTable.integrate_product"]], "trapz() (in module galsim)": [[72, "galsim.trapz"]], "x_max (galsim.lookuptable property)": [[72, "galsim.LookupTable.x_max"]], "x_min (galsim.lookuptable property)": [[72, "galsim.LookupTable.x_min"]], "deconvolution (class in galsim)": [[73, "galsim.Deconvolution"]], "deconvolve() (in module galsim)": [[73, "galsim.Deconvolve"]], "fouriersqrt() (in module galsim)": [[73, "galsim.FourierSqrt"]], "fouriersqrtprofile (class in galsim)": [[73, "galsim.FourierSqrtProfile"]], "transform() (in module galsim)": [[73, "galsim.Transform"]], "transformation (class in galsim)": [[73, "galsim.Transformation"]], "_transform() (in module galsim)": [[73, "galsim._Transform"]], "flux_ratio (galsim.transformation property)": [[73, "galsim.Transformation.flux_ratio"]], "jac (galsim.transformation property)": [[73, "galsim.Transformation.jac"]], "offset (galsim.transformation property)": [[73, "galsim.Transformation.offset"]], "orig_obj (galsim.deconvolution property)": [[73, "galsim.Deconvolution.orig_obj"]], "orig_obj (galsim.fouriersqrtprofile property)": [[73, "galsim.FourierSqrtProfile.orig_obj"]], "original (galsim.transformation property)": [[73, "galsim.Transformation.original"]], "withgsparams() (galsim.deconvolution method)": [[73, "galsim.Deconvolution.withGSParams"]], "withgsparams() (galsim.fouriersqrtprofile method)": [[73, "galsim.FourierSqrtProfile.withGSParams"]], "withgsparams() (galsim.transformation method)": [[73, "galsim.Transformation.withGSParams"]], "angle (class in galsim)": [[75, "galsim.Angle"]], "angleunit (class in galsim)": [[75, "galsim.AngleUnit"]], "_angle() (in module galsim)": [[75, "galsim._Angle"]], "cos() (galsim.angle method)": [[75, "galsim.Angle.cos"]], "deg (galsim.angle property)": [[75, "galsim.Angle.deg"]], "dms() (galsim.angle method)": [[75, "galsim.Angle.dms"]], "from_dms() (galsim.angle static method)": [[75, "galsim.Angle.from_dms"]], "from_hms() (galsim.angle static method)": [[75, "galsim.Angle.from_hms"]], "from_name() (galsim.angleunit static method)": [[75, "galsim.AngleUnit.from_name"]], "hms() (galsim.angle method)": [[75, "galsim.Angle.hms"]], "rad (galsim.angle property)": [[75, "galsim.Angle.rad"]], "sin() (galsim.angle method)": [[75, "galsim.Angle.sin"]], "sincos() (galsim.angle method)": [[75, "galsim.Angle.sincos"]], "tan() (galsim.angle method)": [[75, "galsim.Angle.tan"]], "value (galsim.angleunit property)": [[75, "galsim.AngleUnit.value"]], "wrap() (galsim.angle method)": [[75, "galsim.Angle.wrap"]], "affinetransform (class in galsim)": [[77, "galsim.AffineTransform"]], "astropywcs (class in galsim)": [[77, "galsim.AstropyWCS"]], "basewcs (class in galsim)": [[77, "galsim.BaseWCS"]], "celestialcoord (class in galsim)": [[77, "galsim.CelestialCoord"]], "celestialwcs (class in galsim.wcs)": [[77, "galsim.wcs.CelestialWCS"]], "euclideanwcs (class in galsim.wcs)": [[77, "galsim.wcs.EuclideanWCS"]], "fitswcs() (in module galsim)": [[77, "galsim.FitsWCS"]], "fittedsipwcs() (in module galsim)": [[77, "galsim.FittedSIPWCS"]], "gsfitswcs (class in galsim)": [[77, "galsim.GSFitsWCS"]], "jacobianwcs (class in galsim)": [[77, "galsim.JacobianWCS"]], "localwcs (class in galsim.wcs)": [[77, "galsim.wcs.LocalWCS"]], "offsetshearwcs (class in galsim)": [[77, "galsim.OffsetShearWCS"]], "offsetwcs (class in galsim)": [[77, "galsim.OffsetWCS"]], "pixelscale (class in galsim)": [[77, "galsim.PixelScale"]], "pyastwcs (class in galsim)": [[77, "galsim.PyAstWCS"]], "radecfunction (class in galsim)": [[77, "galsim.RaDecFunction"]], "shearwcs (class in galsim)": [[77, "galsim.ShearWCS"]], "tanwcs() (in module galsim)": [[77, "galsim.TanWCS"]], "uvfunction (class in galsim)": [[77, "galsim.UVFunction"]], "uniformwcs (class in galsim.wcs)": [[77, "galsim.wcs.UniformWCS"]], "wcstoolswcs (class in galsim)": [[77, "galsim.WcsToolsWCS"]], "affine() (galsim.basewcs method)": [[77, "galsim.BaseWCS.affine"]], "anglebetween() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.angleBetween"]], "area() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.area"]], "compatible() (in module galsim.wcs)": [[77, "galsim.wcs.compatible"]], "dec (galsim.celestialcoord property)": [[77, "galsim.CelestialCoord.dec"]], "deproject() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.deproject"]], "deproject_rad() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.deproject_rad"]], "distanceto() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.distanceTo"]], "dudx (galsim.affinetransform property)": [[77, "galsim.AffineTransform.dudx"]], "dudx (galsim.jacobianwcs property)": [[77, "galsim.JacobianWCS.dudx"]], "dudy (galsim.affinetransform property)": [[77, "galsim.AffineTransform.dudy"]], "dudy (galsim.jacobianwcs property)": [[77, "galsim.JacobianWCS.dudy"]], "dvdx (galsim.affinetransform property)": [[77, "galsim.AffineTransform.dvdx"]], "dvdx (galsim.jacobianwcs property)": [[77, "galsim.JacobianWCS.dvdx"]], "dvdy (galsim.affinetransform property)": [[77, "galsim.AffineTransform.dvdy"]], "dvdy (galsim.jacobianwcs property)": [[77, "galsim.JacobianWCS.dvdy"]], "ecliptic() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.ecliptic"]], "file_name (galsim.wcstoolswcs property)": [[77, "galsim.WcsToolsWCS.file_name"]], "fixcolor() (galsim.basewcs method)": [[77, "galsim.BaseWCS.fixColor"]], "from_ecliptic() (galsim.celestialcoord static method)": [[77, "galsim.CelestialCoord.from_ecliptic"]], "from_galactic() (galsim.celestialcoord static method)": [[77, "galsim.CelestialCoord.from_galactic"]], "from_xyz() (galsim.celestialcoord static method)": [[77, "galsim.CelestialCoord.from_xyz"]], "galactic() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.galactic"]], "getdecomposition() (galsim.jacobianwcs method)": [[77, "galsim.JacobianWCS.getDecomposition"]], "getmatrix() (galsim.jacobianwcs method)": [[77, "galsim.JacobianWCS.getMatrix"]], "get_xyz() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.get_xyz"]], "greatcirclepoint() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.greatCirclePoint"]], "inverse() (galsim.wcs.uniformwcs method)": [[77, "galsim.wcs.UniformWCS.inverse"]], "iscelestial() (galsim.basewcs method)": [[77, "galsim.BaseWCS.isCelestial"]], "islocal() (galsim.basewcs method)": [[77, "galsim.BaseWCS.isLocal"]], "ispixelscale() (galsim.basewcs method)": [[77, "galsim.BaseWCS.isPixelScale"]], "isuniform() (galsim.basewcs method)": [[77, "galsim.BaseWCS.isUniform"]], "jac_deproject() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.jac_deproject"]], "jac_deproject_rad() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.jac_deproject_rad"]], "jacobian() (galsim.basewcs method)": [[77, "galsim.BaseWCS.jacobian"]], "local() (galsim.basewcs method)": [[77, "galsim.BaseWCS.local"]], "makeskyimage() (galsim.basewcs method)": [[77, "galsim.BaseWCS.makeSkyImage"]], "maxlinearscale() (galsim.basewcs method)": [[77, "galsim.BaseWCS.maxLinearScale"]], "minlinearscale() (galsim.basewcs method)": [[77, "galsim.BaseWCS.minLinearScale"]], "normal() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.normal"]], "origin (galsim.affinetransform property)": [[77, "galsim.AffineTransform.origin"]], "origin (galsim.astropywcs property)": [[77, "galsim.AstropyWCS.origin"]], "origin (galsim.gsfitswcs property)": [[77, "galsim.GSFitsWCS.origin"]], "origin (galsim.offsetshearwcs property)": [[77, "galsim.OffsetShearWCS.origin"]], "origin (galsim.offsetwcs property)": [[77, "galsim.OffsetWCS.origin"]], "origin (galsim.pyastwcs property)": [[77, "galsim.PyAstWCS.origin"]], "origin (galsim.radecfunction property)": [[77, "galsim.RaDecFunction.origin"]], "origin (galsim.uvfunction property)": [[77, "galsim.UVFunction.origin"]], "origin (galsim.wcstoolswcs property)": [[77, "galsim.WcsToolsWCS.origin"]], "origin (galsim.wcs.localwcs property)": [[77, "galsim.wcs.LocalWCS.origin"]], "pixelarea() (galsim.basewcs method)": [[77, "galsim.BaseWCS.pixelArea"]], "postoimage() (galsim.basewcs method)": [[77, "galsim.BaseWCS.posToImage"]], "postoworld() (galsim.basewcs method)": [[77, "galsim.BaseWCS.posToWorld"]], "precess() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.precess"]], "profiletoimage() (galsim.basewcs method)": [[77, "galsim.BaseWCS.profileToImage"]], "profiletoworld() (galsim.basewcs method)": [[77, "galsim.BaseWCS.profileToWorld"]], "project() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.project"]], "project_rad() (galsim.celestialcoord method)": [[77, "galsim.CelestialCoord.project_rad"]], "ra (galsim.celestialcoord property)": [[77, "galsim.CelestialCoord.ra"]], "rad (galsim.celestialcoord property)": [[77, "galsim.CelestialCoord.rad"]], "radectoxy() (galsim.wcs.celestialwcs method)": [[77, "galsim.wcs.CelestialWCS.radecToxy"]], "radec_func (galsim.radecfunction property)": [[77, "galsim.RaDecFunction.radec_func"]], "radec_to_xyz() (galsim.celestialcoord static method)": [[77, "galsim.CelestialCoord.radec_to_xyz"]], "readfromfitsheader() (in module galsim.wcs)": [[77, "galsim.wcs.readFromFitsHeader"]], "scale (galsim.offsetshearwcs property)": [[77, "galsim.OffsetShearWCS.scale"]], "scale (galsim.offsetwcs property)": [[77, "galsim.OffsetWCS.scale"]], "scale (galsim.pixelscale property)": [[77, "galsim.PixelScale.scale"]], "scale (galsim.shearwcs property)": [[77, "galsim.ShearWCS.scale"]], "shear (galsim.offsetshearwcs property)": [[77, "galsim.OffsetShearWCS.shear"]], "shear (galsim.shearwcs property)": [[77, "galsim.ShearWCS.shear"]], "sheartoimage() (galsim.basewcs method)": [[77, "galsim.BaseWCS.shearToImage"]], "sheartoworld() (galsim.basewcs method)": [[77, "galsim.BaseWCS.shearToWorld"]], "shiftorigin() (galsim.basewcs method)": [[77, "galsim.BaseWCS.shiftOrigin"]], "toimage() (galsim.basewcs method)": [[77, "galsim.BaseWCS.toImage"]], "toworld() (galsim.basewcs method)": [[77, "galsim.BaseWCS.toWorld"]], "u0 (galsim.wcs.euclideanwcs property)": [[77, "galsim.wcs.EuclideanWCS.u0"]], "ufunc (galsim.uvfunction property)": [[77, "galsim.UVFunction.ufunc"]], "uvtoxy() (galsim.wcs.euclideanwcs method)": [[77, "galsim.wcs.EuclideanWCS.uvToxy"]], "v0 (galsim.wcs.euclideanwcs property)": [[77, "galsim.wcs.EuclideanWCS.v0"]], "vfunc (galsim.uvfunction property)": [[77, "galsim.UVFunction.vfunc"]], "wcs (galsim.astropywcs property)": [[77, "galsim.AstropyWCS.wcs"]], "wcsinfo (galsim.pyastwcs property)": [[77, "galsim.PyAstWCS.wcsinfo"]], "withorigin() (galsim.wcs.localwcs method)": [[77, "galsim.wcs.LocalWCS.withOrigin"]], "world_origin (galsim.affinetransform property)": [[77, "galsim.AffineTransform.world_origin"]], "world_origin (galsim.offsetshearwcs property)": [[77, "galsim.OffsetShearWCS.world_origin"]], "world_origin (galsim.offsetwcs property)": [[77, "galsim.OffsetWCS.world_origin"]], "world_origin (galsim.uvfunction property)": [[77, "galsim.UVFunction.world_origin"]], "world_origin (galsim.wcs.localwcs property)": [[77, "galsim.wcs.LocalWCS.world_origin"]], "writetofitsheader() (galsim.basewcs method)": [[77, "galsim.BaseWCS.writeToFitsHeader"]], "x0 (galsim.wcs.celestialwcs property)": [[77, "galsim.wcs.CelestialWCS.x0"]], "x0 (galsim.wcs.euclideanwcs property)": [[77, "galsim.wcs.EuclideanWCS.x0"]], "xfunc (galsim.uvfunction property)": [[77, "galsim.UVFunction.xfunc"]], "xytoradec() (galsim.wcs.celestialwcs method)": [[77, "galsim.wcs.CelestialWCS.xyToradec"]], "xytouv() (galsim.wcs.euclideanwcs method)": [[77, "galsim.wcs.EuclideanWCS.xyTouv"]], "xyz_to_radec() (galsim.celestialcoord static method)": [[77, "galsim.CelestialCoord.xyz_to_radec"]], "y0 (galsim.wcs.celestialwcs property)": [[77, "galsim.wcs.CelestialWCS.y0"]], "y0 (galsim.wcs.euclideanwcs property)": [[77, "galsim.wcs.EuclideanWCS.y0"]], "yfunc (galsim.uvfunction property)": [[77, "galsim.UVFunction.yfunc"]], "doublezernike (class in galsim.zernike)": [[79, "galsim.zernike.DoubleZernike"]], "zernike (class in galsim.zernike)": [[79, "galsim.zernike.Zernike"]], "__add__() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.__add__"]], "__call__() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.__call__"]], "__mul__() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.__mul__"]], "__neg__() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.__neg__"]], "__rmul__() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.__rmul__"]], "__sub__() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.__sub__"]], "describe_zernike() (in module galsim.zernike)": [[79, "galsim.zernike.describe_zernike"]], "doublezernikebasis() (in module galsim.zernike)": [[79, "galsim.zernike.doubleZernikeBasis"]], "evalcartesian() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.evalCartesian"]], "evalcartesiangrad() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.evalCartesianGrad"]], "evalpolar() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.evalPolar"]], "noll_to_zern() (in module galsim.zernike)": [[79, "galsim.zernike.noll_to_zern"]], "rotate() (galsim.zernike.zernike method)": [[79, "galsim.zernike.Zernike.rotate"]], "zernikebasis() (in module galsim.zernike)": [[79, "galsim.zernike.zernikeBasis"]], "zernikegradbases() (in module galsim.zernike)": [[79, "galsim.zernike.zernikeGradBases"]], "zernikerotmatrix() (in module galsim.zernike)": [[79, "galsim.zernike.zernikeRotMatrix"]]}}) \ No newline at end of file diff --git a/docs/_build/html/sed.html b/docs/_build/html/sed.html new file mode 100644 index 00000000000..2a35509c627 --- /dev/null +++ b/docs/_build/html/sed.html @@ -0,0 +1,564 @@ + + + + + + + Spectral Energy Distributions — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Spectral Energy Distributions

+
+
+class galsim.SED(spec, wave_type, flux_type, redshift=0.0, fast=True, interpolant='linear', _blue_limit=0.0, _red_limit=inf, _wave_list=None)[source]
+

Object to represent the spectral energy distributions of stars and galaxies.

+

SEDs are callable, usually returning the flux density in photons/nm/cm^2/s as a function of +wavelength, though SEDs are also used by GalSim to track dimensionless wavelength-dependent +normalizations, and may thus also return dimensionless values. By default, the above wavelength +used by __call__ is nanometers, but it’s possible to use other units via the astropy.units +module (at least, if the SED keyword argument fast=False, see below). For instance,:

+
>>> sed = galsim.SED(...)
+>>> from astropy import units as u
+>>> assert sed(500) == sed(5000 * u.AA)  # 500 nm == 5000 Angstroms
+
+
+

The python type of the return value depends on the type of the input wavelength(s). A scalar +input wavelength yields a scalar flux density, a tuple yields a tuple, a list yields a list, and +a numpy.ndarray yields a numpy.ndarray. A scalar astropy.units.Quantity yields a python scalar, +and a vector astropy.units.Quantity yields a numpy.ndarray.

+

SEDs are immutable; all transformative SED methods return new SEDs, and leave their +originating SEDs unaltered.

+

SEDs have blue_limit and red_limit attributes, which indicate the range over which the +SED is defined. An exception will be raised if the flux density or normalization is requested +outside of this range. Note that blue_limit and red_limit are always in nanometers and +in the observed frame when redshift != 0.

+

SEDs may be multiplied by scalars or scalar functions of wavelength. In particular, an SED +multiplied by a Bandpass will yield the appropriately filtered SED. Two SEDs may be +multiplied together if at least one of them represents a dimensionless normalization.

+

SEDs may be added together if they are at the same redshift. The resulting SED will only be +defined on the wavelength region where both of the operand SEDs are defined. blue_limit and +red_limit will be reset accordingly.

+

The input parameter, spec, may be one of several possible forms:

+
    +
  1. a regular python function (or an object that acts like a function)

  2. +
  3. a LookupTable

  4. +
  5. a file from which a LookupTable can be read in

  6. +
  7. a string which can be evaluated to a function of wave via eval('lambda wave:'+spec), +e.g.:

    +
    spec = '0.8 + 0.2 * (wave-800)'
    +
    +
    +
  8. +
  9. a python scalar (only possible for dimensionless SEDs)

  10. +
+

The argument of spec should be the wavelength in units specified by wave_type, which +should be an instance of astropy.units.Unit of equivalency class astropy.units.spectral, +or one of the case-insensitive aliases ‘nm’, ‘nanometer’, ‘nanometers’, ‘A’, ‘Ang’, ‘Angstrom’, +or ‘Angstroms’. Note that astropy.units.spectral includes not only units with dimensions +of length, but also frequency, energy, or wavenumber.

+

The return value of spec should be a spectral density with units specified by flux_type, +which should be an instance of astropy.units.Unit of equivalency class +astropy.units.spectral_density, or one of the case-insensitive aliases:

+
    +
  1. ‘flambda’: erg/wave_type/cm^2/s, where wave_type is as above.

  2. +
  3. ‘fnu’: erg/Hz/cm^2/s

  4. +
  5. ‘fphotons’: photons/wave_type/cm^2/s, where wave_type is as above.

  6. +
  7. ‘1’: dimensionless

  8. +
+

Note that the astropy.units.spectral_density class includes units with dimensions of +[energy/time/area/unit-wavelength], [energy/time/area/unit-frequency], +[photons/time/area/unit-wavelength], and so on.

+

Finally, the optional fast keyword option is used to specify when unit and dimension changes +are executed, particularly for SEDs specified by a LookupTable. If fast=True, the +default, then the input units/dimensions may be converted to an internal working unit before +interpolation in wavelength is performed. Alternatively, fast=False implies that +interpolation should take place in the native units of the input spec, and subsequently flux +density converted to photons/cm^2/s/nm afterwards. Generally, the former option is faster, but +may be less accurate since interpolation and dimensionality conversion do not commute. One +consequence of using fast=True is that __call__ can not accept an astropy.units.Quantity +in this case.

+
+
Parameters:
+
    +
  • spec – Function defining the z=0 spectrum at each wavelength. See above for +valid options for this parameter.

  • +
  • wave_type – String or astropy.unit specifying units for wavelength input to spec.

  • +
  • flux_type – String or astropy.unit specifying what type of spectral density or +dimensionless normalization spec represents. See above for valid +options for this parameter.

  • +
  • redshift – Optionally shift the spectrum to the given redshift. [default: 0]

  • +
  • fast – Convert units on initialization instead of on __call__. [default: True]

  • +
  • interpolant – If reading from a file, what interpolant to use. [default: ‘linear’]

  • +
+
+
+
+
+__call__(wave)[source]
+

Return photon flux density or dimensionless normalization at wavelength wave.

+

Note that outside of the wavelength range defined by the blue_limit and red_limit +attributes, the SED is considered undefined, and this method will raise an exception if a +wavelength outside the defined range is passed as an argument.

+
+
Parameters:
+

wave – Wavelength in nanometers at which to evaluate the SED. May be a scalar, +a numpy.array, or an astropy.units.Quantity

+
+
Returns:
+

photon flux density in units of photons/nm/cm^2/s if self.spectral, or +dimensionless normalization if self.dimensionless.

+
+
+
+ +
+
+__mul__(other)[source]
+

Multiply the SED by something.

+

There are several possibilities:

+
    +
  1. SED * SED -> SED (at least one must be dimensionless)

  2. +
  3. SED * GSObject -> ChromaticObject

  4. +
  5. SED * Bandpass -> SED (treating throughput similarly to dimensionless SED)

  6. +
  7. SED * callable function -> SED (treating function as dimensionless SED)

  8. +
  9. SED * scalar -> SED

  10. +
+
+ +
+
+_mul_sed(other)[source]
+

Equivalent to self * other when other is an SED, but no sanity checks.

+
+ +
+
+_mul_bandpass(other)[source]
+

Equivalent to self * other when other is a Bandpass

+
+ +
+
+_mul_scalar(other, spectral)[source]
+

Equivalent to self * other when other is a scalar

+
+ +
+
+atRedshift(redshift)[source]
+

Return a new SED with redshifted wavelengths.

+
+
Parameters:
+

redshift – The redshift for the returned SED

+
+
Returns:
+

the redshifted SED.

+
+
+
+ +
+
+calculateDCRMomentShifts(bandpass, **kwargs)[source]
+

Calculates shifts in first and second moments of PSF due to differential chromatic +refraction (DCR).

+

I.e., equations (1) and (2) from Plazas and Bernstein (2012):

+

http://arxiv.org/abs/1204.1346).

+
+
Parameters:
+
    +
  • bandpassBandpass through which object is being imaged.

  • +
  • zenith_angleAngle from object to zenith

  • +
  • parallactic_angle – Parallactic angle, i.e. the position angle of the zenith, +measured from North through East. [default: 0]

  • +
  • obj_coord – Celestial coordinates of the object being drawn as a +CelestialCoord. [default: None]

  • +
  • zenith_coord – Celestial coordinates of the zenith as a CelestialCoord. +[default: None]

  • +
  • HA – Hour angle of the object as an Angle. [default: None]

  • +
  • latitude – Latitude of the observer as an Angle. [default: None]

  • +
  • pressure – Air pressure in kiloPascals. [default: 69.328 kPa]

  • +
  • temperature – Temperature in Kelvins. [default: 293.15 K]

  • +
  • H2O_pressure – Water vapor pressure in kiloPascals. [default: 1.067 kPa]

  • +
+
+
Returns:
+

    +
  • The first element is the vector of DCR first moment shifts

  • +
  • The second element is the 2x2 matrix of DCR second (central) moment shifts.

  • +
+

+
+
Return type:
+

a tuple

+
+
+
+ +
+
+calculateFlux(bandpass)[source]
+

Return the flux (photons/cm^2/s) of the SED through the Bandpass bandpass.

+
+
Parameters:
+

bandpass – A Bandpass object representing a filter, or None to compute the +bolometric flux. For the bolometric flux the integration limits will be +set to (0, infinity), which implies that the SED needs to be evaluable +over this entire range.

+
+
Returns:
+

the flux through the bandpass.

+
+
+
+ +
+
+calculateMagnitude(bandpass)[source]
+

Return the SED magnitude through a Bandpass bandpass.

+

Note that this requires bandpass to have been assigned a zeropoint using +Bandpass.withZeropoint.

+
+
Parameters:
+

bandpass – A Bandpass object representing a filter, or None to compute the +bolometric magnitude. For the bolometric magnitude the integration +limits will be set to (0, infinity), which implies that the SED needs +to be evaluable over this entire range.

+
+
Returns:
+

the bandpass magnitude.

+
+
+
+ +
+
+calculateSeeingMomentRatio(bandpass, alpha=-0.2, base_wavelength=500)[source]
+

Calculates the relative size of a PSF compared to the monochromatic PSF size at +wavelength base_wavelength.

+
+
Parameters:
+
    +
  • bandpassBandpass through which object is being imaged.

  • +
  • alpha – Power law index for wavelength-dependent seeing. [default: +-0.2, the prediction for Kolmogorov turbulence]

  • +
  • base_wavelength – Reference wavelength in nm from which to compute the relative +PSF size. [default: 500]

  • +
+
+
Returns:
+

the ratio of the PSF second moments to the second moments of the reference PSF.

+
+
+
+ +
+
+check_dimensionless()[source]
+

Return boolean indicating if SED is dimensionless.

+
+ +
+
+check_spectral()[source]
+

Return boolean indicating if SED has units compatible with a spectral density.

+
+ +
+
+property dimensionless
+

Whether the object is dimensionless (rather than spectral).

+
+ +
+
+sampleWavelength(nphotons, bandpass, rng=None, npoints=None)[source]
+

Sample a number of random wavelength values from the SED, possibly as observed through +a Bandpass bandpass.

+
+
Parameters:
+
    +
  • nphotons – Number of samples (photons) to randomly draw.

  • +
  • bandpass – A Bandpass object representing a filter, or None to sample over the full +SED wavelength range.

  • +
  • rng – If provided, a random number generator that is any kind of BaseDeviate +object. If rng is None, one will be automatically created from the +system. [default: None]

  • +
  • npoints – Number of points DistDeviate should use for its internal interpolation +tables. [default: None, which uses the DistDeviate default]

  • +
+
+
+
+ +
+
+thin(rel_err=0.0001, trim_zeros=True, preserve_range=True, fast_search=True)[source]
+

Remove some tabulated values while keeping the integral over the set of tabulated values +still accurate to rel_err.

+

This is only relevant if the SED was initialized with a LookupTable or from a file +(which internally creates a LookupTable).

+
+
Parameters:
+
    +
  • rel_err – The relative error allowed in the integral over the SED +[default: 1.e-4]

  • +
  • trim_zeros – Remove redundant leading and trailing points where f=0? (The last +leading point with f=0 and the first trailing point with f=0 will +be retained). Note that if both trim_leading_zeros and +preserve_range are True, then the only the range of x after +zero trimming is preserved. [default: True]

  • +
  • preserve_range – Should the original range (blue_limit and red_limit) of the +SED be preserved? (True) Or should the ends be trimmed to +include only the region where the integral is significant? (False) +[default: True]

  • +
  • fast_search – If set to True, then the underlying algorithm will use a +relatively fast O(N) algorithm to select points to include in the +thinned approximation. If set to False, then a slower O(N^2) +algorithm will be used. We have found that the slower algorithm +tends to yield a thinned representation that retains fewer samples +while still meeting the relative error requirement. +[default: True]

  • +
+
+
Returns:
+

the thinned SED.

+
+
+
+ +
+
+withFlux(target_flux, bandpass)[source]
+

Return a new SED with flux through the Bandpass bandpass set to target_flux.

+

See ChromaticObject docstring for information about how SED normalization affects +ChromaticObject normalization.

+
+
Parameters:
+
    +
  • target_flux – The desired flux normalization of the SED.

  • +
  • bandpass – A Bandpass object defining a filter bandpass.

  • +
+
+
Returns:
+

the new normalized SED.

+
+
+
+ +
+
+withFluxDensity(target_flux_density, wavelength)[source]
+

Return a new SED with flux density set to target_flux_density at wavelength +wavelength.

+

See ChromaticObject docstring for information about how SED normalization affects +ChromaticObject normalization.

+
+
Parameters:
+
    +
  • target_flux_density – The target normalization in photons/nm/cm^2/s.

  • +
  • wavelength – The wavelength, in nm, at which the flux density will be set.

  • +
+
+
Returns:
+

the new normalized SED.

+
+
+
+ +
+
+withMagnitude(target_magnitude, bandpass)[source]
+

Return a new SED with magnitude through the Bandpass bandpass set to +target_magnitude.

+

Note that this requires bandpass to have been assigned a zeropoint using +Bandpass.withZeropoint. See ChromaticObject docstring for information about how SED +normalization affects ChromaticObject normalization.

+
+
Parameters:
+
    +
  • target_magnitude – The desired magnitude of the SED.

  • +
  • bandpass – A Bandpass object defining a filter bandpass.

  • +
+
+
Returns:
+

the new normalized SED.

+
+
+
+ +
+ +
+
+class galsim.EmissionLine(wavelength, fwhm=1.0, flux=1.0, wave_type='nm', flux_type='fphotons', max_wave=1e+30, **kwargs)[source]
+
+
+atRedshift(redshift)[source]
+

Return a new EmissionLine with redshifted wavelengths.

+
+
Parameters:
+

redshift – The redshift for the returned EmissionLine

+
+
Returns:
+

the redshifted EmissionLine.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/sensor.html b/docs/_build/html/sensor.html new file mode 100644 index 00000000000..558c87302b0 --- /dev/null +++ b/docs/_build/html/sensor.html @@ -0,0 +1,373 @@ + + + + + + + Sensor Models — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Sensor Models

+

The Sensor classes implement the process of turning a set of photons incident at the surface +of the detector in the focal plane into an image with counts of electrons in each pixel.

+

The Sensor class itself implements the simplest possible sensor model, which just converts each +photon into an electron in whatever pixel is below the location where the photon hits. +However, it also serves as a base class for other classes that implement more sophisticated +treatments of the photon to electron conversion and the drift from the conversion layer to the +bottom of the detector.

+
+
+class galsim.Sensor[source]
+

The base class for other sensor models, and also an implementation of the simplest possible +sensor model that just converts each photon into an electron and drops it in the appropriate +pixel.

+
+
+accumulate(photons, image, orig_center=None, resume=False)[source]
+

Accumulate the photons incident at the surface of the sensor into the appropriate +pixels in the image.

+

Each photon has a position, which corresponds to the (x,y) position at the top of the +sensor. In general, they may also have incidence directions and wavelengths, although +these are not used by the base class implementation.

+

The base class implementation simply accumulates the photons above each pixel into that +pixel.

+
+
Parameters:
+
    +
  • photons – A PhotonArray instance describing the incident photons.

  • +
  • image – The Image into which the photons should be accumuated.

  • +
  • orig_center – The Position of the (0,0) point in the original image coordinates. +[default: (0,0)]

  • +
  • resume – Resume accumulating on the same image as a previous call to accumulate. +In the base class, this has no effect, but it can provide an efficiency +gain for some derived classes. [default: False]

  • +
+
+
Returns:
+

the total flux that fell onto the image.

+
+
+
+ +
+
+calculate_pixel_areas(image, orig_center=galsim.PositionI(x=0, y=0), use_flux=True)[source]
+

Return the pixel areas according to the given sensor.

+

If the pixels are all the same size, then this should just return 1.0.

+

But if the pixels vary in size, it should return an Image with the pixel areas +relative to the nominal pixel size. The input image gives the flux values if relevant +(e.g. to set the current levels of the brighter-fatter distortions).

+

The returned image will have the same size and bounds as the input image, and will have +for its flux values the net pixel area for each pixel according to the sensor model.

+
+
Parameters:
+
    +
  • image – The Image with the current flux values.

  • +
  • orig_center – The Position of the (0,0) point in the original image coordinates. +[default: (0,0)]

  • +
  • use_flux – Whether to properly handle the current flux in the image (True) or +to just calculate the pixel areas for a zero-flux image (False). +[default: True]

  • +
+
+
Returns:
+

either 1.0 or an Image with the pixel areas +(The base class return 1.0.)

+
+
+
+ +
+ +
+
+class galsim.SiliconSensor(name='lsst_itl_50_8', strength=1.0, rng=None, diffusion_factor=1.0, qdist=3, nrecalc=10000, treering_func=None, treering_center=galsim.PositionD(x=0.0, y=0.0), transpose=False)[source]
+

Bases: Sensor

+

A model of a silicon-based CCD sensor that converts photons to electrons at a wavelength- +dependent depth (probabilistically) and drifts them down to the wells, properly taking +into account the repulsion of previously accumulated electrons (known as the brighter-fatter +effect).

+

There are currently four up-to-date sensors shipped with GalSim, which you can specify as the +name parameter mentioned below. The _50_ indicates 50V back-bias.

+
+
+
lsst_itl_50_8

The ITL sensor being used for LSST, using 8 points along each side of the +pixel boundaries.

+
+
lsst_itl_50_32

The ITL sensor being used for LSST, using 32 points along each side of the +pixel boundaries. (This is more accurate than the lsst_itl_8, but slower.)

+
+
lsst_e2v_50_8

The E2V sensor being used for LSST, using 8 points along each side of the +pixel boundaries.

+
+
lsst_e2v_50_32

The E2V sensor being used for LSST, using 32 points along each side of the +pixel boundaries. (This is more accurate than the lsst_e2v_8, but slower.)

+
+
+
+

The SiliconSensor model is asymmetric in the behavior along rows and columns in the CCD. +The traditional meaning of (x,y) is (col,row), and the brighter-fatter effect is stronger +along the columns than across the rows, since charge flows more easily in the readout +direction.

+

There is also an option to include “tree rings” in the SiliconSensor model, which add small +distortions to the sensor pixel positions due to non-uniform background doping in the silicon +sensor. The tree rings are defined by a center and a radial amplitude function. The radial +function needs to be a galsim.LookupTable instance. Note that if you just want a simple +cosine radial function, you can use the helper class method simple_treerings to build the +LookupTable for you.

+

Note that there is an option to transpose the effect if your definition of the image is to +have the readout “columns” along the x direction. E.g. to conform with the LSST Camera +Coordinate System definitions of x,y, which are transposed relative to the usual FITS meanings. +This only affects the direction of the brighter-fatter effect. It does not change the meaning +of treering_center, which should still be defined in terms of the coordinate system of the +images being passed to accumulate.

+
+
Parameters:
+
    +
  • name – The base name of the files which contains the sensor information, +presumably calculated from the Poisson_CCD simulator, which may +be specified either as an absolute path or as one of the above names +that are in the galsim.meta_data.share_dir/sensors directory. +name.cfg should be the file used to simulate the pixel distortions, +and name.dat should containt the distorted pixel information. +[default: ‘lsst_itl_50_8’]

  • +
  • strength – Set the strength of the brighter-fatter effect relative to the +amount specified by the Poisson simulation results. [default: 1]

  • +
  • rng – A BaseDeviate object to use for the random number generation +for the stochastic aspects of the electron production and drift. +[default: None, in which case one will be made for you]

  • +
  • diffusion_factor – A factor by which to multiply the diffusion. Use 0.0 to turn off the +effect of diffusion entirely. [default: 1.0]

  • +
  • qdist – The maximum number of pixels away to calculate the distortion due to +the charge accumulation. A large value will increase accuracy but +take more time. If it is increased larger than 4, the size of the +Poisson simulation must be increased to match. [default: 3]

  • +
  • nrecalc – The number of electrons to accumulate before recalculating the +distortion of the pixel shapes. [default: 10000]

  • +
  • treering_func – A LookupTable giving the tree ring pattern f(r). [default: None]

  • +
  • treering_center – A PositionD object with the center of the tree ring pattern in pixel +coordinates, which may be outside the pixel region. [default: None; +required if treering_func is provided]

  • +
  • transpose – Transpose the meaning of (x,y) so the brighter-fatter effect is +stronger along the x direction. [default: False]

  • +
+
+
+
+
+accumulate(photons, image, orig_center=galsim.PositionI(x=0, y=0), resume=False)[source]
+

Accumulate the photons incident at the surface of the sensor into the appropriate +pixels in the image.

+
+
Parameters:
+
    +
  • photons – A PhotonArray instance describing the incident photons

  • +
  • image – The Image into which the photons should be accumuated.

  • +
  • orig_center – The Position of the (0,0) point in the original image coordinates. +[default: (0,0)]

  • +
  • resume – Resume accumulating on the same image as a previous call to accumulate. +This skips an initial (slow) calculation at the start of the +accumulation to see what flux is already on the image, which can +be more efficient, especially when the number of pixels is large. +[default: False]

  • +
+
+
Returns:
+

the total flux that fell onto the image.

+
+
+
+ +
+
+calculate_pixel_areas(image, orig_center=galsim.PositionI(x=0, y=0), use_flux=True)[source]
+

Create an image with the corresponding pixel areas according to the SiliconSensor +model.

+

The input image gives the flux values used to set the current levels of the brighter-fatter +distortions.

+

The returned image will have the same size and bounds as the input image, and will have +for its flux values the net pixel area for each pixel according to the SiliconSensor +model.

+

Note: The areas here are in units of the nominal pixel area. This does not account for +any conversion from pixels to sky units using the image wcs (if any).

+
+
Parameters:
+
    +
  • image – The Image with the current flux values.

  • +
  • orig_center – The Position of the (0,0) point in the original image coordinates. +[default: (0,0)]

  • +
  • use_flux – Whether to properly handle the current flux in the image (True) or +to just calculate the pixel areas for a zero-flux image (False). +[default: True] (Note that use_flux=True potentially uses a lot of +memory!)

  • +
+
+
Returns:
+

an Image with the pixel areas

+
+
+
+ +
+
+classmethod simple_treerings(amplitude=0.5, period=100.0, r_max=8000.0, dr=None)[source]
+

Make a simple sinusoidal tree ring pattern that can be used as the treering_func +parameter of SiliconSensor.

+

The functional form is \(f(r) = A \cos(2 \pi r/P)\) where \(A\) is the +amplitude and \(P\) is the period.

+
+
Parameters:
+
    +
  • amplitude – The amplitude of the tree ring pattern distortion. Typically +this is less than 0.01 pixels. [default: 0.5]

  • +
  • period – The period of the tree ring distortion pattern, in pixels. +[default: 100.]

  • +
  • r_max – The maximum value of r to store in the lookup table. [default: 8000]

  • +
  • dr – The spacing to use for the r values. [default: period/100]

  • +
+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/shared.html b/docs/_build/html/shared.html new file mode 100644 index 00000000000..876b91e51d9 --- /dev/null +++ b/docs/_build/html/shared.html @@ -0,0 +1,456 @@ + + + + + + + Shared Data — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Shared Data

+

GalSim includes some ancillary data along with the installed code. They are installed in +the sub-directory share wherever your GalSim module is installed.

+

The location of these files are given by the variable

+
+
+galsim.meta_data.share_dir
+

The installed location of your share directory.

+
+ +
+

Note

+

Normally, the installation process will write a file called meta_data.py which sets +the above variable automatically to the installation directory on your machine.

+

However, if you install into a temporary location and then move the entire galsim directory to +a different location, this will not have the correct location. (It will instead be set to the +temporary location.) If you do this, you should define an environment variable, +GALSIM_SHARE_DIR, to the correct location of the share directory. Alternatively, you can +set the value of galsim.meta_data.share_dir by hand in any Python programs that might need +it.

+
+

Usually, you do not need to use the galsim.meta_data.share_dir variable directly. Routines +that open files that might be in the share directory will automatically prepend this directory +name to the given file name when trying to open the file.

+

The following files are distributed in the share directory. In each case, we provide the +command you would typically use to load the file with the appropriate GalSim class or function.

+
+

Shared SED files

+
+
vega.txt

Use galsim.SED('vega.txt', wave_type='nm', flux_type='flam')

+

Specrum of the star Vega (aka Alpha Lyra), derived from HST CALSPEC data. +File taken from http://www.stsci.edu/hst/observatory/crds/calspec.html +Filename: alpha_lyr_mod_001.fits +Clipped on the red side at 2200 nm +Units converted to nm and erg/s/cm^2/nm.

+
+
CWW_E_ext.sed

Use galsim.SED('CWW_E_ext.sed', wave_type='A', flux_type='flam')

+

E SED of Coleman, Wu, and Weedman (1980) +Extended below 1400 A and beyond 10000 A by +Bolzonella, Miralles, and Pello (2000) using evolutionary models +of Bruzual and Charlot (1993)

+

Obtained from ZPHOT code at

+

http://webast.ast.obs-mip.fr/hyperz/zphot_src_1.1.tar.gz

+

Truncated to wavelengths less than 22050 Angstroms, and thinned by +galsim.utilities.thin_tabulated_values to a relative error of 1.e-5 +with fast_search=False. See devel/modules/getSEDs.py for details.

+
+
CWW_E_ext_more.sed

Use galsim.SED('CWW_E_ext_more.sed', wave_type='A', flux_type='flam')

+

Same as CWW_E_ext.sed, but thinned to a relative error of 1.e-3

+
+
CWW_Im_ext.sed

Use galsim.SED('CWW_Im_ext.sed', wave_type='A', flux_type='flam')

+

Im SED of Coleman, Wu, and Weedman (1980) +Extended below 1400 A and beyond 10000 A by +Bolzonella, Miralles, and Pello (2000) using evolutionary models +of Bruzual and Charlot (1993)

+

Obtained from ZPHOT code at +‘http://webast.ast.obs-mip.fr/hyperz/zphot_src_1.1.tar.gz

+

Truncated to wavelengths less than 22050 Angstroms, and thinned by +galsim.utilities.thin_tabulated_values to a relative error of 1.e-5 +with fast_search=False. See devel/modules/getSEDs.py for details.

+
+
CWW_Im_ext_more.sed

Use galsim.SED('CWW_Im_ext_more.sed', wave_type='A', flux_type='flam')

+

Same as CWW_Im_ext.sed, but thinned to a relative error of 1.e-3

+
+
CWW_Sbc_ext.sed

Use galsim.SED('CWW_Sbc_ext.sed', wave_type='A', flux_type='flam')

+

Sbc SED of Coleman, Wu, and Weedman (1980) +Extended below 1400 A and beyond 10000 A by +Bolzonella, Miralles, and Pello (2000) using evolutionary models +of Bruzual and Charlot (1993)

+

Obtained from ZPHOT code at +‘http://webast.ast.obs-mip.fr/hyperz/zphot_src_1.1.tar.gz

+

Truncated to wavelengths less than 22050 Angstroms, and thinned by +galsim.utilities.thin_tabulated_values to a relative error of 1.e-5 +with fast_search=False. See devel/modules/getSEDs.py for details.

+
+
CWW_Sbc_ext_more.sed

Use galsim.SED('CWW_Sbc_ext_more.sed', wave_type='A', flux_type='flam')

+

Same as CWW_Sbc_ext.sed, but thinned to a relative error of 1.e-3

+
+
+

For more details about how the above files were generated, see the script:

+
+

GalSim/devel/getSEDs.py

+
+
+
+

Shared Bandpass files

+
+
ACS_wfc_F435W.dat

Use galsim.Bandpass('ACS_wfc_F435W.dat', wave_type='nm')

+

ACS wfc_F435W total throughput +File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F435W.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
ACS_wfc_F606W.dat

Use galsim.Bandpass('ACS_wfc_F606W.dat', wave_type='nm')

+

ACS wfc_F606W total throughput +File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F606W.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
ACS_wfc_F775W.dat

Use galsim.Bandpass('ACS_wfc_F775W.dat', wave_type='nm')

+

ACS wfc_F775W total throughput +File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F775W.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
ACS_wfc_F814W.dat

Use galsim.Bandpass('ACS_wfc_F814W.dat', wave_type='nm')

+

ACS wfc_F814W total throughput +File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F814W.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
ACS_wfc_F850LP.dat

Use galsim.Bandpass('ACS_wfc_F850LP.dat', wave_type='nm')

+

ACS wfc_F850LP total throughput +File taken from http://www.stsci.edu/hst/acs/analysis/throughputs/tables/wfc_F850LP.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
LSST_u.dat

Use galsim.Bandpass('LSST_u.dat', wave_type='nm')

+

LSST u-band total throughput at airmass 1.2 +File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_u.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
LSST_g.dat

Use galsim.Bandpass('LSST_g.dat', wave_type='nm')

+

LSST g-band total throughput at airmass 1.2 +File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_g.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
LSST_r.dat

Use galsim.Bandpass('LSST_r.dat', wave_type='nm')

+

LSST r-band total throughput at airmass 1.2 +File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_r.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
LSST_i.dat

Use galsim.Bandpass('LSST_i.dat', wave_type='nm')

+

LSST i-band total throughput at airmass 1.2 +File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_i.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
LSST_z.dat

Use galsim.Bandpass('LSST_z.dat', wave_type='nm')

+

LSST z-band total throughput at airmass 1.2 +File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_z.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
LSST_y.dat

Use galsim.Bandpass('LSST_y.dat', wave_type='nm')

+

LSST Y-band total throughput at airmass 1.2 +File taken from https://raw.githubusercontent.com/lsst/throughputs/master/baseline/total_y.dat

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
WFC3_uvis_F275W.dat

Use galsim.Bandpass('WFC_uvis_F275W.dat', wave_type='nm')

+

WFC3 UVIS f275w total throughput +Average of UVIS1 and UVIS2 throughputs, from files +http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f275w.UVIS1.tab +http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f275w.UVIS2.tab

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
WFC3_uvis_F336W.dat

Use galsim.Bandpass('WFC_uvis_F336W.dat', wave_type='nm')

+

WFC3 UVIS f336w total throughput +Average of UVIS1 and UVIS2 throughputs, from files +http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f336w.UVIS1.tab +http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f336w.UVIS2.tab

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
WFC3_ir_F105W.dat

Use galsim.Bandpass('WFC_ir_F105W.dat', wave_type='nm')

+

WFC3 IR f105w total throughput +File taken from http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f105w.IR.tab

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
WFC3_ir_F125W.dat

Use galsim.Bandpass('WFC_ir_F125W.dat', wave_type='nm')

+

WFC3 IR f125w total throughput +File taken from http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f125w.IR.tab

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
WFC3_ir_F160W.dat

Use galsim.Bandpass('WFC_ir_F160W.dat', wave_type='nm')

+

WFC3 IR f160w total throughput +File taken from http://www.stsci.edu/hst/wfc3/ins_performance/throughputs/Throughput_Tables/f160w.IR.tab

+

Thinned by galsim.utilities.thin_tabulated_values to a relative error of 1.e-3 +with fast_search=False.

+
+
+

For more details about how the above files were generated, see the scripts:

+
+
    +
  • GalSim/devel/getLSSTBandpass.py

  • +
  • GalSim/devel/getACSBandpass.py

  • +
  • GalSim/devel/getWFC3Bandpass.py

  • +
+
+
+
+

Shared Sensor models

+
+
lsst_itl_8

Use galsim.SiliconSensor('lsst_itl_8')

+

The ITL sensor being used for LSST, using 8 points along each side of the +pixel boundaries.

+
+
lsst_itl_32

Use galsim.SiliconSensor('lsst_itl_32')

+

The ITL sensor being used for LSST, using 32 points along each side of the +pixel boundaries. (This is more accurate than the lsst_itl_8, but slower.)

+
+
lsst_etv_32

Use galsim.SiliconSensor('lsst_etv_32')

+

The ETV sensor being used for LSST, using 32 points along each side of the +pixel boundaries. (This file is still somewhat preliminary and may be +updated in the future.)

+
+
+
+
+

Shared HST noise model

+
+
acs_I_unrot_sci_20_cf.fits

Use galsim.getCOSMOSNoise()

+
+
+
+
+

Shared Roman ST files

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_01.txt

Use galsim.roman.getPSF(1, bandpass)

+

Roman PSF information for SCA 1

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_02.txt

Use galsim.roman.getPSF(2, bandpass)

+

Roman PSF information for SCA 2

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_03.txt

Use galsim.roman.getPSF(3, bandpass)

+

Roman PSF information for SCA 3

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_04.txt

Use galsim.roman.getPSF(4, bandpass)

+

Roman PSF information for SCA 4

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_05.txt

Use galsim.roman.getPSF(5, bandpass)

+

Roman PSF information for SCA 5

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_06.txt

Use galsim.roman.getPSF(6, bandpass)

+

Roman PSF information for SCA 6

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_07.txt

Use galsim.roman.getPSF(7, bandpass)

+

Roman PSF information for SCA 7

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_08.txt

Use galsim.roman.getPSF(8, bandpass)

+

Roman PSF information for SCA 8

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_09.txt

Use galsim.roman.getPSF(9, bandpass)

+

Roman PSF information for SCA 9

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_10.txt

Use galsim.roman.getPSF(10, bandpass)

+

Roman PSF information for SCA 10

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_11.txt

Use galsim.roman.getPSF(11, bandpass)

+

Roman PSF information for SCA 11

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_12.txt

Use galsim.roman.getPSF(12, bandpass)

+

Roman PSF information for SCA 12

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_13.txt

Use galsim.roman.getPSF(13, bandpass)

+

Roman PSF information for SCA 13

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_14.txt

Use galsim.roman.getPSF(14, bandpass)

+

Roman PSF information for SCA 14

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_15.txt

Use galsim.roman.getPSF(15, bandpass)

+

Roman PSF information for SCA 15

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_16.txt

Use galsim.roman.getPSF(16, bandpass)

+

Roman PSF information for SCA 16

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_17.txt

Use galsim.roman.getPSF(17, bandpass)

+

Roman PSF information for SCA 17

+
+
Roman_Phase-A_SRR_WFC_Zernike_and_Field_Data_170727_18.txt

Use galsim.roman.getPSF(18, bandpass)

+

Roman PSF information for SCA 18

+
+
Roman_SRR_WFC_Pupil_Mask_Shortwave_2048_reformatted.fits.gz

Use galsim.roman.getPSF(sca, bandpass)

+

Roman Pupil Mask for the shorter wavelength bandpasses. +Relevant for bands Z087, Y106, J129, and H158

+
+
Roman_SRR_WFC_Pupil_Mask_Longwave_2048_reformatted.fits.gz

Use galsim.roman.getPSF(sca, bandpass)

+

Roman Pupil Mask for the longer wavelength bandpasses. +Relevant for bands F184 and W149

+
+
afta_throughput.txt

Use galsim.roman.getBandpasses()

+

Roman throughputs for all the Roman bands in a single file.

+
+
sip_7_6_8.txt

Use galsim.roman.getWCS(world_pos)

+

Roman ST WCS information for all SCAs.

+
+
+
+
+

Shared COSMOS files

+

These files are not shipped with GalSim, but can be installed into the share directory +by the executable galsim_download_cosmos. See Downloading the COSMOS Catalog for details.

+
+
COSMOS_25.2_training_sample
+
Use galsim.RealGalaxyCatalog(sample=25.2)
+
Or galsim.COSMOSCatalog(sample=25.2)
+
+

Download with galsim_download_cosmos -s 25.2

+

A directory containing files for creating a RealGalaxyCatalog or a COSMOSCatalog using the +F814W < 25.2 sample.

+
+
COSMOS_23.5_training_sample
+
Use galsim.RealGalaxyCatalog(sample=23.5)
+
Or galsim.COSMOSCatalog(sample=23.5)
+
+

Download with galsim_download_cosmos -s 23.5

+

A directory containing files for creating a RealGalaxyCatalog or a COSMOSCatalog using the +F814W < 23.5 sample.

+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/shear.html b/docs/_build/html/shear.html new file mode 100644 index 00000000000..dfb1eba1019 --- /dev/null +++ b/docs/_build/html/shear.html @@ -0,0 +1,379 @@ + + + + + + + The Shear class — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

The Shear class

+
+
+class galsim.Shear(*args, **kwargs)[source]
+

A class to represent shears in a variety of ways.

+

The Shear object can be initialized in a variety of ways to represent shape distortions. +A shear is an operation that transforms a circle into an ellipse with minor-to-major axis ratio +b/a, with position angle beta, while conserving the area (see below for a discussion of the +implications of this choice). Given the multiple definitions of ellipticity, we have multiple +definitions of shear:

+
+
reduced shear

\(|g| = \frac{a - b}{a + b}\)

+
+
distortion

\(|e| = \frac{a^2 - b^2}{a^2 + b^2}\)

+
+
conformal shear

\(\eta = \log(b/a)\)

+
+
minor-to-major axis ratio

\(q = \frac{b}{a}\)

+
+
+

These can be thought of as a magnitude and a real-space position angle \(\beta\), or as +two components, e.g., \(g_1\) and \(g_2\), with:

+
+\[\begin{split}g_1 &= |g| \cos(2 \beta) \\ +g_2 &= |g| \sin(2 \beta)\end{split}\]
+

Note: \(\beta\) is _not_ the phase of a complex valued shear. Rather, the complex shear is +\(g_1 + i g_2 = g \exp(2 i \beta)\). Likewise for \(\eta\) or \(e\). +The phase of the complex value is \(2 \beta\).

+

The following are all examples of valid calls to initialize a Shear object:

+
>>> s = galsim.Shear()                    # empty constructor sets ellipticity/shear to zero
+>>> s = galsim.Shear(g1=0.05, g2=0.05)
+>>> s = galsim.Shear(g1=0.05)             # assumes g2=0
+>>> s = galsim.Shear(e1=0.05, e2=0.05)
+>>> s = galsim.Shear(e2=0.05)             # assumes e1=0
+>>> s = galsim.Shear(eta1=0.07, eta2=-0.1)
+>>> s = galsim.Shear(eta=0.05, beta=45.0*galsim.degrees)
+>>> s = galsim.Shear(g=0.05, beta=0.25*numpy.pi*galsim.radians)
+>>> s = galsim.Shear(e=0.3, beta=30.0*galsim.degrees)
+>>> s = galsim.Shear(q=0.5, beta=0.0*galsim.radians)
+>>> s = galsim.Shear(0.05 + 0.03j)        # Uses the g1,g2 reduced shear definition
+
+
+

There can be no mixing and matching, e.g., specifying g1 and e2. It is permissible to +only specify one of two components, with the other assumed to be zero. If a magnitude such as +e, g, eta, or q is specified, then beta is also required to be specified. +It is possible to initialize a Shear with zero reduced shear by specifying no args or kwargs, +i.e. galsim.Shear().

+

In addition, for use cases where extreme efficiency is required, you can skip all the +normal sanity checks and branches in the regular Shear constructor by using a leading +underscore with the complex shear g1 + 1j * g2:

+
>>> s = galsim._Shear(0.05 + 0.03j)  # Equivalent to galsim.Shear(g1=0.05, g2=0.03)
+
+
+

Analagous to the construction options, one can access the shear in the same variety of +definitions.

+
+
Attributes:
+
    +
  • g1 – The first component of the shear in the “reduced shear” definition.

  • +
  • g2 – The second component of the shear in the “reduced shear” definition.

  • +
  • g – The magnitude of the shear in the “reduced shear” definition.

  • +
  • e1 – The first component of the shear in the “distortion” definition.

  • +
  • e2 – The second component of the shear in the “distortion” definition.

  • +
  • e – The magnitude of the shear in the “distortion” definition.

  • +
  • eta1 – The first component of the shear in the “conformal shear” definition.

  • +
  • eta2 – The second component of the shear in the “conformal shear” definition.

  • +
  • eta – The magnitude of the shear in the “conformal shear” definition.

  • +
  • q – The minor-to-major axis ratio

  • +
  • beta – The position angle as an Angle instance

  • +
  • shear – The reduced shear as a complex number g1 + 1j * g2.

  • +
+
+
+
+

Note

+

Since we have defined a Shear as a transformation that preserves area, this means that it +is not a precise description of what happens during the process of weak lensing.

+

The coordinate transformation that occurs during the actual weak lensing process is such +that if a galaxy is sheared by some \((\gamma_1, \gamma_2)\), and then sheared by +\((-\gamma_1, -\gamma_2)`\), it will in the end return to its original shape, but will +have changed in area due to the magnification,

+
+\[\mu = \frac{1}{(1-\kappa)^2 - (\gamma_1^2 + \gamma_2^2)}\]
+

which is not equal to 1 for non-zero shear even for convergence \(\kappa=0\).

+

Application of a Shear using the GSObject.shear method does not include this area +change. To properly incorporate the effective change in area due to shear, it is necessary +to either:

+
    +
  1. define the Shear object, use the GSObject.shear method, and separately use the +GSObject.magnify method, or

  2. +
  3. use the GSObject.lens method that simultaneously magnifies and shears.

  4. +
+
+
+
+property beta
+

The position angle as an Angle instance

+
+ +
+
+property e
+

The magnitude of the shear in the “distortion” definition.

+
+ +
+
+property e1
+

The first component of the shear in the “distortion” definition.

+
+ +
+
+property e2
+

The second component of the shear in the “distortion” definition.

+
+ +
+
+property esq
+

The square of the magnitude of the shear in the “distortion” definition.

+
+ +
+
+property eta
+

The magnitude of the shear in the “conformal shear” definition.

+
+ +
+
+property eta1
+

The first component of the shear in the “conformal shear” definition.

+
+ +
+
+property eta2
+

The second component of the shear in the “conformal shear” definition.

+
+ +
+
+property g
+

The magnitude of the shear in the “reduced shear” definition.

+
+ +
+
+property g1
+

The first component of the shear in the “reduced shear” definition.

+
+ +
+
+property g2
+

The second component of the shear in the “reduced shear” definition.

+
+ +
+
+getMatrix()[source]
+

Return the matrix that tells how this shear acts on a position vector:

+

If a field is sheared by some shear, s, then the position (x,y) -> (x’,y’) +according to:

+
+\[\begin{split}\left( \begin{array}{c} x^\prime \\ y^\prime \end{array} \right) += S \left( \begin{array}{c} x \\ y \end{array} \right)\end{split}\]
+

and \(S\) is the return value of this function S = shear.getMatrix().

+

Specifically, the matrix is

+
+\[\begin{split}S = \frac{1}{\sqrt{1-g^2}} + \left( \begin{array}{cc} 1+g_1 & g_2 \\ + g_2 & 1-g_1 \end{array} \right)\end{split}\]
+
+ +
+
+property q
+

The minor-to-major axis ratio

+
+ +
+
+rotationWith(other)[source]
+

Return the rotation angle associated with the addition of two shears.

+

The effect of two shears is not just a single net shear. There is also a rotation +associated with it. This is easiest to understand in terms of the matrix representations:

+

If shear3 = shear1 + shear2 is a sum of two shears, and the corresponding shear +matrices are \(S_1\), \(S_2\), and \(S_3\), then \(S_3 R = S_1 S_2\), +where \(R\) is a rotation matrix:

+
+\[\begin{split}R = \left( \begin{array}{cc} cos(\theta) & -sin(\theta) \\ + sin(\theta) & cos(\theta) \end{array} \right)\end{split}\]
+

and \(\theta\) is the return value (as a galsim.Angle) from +shear1.rotationWith(shear2).

+
+ +
+
+property shear
+

The reduced shear as a complex number g1 + 1j * g2.

+
+ +
+ +
+
+galsim._Shear(shear)[source]
+

Equivalent to galsim.Shear(shear), but without the overhead of the normal sanity checks +and other options for how to specify the shear.

+
+
Parameters:
+

shear – The complex shear g1 + 1j * g2.

+
+
Returns:
+

a galsim.Shear instance

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/simple.html b/docs/_build/html/simple.html new file mode 100644 index 00000000000..122ed68fa81 --- /dev/null +++ b/docs/_build/html/simple.html @@ -0,0 +1,420 @@ + + + + + + + Simple Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Simple Profiles

+

We have a few profiles that are just simple shapes.

+
+

Gaussian Profile

+
+
+class galsim.Gaussian(half_light_radius=None, sigma=None, fwhm=None, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a 2D Gaussian surface brightness profile.

+

The Gaussian surface brightness profile is characterized by two properties, its flux +and the characteristic size sigma where the radial profile of the circular Gaussian +drops off as

+
+\[I(r) \sim e^{-\frac{r^2}{2 \sigma^2}}\]
+

A Gaussian can be initialized using one (and only one) of three possible size parameters: +sigma, fwhm, or half_light_radius. Exactly one of these three is required.

+
+
Parameters:
+
    +
  • sigma – The value of sigma of the profile. Typically given in arcsec. +[One of sigma, fwhm, or half_light_radius is required.]

  • +
  • fwhm – The full-width-half-max of the profile. Typically given in arcsec. +[One of sigma, fwhm, or half_light_radius is required.]

  • +
  • half_light_radius – The half-light radius of the profile. Typically given in arcsec. +[One of sigma, fwhm, or half_light_radius is required.]

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property fwhm
+

The FWHM of this Gaussian profile

+
+ +
+
+property half_light_radius
+

The half-light radius of this Gaussian profile

+
+ +
+
+property sigma
+

The sigma of this Gaussian profile

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Pixel Profile

+
+
+class galsim.Pixel(scale, flux=1.0, gsparams=None)[source]
+

Bases: Box

+

A class describing a pixel profile. This is just a 2D square top-hat function.

+

This class is typically used to represent a pixel response function. It is used internally by +the GSObject.drawImage function, but there may be cases where the user would want to use +this profile directly.

+
+
Parameters:
+
    +
  • scale – The linear scale size of the pixel. Typically given in arcsec.

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. This should almost +certainly be left at the default value of 1. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property scale
+

The linear scale size of the Pixel.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Box Profile

+
+
+class galsim.Box(width, height, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a box profile. This is just a 2D top-hat function, where the +width and height are allowed to be different.

+
+
Parameters:
+
    +
  • width – The width of the Box.

  • +
  • height – The height of the Box.

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property height
+

The height of the Box.

+
+ +
+
+property width
+

The width of the Box.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

TopHat Profile

+
+
+class galsim.TopHat(radius, flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a radial tophat profile. This profile is a constant value within some +radius, and zero outside this radius.

+
+
Parameters:
+
    +
  • radius – The radius of the TopHat, where the surface brightness drops to 0.

  • +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+property radius
+

The radius of the TopHat profile.

+
+ +
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+

Delta Function

+
+
+class galsim.DeltaFunction(flux=1.0, gsparams=None)[source]
+

Bases: GSObject

+

A class describing a DeltaFunction surface brightness profile.

+

The DeltaFunction surface brightness profile is characterized by a single property, +its flux.

+

A DeltaFunction can be initialized with a specified flux.

+
+
Parameters:
+
    +
  • flux – The flux (in photons/cm^2/s) of the profile. [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
+
+
+
+
+withFlux(flux)[source]
+

Create a version of the current object with a different flux.

+

This function is equivalent to obj.withScaledFlux(flux / obj.flux).

+

It creates a new object that has the same profile as the original, but with the +surface brightness at every location rescaled such that the total flux will be +the given value. Note that if flux is an SED, the return value will be a +ChromaticObject with specified SED.

+
+
Parameters:
+

flux – The new flux for the object.

+
+
Returns:
+

the object with the new flux

+
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/spectral.html b/docs/_build/html/spectral.html new file mode 100644 index 00000000000..bc23aa5f7c3 --- /dev/null +++ b/docs/_build/html/spectral.html @@ -0,0 +1,187 @@ + + + + + + + Spectral Correlated Noise — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Spectral Correlated Noise

+
+
+class galsim.CovarianceSpectrum(Sigma, SEDs)[source]
+

Class to hold a ChromaticSum noise covariance spectrum (which is a generalization of a +power spectrum or equivalently a correlation function).

+

Analogous to how a galsim.CorrelatedNoise object stores the variance and covariance of a +galsim.Image object, a galsim.CovarianceSpectrum stores the variance and covariance of the +Fourier mode amplitudes in different components of a ChromaticSum.

+

Note that the covariance in question exists between different SED components of the +ChromaticSum, and not between different Fourier modes, which are assumed to be uncorrelated. +This structure arises naturally for a ChromaticRealGalaxy (see devel/modules/CGNotes.pdf for +more details).

+
+
Parameters:
+
    +
  • Sigma – A dictionary whose keys are tuples numerically indicating a pair of +ChromaticSum components whose Fourier mode amplitude covariances are +described by the corresponding GSObject values.

  • +
  • SEDsSED instances of associated ChromaticSum components.

  • +
+
+
+
+
+toNoise(bandpass, PSF, wcs, rng=None)[source]
+

Derive the CorrelatedNoise object for the associated ChromaticSum when convolved +with PSF and drawn through bandpass onto pixels with specified wcs.

+
+
Parameters:
+
    +
  • bandpassBandpass object representing filter image is drawn through.

  • +
  • PSF – output chromatic PSF to convolve by.

  • +
  • wcs – WCS of output pixel scale.

  • +
  • rng – Random number generator to forward to resulting CorrelatedNoise object.

  • +
+
+
Returns:
+

CorrelatedNoise object.

+
+
+
+ +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/table.html b/docs/_build/html/table.html new file mode 100644 index 00000000000..7eaf91df5c7 --- /dev/null +++ b/docs/_build/html/table.html @@ -0,0 +1,599 @@ + + + + + + + Lookup Tables — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Lookup Tables

+
+
+class galsim.LookupTable(x, f, interpolant='spline', x_log=False, f_log=False)[source]
+

LookupTable represents a lookup table to store function values that may be slow to calculate, +for which interpolating from a lookup table is sufficiently accurate.

+

A LookupTable may be constructed from two arrays (lists, tuples, or NumPy arrays of +floats/doubles):

+
>>> args = [...]
+>>> vals = []
+>>> for arg in args:
+...     val = calculateVal(arg)
+...     vals.append(val)
+>>> table = galsim.LookupTable(x=args,f=vals)
+
+
+

Then you can use this table as a replacement for the slow calculation:

+
>>> other_args = [...]
+>>> for arg in other_args:
+...     val = table(arg)
+...     [... use val ...]
+
+
+

The default interpolation method is a natural cubic spline. This is usually the best choice, +but we also provide other options, which can be specified by the interpolant kwarg. The +choices include ‘floor’, ‘ceil’, ‘linear’, ‘spline’, or a galsim.Interpolant object:

+
    +
  • ‘floor’ takes the value from the previous argument in the table.

  • +
  • ‘ceil’ takes the value from the next argument in the table.

  • +
  • ‘nearest’ takes the value from the nearest argument in the table.

  • +
  • ‘linear’ does linear interpolation between these two values.

  • +
  • ‘spline’ uses a cubic spline interpolation, so the interpolated values are smooth at +each argument in the table.

  • +
  • a galsim.Interpolant object or a string convertible to one. This option can be used for +Lanczos or Quintic interpolation, for example.

  • +
+

Note that specifying the string ‘nearest’ or ‘linear’ will use a LookupTable-optimized +interpolant instead of galsim.Nearest or galsim.Linear, though the latter options can still +be used by passing an Interpolant object instead of a string. Also note that to use a +galsim.Interpolant in a LookupTable, the input data must be equally spaced, or logarithmically +spaced if x_log is set to True (see below). Finally, although natural cubic spline used +when interpolant=’spline’ and the cubic convolution interpolant used when the interpolant +is galsim.Cubic both produce piecewise cubic polynomial interpolations, their treatments of +the continuity of derivatives are different (the natural spline is smoother).

+

There are also two factory functions which can be used to build a LookupTable:

+
+
+
LookupTable.from_func

makes a LookupTable from a callable function

+
+
LookupTable.from_file

reads in a file of x and f values.

+
+
+
+

The user can also opt to interpolate in log(x) and/or log(f) (if not using a +galsim.Interpolant), though this is not the default. It may be a wise choice depending on the +particular function, e.g., for a nearly power-law f(x) (or at least one that is locally +power-law-ish for much of the x range) then it might be a good idea to interpolate in log(x) and +log(f) rather than x and f.

+
+
Parameters:
+
    +
  • x – The list, tuple, or NumPy array of x values.

  • +
  • f – The list, tuple, or NumPy array of f(x) values.

  • +
  • interpolant – Type of interpolation to use, with the options being ‘floor’, ‘ceil’, +‘nearest’, ‘linear’, ‘spline’, or a galsim.Interpolant or string +convertible to one. [default: ‘spline’]

  • +
  • x_log – Set to True if you wish to interpolate using log(x) rather than x. Note +that all inputs / outputs will still be x, it’s just a question of how the +interpolation is done. [default: False]

  • +
  • f_log – Set to True if you wish to interpolate using log(f) rather than f. Note +that all inputs / outputs will still be f, it’s just a question of how the +interpolation is done. [default: False]

  • +
+
+
+
+
+__call__(x)[source]
+

Interpolate the LookupTable to get f(x) at some x value(s).

+

When the LookupTable object is called with a single argument, it returns the value at that +argument. An exception will be thrown automatically if the x value is outside the +range of the original tabulated values. The value that is returned is the same type as +that provided as an argument, e.g., if a single value x is provided then a single value +of f is returned; if a tuple of x values is provided then a tuple of f values +is returned; and so on. Even if interpolation was done using the x_log option, the +user should still provide x rather than log(x).

+
+
Parameters:
+

x – The x value(s) for which f(x) should be calculated via interpolation on +the original (x,f) lookup table. x can be a single float/double, or a +tuple, list, or arbitrarily shaped 1- or 2-dimensional NumPy array.

+
+
Returns:
+

the interpolated f(x) value(s).

+
+
+
+ +
+
+classmethod from_file(file_name, interpolant='spline', x_log=False, f_log=False, amplitude=1.0)[source]
+

Create a LookupTable from a file of x, f values.

+

This reads in a file, which should contain two columns with the x and f values.

+
+
Parameters:
+
    +
  • file_name – A file from which to read the (x,f) pairs.

  • +
  • interpolant – Type of interpolation to use. [default: ‘spline’]

  • +
  • x_log – Whether the x values should be uniform in log rather than lienar. +[default: False]

  • +
  • f_log – Whether the f values should be interpolated using their logarithms +rather than their raw values. [default: False]

  • +
  • amplitude – An optional scaling of the f values relative to the values in the file +[default: 1.0]

  • +
+
+
+
+ +
+
+classmethod from_func(func, x_min, x_max, npoints=2000, interpolant='spline', x_log=False, f_log=False)[source]
+

Create a LookupTable from a callable function

+

This constructs a LookupTable over the given range from x_min and x_max, calculating the +corresponding f values from the given function (technically any callable object).

+
+
Parameters:
+
    +
  • func – A callable function.

  • +
  • x_min – The minimum x value at which to evalue the function and store in the +lookup table.

  • +
  • x_max – The maximum x value at which to evalue the function and store in the +lookup table.

  • +
  • npoints – Number of x values at which to evaluate the function. [default: 2000]

  • +
  • interpolant – Type of interpolation to use. [default: ‘spline’]

  • +
  • x_log – Whether the x values should be uniform in log rather than lienar. +[default: False]

  • +
  • f_log – Whether the f values should be interpolated using their logarithms +rather than their raw values. [default: False]

  • +
+
+
+
+ +
+
+integrate(x_min=None, x_max=None)[source]
+

Calculate an estimate of the integral of the tabulated function from x_min to x_max:

+
+\[\int_{x_\mathrm{min}}^{x_\mathrm{max}} f(x) dx\]
+

This function is not implemented for LookupTables that use log for either x or f, +or that use a galsim.Interpolant. Also, if x_min or x_max are beyond the range +of the tabulated function, the function will be considered to be zero there.

+
+

Note

+

The simplest version of this function is equivalent in functionality to the numpy +trapz function. However, it is usually significantly faster. If you have a +time-critical integration for which you are currently using np.trapz:

+
>>> ans = np.trapz(f, x)
+
+
+

the following replacement may be faster:

+
>>> ans = galsim.trapz(f, x)
+
+
+

which is an alias for:

+
>>> ans = galsim._LookupTable(x, f, 'linear').integrate()
+
+
+
+
+
Parameters:
+
    +
  • x_min – The minimum abscissa to use for the integral. [default: None, which +means to use self.x_min]

  • +
  • x_max – The maximum abscissa to use for the integral. [default: None, which +means to use self.x_max]

  • +
+
+
Returns:
+

an estimate of the integral

+
+
+
+ +
+
+integrate_product(g, x_min=None, x_max=None, x_factor=1.0)[source]
+

Calculate an estimate of the integral of the tabulated function multiplied by a second +function from x_min to x_max:

+
+\[\int_{x_\mathrm{min}}^{x_\mathrm{max}} f(x) g(x) dx\]
+

If the second function, \(g(x)\), is another LookupTable, then the quadrature will +use the abscissae from both that function and \(f(x)\) (i.e. self). +Otherwise, the second function will be evaluated at the abscissae of \(f(x)\).

+

This function is not implemented for LookupTables that use log for either x or f, +or that use a galsim.Interpolant. Also, if x_min or x_max are beyond the range +of either tabulated function, the function will be considered to be zero there.

+

Also, the second function \(g(x)\) is always approximated with linear interpolation +between the abscissae, even if it is a LookupTable with a different specified +interpolation.

+
+
Parameters:
+
    +
  • g – The function to multiply by the current function for the integral.

  • +
  • x_min – The minimum abscissa to use for the integral. [default: None, which +means to use self.x_min]

  • +
  • x_max – The maximum abscissa to use for the integral. [default: None, which +means to use self.x_max]

  • +
  • x_factor – Optionally scale the x values of f by this factor when doing the integral. +I.e. Find \(\int f(x x_\mathrm{factor}) g(x) dx\). [default: 1]

  • +
+
+
Returns:
+

an estimate of the integral

+
+
+
+ +
+
+property x_max
+

The maximum x value in the lookup table.

+
+ +
+
+property x_min
+

The minimum x value in the lookup table.

+
+ +
+ +
+
+class galsim.LookupTable2D(x, y, f, dfdx=None, dfdy=None, d2fdxdy=None, interpolant='linear', edge_mode='raise', constant=0)[source]
+

LookupTable2D represents a 2-dimensional lookup table to store function values that may be slow +to calculate, for which interpolating from a lookup table is sufficiently accurate. A +LookupTable2D is also useful for evaluating periodic 2-d functions given samples from a single +period.

+

A LookupTable2D representing the function f(x, y) may be constructed from a list or array of +x values, a list or array of y values, and a 2D array of function evaluations at all +combinations of x and y values. For instance:

+
>>> x = np.arange(5)
+>>> y = np.arange(8)
+>>> z = x + y[:, np.newaxis]  # function is x + y, dimensions of z are (8, 5)
+>>> tab2d = galsim.LookupTable2D(x, y, z)
+
+
+

To evaluate new function values with the lookup table, use the () operator:

+
>>> print tab2d(2.2, 3.3)
+5.5
+
+
+

The () operator can also accept sequences (lists, tuples, numpy arrays, …) for the x and y +arguments at which to evaluate the LookupTable2D. Normally, the x and y sequences should have +the same length, which will also be the length of the output sequence:

+
>>> print tab2d([1, 2], [3, 5])
+array([ 4., 7.])
+
+
+

If you add grid=True as an additional kwarg, however, then the () operator will generate +interpolated values at the outer product of x-values and y-values. So in this case, the x and +y sequences can have different lengths Nx and Ny, and the result will be a 2D array with +dimensions (Nx, Ny):

+
>>> print tab2d([1, 2], [3, 5], grid=True)
+array([[ 4., 6.],
+       [ 5., 7.]])
+
+
+

The default interpolation method is linear. Other choices for the interpolant are:

+
+
    +
  • ‘floor’

  • +
  • ‘ceil’

  • +
  • ‘nearest’

  • +
  • ‘spline’ (a Catmull-Rom cubic spline).

  • +
  • a galsim.Interpolant or string convertible to one.

  • +
+
+
>>> tab2d = galsim.LookupTable2D(x, y, z, interpolant='floor')
+>>> tab2d(2.2, 3.7)
+5.0
+>>> tab2d = galsim.LookupTable2D(x, y, z, interpolant='ceil')
+>>> tab2d(2.2, 3.7)
+7.0
+>>> tab2d = galsim.LookupTable2D(x, y, z, interpolant='nearest')
+>>> tab2d(2.2, 3.7)
+6.0
+
+
+

For interpolant=’spline’ or a galsim.Interpolant, the input arrays must be uniformly spaced. +For interpolant=’spline’, the derivatives df / dx, df / dy, and d^2 f / dx dy at grid-points may +also optionally be provided if they’re known, which will generally yield a more accurate +interpolation (these derivatives will be estimated from finite differences if they’re not +provided).

+

The edge_mode keyword describes how to handle extrapolation beyond the initial input range. +Possibilities include:

+
+
    +
  • ‘raise’: raise an exception. (This is the default.)

  • +
  • ‘warn’: issues a warning, then falls back to edge_mode=’constant’.

  • +
  • ‘constant’: Return a constant specified by the constant keyword.

  • +
  • ‘wrap’: infinitely wrap the initial range in both directions.

  • +
+
+

In order for LookupTable2D to determine the wrapping period when edge_mode=’wrap’, either the +x and y grid points need to be equally spaced (in which case the x-period is inferred as +len(x)*(x[1]-x[0]) and similarly for y), or the first/last row/column of f must be identical, +in which case the x-period is inferred as x[-1] - x[0]. (If both conditions are satisfied +(equally-spaced x and y and identical first/last row/column of f, then the x-period is inferred +as len(x)*(x[1]-x[0])):

+
>>> x = np.arange(5)
+>>> y = np.arange(8)
+>>> z = x + y[:, np.newaxis]  # function is x + y, dimensions of z are (8, 5)
+>>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='raise')
+>>> tab2d(7, 7)
+ValueError: Extrapolating beyond input range.
+
+>>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='constant', constant=1.0)
+1.0
+
+>>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='wrap')
+ValueError: Cannot wrap `f` array with unequal first/last column/row.
+
+
+

We extend the x and y arrays with a uniform spacing, though any monotonic spacing would work. +Note that the [(0,1), (0,1)] argument in np.pad below extends the z array by 0 rows/columns in +the leading direction, and 1 row/column in the trailing direction:

+
>>> x = np.append(x, x[-1] + (x[-1]-x[-2]))
+>>> y = np.append(y, y[-1] + (y[-1]-y[-2]))
+>>> z = np.pad(z, [(0,1), (0,1)], mode='wrap')
+>>> tab2d = galsim.LookupTable2D(x, y, z, edge_mode='wrap')
+>>> tab2d(2., 2.)
+4.0
+>>> tab2d(2.+5, 2.)  # The period is 5 in the x direction
+4.0
+>>> tab2d(2.+3*5, 2.+4*8)  # The period is 8 in the y direction
+4.0
+
+
+
+
Parameters:
+
    +
  • x – Strictly increasing array of x positions at which to create table.

  • +
  • y – Strictly increasing array of y positions at which to create table.

  • +
  • f – Nx by Ny input array of function values.

  • +
  • dfdx – Optional first derivative of f wrt x. Only used if interpolant=’spline’. +[default: None]

  • +
  • dfdy – Optional first derivative of f wrt y. Only used if interpolant=’spline’. +[default: None]

  • +
  • d2fdxdy – Optional cross derivative of f wrt x and y. Only used if +interpolant=’spline’. [default: None]

  • +
  • interpolant – Type of interpolation to use. One of ‘floor’, ‘ceil’, ‘nearest’, ‘linear’, +‘spline’, or a galsim.Interpolant or string convertible to one. +[default: ‘linear’]

  • +
  • edge_mode – Keyword controlling how extrapolation beyond the input range is handled. +See above for details. [default: ‘raise’]

  • +
  • constant – A constant to return when extrapolating beyond the input range and +edge_mode='constant'. [default: 0]

  • +
+
+
+
+
+__call__(x, y, grid=False)[source]
+

Interpolate at an arbitrary point or points.

+
+
Parameters:
+
    +
  • x – Either a single x value or an array of x values at which to interpolate.

  • +
  • y – Either a single y value or an array of y values at which to interpolate.

  • +
  • grid – Optional boolean indicating that output should be a 2D array corresponding +to the outer product of input values. If False (default), then the output +array will be congruent to x and y.

  • +
+
+
Returns:
+

a scalar value if x and y are scalar, or a numpy array if x and y are arrays.

+
+
+
+ +
+
+gradient(x, y, grid=False)[source]
+

Calculate the gradient of the function at an arbitrary point or points.

+
+
Parameters:
+
    +
  • x – Either a single x value or an array of x values at which to compute +the gradient.

  • +
  • y – Either a single y value or an array of y values at which to compute +the gradient.

  • +
  • grid – Optional boolean indicating that output should be a 2-tuple of 2D arrays +corresponding to the outer product of input values. If False (default), +then the output arrays will be congruent to x and y.

  • +
+
+
Returns:
+

A tuple of (dfdx, dfdy) where dfdx, dfdy are single values (if x,y were single +values) or numpy arrays.

+
+
+
+ +
+ +
+
+galsim.table._LookupTable2D(x, y, f, interpolant, edge_mode, constant, dfdx=None, dfdy=None, d2fdxdy=None, x0=None, y0=None, xperiod=None, yperiod=None)[source]
+

Make a LookupTable2D but without using any of the sanity checks or array manipulation used +in the normal initializer.

+
+ +
+
+galsim.trapz(f, x, interpolant='linear')[source]
+

Integrate f(x) using the trapezoidal rule.

+

Equivalent to np.trapz(f,x) for 1d array inputs. Intended as a drop-in replacement, +which is usually faster.

+
+
Parameters:
+
    +
  • f – The ordinates of the function to integrate.

  • +
  • x – The abscissae of the function to integrate.

  • +
  • interpolant – The interpolant to use between the tabulated points. [default: ‘linear’, +which matches the behavior of np.trapz]

  • +
+
+
Returns:
+

Estimate of the integral.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/transform.html b/docs/_build/html/transform.html new file mode 100644 index 00000000000..8e4871b0fb9 --- /dev/null +++ b/docs/_build/html/transform.html @@ -0,0 +1,456 @@ + + + + + + + Transformed Profiles — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Transformed Profiles

+

Any profile can be modified in a number of ways, including stretching, dilating, +rotating, and translating, and even more complicated things like forming the +inverse in Fourier space to effect a deconvolution.

+
+

Affine Transformations

+
+
+class galsim.Transformation(obj, jac=None, offset=(0.0, 0.0), flux_ratio=1.0, gsparams=None, propagate_gsparams=True)[source]
+

Bases: GSObject

+

A class for modeling an affine transformation of a GSObject instance.

+

Typically, you do not need to construct a Transformation object explicitly. This is the type +returned by the various transformation methods of GSObject such as GSObject.shear, +GSObject.rotate, GSObject.shift, GSObject.transform, etc.

+

All the various transformations can be described as a combination of a jacobian matrix +(i.e. GSObject.transform) and a translation (GSObject.shift), which are described by +(dudx,dudy,dvdx,dvdy) and (dx,dy) respectively.

+
+

Note

+

The name of the flux_ratio parameter is technically wrong here if the jacobian has a +non-unit determinant, since that would also scale the flux. The flux_ratio parameter +actually only refers to an overall amplitude ratio for the surface brightness profile. +The total flux scaling is actually |det(jac)| * flux_ratio.

+
+
+
Parameters:
+
    +
  • obj – The object to be transformed.

  • +
  • jac – A list, tuple or numpy array ( dudx, dudy, dvdx, dvdy ) describing +the Jacobian of the transformation. Use None to indicate that the +Jacobian is the 2x2 unit matrix. [default: None]

  • +
  • offset – A galsim.PositionD or tuple giving the offset by which to shift the +profile. [default: (0.,0.)]

  • +
  • flux_ratio – A factor by which to multiply the surface brightness of the object. +(Technically, not necessarily the flux. See above.) [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the transformed object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Attributes:
+
    +
  • original – The original object that is being transformed.

  • +
  • jac – The jacobian of the transformation matrix.

  • +
  • offset – The offset being applied.

  • +
  • flux_ratio – The amount by which the overall surface brightness amplitude is multiplied.

  • +
  • gsparams – The usual gsparams attribute that all GSObject classes have.

  • +
+
+
+

Note: if gsparams is unspecified (or None), then the Transformation instance inherits the +GSParams from obj. Also, note that parameters related to the Fourier-space calculations must +be set when initializing obj, NOT when creating the Transform (at which point the accuracy and +threshold parameters will simply be ignored).

+
+
+property flux_ratio
+

The flux ratio of the transformation.

+
+ +
+
+property jac
+

The Jacobian of the transforamtion.

+
+ +
+
+property offset
+

The offset of the transformation.

+
+ +
+
+property original
+

The original object being transformed.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of the wrapped component object.

+
+
+ +
+ +
+
+galsim.Transform(obj, jac=None, offset=(0.0, 0.0), flux_ratio=1.0, gsparams=None, propagate_gsparams=True)[source]
+

A function for transforming either a GSObject or ChromaticObject.

+

This function will inspect its input argument to decide if a Transformation object or a +ChromaticTransformation object is required to represent the resulting transformed object.

+

Note: the name of the flux_ratio parameter is technically wrong here if the jacobian has a +non-unit determinant, since that would also scale the flux. The flux_ratio parameter actually +only refers to an overall amplitude ratio for the surface brightness profile. The total +flux scaling is actually |det(jac)| * flux_ratio.

+
+
Parameters:
+
    +
  • obj – The object to be transformed.

  • +
  • jac – A list or tuple ( dudx, dudy, dvdx, dvdy ) describing the Jacobian +of the transformation. Use None to indicate that the Jacobian is the +2x2 unit matrix. [default: None]

  • +
  • offset – A galsim.PositionD or tuple giving the offset by which to shift the +profile. [default: (0.,0.)]

  • +
  • flux_ratio – A factor by which to multiply the surface brightness of the object. +(Technically, not necessarily the flux. See above.) [default: 1]

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the transformed object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

a Transformation or ChromaticTransformation instance as appropriate.

+
+
+
+ +
+
+galsim._Transform(obj, jac=None, offset=(0.0, 0.0), flux_ratio=1.0)[source]
+

Approximately equivalent to Transform, but without some of the sanity checks (such as +checking for chromatic options) or setting a new gsparams.

+

For a ChromaticObject, you must use the regular Transform.

+
+
Parameters:
+
    +
  • obj – The object to be transformed.

  • +
  • jac – A 2x2 numpy array describing the Jacobian of the transformation. +Use None to indicate that the Jacobian is the 2x2 unit matrix. +[default: None]

  • +
  • offset – The offset to apply. [default (0.,0.)]

  • +
  • flux_ratio – A factor by which to multiply the surface brightness of the object. +[default: 1.]

  • +
+
+
+
+ +
+
+

De-convolution of a GSObject

+
+
+class galsim.Deconvolution(obj, gsparams=None, propagate_gsparams=True)[source]
+

Bases: GSObject

+

A class for deconvolving a GSObject.

+

The Deconvolution class represents a deconvolution kernel. Note that the Deconvolution class, +or compound objects (Sum, Convolution) that include a Deconvolution as one of the components, +cannot be photon-shot using the ‘phot’ method of GSObject.drawImage method.

+

You may also specify a gsparams argument. See the docstring for GSParams for more +information about this option. Note: if gsparams is unspecified (or None), then the +Deconvolution instance inherits the same GSParams as the object being deconvolved.

+

The normal way to use this class is to use the Deconvolve() factory function:

+
>>> inv_psf = galsim.Deconvolve(psf)
+>>> deconv_gal = galsim.Convolve(inv_psf, gal)
+
+
+
+
Parameters:
+
    +
  • obj – The object to deconvolve.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the deconvolved object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+property orig_obj
+

The original object that is being deconvolved.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of the wrapped component object.

+
+
+ +
+ +
+
+galsim.Deconvolve(obj, gsparams=None, propagate_gsparams=True)[source]
+

A function for deconvolving by either a GSObject or ChromaticObject.

+

This function will inspect its input argument to decide if a Deconvolution object or a +ChromaticDeconvolution object is required to represent the deconvolution by a surface +brightness profile.

+
+
Parameters:
+
    +
  • obj – The object to deconvolve.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the deconvolved object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

a Deconvolution or ChromaticDeconvolution instance as appropriate.

+
+
+
+ +
+
+

Fourier-space Square Root

+
+
+class galsim.FourierSqrtProfile(obj, gsparams=None, propagate_gsparams=True)[source]
+

Bases: GSObject

+

A class for computing the Fourier-space sqrt of a GSObject.

+

The FourierSqrtProfile class represents the Fourier-space square root of another profile. +Note that the FourierSqrtProfile class, or compound objects (Sum, Convolution) that include a +FourierSqrtProfile as one of the components cannot be photon-shot using the ‘phot’ method of +GSObject.drawImage method.

+

You may also specify a gsparams argument. See the docstring for GSParams for more +information about this option. Note: if gsparams is unspecified (or None), then the +FourierSqrtProfile instance inherits the same GSParams as the object being operated on.

+

The normal way to use this class is to use the FourierSqrt factory function:

+
>>> fourier_sqrt = galsim.FourierSqrt(obj)
+
+
+
+
Parameters:
+
    +
  • obj – The object to compute Fourier-space square root of.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the transformed object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
+
+
+property orig_obj
+

The original object being Fourier sqrt-ed.

+
+ +
+
+withGSParams(gsparams=None, **kwargs)[source]
+

Create a version of the current object with the given gsparams

+
+

Note

+

Unless you set propagate_gsparams=False, this method will also update the gsparams +of the wrapped component object.

+
+
+ +
+ +
+
+galsim.FourierSqrt(obj, gsparams=None, propagate_gsparams=True)[source]
+

A function for computing the Fourier-space square root of either a GSObject or +ChromaticObject.

+

The FourierSqrt function is principally used for doing an optimal coaddition algorithm +originally developed by Nick Kaiser (but unpublished) and also described by Zackay & Ofek 2015 +(http://adsabs.harvard.edu/abs/2015arXiv151206879Z). See the script make_coadd.py in the +GalSim/examples directory for an example of how it works.

+

This function will inspect its input argument to decide if a FourierSqrtProfile object or a +ChromaticFourierSqrtProfile object is required to represent the operation applied to a surface +brightness profile.

+
+
Parameters:
+
    +
  • obj – The object to compute the Fourier-space square root of.

  • +
  • gsparams – An optional GSParams argument. [default: None]

  • +
  • propagate_gsparams – Whether to propagate gsparams to the transformed object. This +is normally a good idea, but there may be use cases where one +would not want to do this. [default: True]

  • +
+
+
Returns:
+

a FourierSqrtProfile or ChromaticFourierSqrtProfile instance as appropriate.

+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/tutorials.html b/docs/_build/html/tutorials.html new file mode 100644 index 00000000000..474b841d1a4 --- /dev/null +++ b/docs/_build/html/tutorials.html @@ -0,0 +1,744 @@ + + + + + + + Tutorials — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Tutorials

+

The GalSim/examples directory contains demo files serve as tutorials on how to use the +GalSim code.

+

There are versions of each demo in both Python (demo*.py) and YAML (demo*.yaml). +The demos start fairly simple and progress to more sophisticated simulations, adding +a modest number of new features each time with copious documentation about the new features +being introduced.

+

The YAML files are run using the galsim executable, which parses the YAML file into a Python +dict and runs this through The Config Module in GalSim. For complicated simulations, +we generally recommend using config files such as these, since they tend to be more quickly +readable than the Python scripts, which makes it easy to see how to modify them to effect +some desired change in the simulation. For more information about running the galsim +executable, see The galsim Executable.

+

Both versions of each demo produce identical output files. Internally, this serves as a useful +test of the config parsing code. But it also serves as a kind of implicit documentation +about how some of the config features are handled by GalSim.

+
+

Demo 1

+

demo1.py +demo1.yaml

+

This first demo is about as simple as it gets. We draw an image of a single galaxy +convolved with a PSF and write it to disk. We use a circular Gaussian profile for both the +PSF and the galaxy, and add a constant level of Gaussian noise to the image.

+

New features in the Python file:

+
    +
  • obj = galsim.Gaussian(flux, sigma)

  • +
  • obj = galsim.Convolve([list of objects])

  • +
  • image = obj.drawImage(scale)

  • +
  • image.added_flux (Only present after a drawImage command.)

  • +
  • noise = galsim.GaussianNoise(sigma)

  • +
  • image.addNoise(noise)

  • +
  • image.write(file_name)

  • +
  • image.FindAdaptiveMom()

  • +
+

New features in the YAML file:

+
    +
  • top level fields gal, psf, image, output

  • +
  • obj type : Gaussian (flux, sigma)

  • +
  • image : pixel_scale

  • +
  • image : noise

  • +
  • noise type : Gaussian (sigma)

  • +
  • output : dir, file_name

  • +
+
+
+

Demo 2

+

demo2.py +demo2.yaml

+

This demo is a bit more sophisticated, but still pretty basic. We still only make +a single image, but now the galaxy has an exponential radial profile and is sheared. +The PSF is a circular Moffat profile. The noise is drawn from a Poisson distribution +using the flux from both the object and a background sky level to determine the +variance in each pixel.

+

New features in the Python file:

+
    +
  • obj = galsim.Exponential(flux, scale_radius)

  • +
  • obj = galsim.Moffat(beta, flux, half_light_radius)

  • +
  • obj = obj.shear(g1, g2) – with explanation of other ways to specify shear

  • +
  • rng = galsim.BaseDeviate(seed)

  • +
  • noise = galsim.PoissonNoise(rng, sky_level)

  • +
  • galsim.hsm.EstimateShear(image, image_epsf)

  • +
+

New features in the YAML file:

+
    +
  • obj type : Exponential (flux, scale_radius)

  • +
  • obj type : Moffat (flux, beta, half_light_radius)

  • +
  • obj : shear

  • +
  • shear type : G1G2 (g1, g2)

  • +
  • noise type : Poisson (sky_level)

  • +
  • image : random_seed

  • +
+
+
+

Demo 3

+

demo3.py +demo3.yaml

+

This demo gets reasonably close to including all the principal features of an image +from a ground-based telescope. The galaxy is represented as the sum of a bulge and a disk, +where each component is represented by a sheared Sersic profile (with different Sersic +indices). The PSF has both atmospheric and optical components. The atmospheric +component is a Kolmogorov turbulent spectrum. The optical component includes defocus, +coma and astigmatism, as well as obscuration from a secondary mirror. The noise model +includes both a gain and read noise. And finally, we include the effect of a slight +telescope distortion.

+

New features in the Python file:

+
    +
  • obj = galsim.Sersic(n, flux, half_light_radius)

  • +
  • obj = galsim.Sersic(n, flux, scale_radius)

  • +
  • obj = galsim.Kolmogorov(fwhm)

  • +
  • obj = galsim.OpticalPSF(lam_over_diam, defocus, coma1, coma2, astig1, astig2, obscuration)

  • +
  • obj = obj.shear(e, beta) – including how to specify an angle in GalSim

  • +
  • shear = galsim.Shear(q, beta)

  • +
  • obj = obj.shear(shear)

  • +
  • obj3 = x1 * obj1 + x2 * obj2

  • +
  • obj = obj.withFlux(flux)

  • +
  • image = galsim.ImageF(image_size, image_size)

  • +
  • image = obj.drawImage(image, wcs)

  • +
  • image = obj.drawImage(method=’sb’)

  • +
  • world_profile = wcs.toWorld(profile)

  • +
  • shear3 = shear1 + shear2

  • +
  • noise = galsim.CCDNoise(rng, sky_level, gain, read_noise)

  • +
+

New features in the YAML file:

+
    +
  • obj type : Sum (items)

  • +
  • obj type : Convolve (items)

  • +
  • obj type : Sersic (flux, n, half_light_radius)

  • +
  • obj type : Sersic (flux, n, scale_radius)

  • +
  • obj type : Kolmogorov (fwhm)

  • +
  • obj type : OpticalPSF (lam_over_diam, defocus, coma1, coma2, astig1, astig2, obscuration)

  • +
  • obj : ellip

  • +
  • shear type : QBeta (q, beta) – including how to specify an angle

  • +
  • shear type : EBeta (e, beta)

  • +
  • noise type : CCD (sky_level, gain, read_noise)

  • +
  • image : size

  • +
  • image : wcs

  • +
  • wcs type : Shear

  • +
  • output : psf

  • +
+
+
+

Demo 4

+

demo4.py +demo4.yaml

+

This demo is our first one to create multiple images. Typically, you would want each object +to have at least some of its attributes vary when you are drawing multiple images (although +not necessarily – you might just want different noise realization of the same profile). +The easiest way to do this is to read in the properties from a catalog, which is what we +do in this case. The PSF is a truncated Moffat profile, and the galaxy is bulge plus disk. +Both components get many of their parameters from an input catalog. We also shift the +profile by a fraction of a pixel in each direction so the effect of pixelization varies +among the images. Each galaxy has the same applied shear. The noise is simple Poisson noise. +We write the images out into a multi-extension fits file.

+

New features in the Python file:

+
    +
  • cat = galsim.Catalog(file_name, dir)

  • +
  • obj = galsim.Moffat(beta, fwhm, trunc)

  • +
  • obj = galsim.DeVaucouleurs(flux, half_light_radius)

  • +
  • obj = galsim.RandomKnots(npoints, half_light_radius, flux)

  • +
  • obj = galsim.Add([list of objects])

  • +
  • obj = obj.shift(dx,dy)

  • +
  • galsim.fits.writeMulti([list of images], file_name)

  • +
+

New features in the YAML file:

+
    +
  • obj type : Moffat (…, trunc)

  • +
  • obj type : DeVaucouleurs (flux, half_light_radius)

  • +
  • obj type : RandomKnots (npoints, half_light_radius, flux)

  • +
  • value type : Catalog (col)

  • +
  • obj : shift

  • +
  • shift type : XY (x, y)

  • +
  • shear type : E1E2 (e1, e2)

  • +
  • image : xsize, ysize

  • +
  • top level field input

  • +
  • input : catalog (file_name, dir)

  • +
  • output type : MultiFits (file_name, dir)

  • +
  • Using both ellip and shear for the same object

  • +
  • Using variables in a YAML file

  • +
+
+
+

Demo 5

+

demo5.py +demo5.yaml

+

This demo is intended to mimic a Great08 (Bridle, et al, 2010) LowNoise image. +We produce a single image made up of tiles of postage stamps for each individual object. +(We only do 10 x 10 postage stamps rather than 100 x 100 as they did in the interest of time.) +Each postage stamp is 40 x 40 pixels. One image is all stars. A second image is all galaxies. +The stars are truncated Moffat profiles. The galaxies are Exponential profiles. +(Great08 mixed pure bulge and pure disk for its LowNoise run. We just use disks to +make things simpler. However see demo3 for an example of using bulge+disk galaxies.) +The galaxies are oriented randomly, but in 90 degree-rotated pairs to cancel the effect of +shape noise. The applied shear is the same for each galaxy.

+

New features in the Python file:

+
    +
  • ud = galsim.UniformDeviate(seed)

  • +
  • gd = galsim.GaussianDeviate(ud, sigma)

  • +
  • ccdnoise = galsim.CCDNoise(ud)

  • +
  • image *= scalar

  • +
  • bounds = galsim.BoundsI(xmin, xmax, ymin, ymax)

  • +
  • pos = bounds.center

  • +
  • pos.x, pos.y

  • +
  • sub_image = image[bounds]

  • +
  • Build a single large image, and access sub-images within it.

  • +
  • Set the galaxy size based on the PSF size and a resolution factor.

  • +
  • Set the object flux according to a target S/N value.

  • +
  • Use 90 degree-rotated pairs for the intrinsic galaxy shapes.

  • +
  • Shift by a random (dx, dy) drawn from a unit circle top hat.

  • +
+

New features in the YAML file:

+
    +
  • gal : resolution

  • +
  • gal : signal_to_noise

  • +
  • stamp type : Ring (first, num)

  • +
  • value type : RandomGaussian (sigma, min, max)

  • +
  • angle type : Random

  • +
  • shift type : RandomCircle (radius)

  • +
  • image type : Tiled (nx_tiles, ny_tiles, stamp_xsize, stamp_ysize, border)

  • +
  • output type : Fits (file_name, dir)

  • +
  • output.psf : shift

  • +
+
+
+

Demo 6

+

demo6.py +demo6.yaml

+

This demo uses real galaxy images from COSMOS observations. The catalog of real galaxy +images distributed with GalSim only includes 100 galaxies, but you can download a much +larger set of images as described in Downloading the COSMOS Catalog.

+

The galaxy images are already convolved with the effective PSF for the original +observations, so GalSim considers the galaxy profile to be the observed image deconvolved +by that PSF (also distributed with the galaxy data). +In this case, we then randomly rotate the galaxies, apply a given gravitational shear as +well as gravitational magnification, and then finally convolve by a double Gaussian PSF. +The final image can of course have any pixel scale, not just that of the original images. +The output for this demo is to a FITS “data cube”. With DS9, this can be viewed with a +slider to quickly move through the different images.

+

New features in the Python file:

+
    +
  • real_cat = galsim.RealGalaxyCatalog(file_name, dir)

  • +
  • obj = galsim.Gaussian(fwhm, flux)

  • +
  • obj = galsim.RealGalaxy(real_cat, index, flux)

  • +
  • obj = obj.rotate(theta)

  • +
  • obj = obj.magnify(mu)

  • +
  • image += background

  • +
  • noise = galsim.PoissonNoise() # with no sky_level given

  • +
  • obj.drawImage(…, offset)

  • +
  • galsim.fits.writeCube([list of images], file_name)

  • +
+

New features in the YAML file:

+
    +
  • input : real_catalog (file_name, dir, image_dir)

  • +
  • obj type : RealGalaxy (index)

  • +
  • obj : rotate

  • +
  • obj : magnify

  • +
  • image : sky_level

  • +
  • image : offset

  • +
  • value type : Sequence (first, last, step)

  • +
  • output type : DataCube (file_name, dir, nimages)

  • +
  • Using YAML multiple document feature to do more than one thing

  • +
+
+
+

Demo 7

+

demo7.py +demo7.yaml

+

This demo introduces drawing profiles with photon shooting rather than doing the +convolution with an FFT. It makes images using 5 different kinds of PSF and 5 different +kinds of galaxy. Some of the parameters (flux, size and shape) are random variables, so +each of the 25 pairings is drawn 4 times with different realizations of the random numbers. +The profiles are drawn twice: once with the FFT method, and once with photon shooting. +The two images are drawn side by side into the same larger image so it is easy to +visually compare the results. The 100 total profiles are written to a FITS data cube, +which makes it easy to scroll through the images comparing the two drawing methods.

+

New features in the Python file:

+
    +
  • obj = galsim.Airy(lam_over_diam)

  • +
  • obj = galsim.Sersic(n, half_light_radius, trunc)

  • +
  • psf = galsim.OpticalPSF(…, aberrations=aberrations, …)

  • +
  • obj = obj.dilate(scale)

  • +
  • str(obj)

  • +
  • image.scale = pixel_scale

  • +
  • obj.drawImage(image, method=’fft’)

  • +
  • obj.drawImage(image, method=’phot’, max_extra_noise, rng)

  • +
  • dev = galsim.PoissonDeviate(rng, mean)

  • +
  • noise = galsim.DeviateNoise(dev)

  • +
  • writeCube(…, compress=’gzip’)

  • +
  • gsparams = galsim.GSParams(…)

  • +
+

New features in the YAML file:

+
    +
  • obj type : List (items)

  • +
  • obj type : Airy (lam_over_diam)

  • +
  • obj type : Sersic (…, trunc)

  • +
  • obj : dilate

  • +
  • value type : Sequence (…, repeat, index_key)

  • +
  • value type : Random (min, max)

  • +
  • image type : Tiled (…, stamp_size, xborder, yborder)

  • +
  • stamp : draw_method (fft or phot)

  • +
  • stamp : gsparams

  • +
  • output : file_name with .gz, .bz2 or .fz extension automatically uses compression.

  • +
+
+
+

Demo 8

+

demo8.py +demo8.yaml

+

In this demo, we show how to run the GalSim config processing using a python dict rather +than using a config file. The previous demos have shown what Python code corresponds to +the given YAML files. Now we turn the tables +and show how to use some of the machinery in the GalSim configuration processing +from within Python itself.

+

This could be useful if you want to use the config machinery to build the images, but then +rather than write the images to disk, you want to keep them in memory and do further +processing with them. (e.g. Run your shape measurement code on the images from within python.)

+

New features in the Python file:

+
    +
  • galsim.config.Process(config, logger)

  • +
  • galsim.config.ProcessInput(config, logger)

  • +
  • galsim.config.BuildFile(config, file_num, logger)

  • +
  • image = galsim.config.BuildImage(config, image_num, logger)

  • +
  • galsim.fits.read(file_name)

  • +
+

New features in the YAML file:

+
    +
  • stamp : retry_failures

  • +
  • shear type : Eta1Eta2 (eta1, eta2)

  • +
  • image : nproc

  • +
+
+
+

Demo 9

+

demo9.py +demo9.yaml

+

This script simulates cluster lensing or galaxy-galaxy lensing. The gravitational shear +applied to each galaxy is calculated for an NFW halo mass profile. We simulate observations +of galaxies around 20 different clusters – 5 each of 4 different masses. Each cluster +has its own file, organized into 4 directories (one for each mass). For each cluster, we +draw 20 lensed galaxies located at random positions in the image. The PSF is appropriate for a +space-like simulation. (Some of the numbers used are the values for HST.) And we apply +a cubic telescope distortion for the WCS. Finally, we also output a truth catalog for each +output image that could be used for testing the accuracy of shape or flux measurements.

+

New features in the Python file:

+
    +
  • psf = OpticalPSF(lam, diam, …, trefoil1, trefoil2, nstruts, strut_thick, strut_angle)

  • +
  • im = galsim.ImageS(xsize, ysize, wcs)

  • +
  • pos = galsim.PositionD(x, y)

  • +
  • nfw = galsim.NFWHalo(mass, conc, z, omega_m, omega_lam)

  • +
  • g1,g2 = nfw.getShear(pos, z)

  • +
  • mag = nfw.getMagnification(pos, z)

  • +
  • distdev = galsim.DistDeviate(rng, function, x_min, x_max)

  • +
  • pos = bounds.true_center

  • +
  • wcs = galsim.UVFunction(ufunc, vfunc, xfunc, yfunc, origin)

  • +
  • wcs.toWorld(profile, image_pos)

  • +
  • wcs.makeSkyImage(image, sky_level)

  • +
  • image_pos = wcs.toImage(pos)

  • +
  • image.invertSelf()

  • +
  • truth_cat = galsim.OutputCatalog(names, types)

  • +
  • bounds.isDefined()

  • +
  • Make multiple output files.

  • +
  • Place galaxies at random positions on a larger image.

  • +
  • Write a bad pixel mask and a weight image as the second and third HDUs in each file.

  • +
  • Use multiple processes to construct each file in parallel.

  • +
+

New features in the YAML file:

+
    +
  • obj type : OpticalPSF (lam, diam, …, trefoil1, trefoil2, nstruts, strut_thick, strut_angle)

  • +
  • obj type : InclinedExponential (scale_radius, scale_h_over_r, inclination)

  • +
  • angle type : Radians

  • +
  • shear type : NFWHaloShear (redshift)

  • +
  • float type : NFWHaloMagnification (redshift)

  • +
  • float type : RandomDistribution(function, x_min, x_max)

  • +
  • input : nfw_halo (mass, conc, redshift)

  • +
  • shear type : Sum (items)

  • +
  • image type : Scattered (size, nobjects)

  • +
  • wcs type : UVFunction (ufunc, vfunc, xfunc, yfunc, origin)

  • +
  • str type : NumberedFile (root, num, ext, digits)

  • +
  • str type : FormattedStr (format, items)

  • +
  • pos type : RandomCircle (…, inner_radius)

  • +
  • value type : Sequence (…, nitems)

  • +
  • output : nproc

  • +
  • output : weight

  • +
  • output : badpix

  • +
  • output : truth

  • +
  • output : skip

  • +
  • output : noclobber

  • +
+
+
+

Demo 10

+

demo10.py +demo10.yaml

+

This script uses both a variable PSF and variable shear, taken from a power spectrum, along +the lines of a Great10 (Kitching, et al, 2012) image. The galaxies are placed on a grid +(10 x 10 in this case, rather than 100 x 100 in the interest of time.) Each postage stamp +is 48 x 48 pixels. Instead of putting the PSF images on a separate image, we package them +as the second HDU in the file. For the galaxies, we use a random selection from 5 specific +RealGalaxy objects, selected to be 5 particularly irregular ones. (These are taken from +the same catalog of 100 objects that demo6 used.) The galaxies are oriented in a ring +test (Nakajima & Bernstein 2007) of 20 each. And we again output a truth catalog with the +correct applied shear for each object (among other information).

+

New features in the Python file:

+
    +
  • im.wcs = galsim.OffsetWCS(scale, origin)

  • +
  • rng = galsim.BaseDeviate(seed)

  • +
  • obj = galsim.RealGalaxy(real_galaxy_catalog, id)

  • +
  • obj = galsim.Convolve([list], real_space)

  • +
  • ps = galsim.PowerSpectrum(e_power_function, b_power_function)

  • +
  • g1,g2 = ps.buildGrid(grid_spacing, ngrid, rng)

  • +
  • g1,g2 = ps.getShear(pos)

  • +
  • galsim.random.permute(rng, list1, list2, …)

  • +
  • Choosing PSF parameters as a function of (x,y)

  • +
  • Selecting RealGalaxy by ID rather than index.

  • +
  • Putting the PSF image in a second HDU in the same file as the main image.

  • +
  • Using PowerSpectrum for the applied shear.

  • +
  • Doing a full ring test (i.e. not just 90 degree rotated pairs)

  • +
+

New features in the YAML file:

+
    +
  • obj type : Ring (…, full_rotation)

  • +
  • obj type : RealGalaxy (…, id)

  • +
  • type : Eval using world_pos variable, user-defined variables and math functions

  • +
  • type : Current

  • +
  • shear_value : PowerSpectrumShear

  • +
  • pos_value : RTheta (r, theta)

  • +
  • image type : Tiled (…, order)

  • +
  • input : power_spectrum (e_power_function, b_power_function)

  • +
  • output.psf : hdu, signal_to_noise, draw_method, offset

  • +
  • output.truth : hdu

  • +
  • Evaluated values in output.truth.columns

  • +
+
+
+

Demo 11

+

demo11.py +demo11.yaml

+

This script uses a constant PSF from real data (an image read in from a bzipped FITS file, not a +parametric model) and variable shear and magnification according to some cosmological model for +which we have a tabulated shear power spectrum at specific k values only. The 288 galaxies in the 0.1 x +0.1 degree field (representing a number density of 8/arcmin^2) are randomly located and +permitted to overlap. For the galaxies, we use a mix of real and parametric galaxies modeled off +the COSMOS observations with the Hubble Space Telescope. The real galaxies are similar to those +used in demo10. The parametric galaxies are based on parametric fits to the same observed galaxies. +The flux and size distribution are thus realistic for an I < 23.5 magnitude limited sample.

+

New features in the Python file:

+
    +
  • coord = galsim.CelestialCoord(ra, dec)

  • +
  • wcs = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin)

  • +
  • wcs = galsim.TanWCS(affine, world_origin, units)

  • +
  • psf = galsim.InterpolatedImage(psf_filename, scale, flux)

  • +
  • tab = galsim.LookupTable(file)

  • +
  • cosmos_cat = galsim.COSMOSCatalog(file_name, dir)

  • +
  • gal = cosmos_cat.makeGalaxy(gal_type, rng, noise_pad_size)

  • +
  • ps = galsim.PowerSpectrum(…, units)

  • +
  • gal = gal.lens(g1, g2, mu)

  • +
  • image.whitenNoise(correlated_noise)

  • +
  • image.symmetrizeNoise(correlated_noise)

  • +
  • vn = galsim.VariableGaussianNoise(rng, var_image)

  • +
  • image.addNoise(cn)

  • +
  • image.setOrigin(x,y)

  • +
  • angle.dms(), angle.hms()

  • +
  • Power spectrum shears and magnifications for non-gridded positions.

  • +
  • Reading a compressed FITS image (using BZip2 compression).

  • +
  • Writing a compressed FITS image (using Rice compression).

  • +
  • Writing WCS information to a FITS header that ds9 reads as RA, Dec

  • +
+

New features in the YAML file:

+
    +
  • obj type : InterpolatedImage(image, scale)

  • +
  • obj type : COSMOSGalaxy

  • +
  • obj : scale_flus

  • +
  • image : draw_method (no_pixel)

  • +
  • input : power_spectrum (e_power_file, delta2, units)

  • +
  • input : cosmos_catalog (file_name, dir, use_real)

  • +
  • image : index_convention

  • +
  • image.noise : whiten

  • +
  • image.noise : symmetrize

  • +
  • wcs type : Tan(dudx, dudy, dvdx, dvdy, units, origin, ra, dec)

  • +
  • top level field eval_variables

  • +
  • Power spectrum shears and magnifications for non-gridded positions.

  • +
  • Reading a compressed FITS image (using BZip2 compression).

  • +
  • Writing a compressed FITS image (using Rice compression).

  • +
  • Using $ as a shorthand for Eval type.

  • +
+
+
+

Demo 12

+

demo12.py +demo12.yaml

+

This demo introduces wavelength-dependent profiles. Three kinds of chromatic profiles are +demonstrated:

+
    +
  1. A chromatic object representing a DeVaucouleurs galaxy with an early-type SED at redshift 0.8. +This galaxy is drawn using the six LSST filters, which demonstrate that the galaxy is a +g-band dropout.

  2. +
  3. A two-component bulge+disk galaxy, in which the bulge and disk have different SEDs.

  4. +
  5. A wavelength-dependent atmospheric PSF, which includes the effect of differential chromatic +refraction and the wavelength dependence of Kolmogorov-turbulence-induced seeing. This PSF +is convolved with a simple Exponential galaxy.

  6. +
+

New features in the Python file:

+
    +
  • SED = galsim.SED(wave, flambda, wave_type, flux_type)

  • +
  • SED2 = SED.atRedshift(redshift)

  • +
  • bandpass = galsim.Bandpass(filename, wave_type)

  • +
  • bandpass2 = bandpass.truncate(relative_throughput)

  • +
  • bandpass3 = bandpass2.thin(rel_err)

  • +
  • gal = GSObject * SED

  • +
  • obj = galsim.Add([list of ChromaticObjects])

  • +
  • ChromaticObject.drawImage(bandpass)

  • +
  • PSF = galsim.ChromaticAtmosphere(GSObject, base_wavelength, zenith_angle)

  • +
+

New features in the YAML file:

+
    +
  • +
    sedfile_name, wave_type, flux_type, norm_flux_density, norm_wavelength,

    norm_flux, norm_bandpass

    +
    +
    +
  • +
  • bandpass : filename, wave_type, thin

  • +
  • gal : redshift

  • +
  • psf_type : ChromaticAtmosphere (base_profile, base_wavelength, latitude, HA)

  • +
+
+
+

Demo 13

+

demo13.py +demo13.yaml

+

This script is intended to produce a relatively realistic scene of galaxies and stars as will +be observed by the Roman Space Telescope, including the Roman PSF, WCS, and various NIR detector +effects.

+

It introduces several non-idealities arising from NIR detectors, in particular those that will +be observed and accounted for in the Roman Space Telescope. Three such non-ideal effects are +demonstrated, in the order in which they are introduced in the detectors:

+
    +
  1. Reciprocity failure: Flux-dependent sensitivity of the detector.

  2. +
  3. Non-linearity: Charge-dependent gain in converting from units of electrons to ADU. Non-linearity +in some form is also relevant for CCDs in addition to NIR detectors.

  4. +
  5. Interpixel capacitance: Influence of charge in a pixel on the voltage reading of neighboring +ones.

  6. +
+

It also uses chromatic photon shooting, which is generally a more efficient way to simulate +scenes with many faint galaxies. The default FFT method for drawing chromatically is fairly +slow, since it needs to integrate the image over the bandpass. With photon shooting, the +photons are assigned wavelengths according to the SED of the galaxy, and then each photon has +the appropriate application of the chromatic PSF according to the wavelength.

+

New features in the Python file:

+
    +
  • image.quantize()

  • +
  • obj = galsim.DeltaFunction(flux)

  • +
  • galsim.roman.addReciprocityFailure(image)

  • +
  • galsim.roman.applyNonlinearity(image)

  • +
  • galsim.roman.applyIPC(image)

  • +
  • galsim.roman.getBandpasses()

  • +
  • galsim.roman.getPSF()

  • +
  • galsim.roman.getWCS()

  • +
  • galsim.roman.allowedPos()

  • +
  • galsim.roman.getSkyLevel()

  • +
+

New features in the YAML file:

+
    +
  • Top-level field modules

  • +
  • obj type: RomanPSF

  • +
  • image type: RomanSCA

  • +
  • draw_method=phot in conjunction with chromatic objects

  • +
  • Multiple random seeds (particular so one can repeat the same values for multiple images)

  • +
  • rng_num specification in various fields

  • +
  • Multiple inputs of the same type. Use num to specify which item in the list to use each time.

  • +
+
+
+

Advanced Simulations

+
+

Great3 Simulations

+

In the directory GalSim/examples/great3, +there are YAML config files that perform essentially the same +simulations that were done for Great3. The config apparatus had not matured sufficiently by the +time the Great3 sims were run, so these are not what the Great3 team used. However, the files +in this directory produce essentially equivalent simulations as those used in Great3.

+

So far there are only config files for the cgc and rgc branches of Great3, but we plan to add +the files for the other branches (Issue #699).

+

Significant features in these files:

+
    +
  • template option to load another config file and then modify a few aspects of it. (e.g. +rgc.yaml )

  • +
  • template option to load only a particular field from another config file. (e.g. +cgc_psf.yaml )

  • +
  • stamp.reject

  • +
  • custom value type (e.g. Great3Reject in cgc.yaml )

  • +
  • custom extra output type (e.g. noise_free in cgc.yaml )

  • +
  • top-level module field

  • +
  • use of ‘$’ and ‘@’ shorthand in Eval items.

  • +
+
+
+

DES Simulations

+

In the directory examples/des, +there are YAML config files that showcase some of the classes +defined in the galsim.des module. These are mostly gratuitous demos designed to showcase +various features, although meds.yaml +is very close to a real simulation we actually used in DES for testing shear measurements.

+

Significant features in these files:

+
    +
  • top-level module field

  • +
  • special object types from galsim.des module (e.g. DES_Shapelet and DES_PSFEx in +draw_psf.yaml )

  • +
  • special output type from galsim.des module (e.g. MEDS in meds.yaml )

  • +
  • custom value type (e.g. HSM_Shape_Measure in meds.yaml, LogNormal in blend.yaml )

  • +
  • custom WCS type (e.g. DES_Local in meds.yaml )

  • +
  • custom input type (e.g. des_wcs in meds.yaml )

  • +
  • custom stamp types (e.g. Blend in blend.yaml and BlendSet in blendset.yaml )

  • +
  • custom extra output type (e.g. deblend in blend.yaml )

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/units.html b/docs/_build/html/units.html new file mode 100644 index 00000000000..3d6b76e7c6d --- /dev/null +++ b/docs/_build/html/units.html @@ -0,0 +1,729 @@ + + + + + + + Units — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Units

+
+

Size Units

+

GalSim models surface brightnesses of objects in sky coordinates. The physical size of a +galaxy in light years or kpc is not really relevant to its appearance in the sky. Rather, +the size is an angle, being the angle subtended by the galaxy as seen from Earth.

+

The most common choice of unit for these objects is arcseconds. This happens to be the +right order of magnitude for most objects of astronomical interest. The smallest objects +we typically observe are somewhat less than an arcsecond in size. The largest objects +(e.g. M31) are more than a degree, but by far the majority of the objects of interest are +only a few arcseconds across.

+

So this is usually the units one would use for the various GSObject size parameters +(e.g. fwhm, half_light_radius and so forth) when you are building them. However, +this choice is actually up to the user. You may choose to define all your sizes in +degrees or radians if you want. You just need to be consistent with the unit you use +for the sizes of all your objects and with the units of the pixel scale when you are +building your Image (via the scale parameter of the Image constructor) or when +drawing (the scale argument to GSObject.drawImage)

+
+

Note

+

If you are using a more complicated WCS than a simple PixelScale (i.e. using wcs when +building the Image rather than scale), then you need to be even more careful about this. +Some of the FITS-based WCS classes assume you are using arcseconds for the distance unit, +and it is not always easy to coerce them into using a different unit. In these cases, +you are probably well-advised to just stick with arcseconds for your sizes.

+
+

Some classes specify their angular sizes somewhat indirectly. For the Airy class, for instance, +you can specify lam as the wavelength of the light (\(\lambda\), say +at the middle of the bandpass) in nm, and diam, the telescope diameter (\(D\)) in meters. +The ratio of these \(\lambda / D\) (after putting them into the same units) gives the +fundamental scale radius for an Airy profile. But this angle is in radians, which is normally +not particularly convenient to use for the image pixel scale. The Airy constructor thus takes +another parameter, scale_unit, which defaults to arcsec to specify the unit you want to +convert this angle to.

+
+
+

Flux Units

+

The units for the flux of a GSObject are nominally photons/cm^2/s, and the units for an +image are ADUs (analog-to-digital units). There are thus four conversion factors to apply to +go from one to the other.

+
    +
  1. The exposure time (s)

  2. +
  3. The effective collecting area of the telescope (cm^2)

  4. +
  5. The quantum efficiency (QE) of the collector (e-/photon)

  6. +
  7. The gain of the read-out amplifier (e-/ADU)

  8. +
+

In GalSim, we generally just lump the QE in with the gain, so our gain is taken to have units of +photons/ADU, and it really represents gain / QE.

+

Note, however, that the default exposure time, telescope collecting area, and gain are 1 s, 1 cm^2, +and 1 ADU/photon respectively, so users who wish to ignore the intricacies of managing exposure +times, collecting areas, and gains can simply think of the flux of a GSObject in either ADUs or +photons.

+

However, if you prefer to think of your flux as having physical units, then you can declare +the appropriate telescope collecting area (area), the exposure time (exptime), and the +total effective gain (gain) as arguments to GSObject.drawImage.

+
+
+

SED Units

+

These details matter more when working with ChromaticObject instances, where the flux +normalization is handled with an SED object. The units of an input SED can be any of +several possible options:

+
    +
  1. erg/nm/cm^2/s or erg/A/cm^2/s (use flux_type='flambda')

  2. +
  3. erg/Hz/cm^2/s (use flux_typ='fnu')

  4. +
  5. photons/nm/cm^2/s or photons/A/cm^2/s (use flux_typ='fphotons')

  6. +
  7. Any units that qualify as an astropy.units.spectral_density using the AstroPy units +module

  8. +
  9. dimensionless (use flux_typ='1')

  10. +
+
+

Note

+

The last one is a bit different from the others. It is generally only appropriate for the +“SED” of a PSF, not that of a galaxy or star. The PSF may have a different effect as a +function of wavelength, in which case that can be treated similarly to how we treat an SED. +In any object that is a convolution of several components, only one of them should have a +spectral SED. The rest should be dimensionless (possibly flat). The net SED of the +composite object will then also be spectral.

+
+

Internally, all spectral units are converted to photons/nm/cm^2/s. Then when drawing a +ChromaticObject, spectrum is integrated over the Bandpass to obtain the normal units of +photons/cm^2/s. If you trust your SED, you can then just draw with the appropriate area, +exptime and gain when you call ChromaticObject.drawImage.

+

However, it is often more convenient to target a particular flux or magnitude of your object +as observed through a particular Bandpass (probably in ADU) and then ignore all of these +parameters when you are drawing. This is possible using the methods SED.withFlux or +SED.withMagnitude.

+
+
+

Angles

+

For nearly all angular values, we require the argument to be an Angle instance. +We use the LSSTDESC.Coord package for this (and its CelestialCoord class):

+

https://github.com/LSSTDESC/Coord

+

An earlier version of this code was originally implemented in GalSim, so we +still import the relevant classes into the galsim namespace, so for example +gasim.Angle is a valid alias for coord.Angle. You may therefor use either namespace +for your use of these classes.

+
+
+class galsim.Angle(theta, unit=None)[source]
+

A class representing an Angle. Angles are a value with an AngleUnit.

+

Initialization:

+
+

You typically create an Angle by multiplying a number by a coord.AngleUnit, for example:

+
+
>>> pixel = 0.27 * arcsec
+>>> ra = 13.4 * hours
+>>> dec = -32 * degrees
+>>> from math import pi
+>>> theta = pi/2. * radians
+
+
+
+

You can also initialize explicitly, taking a value and a unit:

+
+

coord.Angle.__init__()

+
>>> unit = AngleUnit(math.pi / 100)  # gradians
+>>> phi = Angle(90, unit)
+
+
+
+
+

Built-in units:

+
+

There are five built-in AngleUnits which are always available for use:

+
+
+
coord.radians:
+

coord.AngleUnit(1.)

+
+
coord.degrees:
+

coord.AngleUnit(pi / 180.)

+
+
coord.hours:
+

coord.AngleUnit(pi / 12.)

+
+
coord.arcmin:
+

coord.AngleUnit(pi / 180. / 60.)

+
+
coord.arcsec:
+

coord.AngleUnit(pi / 180. / 3600.)

+
+
+
+
+

Attribute:

+
+

Since extracting the value in radians is extremely common, we have a read-only attribute +to do this quickly:

+
+
+
rad:
+

The measure of the unit in radians.

+
+
+
+

For example:

+
+
>>> theta = 90 * degrees
+>>> print(theta.rad)
+1.5707963267948966
+
+
+
+

It is equivalent to the more verbose:

+
+
>>> x = theta / radians
+>>> print(x)
+1.5707963267948966
+
+
+
+

but without actually requiring the floating point operation of dividing by 1.

+
+

Arithmetic:

+
+

Allowed arithmetic with Angles include the following. +In the list below,

+
+
    +
  • x is an arbitrary float value

  • +
  • unit1 and unit2 are arbitrary AngleUnit instances

  • +
  • theta1 and theta2 are arbitrary Angle instances

  • +
+
>>> x = 37.8
+>>> unit1 = arcmin
+>>> unit2 = degrees
+
+
+
>>> theta1 = x * unit1
+>>> theta2 = x * unit2
+>>> x2 = theta1 / unit2
+>>> theta = theta1 + theta2
+>>> theta = theta1 - theta2
+>>> theta = theta1 * x
+>>> theta = x * theta1
+>>> theta = theta1 / x
+>>> theta = -theta1
+>>> theta += theta1
+>>> theta -= theta1
+>>> theta *= x
+>>> theta /= x
+>>> x = unit1 / unit2   # equivalent to x = (1 * unit1) / unit2
+
+
+
+

The above operations on NumPy arrays containing Angles are permitted as well.

+
+

Trigonometry:

+
+

There are convenience function for getting the sin, cos, and tan of an angle, along with +one for getting sin and cos together, which should be more efficient than doing sin and +cos separately:

+
+
+
coord.Angle.sin()
+
coord.Angle.cos()
+
coord.Angle.tan()
+
coord.Angle.sincos()
+
+
>>> sint = theta.sin()  # equivalent to sint = math.sin(theta.rad)
+>>> cost = theta.cos()  # equivalent to cost = math.cos(theta.rad)
+>>> tant = theta.tan()  # equivalent to tant = math.tan(theta.rad)
+>>> sint, cost = theta.sincos()
+
+
+
+

These functions mean that numpy trig functions will work on Angles or arrays of Angles:

+
+
>>> sint = np.sin(theta)
+>>> cost = np.cos(theta)
+>>> tant = np.tan(theta)
+
+
+
+
+

Wrapping:

+
+

Depending on the context, theta = 2pi radians and theta = 0 radians may mean the same thing. +If you want your angles to be wrapped to [-pi,pi) radians, you can do this by calling

+
+

coord.Angle.wrap()

+
>>> theta = theta.wrap()
+
+
+
+

This could be appropriate before testing for the equality of two angles for example, or +calculating the difference between them.

+

There is also an option to wrap into a different 2 pi range if so desired by specifying +the center of the range.

+
+
+
+cos()[source]
+

Return the cos of an Angle.

+
+ +
+
+property deg
+

Return the Angle in degrees.

+

Equivalent to angle / coord.degrees

+
+ +
+
+dms(sep=':', prec=None, pad=True, plus_sign=False)[source]
+

Return a DMS representation of the angle as a string: +-dd:mm:ss.decimal +An optional sep parameter can change the : to something else (e.g. a space or +nothing at all).

+

Note: the reverse process is effected by Angle.from_dms():

+
+
>>> angle = -(5 * degrees + 21 * arcmin + 25.2 * arcsec)
+>>> dms = angle.dms()
+>>> print(dms)
+-05:21:25.2
+>>> angle2 = Angle.from_dms(dms)
+>>> print(angle2 / degrees)
+-5.356999999999999
+
+
+
+
+
Parameters:
+
    +
  • sep – The token to put between the hh and mm and beteen mm and ss. This may +also be a string of 2 or 3 items, e.g. ‘dm’ or ‘dms’. Or even a +tuple of strings such as (‘degrees ‘, ‘minutes ‘, ‘seconds’). +[default: ‘:’]

  • +
  • prec – The number of digits of precision after the decimal point. +[default: None]

  • +
  • pad – Whether to pad with a leading 0 if necessary to make h 2 digits. +[default: True]

  • +
  • plus_sign – Whether to use a plus sign for positive angles. [default: False]

  • +
+
+
Returns:
+

a string of the DMS representation of the angle.

+
+
+
+ +
+
+static from_dms(str)[source]
+

Convert a string of the form dd:mm:ss.decimal into an Angle.

+

There may be an initial + or - (or neither), then two digits for the degrees, two for the +minutes, and two for the seconds. Then there may be a decimal point followed by more +digits. There may be a colon separating dd, mm, and ss, or whitespace, or nothing at all. +In fact, the code will ignore any non-digits between the degrees, minutes, and seconds.

+

Note: the reverse process is effected by Angle.dms():

+
+
>>> angle = -(5 * degrees + 21 * arcmin + 25.2 * arcsec)
+>>> dms = angle.dms()
+>>> print(dms)
+-05:21:25.2
+>>> angle2 = Angle.from_dms(dms)
+>>> print(angle2 / degrees)
+-5.356999999999999
+
+
+
+
+
Parameters:
+

str – The string to parse.

+
+
Returns:
+

the corresponding Angle instance

+
+
+
+ +
+
+static from_hms(str)[source]
+

Convert a string of the form hh:mm:ss.decimal into an Angle.

+

There may be an initial + or - (or neither), then two digits for the hours, two for the +minutes, and two for the seconds. Then there may be a decimal point followed by more +digits. There may be a colon separating hh, mm, and ss, or whitespace, or nothing at all. +In fact, the code will ignore any non-digits between the hours, minutes, and seconds.

+

Note: the reverse process is effected by Angle.hms():

+
+
>>> angle = -5.357 * hours
+>>> hms = angle.hms()
+>>> print(hms)
+-05:21:25.2
+>>> angle2 = Angle.from_hms(hms)
+>>> print(angle2 / hours)
+-5.356999999999999
+
+
+
+
+
Parameters:
+

str – The string to parse.

+
+
Returns:
+

the corresponding Angle instance

+
+
+
+ +
+
+hms(sep=':', prec=None, pad=True, plus_sign=False)[source]
+

Return an HMS representation of the angle as a string: +-hh:mm:ss.decimal.

+

An optional sep parameter can change the : to something else (e.g. a space or +nothing at all).

+

Note: the reverse process is effected by Angle.from_hms():

+
+
>>> angle = -5.357 * hours
+>>> hms = angle.hms()
+>>> print(hms)
+-05:21:25.2
+>>> angle2 = Angle.from_hms(hms)
+>>> print(angle2 / hours)
+-5.356999999999999
+
+
+
+
+
Parameters:
+
    +
  • sep – The token to put between the hh and mm and beteen mm and ss. This may +also be a string of 2 or 3 items, e.g. ‘hm’ or ‘hms’. Or even a +tuple of strings such as (‘hours ‘, ‘minutes ‘, ‘seconds’). +[default: ‘:’]

  • +
  • prec – The number of digits of precision after the decimal point. +[default: None]

  • +
  • pad – Whether to pad with a leading 0 if necessary to make h,m,s 2 digits. +[default: True]

  • +
  • plus_sign – Whether to use a plus sign for positive angles. [default: False]

  • +
+
+
Returns:
+

a string of the HMS representation of the angle.

+
+
+
+ +
+
+property rad
+

Return the Angle in radians.

+

Equivalent to angle / coord.radians

+
+ +
+
+sin()[source]
+

Return the sin of an Angle.

+
+ +
+
+sincos()[source]
+

Return both the sin and cos of an Angle as a numpy array [sint, cost].

+
+ +
+
+tan()[source]
+

Return the tan of an Angle.

+
+ +
+
+wrap(center=None)[source]
+

Wrap Angle to lie in the range [-pi, pi) radians (or other range of 2pi radians)

+

Depending on the context, theta = 2pi radians and theta = 0 radians are the same thing. +If you want your angles to be wrapped to [-pi, pi) radians, you can do this as follows:

+
+
>>> theta = Angle(700 * degrees)
+>>> theta = theta.wrap()
+>>> print(theta.deg)
+-19.99999999999998
+
+
+
+

This could be appropriate before testing for the equality of two angles for example, or +calculating the difference between them.

+

If you want to wrap to a different range than [-pi, pi), you can set the center argument +to be the desired center of the the range. e.g. for return values to fall in [0, 2pi), +you could call

+
+
>>> theta = theta.wrap(center=180. * degrees)
+>>> print(theta / degrees)
+340.0
+
+
+
+
+
Parameters:
+

center – The center point of the wrapped range. [default: 0 radians]

+
+
Returns:
+

the equivalent angle within the range [center-pi, center+pi)

+
+
+
+ +
+ +
+
+galsim._Angle(theta)[source]
+

Equivalent to Angle(theta, coord.radians), but without the normal overhead (which isn’t +much to be honest, but this is nonetheless slightly quicker).

+
+
Parameters:
+

theta – The numerical value of the angle in radians.

+
+
+
+ +
+
+class galsim.AngleUnit(value)[source]
+

A class for defining angular units used by Angle objects.

+

Initialization:

+
+

An AngleUnit takes a single argument for initialization, a float that specifies the size +of the desired angular unit in radians. For example:

+
+

coord.AngleUnit.__init__()

+
>>> gradian = AngleUnit(2. * math.pi / 400.)
+>>> print(gradian)
+coord.AngleUnit(0.015707963267948967)
+
+
+
+
+

Built-in units:

+
+

There are five built-in AngleUnits which are always available for use:

+
+
+
coord.radians:
+

coord.AngleUnit(1.)

+
+
coord.degrees:
+

coord.AngleUnit(pi / 180.)

+
+
coord.hours:
+

coord.AngleUnit(pi / 12.)

+
+
coord.arcmin:
+

coord.AngleUnit(pi / 180. / 60.)

+
+
coord.arcsec:
+

coord.AngleUnit(pi / 180. / 3600.)

+
+
+
+
+

Attribute:

+
+

An AngleUnit as the following (read-only) attribute:

+
+
+
value:
+

The measure of the unit in radians.

+
+
+
+
+
+
+static from_name(unit)[source]
+

Convert a string into the corresponding AngleUnit.

+

Only the start of the string is checked, so for instance ‘radian’ or ‘radians’ is +equivalent to ‘rad’.

+

Valid options are:

+
+
+
rad:
+

AngleUnit(1.)

+
+
deg:
+

AngleUnit(pi / 180.)

+
+
hour or hr:
+

AngleUnit(pi / 12.)

+
+
arcmin:
+

AngleUnit(pi / 180. / 60.)

+
+
arcsec:
+

AngleUnit(pi / 180. / 3600.)

+
+
+
+

Note: these valid names are listed in AngleUnit.valid_names.

+
+
Parameters:
+

unit – The string name of the unit to return

+
+
Returns:
+

an AngleUnit

+
+
+
+ +
+
+property value
+

A read-only attribute giving the measure of the AngleUnit in radians.

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/utilities.html b/docs/_build/html/utilities.html new file mode 100644 index 00000000000..0aa352d5fc3 --- /dev/null +++ b/docs/_build/html/utilities.html @@ -0,0 +1,256 @@ + + + + + + + Helper Functions and Classes — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Helper Functions and Classes

+

We have a number of helper functions and classes that are used by various parts of the +code base. These may be useful to know about, since they may be useful for other projects +that use galsim and have similar needs.

+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/wcs.html b/docs/_build/html/wcs.html new file mode 100644 index 00000000000..8e316bae30d --- /dev/null +++ b/docs/_build/html/wcs.html @@ -0,0 +1,2723 @@ + + + + + + + World Coordinate Systems — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

World Coordinate Systems

+

The World Coordinate System (or WCS) is the traditional term for the mapping from pixel coordinates +to the coordinate system on the sky. +(I know, the world’s down here, and the sky’s up there, so you’d think it would +be reversed, but that’s the way it goes. Astronomy is full of terms that don’t quite make sense +when you look at them too closely.)

+

There are two kinds of world coordinates that we use here:

+
    +
  • Celestial coordinates are defined in terms of right ascension (RA) and declination (Dec). +They are a spherical coordinate system on the sky, akin to longitude and latitude on Earth. +cf. http://en.wikipedia.org/wiki/Celestial_coordinate_system

  • +
  • Euclidean coordinates are defined relative to a tangent plane projection of the sky. +If you imagine the sky coordinates on an actual sphere with a particular radius, then the +tangent plane is tangent to that sphere. We use the labels (u,v) for the coordinates in +this system, where +v points north and +u points west. (Yes, west, not east. As you look +up into the sky, if north is up, then west is to the right.)

  • +
+

The classes in this file provide a mapping from image coordinates (in pixels) to one of these +two kinds of world coordinates. We use the labels (x,y) for the image coordinates.

+
+

WCS Base Classes

+
+
+class galsim.BaseWCS[source]
+

The base class for all other kinds of WCS transformations.

+

All the functions the user will typically need are defined here. Most subclasses just +define helper functions to implement each particular WCS definition. So this base +class defines the common interface for all WCS classes.

+

There are several types of WCS classes that we implement. The basic class hierarchy is:

+
`BaseWCS`
+    --- `EuclideanWCS`
+            --- `UniformWCS`
+                    --- `LocalWCS`
+    --- `CelestialWCS`
+
+
+

These base classes are not constructible. They do not have __init__ defined.

+
    +
  1. LocalWCS classes are those which really just define a pixel size and shape. +They implicitly have the origin in image coordinates correspond to the origin +in world coordinates. They are primarily designed to handle local transformations +at the location of a single galaxy, where it should usually be a good approximation +to consider the pixel shape to be constant over the size of the galaxy.

    +

    Currently we define the following LocalWCS classes:

    +
    - `PixelScale`
    +- `ShearWCS`
    +- `JacobianWCS`
    +
    +
    +
  2. +
  3. UniformWCS classes have a constant pixel size and shape, but they have an arbitrary origin +in both image coordinates and world coordinates. A LocalWCS class can be turned into a +non-local UniformWCS class when an image has its bounds changed, e.g. by the commands +Image.setCenter, Image.setOrigin or Image.shift.

    +

    Currently we define the following non-local, UniformWCS classes:

    +
    - `OffsetWCS`
    +- `OffsetShearWCS`
    +- `AffineTransform`
    +
    +
    +
  4. +
  5. EuclideanWCS classes use a regular Euclidean coordinate system for the world coordinates, +using PositionD for the world positions. We use the notation (u,v) for the world +coordinates and (x,y) for the image coordinates.

    +

    Currently we define the following non-uniform, EuclideanWCS class:

    +
    - `UVFunction`
    +
    +
    +
  6. +
  7. CelestialWCS classes are defined with their world coordinates on the celestial sphere +in terms of right ascension (RA) and declination (Dec). The pixel size and shape are +always variable. We use CelestialCoord for the world coordinates, which helps +facilitate the spherical trigonometry that is sometimes required.

    +

    Currently we define the following CelestialWCS classes: (All but the first are defined +in the file fitswcs.py.)

    +
      +
    • RaDecFunction

    • +
    • AstropyWCS – requires astropy.wcs python module to be installed

    • +
    • PyAstWCS – requires starlink.Ast python module to be installed

    • +
    • WcsToolsWCS – requires wcstools command line functions to be installed

    • +
    • GSFitsWCS – native code, but has less functionality than the above

    • +
    +
  8. +
+

There are also a few factory functions in fitswcs.py intended to act like class initializers:

+
    +
  • FitsWCS tries to read a fits file using one of the above classes and returns an instance of +whichever one it found was successful. It should always be successful, since its final +attempt uses AffineTransform, which has reasonable defaults when the WCS key words are not +in the file, but of course this will only be a very rough approximation of the true WCS.

  • +
  • TanWCS constructs a simple tangent plane projection WCS directly from the projection +parameters instead of from a fits header.

  • +
  • FittedSIPWCS constructs a TAN-SIP WCS by fitting to a list of reference celestial and image +coordinates.

  • +
+

Some things you can do with a WCS instance:

+
    +
  • Convert positions between image coordinates and world coordinates (sometimes referred +to as sky coordinates):

    +
    >>> world_pos = wcs.toWorld(image_pos)
    +>>> image_pos = wcs.toImage(world_pos)
    +
    +
    +

    Note: the transformation from world to image coordinates is not guaranteed to be +implemented. If it is not implemented for a particular WCS class, a NotImplementedError +will be raised.

    +

    The image_pos parameter should be a PositionD. However, world_pos will +be a CelestialCoord if the transformation is in terms of celestial coordinates +(if wcs.isCelestial() == True). Otherwise, it will be a PositionD as well.

    +
  • +
  • Convert a GSObject that is defined in world coordinates to the equivalent profile defined +in terms of image coordinates (or vice versa):

    +
    >>> image_profile = wcs.toImage(world_profile)
    +>>> world_profile = wcs.toWorld(image_profile)
    +
    +
    +

    For non-uniform WCS types (for which wcs.isUniform() == False), these need either an +image_pos or world_pos parameter to say where this conversion should happen:

    +
    >>> image_profile = wcs.toImage(world_profile, image_pos=image_pos)
    +
    +
    +
  • +
  • Construct a local linear approximation of a WCS at a given location:

    +
    >>> local_wcs = wcs.local(image_pos = image_pos)
    +>>> local_wcs = wcs.local(world_pos = world_pos)
    +
    +
    +

    If wcs.toWorld(image_pos) is not implemented for a particular WCS class, then a +NotImplementedError will be raised if you pass in a world_pos argument.

    +

    The returned local_wcs is usually a JacobianWCS instance, but see the doc string for +local for more details.

    +
  • +
  • Construct a full affine approximation of a WCS at a given location:

    +
    >>> affine_wcs = wcs.affine(image_pos = image_pos)
    +>>> affine_wcs = wcs.affine(world_pos = world_pos)
    +
    +
    +

    This preserves the transformation near the location of image_pos, but it is linear, so +the transformed values may not agree as you get farther from the given point.

    +

    The returned affine_wcs is always an AffineTransform instance.

    +
  • +
  • Get some properties of the pixel size and shape:

    +
    >>> area = local_wcs.pixelArea()
    +>>> min_linear_scale = local_wcs.minLinearScale()
    +>>> max_linear_scale = local_wcs.maxLinearScale()
    +>>> jac = local_wcs.jacobian()
    +>>> # Use jac.dudx, jac.dudy, jac.dvdx, jac.dvdy
    +
    +
    +

    Non-uniform WCS types also have these functions, but for them, you must supply either +image_pos or world_pos. So the following are equivalent:

    +
    >>> area = wcs.pixelArea(image_pos)
    +>>> area = wcs.local(image_pos).pixelArea()
    +
    +
    +
  • +
  • Query some overall attributes of the WCS transformation:

    +
    >>> wcs.isLocal()       # is this a local WCS?
    +>>> wcs.isUniform()     # does this WCS have a uniform pixel size/shape?
    +>>> wcs.isCelestial()   # are the world coordinates on the celestial sphere?
    +>>> wcs.isPixelScale()  # is this either a PixelScale or an OffsetWCS?
    +
    +
    +
  • +
+
+
+affine(image_pos=None, world_pos=None, color=None)[source]
+

Return the local AffineTransform of the WCS at a given point.

+

This returns a linearized version of the current WCS at a given point. It +returns an AffineTransform that is locally approximately the same as the WCS in +the vicinity of the given point.

+

It is similar to jacobian(), except that this preserves the offset information +between the image coordinates and world coordinates rather than setting both +origins to (0,0). Instead, the image origin is taken to be image_pos.

+

For non-celestial coordinate systems, the world origin is taken to be +wcs.toWorld(image_pos). In fact, wcs.affine(image_pos) is really just +shorthand for:

+
>>> wcs.jacobian(image_pos).withOrigin(image_pos, wcs.toWorld(image_pos))
+
+
+

For celestial coordinate systems, there is no well-defined choice for the +origin of the Euclidean world coordinate system. So we just take (u,v) = (0,0) +at the given position. So, wcs.affine(image_pos) is equivalent to:

+
>>> wcs.jacobian(image_pos).withOrigin(image_pos)
+
+
+

You can use the returned AffineTransform to access the relevant values of the 2x2 +Jacobian matrix and the origins directly:

+
>>> affine = wcs.affine(image_pos)
+>>> x,y = np.meshgrid(np.arange(0,32,1), np.arange(0,32,1))
+>>> u = affine.dudx * (x-affine.x0) + jac.dudy * (y-affine.y0) + affine.u0
+>>> v = affine.dvdx * (x-affine.x0) + jac.dvdy * (y-affine.y0) + affine.v0
+>>> # ... use u,v values to work directly in sky coordinates.
+
+
+

As usual, you may provide either image_pos or world_pos as you prefer to +specify the location at which to approximate the WCS.

+
+
Parameters:
+
    +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term for which to evaluate the +local affine transform. [default: None]

  • +
+
+
Returns:
+

an AffineTransform instance

+
+
+
+ +
+
+fixColor(color)[source]
+

Fix the color to a particular value.

+

This changes a color-dependent WCS into the corresponding color-independent WCS +for the given color.

+
+
Parameters:
+

color – The value of the color term to use.

+
+
Returns:
+

the new color-independent WCS

+
+
+
+ +
+
+isCelestial()[source]
+

Return whether the world coordinates are CelestialCoord (i.e. ra,dec).

+

wcs.isCelestial() is shorthand for isinstance(wcs, galsim.CelestialWCS).

+
+ +
+
+isLocal()[source]
+

Return whether the WCS transformation is a local, linear approximation.

+

wcs.isLocal() is shorthand for isinstance(wcs, galsim.LocalWCS).

+
+ +
+
+isPixelScale()[source]
+

Return whether the WCS transformation is a simple PixelScale or OffsetWCS.

+

These are the simplest two WCS transformations. PixelScale is local and OffsetWCS +is non-local. If an Image has one of these WCS transformations as its WCS, then +im.scale works to read and write the pixel scale. If not, im.scale will raise a +TypeError exception.

+

wcs.isPixelScale() is shorthand for isinstance(wcs, (galsim.PixelScale, +galsim.OffsetWCS)).

+
+ +
+
+isUniform()[source]
+

Return whether the pixels in this WCS have uniform size and shape.

+

wcs.isUniform() is shorthand for isinstance(wcs, galsim.UniformWCS).

+
+ +
+
+jacobian(image_pos=None, world_pos=None, color=None)[source]
+

Return the local JacobianWCS of the WCS at a given point.

+

This is basically the same as local(), but the return value is guaranteed to be a +JacobianWCS, which can be useful in some situations, since you can access the values +of the 2x2 Jacobian matrix directly:

+
>>> jac = wcs.jacobian(image_pos)
+>>> x,y = np.meshgrid(np.arange(0,32,1), np.arange(0,32,1))
+>>> u = jac.dudx * x + jac.dudy * y
+>>> v = jac.dvdx * x + jac.dvdy * y
+>>> # ... use u,v values to work directly in world coordinates.
+
+
+

If you do not need the extra functionality, then you should use local() +instead, since it may be more efficient.

+
+
Parameters:
+
    +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term for which to evaluate the +local jacobian. [default: None]

  • +
+
+
Returns:
+

a JacobianWCS instance.

+
+
+
+ +
+
+local(image_pos=None, world_pos=None, color=None)[source]
+

Return the local linear approximation of the WCS at a given point.

+
+
Parameters:
+
    +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term for which to evaluate the +local WCS. [default: None]

  • +
+
+
Returns:
+

a LocalWCS instance.

+
+
+
+ +
+
+makeSkyImage(image, sky_level, color=None)[source]
+

Make an image of the sky, correctly accounting for the pixel area, which might be +variable over the image.

+
+
Note: This uses finite differences of the wcs mapping to calculate the area of each

pixel in world coordinates. It is usually pretty accurate everywhere except +within a few arcsec of the north or south poles.

+
+
+
+
Parameters:
+
    +
  • image – The image onto which the sky values will be put.

  • +
  • sky_level – The sky level in ADU/arcsec^2 (or whatever your world coordinate +system units are, if not arcsec).

  • +
  • color – For color-dependent WCS’s, the color term to use for making the +sky image. [default: None]

  • +
+
+
+
+ +
+
+maxLinearScale(image_pos=None, world_pos=None, color=None)[source]
+

Return the maximum linear scale of the transformation in any direction.

+

This is basically the semi-major axis of the Jacobian. Sometimes you need a +linear scale size for some calculation. This function returns the largest +scale in any direction. The function minLinearScale() returns the smallest.

+

For non-uniform WCS transforms, you must provide either image_pos or world_pos +to say where the pixel is located.

+
+
Parameters:
+
    +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term for which to evaluate the +scale. [default: None]

  • +
+
+
Returns:
+

the maximum pixel area in any direction in arcsec.

+
+
+
+ +
+
+minLinearScale(image_pos=None, world_pos=None, color=None)[source]
+

Return the minimum linear scale of the transformation in any direction.

+

This is basically the semi-minor axis of the Jacobian. Sometimes you need a +linear scale size for some calculation. This function returns the smallest +scale in any direction. The function maxLinearScale() returns the largest.

+

For non-uniform WCS transforms, you must provide either image_pos or world_pos +to say where the pixel is located.

+
+
Parameters:
+
    +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term for which to evaluate the +scale. [default: None]

  • +
+
+
Returns:
+

the minimum pixel area in any direction in arcsec.

+
+
+
+ +
+
+pixelArea(image_pos=None, world_pos=None, color=None)[source]
+

Return the area of a pixel in arcsec**2 (or in whatever units you are using for +world coordinates if it is a EuclideanWCS).

+

For non-uniform WCS transforms, you must provide either image_pos or world_pos +to say where the pixel is located.

+
+
Parameters:
+
    +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term for which to evaluate the +pixel area. [default: None]

  • +
+
+
Returns:
+

the pixel area in arcsec**2.

+
+
+
+ +
+
+posToImage(world_pos, color=None)[source]
+

Convert a position from world coordinates to image coordinates.

+

This is equivalent to wcs.toImage(world_pos).

+
+
Parameters:
+
    +
  • world_pos – The world coordinate position

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
+
+ +
+
+posToWorld(image_pos, color=None, **kwargs)[source]
+

Convert a position from image coordinates to world coordinates.

+

This is equivalent to wcs.toWorld(image_pos).

+
+
Parameters:
+
    +
  • image_pos – The position in image coordinates

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
  • project_center – (Only valid for CelestialWCS) A CelestialCoord to use for +projecting the result onto a tangent plane world system rather +than returning a CelestialCoord. [default: None]

  • +
  • projection – If project_center != None, the kind of projection to use. See +CelestialCoord.project for the valid options. [default: ‘gnomonic’]

  • +
+
+
Returns:
+

world_pos

+
+
+
+ +
+
+profileToImage(world_profile, image_pos=None, world_pos=None, color=None, flux_ratio=1.0, offset=(0, 0))[source]
+

Convert a profile from world coordinates to image coordinates.

+

This is equivalent to wcs.toImage(world_profile, ...).

+
+
Parameters:
+
    +
  • world_profile – The profile in world coordinates to transform.

  • +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
  • flux_ratio – An optional flux scaling to be applied at the same time. +[default: 1]

  • +
  • offset – An optional offset to be applied at the same time. [default: 0,0]

  • +
+
+
+
+ +
+
+profileToWorld(image_profile, image_pos=None, world_pos=None, color=None, flux_ratio=1.0, offset=(0, 0))[source]
+

Convert a profile from image coordinates to world coordinates.

+

This is equivalent to wcs.toWorld(image_profile, ...).

+
+
Parameters:
+
    +
  • image_profile – The profile in image coordinates to transform.

  • +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
  • flux_ratio – An optional flux scaling to be applied at the same time. +[default: 1]

  • +
  • offset – An optional offset to be applied at the same time. [default: 0,0]

  • +
+
+
+
+ +
+
+shearToImage(world_shear, image_pos=None, world_pos=None, color=None)[source]
+

Convert a shear measured in world coordinates to image coordinates

+

This is equivalent to wcs.toImage(shear, ...).

+

This reverses the process described in shearToWorld.

+
+
Parameters:
+
    +
  • world_shear – The shear in world coordinates to convert.

  • +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
Returns:
+

The shear in image coordinates.

+
+
Return type:
+

image_shear

+
+
+
+ +
+
+shearToWorld(image_shear, image_pos=None, world_pos=None, color=None)[source]
+

Convert a shear measured in image coordinates to world coordinates

+

This is equivalent to wcs.toWorld(shear, ...).

+

Specifically, the input shear is taken to be defined such that +e1 means elongated along +the x-axis, -e1 is along the y-axis, +e2 is along the y=x line, and -e2 is along y=-x.

+

If the WCS is a EuclideanWCS, then the returned shear is converted to the equivalent +shear in u-v coordinates. I.e. +e1 is along the u-axis, etc.

+

If the WCS is a CelestialWCS, then the returned shear is converted to sky coordinates where +North is up and West is to the right (as appropriate for looking up into the sky from Earth). +I.e. +e1 is E-W, -e1 is N-S, +e2 is NW-SE, and -e2 is NE-SW. This is the convention used +by many, but not all, codes and catalogs that use or report shears on the spherical sky.

+
+
Parameters:
+
    +
  • image_shear – The shear in image coordinates to convert.

  • +
  • image_pos – The image coordinate position (for non-uniform WCS types)

  • +
  • world_pos – The world coordinate position (for non-uniform WCS types)

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
Returns:
+

The shear in world coordinates.

+
+
Return type:
+

world_shear

+
+
+
+ +
+
+shiftOrigin(origin, world_origin=None, color=None)[source]
+

Shift the origin of the current WCS function, returning the new WCS.

+

This function creates a new WCS instance (always a non-local WCS) that shifts the +origin by the given amount. In other words, it treats the image position origin +the same way the current WCS treats (x,y) = (0,0).

+

If the current WCS is a local WCS, this essentially declares where on the image +you want the origin of the world coordinate system to be. i.e. where is (u,v) = (0,0). +So, for example, to set a WCS that has a constant pixel size with the world coordinates +centered at the center of an image, you could write:

+
>>> wcs = galsim.PixelScale(scale).shiftOrigin(im.center)
+
+
+

This is equivalent to the following:

+
>>> wcs = galsim.OffsetWCS(scale, origin=im.center)
+
+
+

For non-local WCS types, the origin defines the location in the image coordinate system +should mean the same thing as (x,y) = (0,0) does for the current WCS. The following +example should work regardless of what kind of WCS this is:

+
>>> world_pos1 = wcs.toWorld(PositionD(0,0))
+>>> wcs2 = wcs.shiftOrigin(new_origin)
+>>> world_pos2 = wcs2.toWorld(new_origin)
+>>> # world_pos1 should be equal to world_pos2
+
+
+

Furthermore, if the current WCS is a EuclideanWCS (wcs.isCelestial() == False) you may +also provide a world_origin argument which defines what (u,v) position you want to +correspond to the new origin. Continuing the previous example:

+
>>> wcs3 = wcs.shiftOrigin(new_origin, new_world_origin)
+>>> world_pos3 = wcs3.toWorld(new_origin)
+>>> # world_pos3 should be equal to new_world_origin
+
+
+
+
Parameters:
+
    +
  • origin – The image coordinate position to use for what is currently treated +as (0,0).

  • +
  • world_origin – The world coordinate position to use at the origin. Only valid if +wcs.isCelestial() == False. [default: None]

  • +
  • color – For color-dependent WCS’s, the color term to use in the connection +between the current origin and world_origin. [default: None]

  • +
+
+
Returns:
+

the new shifted WCS

+
+
+
+ +
+
+toImage(*args, **kwargs)[source]
+

Convert from world coordinates to image coordinates

+

There are essentially three overloaded versions of this function here.

+
    +
  1. The first converts a position from world coordinates to image coordinates. +If the WCS is a EuclideanWCS, the argument may be either a PositionD or PositionI +argument. If it is a CelestialWCS, then the argument must be a CelestialCoord. +It returns the corresponding position in image coordinates as a PositionD:

    +
    >>> image_pos = wcs.toImage(world_pos)
    +
    +
    +

    Equivalent to posToImage.

    +
  2. +
  3. The second is nearly the same, but takes either u and v values or ra and dec values +(depending on the kind of wcs being used) directly and returns x and y values. +For this version, the inputs may be numpy arrays, in which case the returned values +are also numpy arrays:

    +
    >>> x, y = wcs.toImage(u, v)                 # For EuclideanWCS types
    +>>> x, y = wcs.toImage(ra, dec, units=units) # For CelestialWCS types
    +
    +
    +

    Equivalent to uvToxy or radecToxy.

    +
  4. +
  5. The third converts a surface brightness profile (a GSObject) from world +coordinates to image coordinates, returning the profile in image coordinates +as a new GSObject. For non-uniform WCS transforms, you must provide either +image_pos or world_pos to say where the profile is located so the right +transformation can be performed. And optionally, you may provide a flux scaling +to be performed at the same time:

    +
    >>> image_profile = wcs.toImage(world_profile, image_pos=None, world_pos=None,
    +                                flux_ratio=1, offset=(0,0))
    +
    +
    +

    Equivalent to profileToImage.

    +
  6. +
  7. The fourth converts a shear measurement (a Shear) from image coordinates to world +coordinates. As above, for non-uniform WCS transforms, you must provide either +image_pos or world_pos. If the wcs is a CelestialWCS, then the returned +shear follows the convention used by TreeCorr (among others) for shear on a sphere, +namely in the local coordinates where north is up and west is to the right.

    +
    >>> world_shear = wcs.toWorld(image_shear, image_pos=None, world_pos=None)
    +
    +
    +

    Equivalent to shearToImage.

    +
  8. +
+
+ +
+
+toWorld(*args, **kwargs)[source]
+

Convert from image coordinates to world coordinates.

+

There are essentially four overloaded versions of this function here.

+
    +
  1. The first converts a Position from image coordinates to world coordinates. +It returns the corresponding position in world coordinates as a PositionD if the WCS +is a EuclideanWCS, or a CelestialCoord if it is a CelestialWCS:

    +
    >>> world_pos = wcs.toWorld(image_pos)
    +
    +
    +

    Equivalent to posToWorld.

    +
  2. +
  3. The second is nearly the same, but takes x and y values directly and returns +either u, v or ra, dec, depending on the kind of wcs being used. For this version, +x and y may be numpy arrays, in which case the returned values are also numpy +arrays:

    +
    >>> u, v = wcs.toWorld(x, y)                 # For EuclideanWCS types
    +>>> ra, dec = wcs.toWorld(x, y, units=units) # For CelestialWCS types
    +
    +
    +

    Equivalent to xyTouv or xyToradec.

    +
  4. +
  5. The third converts a surface brightness profile (a GSObject) from image +coordinates to world coordinates, returning the profile in world coordinates +as a new GSObject. For non-uniform WCS transforms, you must provide either +image_pos or world_pos to say where the profile is located, so the right +transformation can be performed. And optionally, you may provide a flux scaling +to be performed at the same time:

    +
    >>> world_profile = wcs.toWorld(image_profile, image_pos=None, world_pos=None,
    +                                flux_ratio=1, offset=(0,0))
    +
    +
    +

    Equivalent to profileToWorld.

    +
  6. +
  7. The fourth converts a shear measurement (a Shear) from image coordinates to world +coordinates. As above, for non-uniform WCS transforms, you must provide either +image_pos or world_pos. If the wcs is a CelestialWCS, then the returned +shear follows the convention used by TreeCorr (among others) for shear on a sphere, +namely in the local coordinates where north is up and west is to the right.

    +
    >>> world_shear = wcs.toWorld(image_shear, image_pos=None, world_pos=None)
    +
    +
    +

    Equivalent to shearToWorld.

    +
  8. +
+
+ +
+
+writeToFitsHeader(header, bounds)[source]
+

Write this WCS function to a FITS header.

+

This is normally called automatically from within the galsim.fits.write() function.

+

The code will attempt to write standard FITS WCS keys so that the WCS will be readable +by other software (e.g. ds9). It may not be able to do so accurately, in which case a +linearized version will be used instead. (Specifically, it will use the local affine +transform with respect to the image center.)

+

However, this is not necessary for the WCS to survive a round trip through the FITS +header, as it will also write GalSim-specific key words that should allow it to +reconstruct the WCS correctly.

+
+
Parameters:
+
    +
  • header – A FitsHeader (or dict-like) object to write the data to.

  • +
  • bounds – The bounds of the image.

  • +
+
+
+
+ +
+ +
+
+class galsim.wcs.CelestialWCS[source]
+

Bases: BaseWCS

+

A CelestialWCS is a BaseWCS whose world coordinates are on the celestial sphere. +We use the CelestialCoord class for the world coordinates.

+
+
+radecToxy(ra, dec, units, color=None)[source]
+

Convert ra,dec from world coordinates to image coordinates.

+

This is equivalent to wcs.toImage(ra,dec, units=units).

+

It is also equivalent to wcs.posToImage(galsim.CelestialCoord(ra * units, dec * units)) +when ra and dec are scalars; however, this routine allows ra and dec to be numpy arrays, +in which case, the calculation will be vectorized, which is often much faster than using +the pos interface.

+
+
Parameters:
+
    +
  • ra – The ra value(s) in world coordinates

  • +
  • dec – The dec value(s) in world coordinates

  • +
  • units – The units to use for the input ra, dec values.

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
+
+ +
+
+property x0
+

The x coordinate of self.origin.

+
+ +
+
+xyToradec(x, y, units=None, color=None)[source]
+

Convert x,y from image coordinates to world coordinates.

+

This is equivalent to wcs.toWorld(x,y, units=units).

+

It is also equivalent to wcs.posToWorld(galsim.PositionD(x,y)).rad when x and y are +scalars if units is ‘radians’; however, this routine allows x and y to be numpy arrays, +in which case, the calculation will be vectorized, which is often much faster than using +the pos interface.

+
+
Parameters:
+
    +
  • x – The x value(s) in image coordinates

  • +
  • y – The y value(s) in image coordinates

  • +
  • units – (Only valid for CelestialWCS, in which case it is required) +The units to use for the returned ra, dec values.

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
Returns:
+

ra, dec

+
+
+
+ +
+
+property y0
+

The y coordinate of self.origin.

+
+ +
+ +
+
+class galsim.wcs.EuclideanWCS[source]
+

Bases: BaseWCS

+

A EuclideanWCS is a BaseWCS whose world coordinates are on a Euclidean plane. +We usually use the notation (u,v) to refer to positions in world coordinates, and +they use the class PositionD.

+
+
+property u0
+

The x component of self.world_origin (aka u).

+
+ +
+
+uvToxy(u, v, color=None)[source]
+

Convert u,v from world coordinates to image coordinates.

+

This is equivalent to wcs.toWorld(u,v).

+

It is also equivalent to wcs.posToImage(galsim.PositionD(u,v)) when u and v are scalars; +however, this routine allows u and v to be numpy arrays, in which case, the calculation +will be vectorized, which is often much faster than using the pos interface.

+
+
Parameters:
+
    +
  • u – The u value(s) in world coordinates

  • +
  • v – The v value(s) in world coordinates

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
+
+ +
+
+property v0
+

The y component of self.world_origin (aka v).

+
+ +
+
+property x0
+

The x component of self.origin.

+
+ +
+
+xyTouv(x, y, color=None)[source]
+

Convert x,y from image coordinates to world coordinates.

+

This is equivalent to wcs.toWorld(x,y).

+

It is also equivalent to wcs.posToWorld(galsim.PositionD(x,y)) when x and y are scalars; +however, this routine allows x and y to be numpy arrays, in which case, the calculation +will be vectorized, which is often much faster than using the pos interface.

+
+
Parameters:
+
    +
  • x – The x value(s) in image coordinates

  • +
  • y – The y value(s) in image coordinates

  • +
  • color – For color-dependent WCS’s, the color term to use. [default: None]

  • +
+
+
Returns:
+

ra, dec

+
+
+
+ +
+
+property y0
+

The y component of self.origin.

+
+ +
+ +
+
+class galsim.wcs.UniformWCS[source]
+

Bases: EuclideanWCS

+

A UniformWCS is a EuclideanWCS which has a uniform pixel size and shape.

+
+
+inverse()[source]
+

Return the inverse transformation, i.e. the transformation that swaps the roles of +the “image” and “world” coordinates.

+
+ +
+ +
+
+class galsim.wcs.LocalWCS[source]
+

Bases: UniformWCS

+

A LocalWCS is a UniformWCS in which (0,0) in image coordinates is at the same place +as (0,0) in world coordinates

+
+
+property origin
+

The image coordinate position to use as the origin.

+
+ +
+
+withOrigin(origin, world_origin=None, color=None)[source]
+

Recenter the current WCS function at a new origin location, returning the new WCS.

+

This function creates a new WCS instance (a non-local WCS) with the same local +behavior as the current WCS, but with the given origin. In other words, you are +declaring where on the image you want the new origin of the world coordinate +system to be. i.e. where is (u,v) = (0,0).

+

So, for example, to set a WCS that has a constant pixel size with the world coordinates +centered at the center of an image, you could write:

+
>>> wcs = galsim.PixelScale(scale).withOrigin(im.center)
+
+
+

This is equivalent to the following:

+
>>> wcs = galsim.OffsetWCS(scale, origin=im.center)
+
+
+

You may also provide a world_origin argument which defines what (u,v) position you +want to correspond to the new origin.

+
>>> wcs2 = galsim.PixelScale(scale).withOrigin(new_origin, new_world_origin)
+>>> world_pos2 = wcs2.toWorld(new_origin)
+>>> assert world_pos2 == new_world_origin
+
+
+
+

Note

+

This is equivalent to shiftOrigin, but for for local WCS’s, the shift is also +the new location of the origin, so withOrigin is a convenient alternate name +for this action. Indeed the shiftOrigin function used to be named withOrigin, +but that name was confusing for non-local WCS’s, as the action in that case is really +shifting the origin, not setting the new value.

+
+
+
Parameters:
+
    +
  • origin – The image coordinate position to use as the origin.

  • +
  • world_origin – The world coordinate position to use as the origin. [default: None]

  • +
  • color – For color-dependent WCS’s, the color term to use in the connection +between the current origin and world_origin. [default: None]

  • +
+
+
Returns:
+

the new recentered WCS

+
+
+
+ +
+
+property world_origin
+

The world coordinate position to use as the origin.

+
+ +
+ +
+
+

Euclidean WCS’s

+
+
+class galsim.PixelScale(scale)[source]
+

Bases: LocalWCS

+

This is the simplest possible WCS transformation. It only involves a unit conversion +from pixels to arcsec (or whatever units you want to take for your world coordinate system).

+

The conversion functions are:

+
+

u = x * scale +v = y * scale

+
+

A PixelScale is initialized with the command:

+
>>> wcs = galsim.PixelScale(scale)
+
+
+
+
Parameters:
+

scale – The pixel scale, typically in units of arcsec/pixel.

+
+
+
+
+property scale
+

The pixel scale

+
+ +
+ +
+
+class galsim.OffsetWCS(scale, origin=None, world_origin=None)[source]
+

Bases: UniformWCS

+

This WCS is similar to PixelScale, except the origin is not necessarily (0,0) in both +the image and world coordinates.

+

The conversion functions are:

+
+

u = (x-x0) * scale + u0 +v = (y-y0) * scale + v0

+
+

An OffsetWCS is initialized with the command:

+
>>> wcs = galsim.OffsetWCS(scale, origin=None, world_origin=None)
+
+
+
+
Parameters:
+
    +
  • scale – The pixel scale, typically in units of arcsec/pixel.

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: PositionD(0., 0.)]

  • +
  • world_origin – Optional origin position for the world coordinate system. +If provided, it should be a PositionD. +[default: galsim.PositionD(0., 0.)]

  • +
+
+
+
+
+property origin
+

The image coordinate position to use as the origin.

+
+ +
+
+property scale
+

The pixel scale.

+
+ +
+
+property world_origin
+

The world coordinate position to use as the origin.

+
+ +
+ +
+
+class galsim.ShearWCS(scale, shear)[source]
+

Bases: LocalWCS

+

This WCS is a uniformly sheared coordinate system.

+

The shear is given as the shape that a round object has when observed in image coordinates.

+

The conversion functions in terms of (g1,g2) are therefore:

+
+

x = (u + g1 u + g2 v) / scale / sqrt(1-g1**2-g2**2) +y = (v - g1 v + g2 u) / scale / sqrt(1-g1**2-g2**2)

+
+

or, writing this in the usual way of (u,v) as a function of (x,y):

+
+

u = (x - g1 x - g2 y) * scale / sqrt(1-g1**2-g2**2) +v = (y + g1 y - g2 x) * scale / sqrt(1-g1**2-g2**2)

+
+

A ShearWCS is initialized with the command:

+
>>> wcs = galsim.ShearWCS(scale, shear)
+
+
+
+
Parameters:
+
    +
  • scale – The pixel scale, typically in units of arcsec/pixel.

  • +
  • shear – The shear, which should be a Shear instance.

  • +
+
+
+

The Shear transformation conserves object area, so if the input scale == 1 then the +transformation represented by the ShearWCS will conserve object area also.

+
+
+property scale
+

The pixel scale.

+
+ +
+
+property shear
+

The applied Shear.

+
+ +
+ +
+
+class galsim.OffsetShearWCS(scale, shear, origin=None, world_origin=None)[source]
+

Bases: UniformWCS

+

This WCS is a uniformly sheared coordinate system with image and world origins +that are not necessarily coincident.

+

The conversion functions are:

+
+

x = ( (1+g1) (u-u0) + g2 (v-v0) ) / scale / sqrt(1-g1**2-g2**2) + x0 +y = ( (1-g1) (v-v0) + g2 (u-u0) ) / scale / sqrt(1-g1**2-g2**2) + y0

+

u = ( (1-g1) (x-x0) - g2 (y-y0) ) * scale / sqrt(1-g1**2-g2**2) + u0 +v = ( (1+g1) (y-y0) - g2 (x-x0) ) * scale / sqrt(1-g1**2-g2**2) + v0

+
+

An OffsetShearWCS is initialized with the command:

+
>>> wcs = galsim.OffsetShearWCS(scale, shear, origin=None, world_origin=None)
+
+
+
+
Parameters:
+
    +
  • scale – The pixel scale, typically in units of arcsec/pixel.

  • +
  • shear – The shear, which should be a Shear instance.

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: PositionD(0., 0.)]

  • +
  • world_origin – Optional origin position for the world coordinate system. +If provided, it should be a PositionD. +[default: PositionD(0., 0.)]

  • +
+
+
+
+
+property origin
+

The image coordinate position to use as the origin.

+
+ +
+
+property scale
+

The pixel scale.

+
+ +
+
+property shear
+

The applied Shear.

+
+ +
+
+property world_origin
+

The world coordinate position to use as the origin.

+
+ +
+ +
+
+class galsim.JacobianWCS(dudx, dudy, dvdx, dvdy)[source]
+

Bases: LocalWCS

+

This WCS is the most general local linear WCS implementing a 2x2 Jacobian matrix.

+

The conversion functions are:

+
+

u = dudx x + dudy y +v = dvdx x + dvdy y

+
+

A JacobianWCS has attributes dudx, dudy, dvdx, dvdy that you can access directly if that +is convenient. You can also access these as a NumPy array directly with:

+
>>> J = jac_wcs.getMatrix()
+
+
+

Also, JacobianWCS has another method that other WCS classes do not have. The call:

+
>>> scale, shear, theta, flip = jac_wcs.getDecomposition()
+
+
+

will return the equivalent expansion, shear, rotation and possible flip corresponding to +this transformation. See the docstring for that method for more information.

+

A JacobianWCS is initialized with the command:

+
>>> wcs = galsim.JacobianWCS(dudx, dudy, dvdx, dvdy)
+
+
+
+
Parameters:
+
    +
  • dudx – du/dx

  • +
  • dudy – du/dy

  • +
  • dvdx – dv/dx

  • +
  • dvdy – dv/dy

  • +
+
+
+
+
+property dudx
+

du/dx

+
+ +
+
+property dudy
+

du/dy

+
+ +
+
+property dvdx
+

dv/dx

+
+ +
+
+property dvdy
+

dv/dy

+
+ +
+
+getDecomposition()[source]
+

Get the equivalent expansion, shear, rotation and possible flip corresponding to +this Jacobian transformation.

+

A non-singular real matrix can always be decomposed into a symmetric positive definite +matrix times an orthogonal matrix:

+
+

M = P Q

+
+

In our case, P includes an overall scale and a shear, and Q is a rotation and possibly +a flip of (x,y) -> (y,x).

+
+

( dudx dudy ) = scale/sqrt(1-g1^2-g2^2) ( 1+g1 g2 ) ( cos(theta) -sin(theta) ) F +( dvdx dvdy ) ( g2 1-g1 ) ( sin(theta) cos(theta) )

+
+
+
where F is either the identity matrix, ( 1 0 ), or a flip matrix, ( 0 1 ).

( 0 1 ) ( 1 0 )

+
+
+

If there is no flip, then this means that the effect of:

+
>>> prof.transform(dudx, dudy, dvdx, dvdy)
+
+
+

is equivalent to:

+
>>> prof.rotate(theta).shear(shear).expand(scale)
+
+
+

in that order. (Rotation and shear do not commute.)

+

The decomposition is returned as a tuple: (scale, shear, theta, flip), where scale is a +float, shear is a Shear, theta is an Angle, and flip is a bool.

+
+ +
+
+getMatrix()[source]
+

Get the Jacobian as a NumPy matrix:

+
+
numpy.array( [[ dudx, dudy ],

[ dvdx, dvdy ]] )

+
+
+
+ +
+ +
+
+class galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=None, world_origin=None)[source]
+

Bases: UniformWCS

+

This WCS is the most general linear transformation. It involves a 2x2 Jacobian +matrix and an offset. You can provide the offset in terms of either the image_pos +(x0,y0) where (u,v) = (0,0), or the world_pos (u0,v0) where (x,y) = (0,0). +Or, in fact, you may provide both, in which case the image_pos (x0,y0) corresponds +to the world_pos (u0,v0).

+

The conversion functions are:

+
+

u = dudx (x-x0) + dudy (y-y0) + u0 +v = dvdx (x-x0) + dvdy (y-y0) + v0

+
+

An AffineTransform has attributes dudx, dudy, dvdx, dvdy, x0, y0, u0, v0 that you can +access directly if that is convenient.

+

An AffineTransform is initialized with the command:

+
>>> wcs = galsim.AffineTransform(dudx, dudy, dvdx, dvdy, origin=None, world_origin=None)
+
+
+
+
Parameters:
+
    +
  • dudx – du/dx

  • +
  • dudy – du/dy

  • +
  • dvdx – dv/dx

  • +
  • dvdy – dv/dy

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: PositionD(0., 0.)]

  • +
  • world_origin – Optional origin position for the world coordinate system. +If provided, it should be a PositionD. +[default: PositionD(0., 0.)]

  • +
+
+
+
+
+property dudx
+

du/dx

+
+ +
+
+property dudy
+

du/dy

+
+ +
+
+property dvdx
+

dv/dx

+
+ +
+
+property dvdy
+

dv/dy

+
+ +
+
+property origin
+

The image coordinate position to use as the origin.

+
+ +
+
+property world_origin
+

The world coordinate position to use as the origin.

+
+ +
+ +
+
+class galsim.UVFunction(ufunc, vfunc, xfunc=None, yfunc=None, origin=None, world_origin=None, uses_color=False)[source]
+

Bases: EuclideanWCS

+

This WCS takes two arbitrary functions for u(x,y) and v(x,y).

+
+
The ufunc and vfunc parameters may be:
    +
  • python functions that take (x,y) arguments

  • +
  • python objects with a __call__ method that takes (x,y) arguments

  • +
  • strings which can be parsed with eval(‘lambda x,y: ‘+str)

  • +
+
+
+

You may also provide the inverse functions x(u,v) and y(u,v) as xfunc and yfunc. +These are not required, but if you do not provide them, then any operation that requires +going from world to image coordinates will raise a NotImplementedError.

+

Note: some internal calculations will be faster if the functions can take NumPy arrays +for x,y and output arrays for u,v. Usually this does not require any change to your +function, but it is worth keeping in mind. For example, if you want to do a sqrt, you +may be better off using numpy.sqrt rather than math.sqrt.

+

A UVFunction is initialized with the command:

+
>>> wcs = galsim.UVFunction(ufunc, vfunc, origin=None, world_origin=None)
+
+
+
+
Parameters:
+
    +
  • ufunc – The function u(x,y)

  • +
  • vfunc – The function v(x,y)

  • +
  • xfunc – The function x(u,v) (optional)

  • +
  • yfunc – The function y(u,v) (optional)

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: PositionD(0., 0.)]

  • +
  • system. (world_origin Optional origin position for the world coordinate) – If provided, it should be a PositionD. +[default: PositionD(0., 0.)]

  • +
  • uses_color – If True, then the functions take three parameters (x,y,c) or (u,v,c) +where the third term is some kind of color value. (The exact meaning +of “color” here is user-defined. You just need to be consistent with +the color values you use when using the wcs.) [default: False]

  • +
+
+
+
+
+property origin
+

The image coordinate position to use as the origin.

+
+ +
+
+property ufunc
+

The input ufunc

+
+ +
+
+property vfunc
+

The input vfunc

+
+ +
+
+property world_origin
+

The world coordinate position to use as the origin.

+
+ +
+
+property xfunc
+

The input xfunc

+
+ +
+
+property yfunc
+

The input yfunc

+
+ +
+ +
+
+

Celestial WCS’s

+
+
+class galsim.RaDecFunction(ra_func, dec_func=None, origin=None)[source]
+

Bases: CelestialWCS

+

This WCS takes an arbitrary function for the Right Ascension (ra) and Declination (dec).

+

In many cases, it can be more convenient to calculate both ra and dec in a single function, +since there will typically be intermediate values that are common to both, so it may be more +efficient to just calculate those once and thence calculate both ra and dec. Thus, we +provide the option to provide either a single function or two separate functions.

+
+
The function parameters used to initialize an RaDecFunction may be:
    +
  • a python functions that take (x,y) arguments

  • +
  • a python object with a __call__ method that takes (x,y) arguments

  • +
  • a string which can be parsed with eval(‘lambda x,y: ‘+str)

  • +
+
+
+

The return values, ra and dec, should be given in _radians_.

+

The first argument is called ra_func, but if dec_func is omitted, then it is assumed +to calculate both ra and dec. The two values should be returned as a tuple (ra,dec).

+

We don’t want a function that returns Angle instances, because we want to allow for the +possibility of using NumPy arrays as inputs and outputs to speed up some calculations. The +function isn’t _required_ to work with NumPy arrays, but it is possible that some things +will be faster if it does. If it were expected to return Angle instances, then it definitely +couldn’t work with arrays.

+

An RaDecFunction is initialized with either of the following commands:

+
>>> wcs = galsim.RaDecFunction(radec_func, origin=None)
+>>> wcs = galsim.RaDecFunction(ra_func, dec_func, origin=None)
+
+
+
+
Parameters:
+
    +
  • ra_func – If dec_func is also given: A function ra(x,y) returning ra in radians. +If dec_func=None: A function returning a tuple (ra,dec), both in radians.

  • +
  • dec_func – Either a function dec(x,y) returning dec in radians, or None (in which +case ra_func is expected to return both ra and dec. [default: None]

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: PositionD(0., 0.)]

  • +
+
+
+
+
+property origin
+

The image coordinate position to use as the origin.

+
+ +
+
+property radec_func
+

The input radec_func

+
+ +
+ +
+
+class galsim.AstropyWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', wcs=None, origin=None)[source]
+

Bases: CelestialWCS

+

This WCS uses astropy.wcs to read WCS information from a FITS file. +It requires the astropy.wcs python module to be installed.

+

Astropy may be installed using pip, fink, or port:

+
>>> pip install astropy
+>>> fink install astropy-py27
+>>> port install py27-astropy
+
+
+

It also comes by default with Enthought and Anaconda. For more information, see their website:

+
+
+

An AstropyWCS is initialized with one of the following commands:

+
>>> wcs = galsim.AstropyWCS(file_name=file_name)  # Open a file on disk
+>>> wcs = galsim.AstropyWCS(header=header)        # Use an existing pyfits header
+>>> wcs = galsim.AstropyWCS(wcs=wcs)              # Use an existing astropy.wcs.WCS instance
+
+
+

Exactly one of the parameters file_name, header or wcs is required. Also, since +the most common usage will probably be the first, you can also give a file_name without it +being named:

+
>>> wcs = galsim.AstropyWCS(file_name)
+
+
+
+
Parameters:
+
    +
  • file_name – The FITS file from which to read the WCS information. This is probably +the usual parameter to provide. [default: None]

  • +
  • dir – Optional directory to prepend to file_name. [default: None]

  • +
  • hdu – Optionally, the number of the HDU to use if reading from a file. +The default is to use either the primary or first extension as +appropriate for the given compression. (e.g. for rice, the first +extension is the one you normally want.) [default: None]

  • +
  • header – The header of an open pyfits (or astropy.io) hdu. Or, it can be +a FitsHeader object. [default: None]

  • +
  • compression – Which decompression scheme to use (if any). See galsim.fits.read() +for the available options. [default: ‘auto’]

  • +
  • wcs – An existing astropy.wcs.WCS instance [default: None]

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: None]

  • +
+
+
+
+
+property origin
+

The origin in image coordinates of the WCS function.

+
+ +
+
+property wcs
+

The underlying astropy.wcs.WCS object.

+
+ +
+ +
+
+class galsim.PyAstWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', wcsinfo=None, origin=None)[source]
+

Bases: CelestialWCS

+

This WCS uses PyAst (the python front end for the Starlink AST code) to read WCS +information from a FITS file. It requires the starlink.Ast python module to be installed.

+

Starlink may be installed using pip:

+
>>> pip install starlink-pyast
+
+
+

For more information, see their website:

+

https://pypi.python.org/pypi/starlink-pyast/

+

A PyAstWCS is initialized with one of the following commands:

+
>>> wcs = galsim.PyAstWCS(file_name=file_name)  # Open a file on disk
+>>> wcs = galsim.PyAstWCS(header=header)        # Use an existing pyfits header
+>>> wcs = galsim.PyAstWCS(wcsinfo=wcsinfo)      # Use an existing starlink.Ast.FrameSet
+
+
+

Exactly one of the parameters file_name, header or wcsinfo is required. Also, +since the most common usage will probably be the first, you can also give a file name without +it being named:

+
>>> wcs = galsim.PyAstWCS(file_name)
+
+
+
+
Parameters:
+
    +
  • file_name – The FITS file from which to read the WCS information. This is probably +the usual parameter to provide. [default: None]

  • +
  • dir – Optional directory to prepend to file_name. [default: None]

  • +
  • hdu – Optionally, the number of the HDU to use if reading from a file. +The default is to use either the primary or first extension as +appropriate for the given compression. (e.g. for rice, the first +extension is the one you normally want.) [default: None]

  • +
  • header – The header of an open pyfits (or astropy.io) hdu. Or, it can be +a FitsHeader object. [default: None]

  • +
  • compression – Which decompression scheme to use (if any). See galsim.fits.read() +for the available options. [default:’auto’]

  • +
  • wcsinfo – An existing starlink.Ast.FrameSet [default: None]

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: None]

  • +
+
+
+
+
+property origin
+

The origin in image coordinates of the WCS function.

+
+ +
+
+property wcsinfo
+

The underlying starlink.Ast.FrameSet for this object.

+
+ +
+ +
+
+class galsim.WcsToolsWCS(file_name, dir=None, origin=None)[source]
+

Bases: CelestialWCS

+

This WCS uses wcstools executables to perform the appropriate WCS transformations +for a given FITS file. It requires wcstools command line functions to be installed.

+

Note: It uses the wcstools executables xy2sky and sky2xy, so it can be quite a bit less +efficient than other options that keep the WCS in memory.

+

See their website for information on downloading and installing wcstools:

+
+
+

A WcsToolsWCS is initialized with the following command:

+
>>> wcs = galsim.WcsToolsWCS(file_name)
+
+
+
+
Parameters:
+
    +
  • file_name – The FITS file from which to read the WCS information.

  • +
  • dir – Optional directory to prepend to file_name. [default: None]

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: None]

  • +
+
+
+
+
+property file_name
+

The file name of the FITS file with the WCS information.

+
+ +
+
+property origin
+

The origin in image coordinates of the WCS function.

+
+ +
+ +
+
+class galsim.GSFitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', origin=None, _data=None, _doiter=True)[source]
+

Bases: CelestialWCS

+

This WCS uses a GalSim implementation to read a WCS from a FITS file.

+

It doesn’t do nearly as many WCS types as the other options, and it does not try to be +as rigorous about supporting all possible valid variations in the FITS parameters. +However, it does several popular WCS types properly, and it doesn’t require any additional +python modules to be installed, which can be helpful.

+

Currrently, it is able to parse the following WCS types: TAN, STG, ZEA, ARC, TPV, TNX

+

A GSFitsWCS is initialized with one of the following commands:

+
>>> wcs = galsim.GSFitsWCS(file_name=file_name)  # Open a file on disk
+>>> wcs = galsim.GSFitsWCS(header=header)        # Use an existing pyfits header
+
+
+

Also, since the most common usage will probably be the first, you can also give a file name +without it being named:

+
>>> wcs = galsim.GSFitsWCS(file_name)
+
+
+

In addition to reading from a FITS file, there is also a factory function that builds +a GSFitsWCS object implementing a TAN projection. See the docstring of TanWCS for +more details.

+
+
Parameters:
+
    +
  • file_name – The FITS file from which to read the WCS information. This is probably +the usual parameter to provide. [default: None]

  • +
  • dir – Optional directory to prepend to file_name. [default: None]

  • +
  • hdu – Optionally, the number of the HDU to use if reading from a file. +The default is to use either the primary or first extension as +appropriate for the given compression. (e.g. for rice, the first +extension is the one you normally want.) [default: None]

  • +
  • header – The header of an open pyfits (or astropy.io) hdu. Or, it can be +a FitsHeader object. [default: None]

  • +
  • compression – Which decompression scheme to use (if any). See galsim.fits.read() +for the available options. [default: ‘auto’]

  • +
  • origin – Optional origin position for the image coordinate system. +If provided, it should be a PositionD or PositionI. +[default: None]

  • +
+
+
+
+
+property origin
+

The origin in image coordinates of the WCS function.

+
+ +
+ +
+
+galsim.FitsWCS(file_name=None, dir=None, hdu=None, header=None, compression='auto', text_file=False, suppress_warning=False)[source]
+

This factory function will try to read the WCS from a FITS file and return a WCS that will +work. It tries a number of different WCS classes until it finds one that succeeds in reading +the file.

+

If none of them work, then the last class it tries, AffineTransform, is guaranteed to succeed, +but it will only model the linear portion of the WCS (the CD matrix, CRPIX, and CRVAL), using +reasonable defaults if even these are missing. If you think that you have the right software +for one of the WCS types, but FitsWCS still defaults to AffineTransform, it may be helpful to +update your installation of astropy and/or starlink (if you don’t already have the latest +version).

+

Note: The list of classes this function will try may be edited, e.g. by an external module +that wants to add an additional WCS type. The list is galsim.fitswcs.fits_wcs_types.

+
+
Parameters:
+
    +
  • file_name – The FITS file from which to read the WCS information. This is probably +the usual parameter to provide. [default: None]

  • +
  • dir – Optional directory to prepend to file_name. [default: None]

  • +
  • hdu – Optionally, the number of the HDU to use if reading from a file. +The default is to use either the primary or first extension as +appropriate for the given compression. (e.g. for rice, the first +extension is the one you normally want.) [default: None]

  • +
  • header – The header of an open pyfits (or astropy.io) hdu. Or, it can be +a FitsHeader object. [default: None]

  • +
  • compression – Which decompression scheme to use (if any). See galsim.fits.read() +for the available options. [default: ‘auto’]

  • +
  • text_file – Normally a file is taken to be a fits file, but you can also give it a +text file with the header information (like the .head file output from +SCamp). In this case you should set text_file = True to tell GalSim +to parse the file this way. [default: False]

  • +
  • suppress_warning – Whether to suppress a warning that the WCS could not be read from the +FITS header, so the WCS defaulted to either a PixelScale or +AffineTransform. [default: False] +(Note: this is (by default) set to True when this function is implicitly +called from one of the galsim.fits.read* functions.)

  • +
+
+
+
+ +
+
+galsim.TanWCS(affine, world_origin, units=coord.arcsec)[source]
+

This is a function that returns a GSFitsWCS object for a TAN WCS projection.

+

The TAN projection is essentially an affine transformation from image coordinates to +Euclidean (u,v) coordinates on a tangent plane, and then a “deprojection” of this plane +onto the sphere given a particular RA, Dec for the location of the tangent point. +The tangent point will correspond to the location of (u,v) = (0,0) in the intermediate +coordinate system.

+
+
Parameters:
+
    +
  • affine – An AffineTransform defining the transformation from image coordinates +to the coordinates on the tangent plane.

  • +
  • world_origin – A CelestialCoord defining the location on the sphere where the +tangent plane is centered.

  • +
  • units – The angular units of the (u,v) intermediate coordinate system. +[default: galsim.arcsec]

  • +
+
+
Returns:
+

a GSFitsWCS describing this WCS.

+
+
+
+ +
+
+galsim.FittedSIPWCS(x, y, ra, dec, wcs_type='TAN', order=3, center=None)[source]
+

A WCS constructed from a list of reference celestial and image +coordinates.

+
+
Parameters:
+
    +
  • x – Image x-coordinates of reference stars in pixels

  • +
  • y – Image y-coordinates of reference stars in pixels

  • +
  • ra – Right ascension of reference stars in radians

  • +
  • dec – Declination of reference stars in radians

  • +
  • wcs_type – The type of the tangent plane projection to use. Should be +one of [‘TAN’, ‘STG’, ‘ZEA’, or ‘ARC’]. [default: ‘TAN’]

  • +
  • order – The order of the Simple Imaging Polynomial (SIP) used to +describe the WCS distortion. SIP coefficients kick in when +order >= 2. If you supply order=1, then just fit a WCS +without any SIP coefficients. [default: 3]

  • +
  • center – A CelestialCoord defining the location on the sphere where +the tangent plane is centered. [default: None, which means +use the average position of the list of reference stars]

  • +
+
+
+
+ +
+
+

Celestial Coordinates

+

Our CelestialCoord class is currently hosted as part of the LSSTDESC.Coord package:

+

https://github.com/LSSTDESC/Coord

+

An earlier version of this code was originally implemented in GalSim, so we +still import the relevant classes into the galsim namespace. You may therefore +use either galsim.CelestialCoord or coord.CelestialCoord as they are equivalent.

+
+
+class galsim.CelestialCoord(ra, dec=None)[source]
+

This class defines a position on the celestial sphere, normally given by two angles, +ra and dec.

+

This class can be used to perform various calculations in spherical coordinates, such +as finding the angular distance between two points in the sky, calculating the angles in +spherical triangles, projecting from sky coordinates onto a Euclidean tangent plane, etc.

+

Initialization:

+
+

A CelestialCoord object is constructed from the right ascension and declination:

+
+

coord.CelestialCoord.__init__()

+
>>> c = CelestialCoord(ra=12*hours, dec=31*degrees)
+>>> print(c)
+coord.CelestialCoord(3.141592653589793 radians, 0.5410520681182421 radians)
+
+
+
+
+

Attributes:

+
+

A CelestialCoord has the following (read-only) attributes:

+
+
+
ra:
+

The right ascension (an Angle instance)

+
+
dec:
+

The declination (an Angle instance)

+
+
+
>>> print(c.ra / degrees, c.dec / degrees)
+180.0 31.0
+
+
+
+

In addition there is a convenience access property that returns ra and dec in radians.

+
+
+
rad:
+

A tuple (ra.rad, dec.rad)

+
+
+
>>> print(c.rad)
+(3.141592653589793, 0.5410520681182421)
+
+
+
+
+

Spherical Geometry:

+
+

The basic spherical geometry operations are available to work with spherical triangles

+

For three coordinates cA, cB, cC making a spherical triangle, one can calculate the +sides and angles via:

+
+
+
coord.CelestialCoord.distanceTo()
+
coord.CelestialCoord.angleBetween()
+
+
>>> cA = CelestialCoord(0 * degrees, 0 * degrees)
+>>> cB = CelestialCoord(0 * degrees, 10 * degrees)
+>>> cC = CelestialCoord(10 * degrees, 0 * degrees)
+>>> a = cB.distanceTo(cC)
+>>> b = cC.distanceTo(cA)
+>>> c = cA.distanceTo(cB)
+>>> print(a.deg, b.deg, c.deg)
+14.106044260566366 10.0 10.0
+>>> A = cA.angleBetween(cB, cC)
+>>> B = cB.angleBetween(cC, cA)
+>>> C = cC.angleBetween(cA, cB)
+>>> print(A.deg, B.deg, C.deg)
+90.0 45.43854858674231 45.43854858674231
+
+
+
+
+

Projections:

+
+

Local tangent plane projections of an area of the sky can be performed using the project +method:

+
+

coord.CelestialCoord.project()

+
>>> center = CelestialCoord(ra=10*hours, dec=30*degrees)
+>>> sky_coord = CelestialCoord(ra=10.5*hours, dec=31*degrees)
+>>> print(sky_coord)
+coord.CelestialCoord(2.748893571891069 radians, 0.5410520681182421 radians)
+>>> u, v = center.project(sky_coord)
+>>> print(u.deg, v.deg)
+-6.452371275343261 1.21794987288635
+
+
+
+

and back:

+
+

coord.CelestialCoord.deproject()

+
>>> sky_coord = center.deproject(u,v)
+>>> print(sky_coord)
+coord.CelestialCoord(2.748893571891069 radians, 0.5410520681182421 radians)
+
+
+
+

where u and v are Angles and center and sky_coord are CelestialCoords.

+
+
+
+angleBetween(coord2, coord3)[source]
+

Find the open angle at the location of the current coord between coord2 and coord3.

+

The current coordinate along with the two other coordinates form a spherical triangle +on the sky. This function calculates the angle between the two sides at the location of +the current coordinate.

+

Note that this returns a signed angle. The angle is positive if the sweep direction from +coord2 to coord3 is counter-clockwise (as observed from Earth). It is negative if +the direction is clockwise.

+
+
Parameters:
+
    +
  • coord2 – A second CelestialCoord

  • +
  • coord3 – A third CelestialCoord

  • +
+
+
Returns:
+

the angle between the great circles joining the other two coordinates to the +current coordinate.

+
+
+
+ +
+
+area(coord2, coord3)[source]
+

Find the area of a spherical triangle in steradians.

+

The current coordinate along with the two other coordinates form a spherical triangle +on the sky. This function calculates the area of that spherical triangle, which is +measured in steradians (i.e. surface area of the triangle on the unit sphere).

+
+
Parameters:
+
    +
  • coord2 – A second CelestialCoord

  • +
  • coord3 – A third CelestialCoord

  • +
+
+
Returns:
+

the area in steradians of the given spherical triangle.

+
+
+
+ +
+
+property dec
+

A read-only attribute, giving the Declination as an Angle

+
+ +
+
+deproject(u, v, projection=None)[source]
+

Do the reverse process from the project() function.

+

i.e. This takes in a position (u,v) and returns the corresponding celestial +coordinate, using the current coordinate as the center point of the tangent plane +projection.

+
+
Parameters:
+
    +
  • u – The u position on the tangent plane to deproject (must be an Angle +instance)

  • +
  • v – The v position on the tangent plane to deproject (must be an Angle +instance)

  • +
  • projection – The name of the projection to be used. [default: gnomonic, see project +docstring for other options]

  • +
+
+
Returns:
+

the corresponding CelestialCoord for that position.

+
+
+
+ +
+
+deproject_rad(u, v, projection=None)[source]
+

This is basically identical to the deproject() function except that the output ra, +dec are returned as a tuple (ra, dec) in radians rather than packaged as a CelestialCoord +object and u and v are in radians rather than Angle instances.

+

The main advantage to this is that it will work if u and v are NumPy arrays, in which +case the output ra, dec will also be NumPy arrays.

+
+
Parameters:
+
    +
  • u – The u position in radians on the tangent plane to deproject

  • +
  • v – The v position in radians on the tangent plane to deproject

  • +
  • projection – The name of the projection to be used. [default: gnomonic, see project +docstring for other options]

  • +
+
+
Returns:
+

the corresponding RA, Dec in radians

+
+
+
+ +
+
+distanceTo(coord2)[source]
+

Returns the great circle distance between this coord and another one. +The return value is an Angle object

+
+
Parameters:
+

coord2 – The CelestialCoord to calculate the distance to.

+
+
Returns:
+

the great circle distance to coord2.

+
+
+
+ +
+
+ecliptic(epoch=2000.0, date=None)[source]
+

Get the longitude and latitude in ecliptic coordinates corresponding to this position.

+

The epoch parameter is used to get an accurate value for the (time-varying) obliquity of +the ecliptic. The formulae for this are quite straightforward. It requires just a single +parameter for the transformation, the obliquity of the ecliptic (the Earth’s axial tilt).

+
+
Parameters:
+
    +
  • epoch – The epoch to be used for estimating the obliquity of the ecliptic, if +date is None. But if date is given, then use that to determine the +epoch. [default: 2000.]

  • +
  • date – If a date is given as a python datetime object, then return the +position in ecliptic coordinates with respect to the sun position at +that date. If None, then return the true ecliptic coordiantes. +[default: None]

  • +
+
+
Returns:
+

the longitude and latitude as a tuple (lambda, beta), given as Angle instances.

+
+
+
+ +
+
+static from_ecliptic(lam, beta, epoch=2000.0, date=None)[source]
+

Create a CelestialCoord from the given ecliptic coordinates

+
+
Parameters:
+
    +
  • lam – The longitude in ecliptic coordinates (an Angle instance)

  • +
  • beta – The latitude in ecliptic coordinates (an Angle instance)

  • +
  • epoch – The epoch to be used for estimating the obliquity of the ecliptic, if +date is None. But if date is given, then use that to determine the +epoch. [default: 2000.]

  • +
  • date – If a date is given as a python datetime object, then return the +position in ecliptic coordinates with respect to the sun position at +that date. If None, then return the true ecliptic coordiantes. +[default: None]

  • +
+
+
Returns:
+

the CelestialCoord corresponding to these ecliptic coordinates.

+
+
+
+ +
+
+static from_galactic(el, b, epoch=2000.0)[source]
+

Create a CelestialCoord from the given galactic coordinates

+
+
Parameters:
+
    +
  • el – The longitude in galactic coordinates (an Angle instance)

  • +
  • b – The latitude in galactic coordinates (an Angle instance)

  • +
  • epoch – The epoch of the returned coordinate. [default: 2000.]

  • +
+
+
Returns:
+

the CelestialCoord corresponding to these galactic coordinates.

+
+
+
+ +
+
+static from_xyz(x, y, z)[source]
+

Construct a CelestialCoord from a given (x,y,z) position in three dimensions.

+

The 3D (x,y,z) position does not need to fall on the unit sphere. The RA, Dec will +be inferred from the relations:

+
+\[\begin{split}x &= r \cos(dec) \cos(ra) \\ +y &= r \cos(dec) \sin(ra) \\ +z &= r \sin(dec)\end{split}\]
+

where \(r\) is arbitrary.

+
+
Parameters:
+
    +
  • x – The x position in 3 dimensions. Corresponds to r cos(dec) cos(ra)

  • +
  • y – The y position in 3 dimensions. Corresponds to r cos(dec) sin(ra)

  • +
  • z – The z position in 3 dimensions. Corresponds to r sin(dec)

  • +
+
+
Returns:
+

a CelestialCoord instance

+
+
+
+ +
+
+galactic(epoch=2000.0)[source]
+

Get the longitude and latitude in galactic coordinates corresponding to this position.

+
+
Parameters:
+

epoch – The epoch of the current coordinate. [default: 2000.]

+
+
Returns:
+

the longitude and latitude as a tuple (el, b), given as Angle instances.

+
+
+
+ +
+
+get_xyz()[source]
+

Get the (x,y,z) coordinates on the unit sphere corresponding to this (RA, Dec).

+
+\[\begin{split}x &= \cos(dec) \cos(ra) \\ +y &= \cos(dec) \sin(ra) \\ +z &= \sin(dec)\end{split}\]
+
+
Returns:
+

a tuple (x,y,z)

+
+
+
+ +
+
+greatCirclePoint(coord2, theta)[source]
+

Returns a point on the great circle connecting self and coord2.

+

Two points, c1 and c2, on the unit sphere define a great circle (so long as the two points +are not either coincident or antipodal). We can define points on this great circle by +their angle from c1, such that the angle for c2 has 0 < theta2 < pi. I.e. theta increases +from 0 as the points move from c1 towards c2.

+

This function then returns the coordinate on this great circle (where c1 is self and +c2 is coord2) that corresponds to the given angle theta.

+
+
Parameters:
+
    +
  • coord2 – Another CelestialCoord defining the great circle to use.

  • +
  • theta – The Angle along the great circle corresponding to the desired point.

  • +
+
+
Returns:
+

the corresponding CelestialCoord

+
+
+
+ +
+
+jac_deproject(u, v, projection=None)[source]
+

Return the jacobian of the deprojection.

+

i.e. if the input position is (u,v) then the return matrix is

+
+\[\begin{split}J \equiv \begin{bmatrix} J00 & J01 \\ J10 & J11 \end{bmatrix} + = \begin{bmatrix} + d\textrm{ra}/du \cos(\textrm{dec}) & d\textrm{ra}/dv \cos(\textrm{dec}) \\ + d\textrm{dec}/du & d\textrm{dec}/dv + \end{bmatrix}\end{split}\]
+
+
Parameters:
+
    +
  • u – The u position (as an Angle instance) on the tangent plane

  • +
  • v – The v position (as an Angle instance) on the tangent plane

  • +
  • projection – The name of the projection to be used. [default: gnomonic, see project +docstring for other options]

  • +
+
+
Returns:
+

the Jacobian as a 2x2 numpy array [[J00, J01], [J10, J11]]

+
+
+
+ +
+
+jac_deproject_rad(u, v, projection=None)[source]
+

Equivalent to jac_deproject, but the inputs are in radians and may be numpy +arrays.

+
+
Parameters:
+
    +
  • u – The u position (in radians) on the tangent plane

  • +
  • v – The v position (in radians) on the tangent plane

  • +
  • projection – The name of the projection to be used. [default: gnomonic, see project +docstring for other options]

  • +
+
+
Returns:
+

the Jacobian as a 2x2 numpy array [[J00, J01], [J10, J11]]

+
+
+
+ +
+
+normal()[source]
+

Return the coordinate in the “normal” convention of having 0 <= ra < 24 hours.

+

This convention is not enforced on construction, so this function exists to make it +easy to convert if desired.

+

Functions such as from_galactic and from_xyz will return normal coordinates.

+
+ +
+
+precess(from_epoch, to_epoch)[source]
+
+
This function precesses equatorial ra and dec from one epoch to another.

It is adapted from a set of fortran subroutines based on (a) pages 30-34 of +the Explanatory Supplement to the AE, (b) Lieske, et al. (1977) A&A 58, 1-16, +and (c) Lieske (1979) A&A 73, 282-284.

+
+
+
+
Parameters:
+
    +
  • from_epoch – The epoch of the current coordinate

  • +
  • to_epoch – The epoch of the returned precessed coordinate

  • +
+
+
Returns:
+

a CelestialCoord object corresponding to the precessed position.

+
+
+
+ +
+
+project(coord2, projection=None)[source]
+

Use the currect coord as the center point of a tangent plane projection to project +the coord2 coordinate onto that plane.

+

This function return a tuple (u,v) in the Euclidean coordinate system defined by +a tangent plane projection around the current coordinate, with +v pointing north and ++u pointing west. (i.e. to the right on the sky if +v is up.)

+

There are currently four options for the projection, which you can specify with the +optional projection keyword argument:

+
+
+
gnomonic:
+

[default] uses a gnomonic projection (i.e. a projection from the center of +the sphere, which has the property that all great circles become straight +lines. For more information, see +http://mathworld.wolfram.com/GnomonicProjection.html +This is the usual TAN projection used by most FITS images.

+
+
stereographic:
+

uses a stereographic proejection, which preserves angles, but +not area. For more information, see +http://mathworld.wolfram.com/StereographicProjection.html

+
+
lambert:
+

uses a Lambert azimuthal projection, which preserves area, but not angles. +For more information, see +http://mathworld.wolfram.com/LambertAzimuthalEqual-AreaProjection.html

+
+
postel:
+

uses a Postel equidistant proejection, which preserves distances from +the projection point, but not area or angles. For more information, see +http://mathworld.wolfram.com/AzimuthalEquidistantProjection.html

+
+
+
+

The distance or angle errors increase with distance from the projection point of course.

+
+
Parameters:
+
    +
  • coord2 – The coordinate to project onto the tangent plane.

  • +
  • projection – The name of the projection to be used. [default: gnomonic, see above +for other options]

  • +
+
+
Returns:
+

(u,v) as Angle instances

+
+
+
+ +
+
+project_rad(ra, dec, projection=None)[source]
+

This is basically identical to the project() function except that the input ra, dec +are given in radians rather than packaged as a CelestialCoord object and the returned +u,v are given in radians.

+

The main advantage to this is that it will work if ra and dec are NumPy arrays, in which +case the output u, v will also be NumPy arrays.

+
+
Parameters:
+
    +
  • ra – The right ascension in radians to project onto the tangent plane.

  • +
  • dec – The declination in radians to project onto the tangent plane.

  • +
  • projection – The name of the projection to be used. [default: gnomonic, see project +docstring for other options]

  • +
+
+
Returns:
+

(u,v) in radians

+
+
+
+ +
+
+property ra
+

A read-only attribute, giving the Right Ascension as an Angle

+
+ +
+
+property rad
+

A convenience property, giving a tuple (ra.rad, dec.rad)

+
+ +
+
+static radec_to_xyz(ra, dec, r=1.0)[source]
+

Convert ra, dec (in radians) to 3D x,y,z coordinates on the unit sphere.

+

The connection between (ra,dec) and (x,y,z) are given by the following formulae: +.. math:

+
x &= r \cos(dec) \cos(ra)  \\
+y &= r \cos(dec) \sin(ra)  \\
+z &= r \sin(dec)
+
+
+

For a single ra,dec pair, the following are essentially equivalent:

+
>>> ra = 12*hours/radians       # May be any angle measured
+>>> dec = 31*degrees/radians    # in radians
+
+
+
>>> CelestialCoord.radec_to_xyz(ra, dec)
+(-0.8571673007021123, 1.0497271911386187e-16, 0.5150380749100542)
+
+
+
>>> CelestialCoord(ra * radians, dec * radians).get_xyz()
+(-0.8571673007021123, 1.0497271911386187e-16, 0.5150380749100542)
+
+
+

However, the advantage of this function is that the input values may be numpy +arrays, in which case, the return values will also be numpy arrays.

+
+
Parameters:
+
    +
  • ra – The right ascension(s) in radians. May be a numpy array.

  • +
  • dec – The declination(s) in radians. May be a numpy array.

  • +
  • r – The distance(s) from Earth (default 1.). May be a numpy array.

  • +
+
+
Returns:
+

x, y, z as a tuple.

+
+
+
+ +
+
+static xyz_to_radec(x, y, z, return_r=False)[source]
+

Convert 3D x,y,z coordinates to ra, dec (in radians).

+

The connection between (ra,dec) and (x,y,z) are given by the following formulae: +.. math:

+
x &= r \cos(dec) \cos(ra)  \\
+y &= r \cos(dec) \sin(ra)  \\
+z &= r \sin(dec)
+
+
+

For a single (x,y,z) position, the following are essentially equivalent:

+
>>> x = 0.839       # May be any any 3D location
+>>> y = 0.123       # Not necessarily on unit sphere
+>>> z = 0.530
+
+
+
>>> CelestialCoord.xyz_to_radec(x, y, z)
+(0.14556615088111796, 0.558616191048523)
+
+
+
>>> c = CelestialCoord.from_xyz(x, y, z)
+>>> c.ra.rad, c.dec.rad
+(0.145566150881118, 0.558616191048523)
+
+
+

However, the advantage of this function is that the input values may be numpy +arrays, in which case, the return values will also be numpy arrays.

+
+
Parameters:
+
    +
  • x – The x position(s) in 3 dimensions. May be a numpy array.

  • +
  • y – The y position(s) in 3 dimensions. May be a numpy array.

  • +
  • z – The z position(s) in 3 dimensions. May be a numpy array.

  • +
  • return_r – Whether to return r as well as ra, dec. (default: False)

  • +
+
+
Returns:
+

ra, dec as a tuple. Or if return_r is True, (ra, dec, r).

+
+
+
+ +
+ +
+
+

WCS Utilities

+
+
+galsim.wcs.readFromFitsHeader(header, suppress_warning=True)[source]
+

Read a WCS function from a FITS header.

+

This is normally called automatically from within the galsim.fits.read function, but +you can also call it directly as:

+
wcs, origin = galsim.wcs.readFromFitsHeader(header)
+
+
+

If the file was originally written by GalSim using one of the galsim.fits.write() functions, +then this should always succeed in reading back in the original WCS. It may not end up +as exactly the same class as the original, but the underlying world coordinate system +transformation should be preserved.

+
+

Note

+

For UVFunction and RaDecFunction, if the functions that were written to the FITS +header were real python functions (rather than a string that is converted to a function), +then the mechanism we use to write to the header and read it back in has some limitations:

+
    +
  1. It apparently only works for cpython implementations.

  2. +
  3. It probably won’t work to write from one version of python and read from another. +(At least for major version differences.)

  4. +
  5. If the function uses globals, you’ll need to make sure the globals are present +when you read it back in as well, or it probably won’t work.

  6. +
  7. It looks really ugly in the header.

  8. +
  9. We haven’t thought much about the security implications of this, so beware using +GalSim to open FITS files from untrusted sources.

  10. +
+
+

If the file was not written by GalSim, then this code will do its best to read the +WCS information in the FITS header. Depending on what kind of WCS is encoded in the +header, this may or may not be successful.

+

If there is no WCS information in the header, then this will default to a pixel scale +of 1.

+

In addition to the wcs, this function will also return the image origin that the WCS +is assuming for the image. If the file was originally written by GalSim, this should +correspond to the original image origin. If not, it will default to (1,1).

+
+
Parameters:
+
    +
  • header – The fits header with the WCS information.

  • +
  • suppress_warning – Whether to suppress a warning that the WCS could not be read from the +FITS header, so the WCS defaulted to either a PixelScale or +AffineTransform. [default: True]

  • +
+
+
Returns:
+

a tuple (wcs, origin) of the wcs from the header and the image origin.

+
+
+
+ +
+
+galsim.wcs.compatible(wcs1, wcs2)[source]
+

A utility to check the compatibility of two WCS. In particular, if two WCS are consistent with +each other modulo a shifted origin, we consider them to be compatible, even though they are not +equal.

+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/wl.html b/docs/_build/html/wl.html new file mode 100644 index 00000000000..b3ceac4b97a --- /dev/null +++ b/docs/_build/html/wl.html @@ -0,0 +1,171 @@ + + + + + + + Weak Lensing — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Weak Lensing

+

GalSim was originally built for the Great03 weak lensing challenge. As such, it includes +various classes and routines for accurately handling and constructing weak lensing shear and +magnification.

+
    +
  • The Shear class is our basic object for handling and manipulating shear values.

  • +
  • PowerSpectrum can be used to generate shear and convergence fields according to an input +power spectrum function.

  • +
  • NFWHalo can generate tangential shear profiles around an NFW halo mass profile.

  • +
  • galsim.pse.PowerSpectrumEstimator can be used to estimate a shear power spectrum from +gridded shear values.

  • +
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/docs/_build/html/zernike.html b/docs/_build/html/zernike.html new file mode 100644 index 00000000000..89c8ea56d2a --- /dev/null +++ b/docs/_build/html/zernike.html @@ -0,0 +1,549 @@ + + + + + + + Zernike Functions — GalSim 2.6.0 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Zernike Functions

+
+
+class galsim.zernike.Zernike(coef, R_outer=1.0, R_inner=0.0)[source]
+

A class to represent a Zernike polynomial series +(http://en.wikipedia.org/wiki/Zernike_polynomials#Zernike_polynomials).

+

Zernike polynomials form an orthonormal basis over the unit circle. The convention used here is +for the normality constant to equal the area of integration, which is pi for the unit circle. +I.e.,

+
+\[\int_\mathrm{unit circle} Z_i Z_j dA = \pi \delta_{i, j}.\]
+

Two generalizations of the unit circle Zernike polynomials are also available in this class: +annular Zernike polynomials, and polynomials defined over non-unit-radius circles.

+

Annular Zernikes are orthonormal over an annulus instead of a circle (see Mahajan, J. Opt. Soc. +Am. 71, 1, (1981)). Similarly, the non-unit-radius polynomials are orthonormal over a region +with outer radius not equal to 1. Taken together, these generalizations yield the +orthonormality condition:

+
+\[\int_\mathrm{annulus} Z_i Z_j dA = +\pi \left(R_\mathrm{outer}^2 - R_\mathrm{inner}^2\right) \delta_{i, j}\]
+

where \(0 <= R_\mathrm{inner} < R_\mathrm{outer}\) indicate the inner and outer radii of +the annulus over which the polynomials are orthonormal.

+

The indexing convention for i and j above is that from Noll, J. Opt. Soc. Am. 66, 207-211(1976). +Note that the Noll indices begin at 1; there is no Z_0. Because of this, the series +coefficients argument coef effectively begins with coef[1] (coef[0] is ignored). +This convention is used consistently throughout GalSim, e.g., OpticalPSF, OpticalScreen, +zernikeRotMatrix, and zernikeBasis.

+

As an example, the first few Zernike polynomials in terms of Cartesian coordinates x and y are

+ + + + + + + + + + + + + + + + + + + + +

Noll index

Polynomial

1

1

2

2 x

3

2 y

4

sqrt(3) (2 (x^2 + y^2) - 1)

+

A few mathematical convenience operations are additionally available. Zernikes can be added, +subtracted, or multiplied together, or multiplied by scalars. Note, however, that two +Zernikes can only be combined this way if they have matching R_outer and R_inner +attributes. Zernike gradients, Laplacians and the determinant of the Hessian matrix are also +available as properties that return new Zernike objects.

+
+
Parameters:
+
    +
  • coef – Zernike series coefficients. Note that coef[i] corresponds to Z_i under the +Noll index convention, and coef[0] is ignored. (I.e., coef[1] is ‘piston’, +coef[4] is ‘defocus’, …)

  • +
  • R_outer – Outer radius. [default: 1.0]

  • +
  • R_inner – Inner radius. [default: 0.0]

  • +
+
+
+
+
+__add__(rhs)[source]
+

Add two Zernikes.

+

Requires that each operand’s R_outer and R_inner attributes are the same.

+
+ +
+
+__sub__(rhs)[source]
+

Subtract two Zernikes.

+

Requires that each operand’s R_outer and R_inner attributes are the same.

+
+ +
+
+__neg__()[source]
+

Negate a Zernike.

+
+ +
+
+__mul__(rhs)[source]
+

Multiply two Zernikes, or multiply a Zernike by a scalar.

+

If both operands are Zernikes, then the R_outer and R_inner attributes of each must +be the same.

+
+ +
+
+__rmul__(rhs)[source]
+

Equivalent to obj * rhs. See __mul__ for details.

+
+ +
+
+__call__(x, y)[source]
+

Evaluate this Zernike polynomial series at Cartesian coordinates x and y. +Synonym for evalCartesian.

+
+
Parameters:
+
    +
  • x – x-coordinate of evaluation points. Can be list-like.

  • +
  • y – y-coordinate of evaluation points. Can be list-like.

  • +
+
+
Returns:
+

Series evaluations as numpy array.

+
+
+
+ +
+
+evalCartesian(x, y)[source]
+

Evaluate this Zernike polynomial series at Cartesian coordinates x and y.

+
+
Parameters:
+
    +
  • x – x-coordinate of evaluation points. Can be list-like.

  • +
  • y – y-coordinate of evaluation points. Can be list-like.

  • +
+
+
Returns:
+

Series evaluations as numpy array.

+
+
+
+ +
+
+evalCartesianGrad(x, y)[source]
+

Evaluate the gradient of this Zernike polynomial series at cartesian coordinates +x and y.

+
+
Parameters:
+
    +
  • x – x-coordinate of evaluation points. Can be list-like.

  • +
  • y – y-coordinate of evaluation points. Can be list-like.

  • +
+
+
Returns:
+

Tuple of arrays for x-gradient and y-gradient.

+
+
+
+ +
+
+evalPolar(rho, theta)[source]
+

Evaluate this Zernike polynomial series at polar coordinates rho and theta.

+
+
Parameters:
+
    +
  • rho – radial coordinate of evaluation points. Can be list-like.

  • +
  • theta – azimuthal coordinate in radians (or as Angle object) of evaluation points. +Can be list-like.

  • +
+
+
Returns:
+

Series evaluations as numpy array.

+
+
+
+ +
+
+rotate(theta)[source]
+

Return new Zernike polynomial series rotated by angle theta.

+

For example:

+
>>> Z = Zernike(coefs)
+>>> Zrot = Z.rotate(theta)
+>>> Z.evalPolar(r, th) == Zrot.evalPolar(r, th + theta)
+
+
+
+
Parameters:
+

theta – angle in radians.

+
+
Returns:
+

A new Zernike object.

+
+
+
+ +
+ +
+
+class galsim.zernike.DoubleZernike(coef, *, uv_outer=1.0, uv_inner=0.0, xy_outer=1.0, xy_inner=0.0)[source]
+

The Cartesian product of two (annular) Zernike polynomial series. Each +double Zernike term is the product of two single Zernike polynomials over +separate annuli:

+
+\[DZ_{k,j}(u, v, x, y) = Z_k(u, v) Z_j(x, y)\]
+

The double Zernike’s therefore satisfy the orthonormality condition:

+
+\[\int_{annuli} DZ_{k,j} DZ_{k',j'} = A_1 A_2 \delta_{k, k'} \delta_{j, j'}\]
+

where \(A_1\) and \(A_2\) are the areas of the two annuli.

+

Double Zernikes series are useful for representing the field and pupil +dependent wavefronts of a telescope. We adopt the typical convention that +the first index (the k index) corresponds to the field-dependence, while +the second index (the j index) corresponds to the pupil-dependence.

+
+
Parameters:
+
    +
  • coef – [kmax, jmax] array of double Zernike coefficients. Like the +single Zernike class, the 0th index of either axis is +ignored.

  • +
  • uv_outer – Outer radius of the first annulus. [default: 1.0]

  • +
  • uv_inner – Inner radius of the first annulus. [default: 0.0]

  • +
  • xy_outer – Outer radius of the second annulus. [default: 1.0]

  • +
  • xy_inner – Inner radius of the second annulus. [default: 0.0]

  • +
+
+
+
+ +
+
+galsim.zernike.noll_to_zern(j)[source]
+

Convert linear Noll index to tuple of Zernike indices. +j is the linear Noll coordinate, n is the radial Zernike index and m is the azimuthal Zernike +index.

+

c.f. https://oeis.org/A176988

+
+
Parameters:
+

j – Zernike mode Noll index

+
+
Returns:
+

(n, m) tuple of Zernike indices

+
+
+
+ +
+
+galsim.zernike.zernikeRotMatrix(jmax, theta)[source]
+

Construct Zernike basis rotation matrix. This matrix can be used to convert a set of Zernike +polynomial series coefficients expressed in one coordinate system to an equivalent set of +coefficients expressed in a rotated coordinate system.

+

For example:

+
>>> Z = Zernike(coefs)
+>>> R = zernikeRotMatrix(jmax, theta)
+>>> rotCoefs = R.dot(coefs)
+>>> Zrot = Zernike(rotCoefs)
+>>> Z.evalPolar(r, th) == Zrot.evalPolar(r, th + theta)
+
+
+

Note that not all values of jmax are allowed. For example, jmax=2 raises an Exception, +since a non-zero Z_2 coefficient will in general rotate into a combination of Z_2 and Z_3 +coefficients, and therefore the needed rotation matrix requires jmax=3. If you run into this +kind of Exception, raising jmax by 1 will eliminate it.

+

Also note that the returned matrix is intended to multiply a vector of Zernike coefficients +coef indexed following the Noll (1976) convention, which starts at 1. Since python +sequences start at 0, we adopt the convention that coef[0] is unused, and coef[i] +corresponds to the coefficient multiplying Z_i. As a result, the size of the returned matrix +is [jmax+1, jmax+1].

+
+
Parameters:
+
    +
  • jmax – Maximum Zernike index (in the Noll convention) over which to construct matrix.

  • +
  • theta – angle of rotation in radians.

  • +
+
+
Returns:
+

Matrix of size [jmax+1, jmax+1].

+
+
+
+ +
+
+galsim.zernike.zernikeBasis(jmax, x, y, R_outer=1.0, R_inner=0.0)[source]
+

Construct basis of Zernike polynomial series up to Noll index jmax, evaluated at a +specific set of points x and y.

+

Useful for fitting Zernike polynomials to data, e.g.:

+
>>> x, y, z = myDataToFit()
+>>> basis = zernikeBasis(11, x, y)
+>>> coefs, _, _, _ = np.linalg.lstsq(basis.T, z)
+>>> resids = Zernike(coefs).evalCartesian(x, y) - z
+
+
+

or equivalently:

+
>>> resids = basis.T.dot(coefs).T - z
+
+
+

Note that since we follow the Noll indexing scheme for Zernike polynomials, which begins at 1, +but python sequences are indexed from 0, the length of the leading dimension in the result is +jmax+1 instead of jmax. We somewhat arbitrarily fill the 0th slice along the first +dimension with 0s (result[0, …] == 0) so that it doesn’t impact the results of +np.linalg.lstsq as in the example above.

+
+
Parameters:
+
    +
  • jmax – Maximum Noll index to use.

  • +
  • x – x-coordinates (can be list-like, congruent to y)

  • +
  • y – y-coordinates (can be list-like, congruent to x)

  • +
  • R_outer – Outer radius. [default: 1.0]

  • +
  • R_inner – Inner radius. [default: 0.0]

  • +
+
+
Returns:
+

[jmax+1, x.shape] array. Slicing over first index gives basis vectors corresponding +to individual Zernike polynomials.

+
+
+
+ +
+
+galsim.zernike.zernikeGradBases(jmax, x, y, R_outer=1.0, R_inner=0.0)[source]
+

Construct bases of Zernike polynomial series gradients up to Noll index jmax, evaluated +at a specific set of points x and y.

+

Note that since we follow the Noll indexing scheme for Zernike polynomials, which begins at 1, +but python sequences are indexed from 0, the length of the leading dimension in the result is +jmax+1 instead of jmax. We somewhat arbitrarily fill the 0th slice along the first +dimension with 0s (result[0, …] == 0).

+
+
Parameters:
+
    +
  • jmax – Maximum Noll index to use.

  • +
  • x – x-coordinates (can be list-like, congruent to y)

  • +
  • y – y-coordinates (can be list-like, congruent to x)

  • +
  • R_outer – Outer radius. [default: 1.0]

  • +
  • R_inner – Inner radius. [default: 0.0]

  • +
+
+
Returns:
+

[2, jmax+1, x.shape] array. The first index selects the gradient in the x/y direction, +slicing over the second index gives basis vectors corresponding to individual Zernike +polynomials.

+
+
+
+ +
+
+galsim.zernike.doubleZernikeBasis(kmax, jmax, u, v, x, y, *, uv_outer=1.0, uv_inner=0.0, xy_outer=1.0, xy_inner=0.0)[source]
+

Construct basis of DoubleZernike polynomial series up to Noll indices +(kmax, jmax), evaluated at (u, v, x, y).

+
+
Parameters:
+
    +
  • kmax – Maximum Noll index for first annulus.

  • +
  • jmax – Maximum Noll index for second annulus.

  • +
  • u – Coordinates in the first annulus.

  • +
  • v – Coordinates in the first annulus.

  • +
  • x – Coordinates in the second annulus.

  • +
  • y – Coordinates in the second annulus.

  • +
  • uv_outer – Outer radius of the first annulus.

  • +
  • uv_inner – Inner radius of the first annulus.

  • +
  • xy_outer – Outer radius of the second annulus.

  • +
  • xy_inner – Inner radius of the second annulus.

  • +
+
+
Returns:
+

[kmax+1, jmax+1, u.shape] array. Slicing over the first two dimensions +gives the basis vectors corresponding to individual DoubleZernike terms.

+
+
+
+ +
+
+galsim.zernike.describe_zernike(j)[source]
+

Create a human-readable string describing the jth (unit circle) Zernike mode as a bivariate +polynomial in x and y.

+
+
Parameters:
+

j – Zernike mode Noll index

+
+
Returns:
+

string description of jth mode.

+
+
+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file