Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
skamann
pampelmuse
Commits
e9027fa2
Commit
e9027fa2
authored
Apr 20, 2021
by
skamann
Browse files
Merge remote-tracking branch 'origin/master'
# Conflicts: # pampelmuse/graphics/pampelmuse_gui.py
parents
9fdda2c0
ccc19f88
Changes
7
Hide whitespace changes
Inline
Side-by-side
pampelmuse/core/fit_layer.py
View file @
e9027fa2
...
...
@@ -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
:
...
...
pampelmuse/core/parameters.py
View file @
e9027fa2
...
...
@@ -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
):
"""
...
...
pampelmuse/core/sources.py
View file @
e9027fa2
...
...
@@ -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:"
)
...
...
pampelmuse/graphics/pampelmuse_gui.py
View file @
e9027fa2
...
...
@@ -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
Non
e
:
if
event
.
inaxes
!=
self
.
ifsPlot
.
axes
or
self
.
ifsInteractMode
!=
'grab'
or
self
.
ifsToolbar
.
in_navigation_mod
e
:
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
Non
e
:
if
self
.
ifsInteractMode
!=
'pick'
or
self
.
ifsToolbar
.
in_navigation_mod
e
:
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
Non
e
:
if
self
.
refInteractMode
!=
'pick'
or
self
.
refToolbar
.
in_navigation_mod
e
:
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
):
...
...
pampelmuse/psf/profiles/maoppy.py
View file @
e9027fa2
...
...
@@ -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
):
"""
Calcul
ate and return the FWHM of the MAOPPY PSF profile for a given
Estim
ate 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
):
"""
...
...
pampelmuse/psf/profiles/profile.py
View file @
e9027fa2
...
...
@@ -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
):
"""
...
...
pampelmuse/psf/variables.py
View file @
e9027fa2
...
...
@@ -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
):
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment