| 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/ | ||||