From 047cf19e49c7725e81b05c8311e98bc5d1e25b66 Mon Sep 17 00:00:00 2001 From: "Matthew R. Becker" Date: Tue, 10 Sep 2024 16:11:41 +0200 Subject: [PATCH] fix: consistent doc string wrapping (#116) * fix: consistent doc string wrapping for angle.py * fix: put back private doc strings * fix: wrap doc strings for bessel, bounds, and box * fix: doc string wrapping for celestial.py * fix: doc string wrapping for convolve.py * fix: wrap doc strings in exp, fitswcs, gsobject, and gaussian * fix: wrap doc strings gsparams and image * fix: wrap interpolant * fix: wrong attribute * fix: wrap doc strings for interpolated images and moffat * fix: wrap doc strings for noise * fix: wrap doc strings for photon arrays * fix: add some caveats for properties * fix: wrap doc strings for position * fix: no round method * fix: wrap doc strings for random, sensor, shear and remove extra text * fix: wrap doc strings for spergel, sum and transform * fix: start wrapping wcs * fix: finish doc strings in wcs * fix: no function signatures * Update interpolatedimage.py Co-authored-by: Ismael Mendoza <11745764+ismael-mendoza@users.noreply.github.com> * Update interpolatedimage.py Co-authored-by: Ismael Mendoza <11745764+ismael-mendoza@users.noreply.github.com> * Update interpolatedimage.py Co-authored-by: Ismael Mendoza <11745764+ismael-mendoza@users.noreply.github.com> --------- Co-authored-by: Ismael Mendoza <11745764+ismael-mendoza@users.noreply.github.com> --- jax_galsim/angle.py | 23 ++-- jax_galsim/bessel.py | 2 +- jax_galsim/bounds.py | 6 +- jax_galsim/box.py | 6 +- jax_galsim/celestial.py | 7 +- jax_galsim/convolve.py | 24 +--- jax_galsim/core/utils.py | 12 +- jax_galsim/exponential.py | 4 +- jax_galsim/fitswcs.py | 3 +- jax_galsim/gaussian.py | 6 +- jax_galsim/gsobject.py | 160 ++++--------------------- jax_galsim/gsparams.py | 16 +-- jax_galsim/image.py | 201 +++++++++++--------------------- jax_galsim/interpolant.py | 122 +++++-------------- jax_galsim/interpolatedimage.py | 6 +- jax_galsim/moffat.py | 10 +- jax_galsim/noise.py | 54 ++------- jax_galsim/photon_array.py | 162 +++++++++++-------------- jax_galsim/position.py | 11 +- jax_galsim/random.py | 1 + jax_galsim/sensor.py | 1 + jax_galsim/shear.py | 27 +++-- jax_galsim/spergel.py | 10 +- jax_galsim/sum.py | 2 +- jax_galsim/transform.py | 8 +- jax_galsim/wcs.py | 189 +++++++----------------------- 26 files changed, 301 insertions(+), 772 deletions(-) diff --git a/jax_galsim/angle.py b/jax_galsim/angle.py index 1141a8c4..2f45daf1 100644 --- a/jax_galsim/angle.py +++ b/jax_galsim/angle.py @@ -30,16 +30,13 @@ class AngleUnit(object): valid_names = ["rad", "deg", "hr", "hour", "arcmin", "arcsec"] def __init__(self, value): - """ - :param value: The measure of the unit in radians. - """ if isinstance(value, AngleUnit): raise TypeError("Cannot construct AngleUnit from another AngleUnit") self._value = cast_to_float(value) @property + @implements(_galsim.AngleUnit.value) def value(self): - """A read-only attribute giving the measure of the AngleUnit in radians.""" return self._value def __rmul__(self, theta): @@ -146,19 +143,13 @@ def __init__(self, theta, unit=None): self._rad = cast_to_float(theta) * unit.value @property + @implements(_galsim.Angle.rad) def rad(self): - """Return the Angle in radians. - - Equivalent to angle / coord.radians - """ return self._rad @property + @implements(_galsim.Angle.deg) def deg(self): - """Return the Angle in degrees. - - Equivalent to angle / coord.degrees - """ return self / degrees def __neg__(self): @@ -213,20 +204,20 @@ def wrap(self, center=None): ) # How many full cycles to subtract return _Angle(self._rad - offset * 2.0 * jnp.pi) + @implements(_galsim.Angle.sin) def sin(self): - """Return the sin of an Angle.""" return jnp.sin(self._rad) + @implements(_galsim.Angle.cos) def cos(self): - """Return the cos of an Angle.""" return jnp.cos(self._rad) + @implements(_galsim.Angle.tan) def tan(self): - """Return the tan of an Angle.""" return jnp.tan(self._rad) + @implements(_galsim.Angle.sincos) def sincos(self): - """Return both the sin and cos of an Angle as a numpy array [sint, cost].""" sin = jnp.sin(self._rad) cos = jnp.cos(self._rad) return sin, cos diff --git a/jax_galsim/bessel.py b/jax_galsim/bessel.py index 13ac04c8..616b4161 100644 --- a/jax_galsim/bessel.py +++ b/jax_galsim/bessel.py @@ -105,9 +105,9 @@ def si(x): ) +@implements(_galsim.bessel.kv) @jax.jit def kv(nu, x): - """Modified Bessel 2nd kind""" nu = 1.0 * nu x = 1.0 * x return _tfp_bessel_kve(nu, x) / jnp.exp(jnp.abs(x)) diff --git a/jax_galsim/bounds.py b/jax_galsim/bounds.py index c30de770..d7dc7101 100644 --- a/jax_galsim/bounds.py +++ b/jax_galsim/bounds.py @@ -96,12 +96,8 @@ def _parse_args(self, *args, **kwargs): self._isdefined = False @property + @implements(_galsim.Bounds.true_center) 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 _galsim.GalSimUndefinedBoundsError( "true_center is invalid for an undefined Bounds" diff --git a/jax_galsim/box.py b/jax_galsim/box.py index 760f0ad1..a2475019 100644 --- a/jax_galsim/box.py +++ b/jax_galsim/box.py @@ -28,13 +28,13 @@ def _maxL(self): return jnp.maximum(self.width, self.height) @property + @implements(_galsim.Box.width) def width(self): - """The width of the `Box`.""" return self.params["width"] @property + @implements(_galsim.Box.height) def height(self): - """The height of the `Box`.""" return self.params["height"] def __hash__(self): @@ -115,7 +115,6 @@ def tree_unflatten(cls, aux_data, children): **aux_data, ) - @implements(_galsim.Box._shoot) def _shoot(self, photons, rng): ud = UniformDeviate(rng) @@ -134,6 +133,7 @@ def __init__(self, scale, flux=1.0, gsparams=None): ) @property + @implements(_galsim.Pixel.scale) def scale(self): """The linear scale size of the `Pixel`.""" return self.width diff --git a/jax_galsim/celestial.py b/jax_galsim/celestial.py index 449932d2..25e5d702 100644 --- a/jax_galsim/celestial.py +++ b/jax_galsim/celestial.py @@ -79,18 +79,18 @@ def __init__(self, ra, dec=None): self._dec = dec @property + @implements(_galsim.celestial.CelestialCoord.ra) def ra(self): - """A read-only attribute, giving the Right Ascension as an Angle""" return self._ra @property + @implements(_galsim.celestial.CelestialCoord.dec) def dec(self): - """A read-only attribute, giving the Declination as an Angle""" return self._dec @property + @implements(_galsim.celestial.CelestialCoord.rad) def rad(self): - """A convenience property, giving a tuple (ra.rad, dec.rad)""" return (self._ra.rad, self._dec.rad) @jax.jit @@ -930,6 +930,7 @@ def from_galsim(gcoord): return _CelestialCoord(_Angle(gcoord.ra.rad), _Angle(gcoord.dec.rad)) +@implements(_coord._CelestialCoord) def _CelestialCoord(ra, dec): ret = CelestialCoord.__new__(CelestialCoord) ret._ra = ra diff --git a/jax_galsim/convolve.py b/jax_galsim/convolve.py index 94d41275..62409d65 100644 --- a/jax_galsim/convolve.py +++ b/jax_galsim/convolve.py @@ -140,25 +140,17 @@ def __init__(self, *args, **kwargs): self._params = {"obj_list": self._obj_list} @property + @implements(_galsim.Convolution.obj_list) def obj_list(self): - """The list of objects being convolved.""" return self._obj_list @property + @implements(_galsim.Convolution.real_space) def real_space(self): - """Whether this `Convolution` should be drawn using real-space convolution rather - than FFT convolution. - """ return self._real_space + @implements(_galsim.Convolution.withGSParams) 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 from copy import copy @@ -391,8 +383,8 @@ def _inv_min_acc_kvalue(self): return 1.0 / self._min_acc_kvalue @property + @implements(_galsim.Deconvolution.orig_obj) def orig_obj(self): - """The original object that is being deconvolved.""" return self._orig_obj @property @@ -401,14 +393,8 @@ def _noise(self): galsim_warn("Unable to propagate noise in galsim.Deconvolution") return None + @implements(_galsim.Deconvolution.withGSParams) 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 from copy import copy diff --git a/jax_galsim/core/utils.py b/jax_galsim/core/utils.py index d6c82987..ce3222d4 100644 --- a/jax_galsim/core/utils.py +++ b/jax_galsim/core/utils.py @@ -277,7 +277,6 @@ def _func(i, args): # # fmt: on -_galsim_signature_re = re.compile(r"^([\w., ]+=)?\s*[\w\.]+\([\w\W]*?\)$", re.MULTILINE) _docreference = re.compile(r":doc:`(.*?)\s*<.*?>`") @@ -337,18 +336,9 @@ def _parse_galsimdoc(docstr): docstr = _docreference.sub(lambda match: f"{match.groups()[0]}", docstr) signature, body = "", docstr - match = _galsim_signature_re.match(body) - if match: - signature = match.group() - body = docstr[match.end() :] firstline, body = _break_off_body_section_by_newline(body) - match = _galsim_signature_re.match(body) - if match: - signature = match.group() - body = body[match.end() :] - summary = firstline if not summary: summary, body = _break_off_body_section_by_newline(body) @@ -429,9 +419,9 @@ def decorator(wrapped_fun): docstr += f"\nLAX-backend implementation of :func:`{name}`.\n" if lax_description: docstr += "\n" + lax_description.strip() + "\n" - docstr += "\n*Original docstring below.*\n" if parsed.front_matter: + docstr += "\n*Original docstring below.*\n" docstr += "\n" + parsed.front_matter.strip() + "\n" except Exception: docstr = original_fun.__doc__ diff --git a/jax_galsim/exponential.py b/jax_galsim/exponential.py index 158b0eb5..3d556d59 100644 --- a/jax_galsim/exponential.py +++ b/jax_galsim/exponential.py @@ -51,8 +51,8 @@ def __init__( super().__init__(scale_radius=scale_radius, flux=flux, gsparams=gsparams) @property + @implements(_galsim.Exponential.scale_radius) def scale_radius(self): - """The scale radius of the profile.""" return self.params["scale_radius"] @property @@ -68,8 +68,8 @@ def _norm(self): return self.flux * Exponential._inv_twopi * self._inv_r0**2 @property + @implements(_galsim.Exponential.half_light_radius) def half_light_radius(self): - """The half-light radius of the profile.""" return self.params["scale_radius"] * Exponential._hlr_factor def __hash__(self): diff --git a/jax_galsim/fitswcs.py b/jax_galsim/fitswcs.py index 328d99be..5c59da80 100644 --- a/jax_galsim/fitswcs.py +++ b/jax_galsim/fitswcs.py @@ -176,8 +176,8 @@ def tree_unflatten(cls, aux_data, children): # 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 + @implements(_galsim.GSFitsWCS.origin) def origin(self): - """The origin in image coordinates of the WCS function.""" return PositionD(0.0, 0.0) def _read_header(self, header): @@ -821,6 +821,7 @@ def _writeHeader(self, header, bounds): def _readHeader(header): return GSFitsWCS(header=header) + @implements(_galsim.GSFitsWCS.copy) def copy(self): # The copy module version of copying the dict works fine here. return copy.copy(self) diff --git a/jax_galsim/gaussian.py b/jax_galsim/gaussian.py index e5daacc4..3b937a11 100644 --- a/jax_galsim/gaussian.py +++ b/jax_galsim/gaussian.py @@ -63,18 +63,18 @@ def __init__( super().__init__(sigma=sigma, flux=flux, gsparams=gsparams) @property + @implements(_galsim.Gaussian.sigma) def sigma(self): - """The sigma of this Gaussian profile""" return self.params["sigma"] @property + @implements(_galsim.Gaussian.half_light_radius) def half_light_radius(self): - """The half-light radius of this Gaussian profile""" return self.sigma * Gaussian._hlr_factor @property + @implements(_galsim.Gaussian.fwhm) def fwhm(self): - """The FWHM of this Gaussian profile""" return self.sigma * Gaussian._fwhm_factor @property diff --git a/jax_galsim/gsobject.py b/jax_galsim/gsobject.py index 0fd0dbb6..9441c854 100644 --- a/jax_galsim/gsobject.py +++ b/jax_galsim/gsobject.py @@ -43,8 +43,8 @@ def __setstate__(self, d): self.__dict__ = d @property + @implements(_galsim.GSObject.flux) def flux(self): - """The flux of the profile.""" return self._flux @property @@ -53,8 +53,8 @@ def _flux(self): return self._params["flux"] @property + @implements(_galsim.GSObject.gsparams) def gsparams(self): - """A `GSParams` object that sets various parameters relevant for speed/accuracy trade-offs.""" return self._gsparams @property @@ -63,49 +63,43 @@ def params(self): return self._params @property + @implements(_galsim.GSObject.maxk) def maxk(self): - """The value of k beyond which aliasing can be neglected.""" return self._maxk @property + @implements(_galsim.GSObject.stepk) def stepk(self): - """The sampling in k space necessary to avoid folding of image in x space.""" return self._stepk @property + @implements(_galsim.GSObject.nyquist_scale) def nyquist_scale(self): - """The pixel spacing that does not alias maxk.""" return jnp.pi / self.maxk @property + @implements(_galsim.GSObject.has_hard_edges) 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 + @implements(_galsim.GSObject.is_axisymmetric) def is_axisymmetric(self): - """Whether the profile is axially symmetric; affects efficiency of evaluation.""" return self._is_axisymmetric @property + @implements(_galsim.GSObject.is_analytic_x) 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 + @implements(_galsim.GSObject.is_analytic_k) 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 + @implements(_galsim.GSObject.centroid) def centroid(self): - """The (x, y) centroid of an object as a `PositionD`.""" return self._centroid @property @@ -213,15 +207,8 @@ def xValue(self, *args, **kwargs): pos = parse_pos_args(args, kwargs, "x", "y") return self._xValue(pos) + @implements(_galsim.GSObject._xValue) def _xValue(self, pos): - """Equivalent to `xValue`, but ``pos`` must be a PositionD. - - 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__ ) @@ -231,8 +218,8 @@ def kValue(self, *args, **kwargs): kpos = parse_pos_args(args, kwargs, "kx", "ky") return self._kValue(kpos) + @implements(_galsim.GSObject._kValue) def _kValue(self, kpos): - """Equivalent to `kValue`, but ``kpos`` must be a `galsim.PositionD` instance.""" raise NotImplementedError( "%s does not implement kValue" % self.__class__.__name__ ) @@ -300,81 +287,30 @@ def shear(self, *args, **kwargs): shear = Shear(**kwargs) return Transform(self, shear.getMatrix()) + @implements(_galsim.GSObject._shear) 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. - """ from jax_galsim.transform import Transform return Transform(self, shear.getMatrix()) + @implements(_galsim.GSObject.lens) 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. - """ from jax_galsim.shear import Shear from jax_galsim.transform import Transform shear = Shear(g1=g1, g2=g2) return Transform(self, shear.getMatrix() * jnp.sqrt(mu)) + @implements(_galsim.GSObject._lens) 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. - """ from .shear import _Shear from .transform import Transform shear = _Shear(g1 + 1j * g2) return Transform(self, shear.getMatrix() * jnp.sqrt(mu)) + @implements(_galsim.GSObject.rotate) def rotate(self, theta): - """Rotate this object by an `Angle` ``theta``. - - Parameters: - theta: Rotation angle (`Angle` object, positive means anticlockwise). - - Returns: - the rotated object. - - Note: Not differentiable with respect to theta (yet). - """ from jax_galsim.transform import Transform from .angle import Angle @@ -397,19 +333,8 @@ def shift(self, *args, **kwargs): offset = parse_pos_args(args, kwargs, "dx", "dy") return Transform(self, offset=offset) + @implements(_galsim.GSObject._shift) 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. - """ from jax_galsim.transform import Transform new_obj = Transform(self, offset=(dx, dy)) @@ -885,13 +810,8 @@ def drawReal(self, image, add_to_image=False): return temp.array.sum(dtype=float) + @implements(_galsim.GSObject._drawReal) def _drawReal(self, image, jac=None, offset=(0.0, 0.0), flux_scaling=1.0): - """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__ ) @@ -962,24 +882,8 @@ def drawFFT_makeKImage(self, image): kimage = ImageCF(bounds=bounds, scale=dk) return kimage, N + @implements(_galsim.GSObject.drawFFT_finish) 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. - """ from jax_galsim.bounds import BoundsI from jax_galsim.image import Image @@ -1009,32 +913,8 @@ def drawFFT_finish(self, image, kimage, wrap_size, add_to_image): return temp.array.sum(dtype=float) + @implements(_galsim.GSObject.drawFFT) 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 _galsim.GalSimValueError( "drawFFT requires an image with a PixelScale wcs", image diff --git a/jax_galsim/gsparams.py b/jax_galsim/gsparams.py index 89a4fd37..69499371 100644 --- a/jax_galsim/gsparams.py +++ b/jax_galsim/gsparams.py @@ -47,12 +47,8 @@ def from_galsim(cls, gsparams): ) @staticmethod + @implements(_galsim.GSParams.check) 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: if default is not None: if isinstance(default, GSParams): @@ -65,10 +61,8 @@ def check(gsparams, default=None, **kwargs): raise TypeError("Invalid GSParams: %s" % gsparams) return gsparams.withParams(**kwargs) + @implements(_galsim.GSParams.withParams) 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: @@ -80,12 +74,8 @@ def withParams(self, **kwargs): return GSParams(**d) @staticmethod + @implements(_galsim.GSParams.combine) 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:]): diff --git a/jax_galsim/image.py b/jax_galsim/image.py index acd701cf..f8494a99 100644 --- a/jax_galsim/image.py +++ b/jax_galsim/image.py @@ -325,64 +325,56 @@ def __str__(self): # Read-only attributes: @property + @implements(_galsim.Image.dtype) def dtype(self): - """The dtype of the underlying numpy array.""" return self._dtype @property + @implements(_galsim.Image.bounds) def bounds(self): - """The bounds of the `Image`.""" return self._bounds @property + @implements(_galsim.Image.array) def array(self): - """The underlying numpy array.""" return self._array @property + @implements(_galsim.Image.nrow) def nrow(self): - """The number of rows in the image""" return self._array.shape[0] @property + @implements(_galsim.Image.ncol) def ncol(self): - """The number of columns in the image""" return self._array.shape[1] @property + @implements(_galsim.Image.isconst) def isconst(self): - """Whether the `Image` is constant. I.e. modifying its values is an error.""" return self._is_const @property + @implements(_galsim.Image.iscomplex) def iscomplex(self): - """Whether the `Image` values are complex.""" return self._array.dtype.kind == "c" @property + @implements(_galsim.Image.isinteger) def isinteger(self): - """Whether the `Image` values are integral.""" return self._array.dtype.kind in ("i", "u") @property + @implements( + _galsim.Image.iscontiguous, lax_description="In JAX all arrays are contiguous." + ) 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 True # In JAX all arrays are contiguous (almost) # Allow scale to work as a PixelScale wcs. @property + @implements(_galsim.Image.scale) 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: @@ -404,57 +396,43 @@ def scale(self, value): # Convenience functions @property + @implements(_galsim.Image.xmin) def xmin(self): - """Alias for self.bounds.xmin.""" return self._bounds.xmin @property + @implements(_galsim.Image.xmax) def xmax(self): - """Alias for self.bounds.xmax.""" return self._bounds.xmax @property + @implements(_galsim.Image.ymin) def ymin(self): - """Alias for self.bounds.ymin.""" return self._bounds.ymin @property + @implements(_galsim.Image.ymax) def ymax(self): - """Alias for self.bounds.ymax.""" return self._bounds.ymax @property + @implements(_galsim.Image.outer_bounds) 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 + @implements(_galsim.Image.real) 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 self.__class__( self.array.real, bounds=self.bounds, wcs=self.wcs, make_const=self._is_const ) @property + @implements(_galsim.Image.imag) 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 self.__class__( self.array.imag, bounds=self.bounds, @@ -464,26 +442,16 @@ def imag(self): ) @property + @implements(_galsim.Image.conjugate) 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 self.__class__(self.array.conjugate(), bounds=self.bounds, wcs=self.wcs) + @implements(_galsim.Image.copy) def copy(self): - """Make a copy of the `Image`""" return self.__class__(self.array.copy(), bounds=self.bounds, wcs=self.wcs) + @implements(_galsim.Image.get_pixel_centers) 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 = jnp.meshgrid( jnp.arange(self.array.shape[1], dtype=float), jnp.arange(self.array.shape[0], dtype=float), @@ -500,19 +468,8 @@ def _make_empty(self, shape, dtype): else: return jnp.zeros(shape=shape, dtype=dtype) + @implements(_galsim.Image.resize) 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): @@ -522,11 +479,8 @@ def resize(self, bounds, wcs=None): if wcs is not None: self.wcs = wcs + @implements(_galsim.Image.subImage) 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(): @@ -548,11 +502,8 @@ def subImage(self, bounds): # reorigin that you need to update the wcs. So that's taken care of in im.shift. return self.__class__(subarray, bounds=bounds, wcs=self.wcs) + @implements(_galsim.Image.setSubImage) 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 an immutable Image", self) if not isinstance(bounds, BoundsI): @@ -670,11 +621,8 @@ def wrap(self, bounds, hermitian=False): "Invalid value for hermitian", hermitian, (False, "x", "y") ) + @implements(_galsim.Image._wrap) def _wrap(self, bounds, hermx, hermy): - """A version of `wrap` without the sanity checks. - - Equivalent to ``image.wrap(bounds, hermitian=='x', hermitian=='y')``. - """ if not hermx and not hermy: from jax_galsim.core.wrap_image import wrap_nonhermitian @@ -826,13 +774,8 @@ def calculate_inverse_fft(self): return out @classmethod + @implements(_galsim.Image.good_fft_size) 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. - """ # we use the math module here since this function should not be jitted. import math @@ -848,8 +791,8 @@ def good_fft_size(cls, input_size): Nk = max(int(math.ceil(math.exp(min(log2n, log2n3)) - 1.0e-5)), 2) return Nk + @implements(_galsim.Image.copyFrom) def copyFrom(self, rhs): - """Copy the contents of another image""" if self.isconst: raise GalSimImmutableError("Cannot modify an immutable Image", self) if not isinstance(rhs, Image): @@ -924,13 +867,8 @@ def shift(self, *args, **kwargs): delta = parse_pos_args(args, kwargs, "dx", "dy", integer=True) self._shift(delta) + @implements(_galsim.Image._shift) 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`. - """ self._bounds = self._bounds.shift(delta) if self.wcs is not None: self.wcs = self.wcs.shiftOrigin(delta) @@ -983,10 +921,8 @@ def getValue(self, x, y): ) return self._getValue(x, y) + @implements(_galsim.Image._getValue) 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] @implements(_galsim.Image.setValue) @@ -1006,15 +942,8 @@ def setValue(self, *args, **kwargs): ) self._setValue(pos.x, pos.y, value) + @implements(_galsim.Image._setValue) 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 = self._array.at[y - self.ymin, x - self.xmin].set(value) @implements(_galsim.Image.addValue) @@ -1034,23 +963,12 @@ def addValue(self, *args, **kwargs): ) self._addValue(pos.x, pos.y, value) + @implements(_galsim.Image._addValue) 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 = self._array.at[y - self.ymin, x - self.xmin].add(value) + @implements(_galsim.Image.fill) 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 an immutable Image", self) if not self.bounds.isDefined(): @@ -1059,22 +977,18 @@ def fill(self, value): ) self._fill(value) + @implements(_galsim.Image._fill) def _fill(self, value): - """Equivalent to `fill`, except that there are no checks that the bounds are defined.""" self._array = jnp.zeros_like(self._array) + value + @implements(_galsim.Image.setZero) def setZero(self): - """Set all pixel values to zero.""" if self.isconst: raise GalSimImmutableError("Cannot modify an immutable Image", self) self._fill(0) + @implements(_galsim.Image.invertSelf) 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 an immutable Image", self) if not self.bounds.isDefined(): @@ -1083,21 +997,14 @@ def invertSelf(self): ) self._invertSelf() + @implements(_galsim.Image._invertSelf) def _invertSelf(self): - """Equivalent to `invertSelf`, except that there are no checks that the bounds are defined.""" array = 1.0 / self._array array = array.at[jnp.isinf(array)].set(0.0) self._array = array.astype(self._array.dtype) + @implements(_galsim.Image.replaceNegative) 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] - """ if self.isconst: raise GalSimImmutableError("Cannot modify an immutable Image", self) self._array = self.array.at[self.array < 0].set(replace_value) @@ -1214,48 +1121,80 @@ def _Image(array, bounds, wcs): # These are essentially aliases for the regular Image with the correct dtype +@implements( + _galsim.ImageUS, + lax_description=IMAGE_LAX_DOCS, +) def ImageUS(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.uint16)""" kwargs["dtype"] = jnp.uint16 return Image(*args, **kwargs) +@implements( + _galsim.ImageUI, + lax_description=IMAGE_LAX_DOCS, +) def ImageUI(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.uint32)""" kwargs["dtype"] = jnp.uint32 return Image(*args, **kwargs) +@implements( + _galsim.ImageS, + lax_description=IMAGE_LAX_DOCS, +) def ImageS(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.int16)""" kwargs["dtype"] = jnp.int16 return Image(*args, **kwargs) +@implements( + _galsim.ImageI, + lax_description=IMAGE_LAX_DOCS, +) def ImageI(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.int32)""" kwargs["dtype"] = jnp.int32 return Image(*args, **kwargs) +@implements( + _galsim.ImageF, + lax_description=IMAGE_LAX_DOCS, +) def ImageF(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.float32)""" kwargs["dtype"] = jnp.float32 return Image(*args, **kwargs) +@implements( + _galsim.ImageD, + lax_description=IMAGE_LAX_DOCS, +) def ImageD(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.float64)""" kwargs["dtype"] = jnp.float64 return Image(*args, **kwargs) +@implements( + _galsim.ImageCF, + lax_description=IMAGE_LAX_DOCS, +) def ImageCF(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.complex64)""" kwargs["dtype"] = jnp.complex64 return Image(*args, **kwargs) +@implements( + _galsim.ImageCD, + lax_description=IMAGE_LAX_DOCS, +) def ImageCD(*args, **kwargs): """Alias for galsim.Image(..., dtype=numpy.complex128)""" kwargs["dtype"] = jnp.complex128 diff --git a/jax_galsim/interpolant.py b/jax_galsim/interpolant.py index 7d8a1573..3cf83e62 100644 --- a/jax_galsim/interpolant.py +++ b/jax_galsim/interpolant.py @@ -44,31 +44,8 @@ def __setstate__(self, d): self.__dict__ = d @staticmethod + @implements(_galsim.interpolant.Interpolant.from_name) 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 galsim.deprecated import depr @@ -119,19 +96,20 @@ def from_name(name, tol=None, gsparams=None): ) @property + @implements(_galsim.interpolant.Interpolant.gsparams) def gsparams(self): - """The `GSParams` of the `Interpolant`""" return self._gsparams @property + @implements(_galsim.interpolant.Interpolant.tol) def tol(self): from galsim.deprecated import depr depr("interpolant.tol", 2.2, "interpolant.gsparams.kvalue_accuracy") return self._gsparams.kvalue_accuracy + @implements(_galsim.interpolant.Interpolant.withGSParams) def withGSParams(self, gsparams=None, **kwargs): - """Create a version of the current interpolant with the given gsparams""" if gsparams == self.gsparams: return self # Checking gsparams @@ -165,17 +143,8 @@ def __ne__(self, other): def __hash__(self): return hash(repr(self)) + @implements(_galsim.interpolant.Interpolant.xval) 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. - """ if jnp.ndim(x) > 1: raise GalSimValueError("xval only takes scalar or 1D array values", x) @@ -184,17 +153,8 @@ def xval(self, x): def _xval_noraise(self, x): return self.__class__._xval(x) + @implements(_galsim.interpolant.Interpolant.kval) 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. - """ if jnp.ndim(k) > 1: raise GalSimValueError("kval only takes scalar or 1D array values", k) @@ -203,17 +163,8 @@ def kval(self, k): def _kval_noraise(self, k): return self.__class__._uval(k / 2.0 / jnp.pi) + @implements(_galsim.interpolant.Interpolant.unit_integrals) 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 def tree_flatten(self): @@ -233,14 +184,15 @@ def tree_unflatten(cls, aux_data, children): return cls(**aux_data) @property + @implements(_galsim.interpolant.Interpolant.positive_flux) def positive_flux(self): - """The positive-flux fraction of the interpolation kernel.""" if not hasattr(self, "_positive_flux"): # subclasses can define this method if _positive_flux is not set self._comp_fluxes() return self._positive_flux @property + @implements(_galsim.interpolant.Interpolant.negative_flux) def negative_flux(self): """The negative-flux fraction of the interpolation kernel.""" if not hasattr(self, "_negative_flux"): @@ -350,13 +302,13 @@ def urange(self): return 1.0 / self._gsparams.kvalue_accuracy @property + @implements(_galsim.interpolant.Delta.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" return 0.0 @property + @implements(_galsim.interpolant.Delta.ixrange) def ixrange(self): - """The total integral range of the interpolant. Typically 2 * xrange.""" return 0 def _shoot(self, photons, rng): @@ -395,13 +347,13 @@ def urange(self): return 1.0 / (np.pi * self._gsparams.kvalue_accuracy) @property + @implements(_galsim.interpolant.Nearest.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" return 0.5 @property + @implements(_galsim.interpolant.Nearest.ixrange) def ixrange(self): - """The total integral range of the interpolant. Typically 2 * xrange.""" return 1 def _shoot(self, photons, rng): @@ -469,22 +421,13 @@ def urange(self): return 0.5 @property + @implements(_galsim.interpolant.SincInterpolant.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" # Technically infinity, but truncated by the tolerance. return 1.0 / (np.pi * self._gsparams.kvalue_accuracy) + @implements(_galsim.interpolant.SincInterpolant.unit_integrals) 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) return _sinc_unit_integrals(self.ixrange)[:n] @@ -535,13 +478,13 @@ def urange(self): return 1.0 / np.sqrt(self._gsparams.kvalue_accuracy) / np.pi @property + @implements(_galsim.interpolant.Linear.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" return 1.0 @property + @implements(_galsim.interpolant.Linear.ixrange) def ixrange(self): - """The total integral range of the interpolant. Typically 2 * xrange.""" return 2 def _shoot(self, photons, rng): @@ -606,13 +549,13 @@ def urange(self): ) @property + @implements(_galsim.interpolant.Cubic.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" return 2.0 @property + @implements(_galsim.interpolant.Cubic.ixrange) def ixrange(self): - """The total integral range of the interpolant. Typically 2 * xrange.""" return 4 @@ -701,13 +644,13 @@ def urange(self): ) @property + @implements(_galsim.interpolant.Quintic.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" return 3.0 @property + @implements(_galsim.interpolant.Quintic.ixrange) def ixrange(self): - """The total integral range of the interpolant. Typically 2 * xrange.""" return 6 @@ -1573,52 +1516,43 @@ def urange(self): return self._umax @property + @implements(_galsim.interpolant.Lanczos.n) def n(self): - """The order of the Lanczos function.""" return self._n @property + @implements(_galsim.interpolant.Lanczos.conserve_dc) def conserve_dc(self): - """Whether this interpolant is modified to improve flux conservation.""" return self._conserve_dc @property + @implements(_galsim.interpolant.Lanczos.xrange) def xrange(self): - """The maximum extent of the interpolant from the origin (in pixels).""" return self._n @property + @implements(_galsim.interpolant.Lanczos.ixrange) def ixrange(self): - """The total integral range of the interpolant. Typically 2 * xrange.""" return 2 * self._n @property + @implements(_galsim.interpolant.Lanczos.positive_flux) def positive_flux(self): - """The positive-flux fraction of the interpolation kernel.""" if self._conserve_dc: return self._posflux_conserve_dc[self._n] else: return self._posflux_no_conserve_dc[self._n] @property + @implements(_galsim.interpolant.Lanczos.negative_flux) def negative_flux(self): - """The negative-flux fraction of the interpolation kernel.""" if self._conserve_dc: return self._negflux_conserve_dc[self._n] else: return self._negflux_no_conserve_dc[self._n] + @implements(_galsim.interpolant.Lanczos.unit_integrals) 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. - """ if max_len is not None and max_len < self._n + 1: n = max_len else: diff --git a/jax_galsim/interpolatedimage.py b/jax_galsim/interpolatedimage.py index d9ec1172..3620e30b 100644 --- a/jax_galsim/interpolatedimage.py +++ b/jax_galsim/interpolatedimage.py @@ -232,18 +232,18 @@ def _stepk(self): return super()._stepk @property + @implements(_galsim.interpolatedimage.InterpolatedImage.x_interpolant) def x_interpolant(self): - """The real-space `Interpolant` for this profile.""" return self._original._x_interpolant @property + @implements(_galsim.interpolatedimage.InterpolatedImage.k_interpolant) def k_interpolant(self): - """The Fourier-space `Interpolant` for this profile.""" return self._original._k_interpolant @property + @implements(_galsim.interpolatedimage.InterpolatedImage.image) def image(self): - """The underlying `Image` being interpolated.""" return self._original._image def __hash__(self): diff --git a/jax_galsim/moffat.py b/jax_galsim/moffat.py index c930f370..186e6f2c 100644 --- a/jax_galsim/moffat.py +++ b/jax_galsim/moffat.py @@ -145,18 +145,18 @@ def __init__( ) @property + @implements(_galsim.moffat.Moffat.beta) def beta(self): - """The beta parameter of this `Moffat` profile.""" return self._params["beta"] @property + @implements(_galsim.moffat.Moffat.trunc) def trunc(self): - """The truncation radius (if any) of this `Moffat` profile.""" return self._params["trunc"] @property + @implements(_galsim.moffat.Moffat.scale_radius) def scale_radius(self): - """The scale radius of this `Moffat` profile.""" return self.params["scale_radius"] @property @@ -204,15 +204,15 @@ def _fluxFactor(self): ) @property + @implements(_galsim.moffat.Moffat.half_light_radius) def half_light_radius(self): - """The half-light radius of this `Moffat` profile.""" return self._r0 * jnp.sqrt( jnp.power(1.0 - 0.5 * self._fluxFactor, 1.0 / (1.0 - self.beta)) - 1.0 ) @property + @implements(_galsim.moffat.Moffat.fwhm) def fwhm(self): - """The FWHM of this `Moffat` profle.""" return self._r0 * (2.0 * jnp.sqrt(2.0 ** (1.0 / self.beta) - 1.0)) @property diff --git a/jax_galsim/noise.py b/jax_galsim/noise.py index df7014d4..ddb4e3bb 100644 --- a/jax_galsim/noise.py +++ b/jax_galsim/noise.py @@ -50,44 +50,26 @@ def __init__(self, rng=None): self._rng = rng @property + @implements(_galsim.noise.BaseNoise.rng) def rng(self): - """The `BaseDeviate` of this noise object.""" return self._rng + @implements(_galsim.noise.BaseNoise.getVariance) 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") + @implements(_galsim.noise.BaseNoise.withVariance) 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") + @implements(_galsim.noise.BaseNoise.withScaledVariance) 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): @@ -114,22 +96,8 @@ def __div__(self, variance_ratio): __rmul__ = __mul__ __truediv__ = __div__ + @implements(_galsim.noise.BaseNoise.applyTo) 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) @@ -169,8 +137,8 @@ def __init__(self, rng=None, sigma=1.0): self._sigma = cast_to_float(sigma) @property + @implements(_galsim.noise.GaussianNoise.sigma) def sigma(self): - """The input sigma value.""" return self._sigma def _applyTo(self, image): @@ -228,8 +196,8 @@ def __init__(self, rng=None, sky_level=0.0): self._sky_level = cast_to_float(sky_level) @property + @implements(_galsim.noise.PoissonNoise.sky_level) def sky_level(self): - """The input sky_level.""" return self._sky_level def _applyTo(self, image): @@ -336,18 +304,18 @@ def _gd(self): ) @property + @implements(_galsim.noise.CCDNoise.sky_level) def sky_level(self): - """The input sky_level.""" return self._sky_level @property + @implements(_galsim.noise.CCDNoise.gain) def gain(self): - """The input gain.""" return self._gain @property + @implements(_galsim.noise.CCDNoise.read_noise) def read_noise(self): - """The input read_noise.""" return self._read_noise def _applyTo(self, image): @@ -543,8 +511,8 @@ def __init__(self, rng, var_image): self._var_image = ImageD(var_image) @property + @implements(_galsim.noise.VariableGaussianNoise.var_image) 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 diff --git a/jax_galsim/photon_array.py b/jax_galsim/photon_array.py index b4a7a1d7..a0cade02 100644 --- a/jax_galsim/photon_array.py +++ b/jax_galsim/photon_array.py @@ -277,10 +277,11 @@ def _num_keep(self, num_keep): self._set_self_at_inds(sinds) @property + @implements( + _galsim.photon_array.PhotonArray.x, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def x(self): - """The incidence x position in image coordinates (pixels), typically at the top of - the detector. - """ return self._x @x.setter @@ -288,10 +289,11 @@ def x(self, value): self._x = self._x.at[:].set(value) @property + @implements( + _galsim.photon_array.PhotonArray.y, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def y(self): - """The incidence y position in image coordinates (pixels), typically at the top of - the detector. - """ return self._y @y.setter @@ -299,8 +301,11 @@ def y(self, value): self._y = self._y.at[:].set(value) @property + @implements( + _galsim.photon_array.PhotonArray.flux, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def flux(self): - """The flux of the photons.""" # we use jax.lax.cond to save some multiplications when # there are no masked photos. return jax.lax.cond( @@ -322,8 +327,11 @@ def flux(self, value): ) @property + @implements( + _galsim.photon_array.PhotonArray.dxdz, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def dxdz(self): - """The tangent of the inclination angles in the x direction: dx/dz.""" return self._dxdz @dxdz.setter @@ -332,8 +340,11 @@ def dxdz(self, value): self._dydz = _zero_if_needed_on_set(self._dxdz, self._dydz) @property + @implements( + _galsim.photon_array.PhotonArray.dydz, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def dydz(self): - """The tangent of the inclination angles in the y direction: dy/dz.""" return self._dydz @dydz.setter @@ -342,8 +353,11 @@ def dydz(self, value): self._dxdz = _zero_if_needed_on_set(self._dydz, self._dxdz) @property + @implements( + _galsim.photon_array.PhotonArray.wavelength, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def wavelength(self): - """The wavelength of the photons (in nm).""" return self._wave @wavelength.setter @@ -351,8 +365,11 @@ def wavelength(self, value): self._wave = self._wave.at[:].set(value) @property + @implements( + _galsim.photon_array.PhotonArray.pupil_u, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def pupil_u(self): - """Horizontal location of photon as it intersected the entrance pupil plane.""" return self._pupil_u @pupil_u.setter @@ -361,8 +378,11 @@ def pupil_u(self, value): self._pupil_v = _zero_if_needed_on_set(self._pupil_u, self._pupil_v) @property + @implements( + _galsim.photon_array.PhotonArray.pupil_v, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def pupil_v(self): - """Vertical location of photon as it intersected the entrance pupil plane.""" return self._pupil_v @pupil_v.setter @@ -371,52 +391,63 @@ def pupil_v(self, value): self._pupil_u = _zero_if_needed_on_set(self._pupil_v, self._pupil_u) @property + @implements( + _galsim.photon_array.PhotonArray.time, + lax_description="JAX-GalSim PhotonArray properties do not support assignment at indices.", + ) def time(self): - """Time stamp of when photon encounters the pupil plane.""" return self._time @time.setter def time(self, value): self._time = self._time.at[:].set(value) + @implements(_galsim.photon_array.PhotonArray.hasAllocatedAngles) def hasAllocatedAngles(self): - """Returns whether the arrays for the incidence angles `dxdz` and `dydz` have been - allocated. - """ return jnp.any(jnp.isfinite(self.dxdz) | jnp.isfinite(self.dydz)) + @implements( + _galsim.photon_array.PhotonArray.allocateAngles, + lax_description="This is a no-op for JAX-Galsim.", + ) def allocateAngles(self): - """Allocate memory for the incidence angles, `dxdz` and `dydz`.""" pass + @implements(_galsim.photon_array.PhotonArray.hasAllocatedWavelengths) def hasAllocatedWavelengths(self): - """Returns whether the `wavelength` array has been allocated.""" return jnp.any(jnp.isfinite(self.wavelength)) + @implements( + _galsim.photon_array.PhotonArray.allocateWavelengths, + lax_description="This is a no-op for JAX-Galsim.", + ) def allocateWavelengths(self): - """Allocate the memory for the `wavelength` array.""" pass + @implements(_galsim.photon_array.PhotonArray.hasAllocatedPupil) def hasAllocatedPupil(self): - """Returns whether the arrays for the pupil coordinates `pupil_u` and `pupil_v` have been - allocated. - """ return jnp.any(jnp.isfinite(self.pupil_u) | jnp.isfinite(self.pupil_v)) + @implements( + _galsim.photon_array.PhotonArray.allocatePupil, + lax_description="This is a no-op for JAX-Galsim.", + ) def allocatePupil(self): - """Allocate the memory for the pupil coordinates, `pupil_u` and `pupil_v`.""" pass + @implements(_galsim.photon_array.PhotonArray.hasAllocatedTimes) def hasAllocatedTimes(self): - """Returns whether the array for the time stamps `time` has been allocated.""" return jnp.any(jnp.isfinite(self.time)) + @implements( + _galsim.photon_array.PhotonArray.allocateTimes, + lax_description="This is a no-op for JAX-Galsim.", + ) def allocateTimes(self): - """Allocate the memory for the time stamps, `time`.""" return True + @implements(_galsim.photon_array.PhotonArray.isCorrelated) def isCorrelated(self): - """Returns whether the photons are correlated""" from .deprecated import depr depr( @@ -428,8 +459,8 @@ def isCorrelated(self): ) return self._is_corr + @implements(_galsim.photon_array.PhotonArray.setCorrelated) def setCorrelated(self, is_corr=True): - """Set whether the photons are correlated""" from .deprecated import depr depr( @@ -441,36 +472,24 @@ def setCorrelated(self, is_corr=True): ) self._is_corr = jnp.array(is_corr, dtype=bool) + @implements(_galsim.photon_array.PhotonArray.getTotalFlux) def getTotalFlux(self): - """Return the total flux of all the photons.""" return self.flux.sum() + @implements(_galsim.photon_array.PhotonArray.setTotalFlux) def setTotalFlux(self, flux): - """Rescale the photon fluxes to achieve the given total flux. - - Parameter: - flux: The target flux - """ self.scaleFlux(flux / self.getTotalFlux()) return self + @implements(_galsim.photon_array.PhotonArray.scaleFlux) 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 return self + @implements(_galsim.photon_array.PhotonArray.scaleXY) 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 @@ -541,6 +560,7 @@ def copyFrom( rhs, target_indices, source_indices, do_xy, do_flux, do_other ) + @implements(_galsim.photon_array.PhotonArray._copyFrom) def _copyFrom( self, rhs, @@ -550,9 +570,6 @@ def _copyFrom( 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 @@ -651,14 +668,8 @@ def _assign_from_categorical_index(self, cat_inds, cat_ind_to_assign, rhs): return self + @implements(_galsim.photon_array.PhotonArray.convolve) 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 @@ -857,23 +868,8 @@ def addTo(self, image): return _flux_sum @classmethod + @implements(_galsim.photon_array.PhotonArray.makeFromImage) def makeFromImage(cls, image, max_flux=1.0, 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` - """ - if max_flux <= 0: raise GalSimRangeError("max_flux must be positive", max_flux, 0.0) @@ -899,22 +895,8 @@ def makeFromImage(cls, image, max_flux=1.0, rng=None): return photons + @implements(_galsim.photon_array.PhotonArray.write) 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. - """ import numpy as np from jax_galsim import fits @@ -961,18 +943,8 @@ def write(self, file_name): fits.writeFile(file_name, table) @classmethod + @implements(_galsim.photon_array.PhotonArray.read) 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) diff --git a/jax_galsim/position.py b/jax_galsim/position.py index b805a4ab..4b5f0ad7 100644 --- a/jax_galsim/position.py +++ b/jax_galsim/position.py @@ -128,17 +128,8 @@ def __hash__(self): (self.__class__.__name__, ensure_hashable(self.x), ensure_hashable(self.y)) ) + @implements(_galsim.Position.shear) 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() shear_pos = jnp.dot(shear_mat, self._array) return PositionD(shear_pos[0], shear_pos[1]) diff --git a/jax_galsim/random.py b/jax_galsim/random.py index a04fa50a..4699739f 100644 --- a/jax_galsim/random.py +++ b/jax_galsim/random.py @@ -134,6 +134,7 @@ def _key(self): def _key(self, key): self._state.key = key + @implements(_galsim.BaseDeviate.serialize) def serialize(self): return repr(ensure_hashable(jrandom.key_data(self._key))) diff --git a/jax_galsim/sensor.py b/jax_galsim/sensor.py index 02a3ff99..bd5af2d6 100644 --- a/jax_galsim/sensor.py +++ b/jax_galsim/sensor.py @@ -24,6 +24,7 @@ def accumulate(self, photons, image, orig_center=None, resume=False): def calculate_pixel_areas(self, image, orig_center=PositionI(0, 0), use_flux=True): return 1.0 + @implements(_galsim.Sensor.updateRNG) def updateRNG(self, rng): pass diff --git a/jax_galsim/shear.py b/jax_galsim/shear.py index 76b60bb2..5af34c41 100644 --- a/jax_galsim/shear.py +++ b/jax_galsim/shear.py @@ -139,69 +139,68 @@ def __init__(self, *args, **kwargs): ) @property + @implements(_galsim.Shear.g1) def g1(self): - """The first component of the shear in the "reduced shear" definition.""" return self._g.real @property + @implements(_galsim.Shear.g2) def g2(self): - """The second component of the shear in the "reduced shear" definition.""" return self._g.imag @property + @implements(_galsim.Shear.g) def g(self): - """The magnitude of the shear in the "reduced shear" definition.""" return jnp.abs(self._g) @property + @implements(_galsim.Shear.beta) def beta(self): - """The position angle as an `Angle` instance""" return _Angle(0.5 * jnp.angle(self._g)) @property + @implements(_galsim.Shear.shear) def shear(self): - """The reduced shear as a complex number g1 + 1j * g2.""" - return self._g @property + @implements(_galsim.Shear.e1) def e1(self): - """The first component of the shear in the "distortion" definition.""" return self._g.real * self._g2e(self.g**2) @property + @implements(_galsim.Shear.e2) def e2(self): - """The second component of the shear in the "distortion" definition.""" return self._g.imag * self._g2e(self.g**2) @property + @implements(_galsim.Shear.e) def e(self): - """The magnitude of the shear in the "distortion" definition.""" return self.g * self._g2e(self.g**2) @property + @implements(_galsim.Shear.esq) def esq(self): - """The square of the magnitude of the shear in the "distortion" definition.""" return self.e**2 @property + @implements(_galsim.Shear.eta1) def eta1(self): - """The first component of the shear in the "conformal shear" definition.""" return self._g.real * self._g2eta(self.g) @property + @implements(_galsim.Shear.eta2) def eta2(self): - """The second component of the shear in the "conformal shear" definition.""" return self._g.imag * self._g2eta(self.g) @property + @implements(_galsim.Shear.eta) def eta(self): - """The magnitude of the shear in the "conformal shear" definition.""" return self.g * self._g2eta(self.g) @property + @implements(_galsim.Shear.q) def q(self): - """The minor-to-major axis ratio""" return (1.0 - self.g) / (1.0 + self.g) # Helpers to convert between different conventions diff --git a/jax_galsim/spergel.py b/jax_galsim/spergel.py index 78559594..5c02a741 100644 --- a/jax_galsim/spergel.py +++ b/jax_galsim/spergel.py @@ -254,13 +254,13 @@ def __init__( ) @property + @implements(_galsim.spergel.Spergel.nu) def nu(self): - """The Spergel index, nu""" return self._params["nu"] @property + @implements(_galsim.spergel.Spergel.scale_radius) def scale_radius(self): - """The scale radius of this `Spergel` profile.""" return self.params["scale_radius"] @property @@ -280,8 +280,8 @@ def _inv_r0_sq(self): return self._inv_r0 * self._inv_r0 @property + @implements(_galsim.spergel.Spergel.half_light_radius) def half_light_radius(self): - """The half-light radius of this `Spergel` profile.""" return self._r0 * calculateFluxRadius(0.5, self.nu) @property @@ -301,12 +301,12 @@ def _xnorm0(self): self.nu > 0, _gamma(self.nu) * jnp.power(2.0, self.nu - 1.0), jnp.inf ) + @implements(_galsim.spergel.Spergel.calculateFluxRadius) def calculateFluxRadius(self, f): - """Return the radius within which the total flux is f""" return calculateFluxRadius(f, self.nu) + @implements(_galsim.spergel.Spergel.calculateIntegratedFlux) def calculateIntegratedFlux(self, r): - """Return the integrated flux out to a given radius, r (unit of scale radius)""" return fluxfractionFunc(r, self.nu, 0.0) def __hash__(self): diff --git a/jax_galsim/sum.py b/jax_galsim/sum.py index 03307335..453dc293 100644 --- a/jax_galsim/sum.py +++ b/jax_galsim/sum.py @@ -69,8 +69,8 @@ def __init__(self, *args, gsparams=None, propagate_gsparams=True): self._params = {"obj_list": args} @property + @implements(_galsim.Sum.obj_list) def obj_list(self): - """The list of objects being added.""" return self._params["obj_list"] @property diff --git a/jax_galsim/transform.py b/jax_galsim/transform.py index 6e5b2c4f..61f9d518 100644 --- a/jax_galsim/transform.py +++ b/jax_galsim/transform.py @@ -106,23 +106,23 @@ def _jac(self): return jnp.asarray(jac, dtype=float).reshape((2, 2)) @property + @implements(_galsim.transform.Transformation.original) def original(self): - """The original object being transformed.""" return self._original @property + @implements(_galsim.transform.Transformation.jac) def jac(self): - """The Jacobian of the transforamtion.""" return self._jac @property + @implements(_galsim.transform.Transformation.offset) def offset(self): - """The offset of the transformation.""" return self._offset @property + @implements(_galsim.transform.Transformation.flux_ratio) def flux_ratio(self): - """The flux ratio of the transformation.""" return self._flux_ratio @property diff --git a/jax_galsim/wcs.py b/jax_galsim/wcs.py index 1f1d3c05..52833939 100644 --- a/jax_galsim/wcs.py +++ b/jax_galsim/wcs.py @@ -290,60 +290,33 @@ class EuclideanWCS(BaseWCS): # All EuclideanWCS classes must define origin and world_origin. # Sometimes it is convenient to access x0,y0,u0,v0 directly. @property + @implements(_galsim.wcs.EuclideanWCS.x0) def x0(self): - """The x component of self.origin.""" return self.origin.x @property + @implements(_galsim.wcs.EuclideanWCS.y0) def y0(self): - """The y component of self.origin.""" return self.origin.y @property + @implements(_galsim.wcs.EuclideanWCS.u0) def u0(self): - """The x component of self.world_origin (aka u).""" return self.world_origin.x @property + @implements(_galsim.wcs.EuclideanWCS.v0) def v0(self): - """The y component of self.world_origin (aka v).""" return self.world_origin.y + @implements(_galsim.wcs.EuclideanWCS.xyTouv) 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) + @implements(_galsim.wcs.EuclideanWCS.uvToxy) 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) @@ -526,10 +499,8 @@ def _local(self, image_pos, color): return self._local_wcs # UniformWCS transformations can be inverted easily, so might as well provide that function. + @implements(_galsim.wcs.UniformWCS.inverse) 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 @@ -562,13 +533,13 @@ def isLocal(self): # The origins are definitionally (0,0) for these. So just define them here. @property + @implements(_galsim.wcs.LocalWCS.origin) def origin(self): - """The image coordinate position to use as the origin.""" return PositionD(0, 0) @property + @implements(_galsim.wcs.LocalWCS.world_origin) 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. @@ -605,35 +576,17 @@ def _isCelestial(self): # CelestialWCS classes still have origin, but not world_origin. @property + @implements(_galsim.wcs.CelestialWCS.x0) def x0(self): - """The x coordinate of self.origin.""" return self.origin.x @property + @implements(_galsim.wcs.CelestialWCS.y0) def y0(self): - """The y coordinate of self.origin.""" return self.origin.y + @implements(_galsim.wcs.CelestialWCS.xyToradec) 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: @@ -648,22 +601,8 @@ def xyToradec(self, x, y, units=None, color=None): ) return self._xyToradec(x, y, units, color) + @implements(_galsim.wcs.CelestialWCS.radecToxy) 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): @@ -854,8 +793,8 @@ def _scale(self): # Help make sure PixelScale is read-only. @property + @implements(_galsim.wcs.PixelScale.scale) def scale(self): - """The pixel scale""" return self._scale def _u(self, x, y, color=None): @@ -958,13 +897,13 @@ def _gfactor(self): # Help make sure ShearWCS is read-only. @property + @implements(_galsim.wcs.ShearWCS.scale) def scale(self): - """The pixel scale.""" return self._scale @property + @implements(_galsim.wcs.ShearWCS.shear) def shear(self): - """The applied `Shear`.""" return self._shear def _u(self, x, y, color=None): @@ -1037,6 +976,7 @@ def _writeHeader(self, header, bounds): header["GS_G2"] = (cast_to_python_float(self.shear.g2), "GalSim image shear g2") return self.affine()._writeLinearWCS(header, bounds) + @implements(_galsim.wcs.ShearWCS.copy) def copy(self): return ShearWCS(self._scale, self._shear) @@ -1075,23 +1015,23 @@ def _det(self): # Help make sure JacobianWCS is read-only. @property + @implements(_galsim.wcs.JacobianWCS.dudx) def dudx(self): - """du/dx""" return self._params["dudx"] @property + @implements(_galsim.wcs.JacobianWCS.dudy) def dudy(self): - """du/dy""" return self._params["dudy"] @property + @implements(_galsim.wcs.JacobianWCS.dvdx) def dvdx(self): - """dv/dx""" return self._params["dvdx"] @property + @implements(_galsim.wcs.JacobianWCS.dvdy) def dvdy(self): - """dv/dy""" return self._params["dvdy"] def _u(self, x, y, color=None): @@ -1152,12 +1092,8 @@ def _shearToImage(self, world_shear): def _pixelArea(self): return abs(self._det) + @implements(_galsim.wcs.JacobianWCS.getMatrix) def getMatrix(self): - """Get the Jacobian as a NumPy matrix: - - numpy.array( [[ dudx, dudy ], - [ dvdx, dvdy ]] ) - """ return jnp.array([[self.dudx, self.dudy], [self.dvdx, self.dvdy]], dtype=float) @implements(_galsim.JacobianWCS.getDecomposition) @@ -1313,18 +1249,18 @@ def __init__(self, scale, origin=None, world_origin=None): self._local_wcs = PixelScale(scale) @property + @implements(_galsim.wcs.OffsetWCS.scale) def scale(self): - """The pixel scale.""" return self._scale @property + @implements(_galsim.wcs.OffsetWCS.origin) def origin(self): - """The image coordinate position to use as the origin.""" return self._origin @property + @implements(_galsim.wcs.OffsetWCS.world_origin) def world_origin(self): - """The world coordinate position to use as the origin.""" return self._world_origin def _writeHeader(self, header, bounds): @@ -1383,23 +1319,23 @@ def __init__(self, scale, shear, origin=None, world_origin=None): self._local_wcs = ShearWCS(scale, shear) @property + @implements(_galsim.wcs.OffsetShearWCS.scale) def scale(self): - """The pixel scale.""" return self._local_wcs.scale @property + @implements(_galsim.wcs.OffsetShearWCS.shear) def shear(self): - """The applied `Shear`.""" return self._local_wcs.shear @property + @implements(_galsim.wcs.OffsetShearWCS.origin) def origin(self): - """The image coordinate position to use as the origin.""" return self._origin @property + @implements(_galsim.wcs.OffsetShearWCS.world_origin) def world_origin(self): - """The world coordinate position to use as the origin.""" return self._world_origin def _newOrigin(self, origin, world_origin): @@ -1428,6 +1364,7 @@ def _writeHeader(self, header, bounds): ) return self.affine()._writeLinearWCS(header, bounds) + @implements(_galsim.wcs.OffsetShearWCS.copy) def copy(self): return OffsetShearWCS(self.scale, self.shear, self.origin, self.world_origin) @@ -1469,33 +1406,33 @@ def __init__(self, dudx, dudy, dvdx, dvdy, origin=None, world_origin=None): self._local_wcs = JacobianWCS(dudx, dudy, dvdx, dvdy) @property + @implements(_galsim.wcs.AffineTransform.dudx) def dudx(self): - """du/dx""" return self._local_wcs.dudx @property + @implements(_galsim.wcs.AffineTransform.dudy) def dudy(self): - """du/dy""" return self._local_wcs.dudy @property + @implements(_galsim.wcs.AffineTransform.dvdx) def dvdx(self): - """dv/dx""" return self._local_wcs.dvdx @property + @implements(_galsim.wcs.AffineTransform.dvdy) def dvdy(self): - """dv/dy""" return self._local_wcs.dvdy @property + @implements(_galsim.wcs.AffineTransform.origin) def origin(self): - """The image coordinate position to use as the origin.""" return self._origin @property + @implements(_galsim.wcs.AffineTransform.world_origin) def world_origin(self): - """The world coordinate position to use as the origin.""" return self._world_origin def _writeHeader(self, header, bounds): @@ -1556,6 +1493,7 @@ def _newOrigin(self, origin, world_origin): self.dudx, self.dudy, self.dvdx, self.dvdy, origin, world_origin ) + @implements(_galsim.wcs.AffineTransform.copy) def copy(self): return AffineTransform( self.dudx, self.dudy, self.dvdx, self.dvdy, self.origin, self.world_origin @@ -1577,65 +1515,16 @@ def __hash__(self): return hash(repr(self)) +@implements(_galsim.wcs.compatible) 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) +@implements(_galsim.wcs.readFromFitsHeader) 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 . import fits from .fitswcs import FitsWCS