Commit c8ab3a95 authored by Damien Naudet's avatar Damien Naudet

Some more work to prepare for the integration of silx's FitManager.

parent 9121ec7f
......@@ -160,6 +160,8 @@ class FitWidget(Qt.QWidget):
FitTypes = OrderedDict([('Gaussian', FitTypes.GAUSSIAN),
('Centroid', FitTypes.CENTROID)])
# ,
# ('Silx', FitTypes.SILX)])
__sigFitDone = Qt.Signal()
......@@ -176,6 +178,8 @@ class FitWidget(Qt.QWidget):
self.__outputFile = None
self.__nPeaks = 1
layout = Qt.QGridLayout(self)
self.__roiWidget = roiWidget = Roi3DSelectorWidget()
......@@ -194,7 +198,18 @@ class FitWidget(Qt.QWidget):
fitTypeCb.setCurrentIndex(0)
fitLayout.addWidget(Qt.QLabel('Fit :'))
fitLayout.addWidget(fitTypeCb)
layout.addLayout(fitLayout, 2, 0, alignment=Qt.Qt.AlignLeft)
fitTypeCb.currentIndexChanged[str].connect(
self.__slotCurrentTextChanged)
self.__nPeaksSpinBox = spinbox = Qt.QSpinBox()
# layout.addLayout(fitLayout, 2, 0, alignment=Qt.Qt.AlignLeft)
# spinbox.setMinimum(1)
# spinbox.setMaximum(20)
# spinbox.setValue(self.__nPeaks)
# spinbox.setToolTip('Max. number of expected peaks.')
# spinbox.valueChanged.connect(self.__slotValueChanged)
# fitLayout.addWidget(spinbox)
# fitLayout.addWidget(Qt.QLabel('peak(s)'))
runLayout = Qt.QHBoxLayout()
self.__runButton = runButton = Qt.QPushButton('Run')
......@@ -261,6 +276,19 @@ class FitWidget(Qt.QWidget):
self.__fileEdit.clear()
self.__runButton.setEnabled(outputFile is not None)
def __slotValueChanged(self, value):
self.__nPeaks = value
def __slotCurrentTextChanged(self, text):
blocked = self.__nPeaksSpinBox.blockSignals(True)
if text in ('Gaussian', 'Silx'):
self.__nPeaksSpinBox.setEnabled(True)
self.__nPeaksSpinBox.setValue(self.__nPeaks)
else:
self.__nPeaksSpinBox.setEnabled(False)
self.__nPeaksSpinBox.setValue(1)
self.__nPeaksSpinBox.blockSignals(blocked)
def __slotRunClicked(self):
# TODO : put some safeguards
......@@ -279,7 +307,8 @@ class FitWidget(Qt.QWidget):
self.__fitter = fitter = PeakFitter(self.__qspaceH5.filename,
fit_type=fitType,
roi_indices=roiIndices)
roi_indices=roiIndices,
n_peaks=self.__nPeaks)
self.__statusLabel.setText('Running...')
self.__progTimer = timer = Qt.QTimer()
......
......@@ -43,6 +43,7 @@ from kmap.gui.model.TreeView import TreeView
from .fitview.FitModel import FitModel, FitH5Node
from .fitview.DropPlotWidget import DropPlotWidget
from.fitview.ResultPlot import plotCentroid, plotGaussian, plotSilx
class FitView(Qt.QMainWindow):
sigPointSelected = Qt.Signal(object)
......@@ -71,9 +72,7 @@ class FitView(Qt.QMainWindow):
with fitH5:
# only one entry per file supposed right now
# only one process per entry supposed right now
self.__entry = fitH5.entries()[0]
self.__process = fitH5.processes(self.__entry)[0]
centralWid = Qt.QWidget()
layout = Qt.QGridLayout(centralWid)
......@@ -237,9 +236,9 @@ class FitView(Qt.QMainWindow):
if processes:
process = processes[0]
if process == 'gaussian':
_initLeastSq(self.__plots, fitH5.filename, entry, process)
elif process == 'centroid':
if entry in ('Gaussian', 'SilxFit'):
_initGaussian(self.__plots, fitH5.filename, entry, process)
elif process == 'Centroid':
_initCentroid(self.__plots, fitH5.filename, entry, process)
def __slotPointSelected(self, point):
......@@ -252,6 +251,7 @@ class FitView(Qt.QMainWindow):
for plot in self.__plots:
if plot != sender:
plot.selectPoint(point.x, point.y)
self.__plotFitResults(point.xIdx)
self.sigPointSelected.emit(point)
......@@ -287,131 +287,28 @@ class FitView(Qt.QMainWindow):
plot.clearMarkers()
# TODO : refactor
process = self.__process
if process == 'gaussian':
_plotGaussian(self.__fitPlots, xIdx,
fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
elif process == 'centroid':
_plotCentroid(self.__fitPlots, xIdx,
fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
if entry == 'Gaussian':
plotGaussian(self.__fitPlots, xIdx,
fitH5, entry,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
elif entry == 'Centroid':
plotCentroid(self.__fitPlots, xIdx,
fitH5, entry,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
elif entry == 'SilxFit':
plotSilx(self.__fitPlots, xIdx,
fitH5, entry,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
else:
# TODO : popup
raise ValueError('Unknown process {0}.'.format(process))
# TODO : allow users to register plot functions associated with the kind
# of process results that are being displayed
def _plotGaussian(plots, index, fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plots the "Gaussian" fit results
:param plots: plot widgets
:param index: index of the selected point (in the results array)
:param fitH5: instance of FitH5. This instance may be already opened by
the caller.
:param entry: name of the entry to plot
:param process: name of the process
:param xData: x axis values of the fitted data
:param acqX: x axis values of the acquired data
:param acqY: y axis values of the acquired data
:return:
"""
# TODO : put all this in a toolbox, so it can be shared between
# the plot and the fit functions
_const_inv_2_pi_ = np.sqrt(2 * np.pi)
_gauss_fn = lambda p, pos: (
p[0] * (1. / (_const_inv_2_pi_ * p[2])) *
np.exp(-0.5 * ((pos - p[1]) / p[2]) ** 2))
with fitH5:
xFitQX = fitH5.get_qx(entry)
xFitQY = fitH5.get_qy(entry)
xFitQZ = fitH5.get_qz(entry)
heights = fitH5.get_result(entry, process, 'intensity')
positions = fitH5.get_result(entry, process, 'position')
widths = fitH5.get_result(entry, process, 'width')
h_x = heights.qx[index]
p_x = positions.qx[index]
w_x = widths.qx[index]
h_y = heights.qy[index]
p_y = positions.qy[index]
w_y = widths.qy[index]
h_z = heights.qz[index]
p_z = positions.qz[index]
w_z = widths.qz[index]
params = [h_x, p_x, w_x]
fitted = _gauss_fn(params, xFitQX)
plots[0].addCurve(xFitQX, fitted, legend='QX LSQ gauss. fit')
plots[0].addCurve(xAcqQX, yAcqQX, legend='measured')
plots[0].setGraphTitle('QX / LSQ')
params = [h_y, p_y, w_y]
fitted = _gauss_fn(params, xFitQY)
plots[1].addCurve(xFitQY, fitted, legend='QY LSQ gauss. fit')
plots[1].addCurve(xAcqQY, yAcqQY, legend='measured')
plots[1].setGraphTitle('QY / LSQ')
params = [h_z, p_z, w_z]
fitted = _gauss_fn(params, xFitQZ)
plots[2].addCurve(xFitQZ, fitted, legend='QZ LSQ gauss. fit')
plots[2].addCurve(xAcqQZ, yAcqQZ, legend='measured')
plots[2].setGraphTitle('QZ / LSQ')
def _plotCentroid(plots, index, fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plot the results from a "centroid" fit.
:param plots: the plot widgets
:param index: index of the sample point
:param fitH5: fitH5 file
:param entry: name of the entry in the fitH5
:param process: name of the process in the fitH5
:param xAcqQX: measured Qx data, x axis
:param xAcqQY: measured Qy data, x axis
:param xAcqQZ: measured Qz data, x axis
:param yAcqQX: measured Qx data, y axis
:param yAcqQY:measured Qy data, y axis
:param yAcqQZ:measured Qz data, y axis
:return:
"""
# TODO : put all this in a toolbox, so it can be shared between
# the plot and the fit functions
positions = fitH5.get_result(entry, process, 'COM')
plots[0].addCurve(xAcqQX, yAcqQX, legend='measured')
plots[0].addXMarker(positions.qx[index], legend='center of mass')
plots[0].setGraphTitle('QX center of mass')
plots[1].addCurve(xAcqQY, yAcqQY, legend='measured')
plots[1].addXMarker(positions.qy[index], legend='center of mass')
plots[1].setGraphTitle('QY center of mass')
plots[2].addCurve(xAcqQZ, yAcqQZ, legend='measured')
plots[2].addXMarker(positions.qz[index], legend='center of mass')
plots[2].setGraphTitle('QZ center of mass')
raise ValueError('Unknown entry {0}.'.format(entry))
def _initLeastSq(plots, fitH5Name, entry, process):
def _initGaussian(plots, fitH5Name, entry, process):
"""
Sets up the plots when the interface is shown for the first time.
:param plots: the plot widgets
......
......@@ -112,7 +112,8 @@ class DropPlotWidget(XsocsPlot2D):
scan_y = h5f.scan_y(entry)[errorPts]
data = data[errorPts]
self.__legend = self.setPlotData(scan_x, scan_y, data)
self.__legend = self.setPlotData(scan_x, scan_y,
data, dataIndices=errorPts)
self.setGraphTitle('Errors[qx]' + FitH5QAxis.axis_names[q_axis])
......
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2015-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
from __future__ import absolute_import
__authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
import numpy as np
from silx.math.fit import sum_agauss
from ....process.fit_funcs import gaussian
# TODO : allow users to register plot functions associated with the kind
# of process results that are being displayed
def plotGaussian(plots, index, fitH5,
entry,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plots the "Gaussian" fit results
:param plots: plot widgets
:param index: index of the selected point (in the results array)
:param fitH5: instance of FitH5. This instance may be already opened by
the caller.
:param entry: name of the entry to plot
:param xData: x axis values of the fitted data
:param acqX: x axis values of the acquired data
:param acqY: y axis values of the acquired data
:return:
"""
plots[0].addCurve(xAcqQX, yAcqQX, legend='measured')
plots[0].setGraphTitle('QX / Gaussians')
plots[1].addCurve(xAcqQY, yAcqQY, legend='measured')
plots[1].setGraphTitle('QY / Gaussians')
plots[2].addCurve(xAcqQZ, yAcqQZ, legend='measured')
plots[2].setGraphTitle('QZ / Gaussians')
with fitH5:
xFitQX = fitH5.get_qx(entry)
xFitQY = fitH5.get_qy(entry)
xFitQZ = fitH5.get_qz(entry)
sumX = None
sumY = None
sumZ = None
processes = fitH5.processes(entry)
for process in processes:
heights = fitH5.get_result(entry, process, 'intensity')
positions = fitH5.get_result(entry, process, 'position')
widths = fitH5.get_result(entry, process, 'width')
h_x = heights.qx[index]
p_x = positions.qx[index]
w_x = widths.qx[index]
h_y = heights.qy[index]
p_y = positions.qy[index]
w_y = widths.qy[index]
h_z = heights.qz[index]
p_z = positions.qz[index]
w_z = widths.qz[index]
params = [h_x, p_x, w_x]
if np.all(np.isfinite(params)):
fitted = gaussian(xFitQX, *params)
plots[0].addCurve(xFitQX, fitted,
legend='QX process={0}'.format(process))
if sumX is None:
sumX = fitted
else:
sumX += fitted
params = [h_y, p_y, w_y]
if np.all(np.isfinite(params)):
fitted = gaussian(xFitQY, *params)
plots[1].addCurve(xFitQY, fitted,
legend='QY process={0}'.format(process))
if sumY is None:
sumY = fitted
else:
sumY += fitted
params = [h_z, p_z, w_z]
if np.all(np.isfinite(params)):
fitted = gaussian(xFitQZ, *params)
plots[2].addCurve(xFitQZ, fitted,
legend='QZ process={0}'.format(process))
if sumZ is None:
sumZ = fitted
else:
sumZ += fitted
if sumX is not None:
plots[0].addCurve(xAcqQX, sumX, legend='Sum')
if sumY is not None:
plots[1].addCurve(xAcqQY, sumY, legend='Sum')
if sumZ is not None:
plots[2].addCurve(xAcqQZ, sumZ, legend='Sum')
# TODO : allow users to register plot functions associated with the kind
# of process results that are being displayed
def plotSilx(plots, index, fitH5,
entry,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plots the "Gaussian" fit results
:param plots: plot widgets
:param index: index of the selected point (in the results array)
:param fitH5: instance of FitH5. This instance may be already opened by
the caller.
:param entry: name of the entry to plot
:param xData: x axis values of the fitted data
:param acqX: x axis values of the acquired data
:param acqY: y axis values of the acquired data
:return:
"""
plots[0].addCurve(xAcqQX, yAcqQX, legend='measured')
plots[0].setGraphTitle('QX / Gaussians')
plots[1].addCurve(xAcqQY, yAcqQY, legend='measured')
plots[1].setGraphTitle('QY / Gaussians')
plots[2].addCurve(xAcqQZ, yAcqQZ, legend='measured')
plots[2].setGraphTitle('QZ / Gaussians')
with fitH5:
xFitQX = fitH5.get_qx(entry)
xFitQY = fitH5.get_qy(entry)
xFitQZ = fitH5.get_qz(entry)
paramsX = []
paramsY = []
paramsZ = []
processes = fitH5.processes(entry)
for process in processes:
heights = fitH5.get_result(entry, process, 'intensity')
positions = fitH5.get_result(entry, process, 'position')
widths = fitH5.get_result(entry, process, 'fwhm')
h_x = heights.qx[index]
p_x = positions.qx[index]
w_x = widths.qx[index]
h_y = heights.qy[index]
p_y = positions.qy[index]
w_y = widths.qy[index]
h_z = heights.qz[index]
p_z = positions.qz[index]
w_z = widths.qz[index]
params = [h_x, p_x, w_x]
if np.all(np.isfinite(params)):
fitted = sum_agauss(xFitQX, *params)
plots[0].addCurve(xFitQX, fitted,
legend='QX process={0}'.format(process))
paramsX.extend(params)
params = [h_y, p_y, w_y]
if np.all(np.isfinite(params)):
fitted = sum_agauss(xFitQY, *params)
plots[1].addCurve(xFitQY, fitted,
legend='QY process={0}'.format(process))
paramsY.extend(params)
params = [h_z, p_z, w_z]
if np.all(np.isfinite(params)):
fitted = sum_agauss(xFitQZ, *params)
plots[2].addCurve(xFitQZ, fitted,
legend='QZ process={0}'.format(process))
paramsZ.extend(params)
if paramsX:
plots[0].addCurve(xAcqQX,
sum_agauss(xAcqQX, *paramsX),
legend='Sum')
if paramsY:
plots[1].addCurve(xAcqQY,
sum_agauss(xAcqQY, *paramsY),
legend='Sum')
if paramsZ:
plots[2].addCurve(xAcqQZ,
sum_agauss(xAcqQZ, *paramsZ),
legend='Sum')
def plotCentroid(plots, index, fitH5,
entry,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plot the results from a "centroid" fit.
:param plots: the plot widgets
:param index: index of the sample point
:param fitH5: fitH5 file
:param entry: name of the entry in the fitH5
:param process: name of the process in the fitH5
:param xAcqQX: measured Qx data, x axis
:param xAcqQY: measured Qy data, x axis
:param xAcqQZ: measured Qz data, x axis
:param yAcqQX: measured Qx data, y axis
:param yAcqQY:measured Qy data, y axis
:param yAcqQZ:measured Qz data, y axis
:return:
"""
# TODO : put all this in a toolbox, so it can be shared between
# the plot and the fit functions
process = fitH5.processes(entry)[0]
positions = fitH5.get_result(entry, process, 'COM')
plots[0].addCurve(xAcqQX, yAcqQX, legend='measured')
plots[0].addXMarker(positions.qx[index], legend='center of mass')
plots[0].setGraphTitle('QX center of mass')
plots[1].addCurve(xAcqQY, yAcqQY, legend='measured')
plots[1].addXMarker(positions.qy[index], legend='center of mass')
plots[1].setGraphTitle('QY center of mass')
plots[2].addCurve(xAcqQZ, yAcqQZ, legend='measured')
plots[2].addXMarker(positions.qz[index], legend='center of mass')
plots[2].setGraphTitle('QZ center of mass')
if __name__ == '__main__':
pass
......@@ -31,6 +31,7 @@ __date__ = "01/06/2016"
__license__ = "MIT"
import numpy as np
import time
from scipy.optimize import leastsq
......@@ -50,52 +51,197 @@ class Fitter(object):
self._qy = qy
self._qz = qz
def fit(self, i_cube, qx_profile, qy_profile, qz_profile):
def fit(self, i_fit, i_cube, qx_profile, qy_profile, qz_profile):
raise NotImplementedError('Not implemented.')
class GaussianFitter(Fitter):
def ptime(self):
print(self.t_set, self.t_estim, self.t_fit, self.t_parse, self.t_write)
def __init__(self, *args, **kwargs):
super(GaussianFitter, self).__init__(*args, **kwargs)
self._z_0 = [1.0, self._qz.mean(), 1.0]
self._y_0 = [1.0, self._qy.mean(), 1.0]
self._x_0 = [1.0, self._qx.mean(), 1.0]
def fit(self, i_cube, qx_profile, qy_profile, qz_profile):
self._n_peaks = self._shared_results._n_peaks
self._z_0 = np.tile([1.0, self._qz.mean(), 1.0],
self._shared_results._n_peaks)
self._y_0 = np.tile([1.0, self._qy.mean(), 1.0],
self._shared_results._n_peaks)
self._x_0 = np.tile([1.0, self._qx.mean(), .1],
self._shared_results._n_peaks)
self.t_set = 0.
self.t_estim = 0.
self.t_fit = 0.
self.t_write = 0.
self.t_parse = 0.
def fit(self, i_fit, i_cube, qx_profile, qy_profile, qz_profile):
z_fit, success_z = gaussian_fit(self._qz, qz_profile, self._z_0)
y_fit, success_y = gaussian_fit(self._qy, qy_profile, self._y_0)
t0 = time.time()
x_fit, success_x = gaussian_fit(self._qx, qx_profile, self._x_0)
self.t_fit += time.time() - t0
self._shared_results.set_qz_results(i_fit, z_fit, success_z)
self._shared_results.set_qy_results(i_fit, y_fit, success_y)
t0 = time.time()
self._shared_results.set_qx_results(i_fit, x_fit, success_x)
self.t_write += time.time() - t0
class SilxFitter(Fitter):
p_types = ['A', 'P', 'F']
def __init__(self, *args, **kwargs):
from silx.math.fit import fittheories
from silx.math.fit.fitmanager import FitManager
super(SilxFitter, self).__init__(*args, **kwargs)
self._n_peaks = self._shared_results._n_peaks
self._fit = FitManager()
self._fit.loadtheories(fittheories)
self._fit.settheory('Area Gaussians')
self._results = np.zeros(3 * self._n_peaks)
def fit(self, i_fit, i_cube, qx_profile, qy_profile, qz_profile):
fit = self._fit
results = self._results
failed = False
fit.setdata(x=self._qx, y=qx_profile)
try:
fit.estimate()
fit.runfit()
except Exception as ex:
failed = True
results[:] = np.nan
if not failed:
for param in fit.fit_results:
p_name = param['name']