# 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 numpy as np
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)