spread_functions.py | spread_functions.py | |||
---|---|---|---|---|
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | |||
import numpy as np | import numpy as np | |||
import importlib.util | from scipy.interpolate import InterpolatedUnivariateSpline | |||
from scipy.interpolate import RectBivariateSpline, InterpolatedUnivariateSp | ||||
line, interp2d | from astropy.io import fits | |||
try: | try: | |||
from astropy.io import fits | import mpdaf | |||
mpdaf_there=True | ||||
except ImportError: | except ImportError: | |||
import pyfits as fits | mpdaf_there=False | |||
import logging | import logging | |||
logging.basicConfig(level=logging.INFO) | ||||
logger = logging.getLogger('GalPaK: PSF') | ||||
# | # | |||
# This file contains the PointSpreadFunction and LineSpreadFunction interfa ces | # This file contains the PointSpreadFunction and LineSpreadFunction interfa ces | |||
# as well as some basic implementations of these interfaces : | # as well as some basic implementations of these interfaces : | |||
# - Gaussian PSF | # - Gaussian PSF | |||
# - Moffat PSF | # - Moffat PSF | |||
# - Gaussian LSF | # - Gaussian LSF | |||
# - MUSE LSF (only if mpdaf module is available) | # - MUSE LSF (only if mpdaf module is available) | |||
# | # | |||
# The instrument will use both 2D PSF and 1D LSF | # The instrument will use both 2D PSF and 1D LSF | |||
# to create a 3D PSF with which it will convolve the cubes. | # to create a 3D PSF with which it will convolve the cubes. | |||
# | # | |||
## POINT SPREAD FUNCTIONS ################################################# ##### | ## POINT SPREAD FUNCTIONS ################################################# ##### | |||
class PointSpreadFunction: | class PointSpreadFunction: | |||
""" | """ | |||
This is the interface all Point Spread Functions (PSF) should implement . | This is the interface all Point Spread Functions (PSF) should implement . | |||
""" | """ | |||
logger = logger | logging.basicConfig(level=logging.INFO) | |||
logger = logging.getLogger('GalPaK: PSF') | ||||
def as_image(self, for_cube): | def as_image(self, for_cube): | |||
""" | """ | |||
Should return this PSF as a 2D image shaped [for_cube]. | Should return this PSF as a 2D image shaped [for_cube]. | |||
for_cube: HyperspectralCube | for_cube: HyperspectralCube | |||
Has additional properties computed and attributed by GalPaK : | Has additional properties computed and attributed by GalPaK : | |||
- xy_step (in ") | - xy_step (in ") | |||
- z_step (in µm) | - z_step (in µm) | |||
- z_central (in µm) | - z_central (in µm) | |||
skipping to change at line 89 | skipping to change at line 90 | |||
class ImagePointSpreadFunction(PointSpreadFunction): | class ImagePointSpreadFunction(PointSpreadFunction): | |||
""" | """ | |||
A custom point spread function using a provided 2D image | A custom point spread function using a provided 2D image | |||
that should have the same shape as the cube's (x,y) and | that should have the same shape as the cube's (x,y) and | |||
centroid should be at | centroid should be at | |||
xo = (shape[1] - 1) / 2 - (shape[1] % 2 - 1) | xo = (shape[1] - 1) / 2 - (shape[1] % 2 - 1) | |||
yo = (shape[0] - 1) / 2 - (shape[0] % 2 - 1) | yo = (shape[0] - 1) / 2 - (shape[0] % 2 - 1) | |||
""" | """ | |||
def __init__(self, image_2d): | def __init__(self, image_psf): | |||
""" | """ | |||
accepts fits file or ndarray | accepts fits file or ndarray | |||
""" | """ | |||
if isinstance(image_2d, str): | if isinstance(image_psf, str): | |||
self.filename = image_2d | self.filename = image_psf | |||
my_image = fits.open(image_2d) | my_image = fits.open(image_psf) | |||
if my_image['PRIMARY'].data is not None: | if my_image['PRIMARY'].data is not None: | |||
image_2d = my_image['PRIMARY'].data | image_2d = my_image['PRIMARY'].data | |||
header = my_image['PRIMARY'].header | self.header = my_image['PRIMARY'].header | |||
elif my_image['DATA'].data is not None: | elif my_image['DATA'].data is not None: | |||
image_2d = my_image['DATA'].data | image_2d = my_image['DATA'].data | |||
header = my_image['DATA'].header | self.header = my_image['DATA'].header | |||
elif isinstance(image_2d,np.ndarray): | elif isinstance(image_psf, np.ndarray): | |||
image_2d = image_2d | image_2d = image_psf | |||
self.filename = str(image_2d.__class__) | self.filename = str(image_psf.__class__) | |||
self.header = None | ||||
elif mpdaf_there: | ||||
if isinstance(image_psf, mpdaf.obj.Image): | ||||
self.logger.info('Provided image is a Image object') | ||||
self.header = image_psf.data_header | ||||
image_2d = image_psf.data.data | ||||
self.filename = image_psf.filename | ||||
else: | else: | |||
raise ValueError(' PSF provided is not a fits file nor an array !!') | raise ValueError(' PSF provided is not a fits file nor an array !!') | |||
logger.info( "Normalizing PSF image") | self.logger.info( "Normalizing PSF image") | |||
self.image_2d = image_2d / image_2d.sum() | self.image_2d = image_2d / image_2d.sum() | |||
self.header = header | ||||
if isinstance(self.image_2d,np.ndarray) is False: | if isinstance(self.image_2d,np.ndarray) is False: | |||
raise ValueError(' PSF provided could not be stored in an ndarr ay') | raise ValueError(' PSF provided could not be stored in an ndarr ay') | |||
if len(self.image_2d.shape)!=2: | if len(self.image_2d.shape)!=2: | |||
raise ValueError(' PSF provided is not a 2D image') | raise ValueError(' PSF provided is not a 2D image') | |||
def as_image(self, for_cube): | def as_image(self, for_cube): | |||
#check for size | #check for size | |||
if for_cube.shape[1:] != self.image_2d.shape: | if for_cube.shape[1:] != self.image_2d.shape: | |||
raise ValueError(' PSF Image and cube have different sizes: %s vs. %s' % (str(for_cube.shape[1:]),str(self.image_2d.shape)) ) | raise ValueError(' PSF Image and cube have different sizes: %s vs. %s' % (str(for_cube.shape[1:]),str(self.image_2d.shape)) ) | |||
return self.image_2d | return self.image_2d | |||
def __str__(self): | def __str__(self): | |||
return """[PSF] : | return """[PSF] : | |||
type = custom | type = custom | |||
filename = {i.filename}""".format(i=self) | image_psf = {i.filename}""".format(i=self) | |||
class GaussianPointSpreadFunction(PointSpreadFunction): | class GaussianPointSpreadFunction(PointSpreadFunction): | |||
""" | """ | |||
The default Gaussian Point Spread Function. | The default Gaussian Point Spread Function. | |||
fwhm: float | fwhm: float | |||
Full Width Half Maximum in arcsec, aka. "seeing". | Full Width Half Maximum in arcsec, aka. "seeing". | |||
pa: float [default is 0.] | pa: float [default is 0.] | |||
Position Angle of major-axis, anti-clockwise rotation from Y-axis, in angular degrees. | Position Angle of major-axis, anti-clockwise rotation from Y-axis, in angular degrees. | |||
ba: float [default is 1.0] | ba: float [default is 1.0] | |||
skipping to change at line 168 | skipping to change at line 175 | |||
if xo is None: | if xo is None: | |||
xo = (shape[1] - 1) / 2 #center of array | xo = (shape[1] - 1) / 2 #center of array | |||
#force PSF to be centered on even grid by adding 0.5pixel if ev en | #force PSF to be centered on even grid by adding 0.5pixel if ev en | |||
#because of the upcoming padding in the convolution | #because of the upcoming padding in the convolution | |||
xo = xo - (shape[1] % 2 - 1) /2 | xo = xo - (shape[1] % 2 - 1) /2 | |||
if yo is None: | if yo is None: | |||
yo = (shape[0] - 1) / 2 #center of array | yo = (shape[0] - 1) / 2 #center of array | |||
# force PSF to be centered on even grid by adding 0.5pixel if e ven | # force PSF to be centered on even grid by adding 0.5pixel if e ven | |||
# because of the upcoming padding in the convolution | # because of the upcoming padding in the convolution | |||
yo = yo - (shape[0] % 2 - 1) /2 | yo = yo - (shape[0] % 2 - 1) /2 | |||
logger.info("Generating PSF at x0,y0 (%s,%s)" % (xo,yo)) | self.logger.info("Generating PSF at x0,y0 (%s,%s)" % (xo,yo)) | |||
y, x = np.indices(shape) | y, x = np.indices(shape) | |||
r = self._radius(xo, yo, x, y) | r = self._radius(xo, yo, x, y) | |||
fwhm = self.fwhm / for_cube.xy_step #in pixels | fwhm = self.fwhm / for_cube.xy_step #in pixels | |||
psf = np.exp(-0.5 * (r / (fwhm / 2.35482)) ** 2) | psf = np.exp(-0.5 * (r / (fwhm / 2.35482)) ** 2) | |||
return psf / psf.sum() | return psf / psf.sum() | |||
class MoffatPointSpreadFunction(PointSpreadFunction): | class MoffatPointSpreadFunction(PointSpreadFunction): | |||
skipping to change at line 206 | skipping to change at line 213 | |||
def __init__(self, fwhm=None, alpha=None, beta=None, pa=0, ba=1): | def __init__(self, fwhm=None, alpha=None, beta=None, pa=0, ba=1): | |||
self.beta = beta | self.beta = beta | |||
self.pa = pa | self.pa = pa | |||
self.ba = ba | self.ba = ba | |||
self.alpha = alpha | self.alpha = alpha | |||
self.fwhm = fwhm | self.fwhm = fwhm | |||
if not ((alpha is None) or (fwhm is None)): | if not ((alpha is None) or (fwhm is None)): | |||
self.logger.warning("Moffat psf: alpha and fwhm are both specif ied. Will use alpha. ") | self.logger.warning("Moffat psf: alpha and fwhm are both specif ied. Will use alpha. ") | |||
elif ((alpha is None) and (fwhm is None)): | elif ((alpha is None) and (fwhm is None)): | |||
raise Error("Moffat psf: alpha and fwhm are not specified. Need at least one.") | raise Exception("Moffat psf: alpha and fwhm are not specified. Need at least one.") | |||
else: | else: | |||
self.alpha = alpha | self.alpha = alpha | |||
self.fwhm = fwhm | self.fwhm = fwhm | |||
if fwhm is None: | if fwhm is None: | |||
self.fwhm = self.calculate_fwhm() | self.fwhm = self.calculate_fwhm() | |||
if alpha is None: | if alpha is None: | |||
self.alpha = self.calculate_alpha() | self.alpha = self.calculate_alpha() | |||
skipping to change at line 247 | skipping to change at line 254 | |||
if xo is None: | if xo is None: | |||
xo = (shape[1] - 1) / 2 #center of array | xo = (shape[1] - 1) / 2 #center of array | |||
#force PSF to be centered on even grid by adding 0.5pixel if ev en | #force PSF to be centered on even grid by adding 0.5pixel if ev en | |||
#because of the upcoming padding in the convolution | #because of the upcoming padding in the convolution | |||
xo = xo - (shape[1] % 2 - 1) /2 | xo = xo - (shape[1] % 2 - 1) /2 | |||
if yo is None: | if yo is None: | |||
yo = (shape[0] - 1) / 2 #center of array | yo = (shape[0] - 1) / 2 #center of array | |||
# force PSF to be centered on even grid by adding 0.5pixel if e ven | # force PSF to be centered on even grid by adding 0.5pixel if e ven | |||
# because of the upcoming padding in the convolution | # because of the upcoming padding in the convolution | |||
yo = yo - (shape[0] % 2 - 1) /2 | yo = yo - (shape[0] % 2 - 1) /2 | |||
logger.info("Generating PSF at x0,y0 (%s,%s)" % (xo,yo)) | self.logger.info("Generating PSF at x0,y0 (%s,%s)" % (xo,yo)) | |||
y, x = np.indices(shape) | y, x = np.indices(shape) | |||
r = self._radius(xo, yo, x, y) | r = self._radius(xo, yo, x, y) | |||
pix_alpha = self.alpha / for_cube.xy_step | pix_alpha = self.alpha / for_cube.xy_step | |||
psf = (1. + (r / pix_alpha) ** 2) ** (-self.beta) | psf = (1. + (r / pix_alpha) ** 2) ** (-self.beta) | |||
return psf / psf.sum() | return psf / psf.sum() | |||
def calculate_fwhm(self): | def calculate_fwhm(self): | |||
fwhm = self.alpha * (2. * np.sqrt(2. ** (1. / self.beta) - 1)) | fwhm = self.alpha * (2. * np.sqrt(2. ** (1. / self.beta) - 1)) | |||
return fwhm | return fwhm | |||
def calculate_alpha(self): | def calculate_alpha(self): | |||
alpha = self.fwhm / (2. * np.sqrt(2. ** (1. / self.beta) - 1)) | alpha = self.fwhm / (2. * np.sqrt(2. ** (1. / self.beta) - 1)) | |||
return alpha | return alpha | |||
if importlib.util.find_spec('maoppy') is not None: | try: | |||
import maoppy | import maoppy | |||
RAD2ARCSEC = maoppy.utils.RAD2ARCSEC | RAD2ARCSEC = maoppy.utils.RAD2ARCSEC | |||
from maoppy.psfmodel import Psfao, oversample | from maoppy.psfmodel import Psfao, oversample | |||
from maoppy.psffit import psffit | from maoppy.psffit import psffit | |||
from maoppy.instrument import muse_nfm,muse_wfm | from maoppy.instrument import muse_nfm,muse_wfm | |||
from maoppy.utils import circavgplt,circavg | from maoppy.utils import circavgplt,circavg | |||
logger.info("Found package Maoppy version {}".format(maoppy.__version__ )) | logging.info("Found package Maoppy version {}".format(maoppy.__version_ _)) | |||
class MAOPPYPointSpreadFunction(Psfao, PointSpreadFunction): | class CommonMeta(type(PointSpreadFunction), type(maoppy.psfmodel.Psfao) | |||
): | ||||
pass | ||||
class MAOPPYPointSpreadFunction(type(maoppy.psfmodel.Psfao), type(Point | ||||
SpreadFunction), metaclass=CommonMeta): | ||||
""" | """ | |||
This class uses the MAOPPY PSF profile described by Fetick et al. | This class uses the MAOPPY PSF profile described by Fetick et al. | |||
(2019). | (2019). | |||
References | References | |||
---------- | ---------- | |||
Fetick et al. (2019, A&A, 628, A99) | Fetick et al. (2019, A&A, 628, A99) | |||
https://ui.adsabs.harvard.edu/abs/2019A&A...628A..99F/abstract | https://ui.adsabs.harvard.edu/abs/2019A&A...628A..99F/abstract | |||
""" | """ | |||
skipping to change at line 429 | skipping to change at line 439 | |||
xo = xo - (shape[1] % 2 - 1) / 2 | xo = xo - (shape[1] % 2 - 1) / 2 | |||
if yo is None: | if yo is None: | |||
yo = (shape[0] - 1) / 2 # center of array | yo = (shape[0] - 1) / 2 # center of array | |||
# force PSF to be centered on odd grid by adding 0.5pixel i f even | # force PSF to be centered on odd grid by adding 0.5pixel i f even | |||
# because of the upcoming padding in the convolution | # because of the upcoming padding in the convolution | |||
yo = yo - (shape[0] % 2 - 1) / 2 | yo = yo - (shape[0] % 2 - 1) / 2 | |||
self.logger.info("Generating PSF at x0,y0 (%s,%s)" % (xo, yo)) | self.logger.info("Generating PSF at x0,y0 (%s,%s)" % (xo, yo)) | |||
self.center = (xo, yo) | self.center = (xo, yo) | |||
self.system._resolution_rad = for_cube.xy_step / RAD2ARCSEC | self.system._rsolution_rad = for_cube.xy_step / RAD2ARCSEC | |||
if 'angstr' in for_cube.z_cunit.lower(): | if 'angstr' in for_cube.z_cunit.lower(): | |||
wvl_um = for_cube.z_central / 1e4 | wvl_um = for_cube.z_central / 1e4 | |||
elif 'micron' in for_cube.z_cunit.lower(): | elif 'micron' in for_cube.z_cunit.lower(): | |||
wvl_um = for_cube.z_central | wvl_um = for_cube.z_central | |||
elif 'nm' in for_cube.z_cunit.lower(): | elif 'nm' in for_cube.z_cunit.lower(): | |||
wvl_um = for_cube.z_central / 1e3 | wvl_um = for_cube.z_central / 1e3 | |||
else: | else: | |||
raise NotImplementedError("Cube units not supported") | raise NotImplementedError("Cube units not supported") | |||
self.samp = self.system.samp(wvl_um * 1e-6) | self.samp = self.system.samp(wvl_um * 1e-6) | |||
skipping to change at line 638 | skipping to change at line 648 | |||
self.guess=np.array(x0) | self.guess=np.array(x0) | |||
not_fitted = np.array(self.param_name_gpk)[self.fixed] | not_fitted = np.array(self.param_name_gpk)[self.fixed] | |||
self.logger.info("MAOPPY fit: parameters fixed: {}".format(not_ fitted)) | self.logger.info("MAOPPY fit: parameters fixed: {}".format(not_ fitted)) | |||
if bounds_dict is not None: | if bounds_dict is not None: | |||
self.logger.info("MAOPPY fit: setting bounds with {}".forma t(bounds_dict)) | self.logger.info("MAOPPY fit: setting bounds with {}".forma t(bounds_dict)) | |||
Pmodel = self.set_bounds(Pmodel, bounds_dict) | Pmodel = self.set_bounds(Pmodel, bounds_dict) | |||
# %% Fit the PSF with Psfao | # %% Fit the PSF with Psfao | |||
self.logger.info("MAOPPY fitting: mode = {}; option fit_backgro und={} ".format(self.mode, fit_background )) | self.logger.info("MAOPPY fitting: mode = {}; option fit_backgro und={} positive={} ".format(self.mode, fit_background, positive_bck )) | |||
fitresult = psffit(psf_padded, Pmodel, self.guess, weights=weig hts, fixed=self.fixed, \ | fitresult = psffit(psf_padded, Pmodel, self.guess, weights=weig hts, fixed=self.fixed, \ | |||
flux_bck = (True, fit_background), positive_bck = posit ive_bck, npixfit = None ) | flux_bck = (True, fit_background), positive_bck = posit ive_bck, npixfit = None ) | |||
J = fitresult.jac | J = fitresult.jac | |||
cov = np.linalg.inv(J.T.dot(J)) | cov = np.linalg.inv(J.T.dot(J)) | |||
values={} | values={} | |||
errors={} | errors={} | |||
_idx = (fixed==False).cumsum() | _idx = (fixed==False).cumsum() | |||
for i, p in enumerate(self.param_name_gpk): | for i, p in enumerate(self.param_name_gpk): | |||
if p in parameters: | if p in parameters: | |||
skipping to change at line 707 | skipping to change at line 717 | |||
psf=None, | psf=None, | |||
message=fitresult.message) | message=fitresult.message) | |||
#return fitresult | #return fitresult | |||
def from_fitter(self,results): | def from_fitter(self,results): | |||
self.set_p(results.best_fit.values()) | self.set_p(results.best_fit.values()) | |||
self.image_2d = results.psf | self.image_2d = results.psf | |||
self.flux = results.flux * 1e-20 #erg/s/cm2/AA | self.flux = results.flux * 1e-20 #erg/s/cm2/AA | |||
flux_Jy = 3.34e4 * (self.wvl_um*1e4) ** 2 * results.flux * 1e-2 0 | flux_Jy = 3.34e4 * (self.wvl_um*1e4) ** 2 * results.flux * 1e-2 0 | |||
self.mAB = -2.5 * np.log10(flux_Jy / 3631.) | self.mAB = -2.5 * np.log10(flux_Jy / 3631.) | |||
else: | except ImportError: | |||
logger.warning('Package "maoppy" is not installed [not required]. Canno | logging.warning('Package "maoppy" is not installed [not required]. Cann | |||
t use MAOPPY PSF model.') | ot use MAOPPY PSF model.') | |||
## LINE SPREAD FUNCTIONS ################################################## ##### | ## LINE SPREAD FUNCTIONS ################################################## ##### | |||
class LineSpreadFunction: | class LineSpreadFunction: | |||
""" | """ | |||
This is the interface all Line Spread Functions (LSF) should implement. | This is the interface all Line Spread Functions (LSF) should implement. | |||
""" | """ | |||
z_cunit = 'Undef' | z_cunit = 'Undef' | |||
End of changes. 21 change blocks. | ||||
32 lines changed or deleted | 43 lines changed or added | |||
This html diff was produced by rfcdiff 1.41. The latest version is available from http://tools.ietf.org/tools/rfcdiff/ |