Commit e9027fa2 authored by skamann's avatar skamann
Browse files

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	pampelmuse/graphics/pampelmuse_gui.py
parents 9fdda2c0 ccc19f88
......@@ -1160,10 +1160,14 @@ class FitLayer(object):
else:
background_x0 = None
result = psf.fitter(psf=data, weights=weights, parameters=psf_parameters if is_psf_source[i] else [],
fit_positions=fit_positions, fit_background=local_background,
flux_x0=self.fluxes[self.catalog.at[i, 'component']], background_x0=background_x0,
psf_padding=int(round(self.psf_radius-psf.maxrad)) if is_psf_source[i] else 0)
try:
result = psf.fitter(psf=data, weights=weights, parameters=psf_parameters if is_psf_source[i] else [],
fit_positions=fit_positions, fit_background=local_background,
flux_x0=self.fluxes[self.catalog.at[i, 'component']], background_x0=background_x0,
psf_padding=int(round(self.psf_radius-psf.maxrad)) if is_psf_source[i] else 0)
except ValueError as msg:
logger.error("Unexpected fit problem for sources #{0}: {1}".format(i, msg))
continue
# check if result of a successful fit makes any sense:
if not result.success:
......
......@@ -457,7 +457,7 @@ class Parameters(object):
logger.info(log_string)
def polynomial_fit(self, name, order, mask=None, polynom="plain"):
def polynomial_fit(self, name, order, mask=None, polynom="plain", bounds=None):
"""
Fit a parameter using a polynomial.
......@@ -478,6 +478,13 @@ class Parameters(object):
polynom : str, optional
The function used in the fitting. May be either 'plain',
'legendre', 'hermite', or 'chebyshev'.
bounds : tuple
In case the polynomial fit should be
Returns
-------
success : bool
Flag indicating if the fit has been successful.
"""
# sanity check on provided parameter
if name not in self.names:
......@@ -503,8 +510,11 @@ class Parameters(object):
mask = self.mask[(name, 'value')]
# carry out fit
fit = fit_function.fit(abscissa[~mask], self.data.loc[~mask, (name, 'value')], order)
self.data[(name, 'fit')] = fit(abscissa)
f = fit_function.fit(abscissa[~mask], self.data.loc[~mask, (name, 'value')], order)
fitted_values = f(abscissa)
self.data[(name, 'fit')] = fitted_values
def make_hdu(self, include_fit=True, header_keys=None):
"""
......
......@@ -774,9 +774,9 @@ class Sources(object):
"""
logger.info("Current status of catalogue of sources:")
logger.info(" no. of PSF sources: {0}".format(self.psf_indices.size))
logger.info(" no. of resolved sources: {0}".format(self.is_resolved.sum()))
logger.info(" no. of resolved sources: {0}".format(np.sum(self.status == 2)))
logger.info(" no. of nearby sources: {0}".format(np.sum(self.status == 1)))
logger.info(" no. of unresolved sources: {0}".format(self.is_unresolved.sum()))
logger.info(" no. of unresolved sources: {0}".format(np.sum(self.status ==0)))
logger.info(" no. of background components: {0}".format(self.n_background))
logger.debug("Overview of all components used in fit:")
......
......@@ -52,7 +52,7 @@ from astropy import wcs
from astropy.io import fits
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt
from PyQt5.QtGui import QDoubleValidator
from PyQt5.QtWidgets import QMainWindow, QHBoxLayout, QVBoxLayout, QFileDialog
from PyQt5.QtWidgets import QAction, QMainWindow, QHBoxLayout, QVBoxLayout, QFileDialog
from .dynamic_plot_canvas import DynamicPlotCanvas
from .dynamic_ima_canvas import DynamicImaCanvas
from .Ui_pampelmuse_gui import Ui_PampelMuseGui
......@@ -71,12 +71,27 @@ class ToolbarWithoutMessage(NavigationToolbar):
"""
A small subclass of the navigation toolbar that does not display the
coordinates under the mouse pointer. This is used for the reference
image and IFS data because to save space and because this information
image and IFS data in order to save space and because this information
is already displayed in the status bar.
The class also keeps track of the status of the toolbar buttons (checked
or unchecked), as this information is used to determine the interaction
status of the GUI.
"""
def __init__(self, canvas, parent, coordinates=True):
super(ToolbarWithoutMessage, self).__init__(canvas=canvas, parent=parent, coordinates=coordinates)
self.actionTriggered.connect(self.on_actionTriggered)
self.in_navigation_mode = False
def set_message(self, msg):
pass
@pyqtSlot(QAction)
def on_actionTriggered(self, action):
self.in_navigation_mode = action.isChecked()
class PampelMuseGui(QMainWindow, Ui_PampelMuseGui):
"""
......@@ -767,7 +782,7 @@ class PampelMuseGui(QMainWindow, Ui_PampelMuseGui):
"""
# do nothing if event happened outside axes
if event.inaxes != self.ifsPlot.axes or self.ifsInteractMode != 'grab' or self.ifsToolbar._active is not None:
if event.inaxes != self.ifsPlot.axes or self.ifsInteractMode != 'grab' or self.ifsToolbar.in_navigation_mode:
return
# check if click was inside field of view of IFU
......@@ -791,7 +806,7 @@ class PampelMuseGui(QMainWindow, Ui_PampelMuseGui):
event : a matplotlib.backend_bases.PickEvent instance
The mouse event that triggered the method call.
"""
if self.ifsInteractMode != 'pick' or self.ifsToolbar._active is not None:
if self.ifsInteractMode != 'pick' or self.ifsToolbar.in_navigation_mode:
return
if len(event.ind) != 1:
self.statusBar.showMessage("Multiple sources selected", 5000)
......@@ -812,7 +827,7 @@ class PampelMuseGui(QMainWindow, Ui_PampelMuseGui):
event : a matplotlib.backend_bases.PickEvent instance
The mouse event that triggered the method call.
"""
if self.refInteractMode != 'pick' or self.refToolbar._active is not None:
if self.refInteractMode != 'pick' or self.refToolbar.in_navigation_mode:
return
# do nothing if more than one source selected
......@@ -1406,7 +1421,7 @@ class PampelMuseGui(QMainWindow, Ui_PampelMuseGui):
self.ifsPlot.loadLayer(self.ifuLayerSelectSlider.value(), residuals=False)
# change cuts unless absolute values were set by the user:
if self.ifuCutsComboBox.currentText() is not "User":
if self.ifuCutsComboBox.currentText() != "User":
self.on_ifuCutsComboBox_currentIndexChanged(self.ifuCutsComboBox.currentText())
@pyqtSlot(bool)
......@@ -1423,7 +1438,7 @@ class PampelMuseGui(QMainWindow, Ui_PampelMuseGui):
self.ifsPlot.loadLayer(self.ifuLayerSelectSlider.value(), residuals=True)
# change cuts unless absolute values were set by the user:
if self.ifuCutsComboBox.currentText() is not "User":
if self.ifuCutsComboBox.currentText() != "User":
self.on_ifuCutsComboBox_currentIndexChanged(self.ifuCutsComboBox.currentText())
def plot_sources_on_ref(self):
......
......@@ -35,12 +35,13 @@ import io
import logging
import numpy as np
from contextlib import redirect_stdout
from scipy.interpolate import RectBivariateSpline, interp2d
from scipy.interpolate import RectBivariateSpline, InterpolatedUnivariateSpline, interp2d
from .profile import Profile, FitResult
if importlib.util.find_spec('maoppy') is not None:
from maoppy.psfmodel import Psfao, psffit, oversample
from maoppy.instrument import muse_nfm
from maoppy.utils import circavgplt
else:
logging.error('Package "maoppy" is not installed. Cannot use MAOPPY PSF model.')
......@@ -424,7 +425,6 @@ class Maoppy(Profile):
centre : tuple of 2 floats
The central coordinates of the PSF along the y- and x-axes.
"""
if shape is None:
shape = self.maoppy_shape
return shape[0]/2 - (k - 1.)/(2.*k), shape[1]/2 - (k - 1.)/(2.*k)
......@@ -432,9 +432,14 @@ class Maoppy(Profile):
@classmethod
def calculate_fwhm(cls, r0, bck, amp, alpha, e, beta, wvl=6e-7, **kwargs):
"""
Calculate and return the FWHM of the MAOPPY PSF profile for a given
Estimate and return the FWHM of the MAOPPY PSF profile for a given
set of parameters.
To estimate the FWHM, the method creates the MAOPPY PSF on a 2dim.
grid, then determines the symmetric radial profile, and tries to
obtain the radii where the intensity drops to half its central value
using univariate spline interpolation.
Parameters
----------
r0 : float
......@@ -469,11 +474,18 @@ class Maoppy(Profile):
raise IOError('Unknown argument(s) to call of Maoppy.fwhm: {0}'.format(kwargs))
psf = Psfao(Npix=(30, 30), system=muse_nfm, samp=muse_nfm.samp(wvl))
theta = 0.
f = psf((r0, bck, amp, alpha, 1-e, theta, beta))[:15]
f = psf((r0, bck, amp, alpha, 1 - e, theta, beta))
return 2.*np.sqrt(np.sum(f > (0.5*f.max()))/np.pi)
r, fr = circavgplt(f)
f = InterpolatedUnivariateSpline(r, fr - f.max() / 2.)
roots = f.roots()
if len(roots) != 2:
logger.error("Could not estimate FWHM of MAOPPY profile.")
return np.nan
else:
return roots.max() - roots.min()
@property
def fwhm(self):
......@@ -483,26 +495,24 @@ class Maoppy(Profile):
fwhm : float
The FWHM of the MAOPPY PSF profile with the given parameters.
"""
from scipy.stats import binned_statistic
# get mean radii for fluxes for annuli of widths ~1 pixel
bstat = binned_statistic(self.r, [self.r, self.f], bins=self.maxrad)
r_bin, f_bin = bstat.statistic
# interpolate along flux axis to get FWHM. Note that flux values must be monotonically INCREASING
fwhm = 2.*np.interp(0.5*self.f.max(), f_bin[::-1], r_bin[::-1])
return fwhm
return Maoppy.calculate_fwhm(r0=self.r0, bck=self.bck, amp=self.amp, alpha=self.alpha, e=self.e,
beta=self.beta, wvl=self.wvl)
@property
def strehl(self):
"""
Returns the Strehl ratio for the current set of parameters. The Strehl
ratio is calculated using eq. 14 from Fetick et al. (2019).
Returns
-------
strehl : float
The Strehl ratio for the current set of parameters.
"""
f_ao = muse_nfm.Nact/(2.*muse_nfm.D)
psf = Psfao(Npix=(30, 30), system=muse_nfm, samp=muse_nfm.samp(self.wvl))
return psf.strehlOTF((self.r0, self.bck, self.amp, self.alpha, 1 - self.e, self.theta, self.beta))
return np.exp(-self.amp - self.bck*np.pi*(f_ao**2) - (0.023*6.*np.pi*(self.r0*f_ao)**(-5./3.))/5.)
# Analyytical calculation based on eq. 14 from the paper by Fetick et al. (2019). This seems to underestimate
# the Strehl significantly for NFM data.
# f_ao = muse_nfm.Nact/(2.*muse_nfm.D)
# return np.exp(-self.amp - self.bck*np.pi*(f_ao**2) - (0.023*6.*np.pi*(self.r0*f_ao)**(-5./3.))/5.)
def fitter(self, psf, weights, parameters, fit_positions=True, fit_background=False, psf_padding=0, **kwargs):
"""
......
......@@ -1433,6 +1433,14 @@ class Profile(Canvas):
return radii, np.cumsum(cog_flux)
@classmethod
def calculate_fwhm(cls, **kwargs):
"""
Calculate FWHM for a given set of parameters. Must be overwritten by
classes inheriting from Profile().
"""
raise NotImplementedError
@property
def fwhm(self):
"""
......
......@@ -263,7 +263,8 @@ class Variables(Parameters):
self.lut.index = index
else:
if not self.lut.index.isin(index).all():
logger.warning('Changing dispersion axis as requested will alter existing values of look-up table.')
logger.warning(
'Changing dispersion axis as requested will alter existing values of look-up table.')
self.lut = self.lut.reindex(index=index, method='nearest')
def fit_lut(self, order, mask=None):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment