Commit e71dc3fe authored by Damien Naudet's avatar Damien Naudet

Status is now at entry level.

Displaying status in the FitView.
parent 62cbb74b
......@@ -368,10 +368,6 @@ class FitWidget(Qt.QWidget):
zresult = results.results(process, param,
results.QZ_AXIS)
xstatus = results.qx_status(process)
ystatus = results.qy_status(process)
zstatus = results.qz_status(process)
fitH5.set_qx_result(entry,
process,
param,
......@@ -387,15 +383,19 @@ class FitWidget(Qt.QWidget):
param,
zresult)
fitH5.set_status(entry,
process,
FitH5QAxis.qx_axis,
xstatus)
fitH5.set_status(entry,
process,
FitH5QAxis.qy_axis,
ystatus)
fitH5.set_status(entry,
process,
FitH5QAxis.qz_axis,
zstatus)
xstatus = results.qx_status()
ystatus = results.qy_status()
zstatus = results.qz_status()
fitH5.set_status(entry,
FitH5QAxis.qx_axis,
xstatus)
fitH5.set_status(entry,
FitH5QAxis.qy_axis,
ystatus)
fitH5.set_status(entry,
FitH5QAxis.qz_axis,
zstatus)
if __name__ == '__main__':
pass
\ No newline at end of file
......@@ -86,7 +86,7 @@ class FitView(Qt.QMainWindow):
tree = self.__tree = TreeView()
tree.setModel(self.__model)
tree.setRootIndex(self.__model.index(0, 0, tree.rootIndex()))
# tree.setRootIndex(self.__model.index(0, 0, tree.rootIndex()))
tree.setSelectionBehavior(Qt.QAbstractItemView.SelectItems)
tree.header().setStretchLastSection(False)
tree.setShowUniqueGroup(True)
......@@ -193,6 +193,10 @@ class FitView(Qt.QMainWindow):
"""
self.__initPlots()
self.__startModel()
tree = self.__tree
root = self.__model.index(0, 0, tree.rootIndex())
tree.setRootIndex(self.__model.index(0, 0, root))
tree.expandAll()
def __startModel(self):
"""
......@@ -270,11 +274,11 @@ class FitView(Qt.QMainWindow):
# TODO : refactor
process = self.__process
if process == 'gaussian':
_plotLeastSq(self.__fitPlots, xIdx,
fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
_plotGaussian(self.__fitPlots, xIdx,
fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ)
elif process == 'centroid':
_plotCentroid(self.__fitPlots, xIdx,
......@@ -289,12 +293,12 @@ class FitView(Qt.QMainWindow):
# TODO : allow users to register plot functions associated with the kind
# of process results that are being displayed
def _plotLeastSq(plots, index, fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
def _plotGaussian(plots, index, fitH5,
entry, process,
xAcqQX, xAcqQY, xAcqQZ,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plots the "leastsq" fit results
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
......@@ -360,17 +364,17 @@ def _plotCentroid(plots, index, fitH5,
yAcqQX, yAcqQY, yAcqQZ):
"""
Plot the results from a "centroid" fit.
:param plots:
:param index:
:param fitH5:
:param entry:
:param process:
:param xAcqQX:
:param xAcqQY:
:param xAcqQZ:
:param yAcqQX:
:param yAcqQY:
:param yAcqQZ:
: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:
"""
......@@ -395,10 +399,10 @@ def _plotCentroid(plots, index, fitH5,
def _initLeastSq(plots, fitH5Name, entry, process):
"""
Sets up the plots when the interface is shown for the first time.
:param plots:
:param fitH5Name:
:param entry:
:param process:
:param plots: the plot widgets
:param fitH5Name: fitH5 file name
:param entry: name of the entry in the fitH5
:param process: name of the process in the fitH5
:return:
"""
# hard coded result name, this isn't satisfactory but I can't think
......@@ -424,10 +428,10 @@ def _initLeastSq(plots, fitH5Name, entry, process):
def _initCentroid(plots, fitH5Name, entry, process):
"""
Sets up the plots when the interface is shown for the first time.
:param plots:
:param fitH5Name:
:param entry:
:param process:
:param plots: the plot widgets
:param fitH5Name: fitH5 file name
:param entry: name of the entry in the fitH5
:param process: name of the process in the fitH5
:return:
"""
# hard coded result name, this isn't satisfactory but I can't think
......
......@@ -134,7 +134,8 @@ class ROIPlotIntensityMap(PlotIntensityMap):
qapp = Qt.QApplication.instance()
with self.__qspaceH5 as qspaceH5:
intensities = np.zeros((qspaceH5.qspace_sum.size,), dtype=np.float64)
intensities = np.zeros((qspaceH5.qspace_sum.size,),
dtype=np.float64)
progress.setRange(0, qspaceH5.qspace_sum.size - 1)
zslice, yslice, xslice = self.__roiSlices
......
......@@ -29,9 +29,12 @@ __authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
import numpy as np
from silx.gui import qt as Qt
from kmap.io.FitH5 import FitH5, FitH5QAxis
from ....process.fitresults import FitStatus
from ...widgets.XsocsPlot2D import XsocsPlot2D
......@@ -58,10 +61,16 @@ class DropPlotWidget(XsocsPlot2D):
stream = Qt.QDataStream(qByteArray, Qt.QIODevice.ReadOnly)
h5File = stream.readString()
entry = stream.readString()
process = stream.readString()
result = stream.readString()
q_axis = stream.readInt()
self.plotFitResult(h5File, entry, process, result, q_axis)
type = stream.readString()
if type == 'result':
process = stream.readString()
result = stream.readString()
self.plotFitResult(h5File, entry, process, result, q_axis)
elif type == 'status':
self.plotFitStatus(h5File, entry, q_axis)
def dragEnterEvent(self, event):
# super(DropWidget, self).dragEnterEvent(event)
......@@ -81,7 +90,20 @@ class DropPlotWidget(XsocsPlot2D):
scan_y = h5f.scan_y(entry)
self.__legend = self.setPlotData(scan_x, scan_y, data)
self.setGraphTitle(result + '/' + FitH5QAxis.axis_names[q_axis])
self.setGraphTitle(result + '[' + FitH5QAxis.axis_names[q_axis] + ']')
def plotFitStatus(self, fitH5Name, entry, q_axis):
with FitH5(fitH5Name) as h5f:
data = h5f.get_status(entry, q_axis)
errorPts = np.where(data != FitStatus.OK)[0]
if len(errorPts) == 0:
return
scan_x = h5f.scan_x(entry)[errorPts]
scan_y = h5f.scan_y(entry)[errorPts]
data = data[errorPts]
self.__legend = self.setPlotData(scan_x, scan_y, data)
self.setGraphTitle('Errors[qx]' + FitH5QAxis.axis_names[q_axis])
if __name__ == '__main__':
......
......@@ -29,15 +29,16 @@ __authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "01/01/2017"
import numpy as np
from silx.gui import qt as Qt
import numpy as np
from kmap.gui.model.Model import Model, RootNode
from kmap.gui.project.Hdf5Nodes import H5File
from kmap.gui.model.ModelDef import ModelRoles
from kmap.io.FitH5 import FitH5, FitH5QAxis
from ....process.peak_fit import FitStatus
from ...widgets.XsocsPlot2D import XsocsPlot2D
from ...project.Hdf5Nodes import H5Base, H5NodeClassDef
......@@ -103,8 +104,31 @@ class FitEntryNode(H5Base):
child = FitProcessNode(self.h5File, base + '/' + process)
children.append(child)
statusNode = FitStatusNode(self.h5File, base)
children.append(statusNode)
return children
def mimeData(self, column, stream):
# TODO : put column value in enum
if column == 1:
q_axis = FitH5QAxis.qx_axis
elif column == 2:
q_axis = FitH5QAxis.qy_axis
elif column == 3:
q_axis = FitH5QAxis.qz_axis
else:
raise ValueError('Unexpected column.')
h5file = self.h5File
entry = self.entry
stream.writeString(h5file)
stream.writeString(entry)
stream.writeInt(q_axis)
return True
class FitProcessNode(FitEntryNode):
"""
......@@ -117,16 +141,104 @@ class FitProcessNode(FitEntryNode):
entry = self.entry
process = self.process
children = []
print 'PATH', self.h5Path, self.entry, self.process
with FitH5(self.h5File, mode='r') as h5f:
results = h5f.get_result_names(entry, process)
for result in results:
child = FitResultNode(self.h5File, base + '/' + result)
child = FitResultNode(self.h5File,
base + '/' + result)
children.append(child)
return children
class FitStatusNode(FitEntryNode):
"""
Preview of the points where the fit has failed.
"""
def __init__(self, *args, **kwargs):
self.dragEnabledColumns = [False, True, True, True]
super(FitStatusNode, self).__init__(*args, **kwargs)
self.nodeName = 'Status'
self.__nErrors = [0, 0, 0]
def _setupNode(self):
width = 100
plot = PlotGrabber()
plot.setFixedSize(Qt.QSize(width, 100))
plot.toPixmap()
qApp = Qt.qApp
qApp.processEvents()
with FitH5(self.h5File) as fitH5:
x = fitH5.scan_x(self.entry)
y = fitH5.scan_y(self.entry)
status = fitH5.get_qx_status(self.entry)
errorPts = np.where(status != FitStatus.OK)[0]
self.__nErrors[0] = len(errorPts)
if len(errorPts) != 0:
plot.setPlotData(x[errorPts], y[errorPts], status[errorPts])
pixmap = plot.toPixmap()
else:
label = Qt.QLabel('No errors')
label.setFixedWidth(width)
label.setAlignment(Qt.Qt.AlignCenter)
label.setAttribute(Qt.Qt.WA_TranslucentBackground)
pixmap = Qt.QPixmap.grabWidget(label)
self.setData(1, pixmap, Qt.Qt.DecorationRole)
qApp.processEvents()
status = fitH5.get_qy_status(self.entry)
errorPts = np.where(status != FitStatus.OK)[0]
self.__nErrors[1] = len(errorPts)
if len(errorPts) != 0:
plot.setPlotData(x[errorPts], y[errorPts], status[errorPts])
pixmap = plot.toPixmap()
else:
label = Qt.QLabel('No errors')
label.setFixedWidth(width)
label.setAlignment(Qt.Qt.AlignCenter)
label.setAttribute(Qt.Qt.WA_TranslucentBackground)
pixmap = Qt.QPixmap.grabWidget(label)
self.setData(2, pixmap, Qt.Qt.DecorationRole)
qApp.processEvents()
status = fitH5.get_qz_status(self.entry)
errorPts = np.where(status != FitStatus.OK)[0]
self.__nErrors[2] = len(errorPts)
if len(errorPts) != 0:
plot.setPlotData(x[errorPts], y[errorPts], status[errorPts])
pixmap = plot.toPixmap()
else:
label = Qt.QLabel('No errors')
label.setFixedWidth(width)
label.setAlignment(Qt.Qt.AlignCenter)
label.setAttribute(Qt.Qt.WA_TranslucentBackground)
pixmap = Qt.QPixmap.grabWidget(label)
self.setData(3, pixmap, Qt.Qt.DecorationRole)
qApp.processEvents()
def _loadChildren(self):
return []
def mimeData(self, column, stream):
if column < 1 or column > 3:
return False
if self.__nErrors[column - 1] == 0:
return False
if not FitEntryNode.mimeData(self, column, stream):
return False
stream.writeString('status')
return True
class FitResultNode(FitProcessNode):
"""
Node linked to a result group in a FitH5 file.
......@@ -176,6 +288,18 @@ class FitResultNode(FitProcessNode):
def _loadChildren(self):
return []
def mimeData(self, column, stream):
if not FitProcessNode.mimeData(self, column, stream):
return False
process = self.process
result = self.result
stream.writeString('result')
stream.writeString(process)
stream.writeString(result)
return True
class FitRootNode(RootNode):
"""
......@@ -195,36 +319,19 @@ class FitModel(Model):
if len(indexes) > 1:
raise ValueError('Drag&Drop of more than one item is not'
'supported yet.')
mimeData = Qt.QMimeData()
index = indexes[0]
node = index.data(ModelRoles.InternalDataRole)
if not isinstance(node, FitResultNode):
if not isinstance(node, (FitResultNode, FitStatusNode)):
return super(Model, self).mimeData(indexes)
if index.column() == 1:
q_axis = FitH5QAxis.qx_axis
elif index.column() == 2:
q_axis = FitH5QAxis.qy_axis
elif index.column() == 3:
q_axis = FitH5QAxis.qz_axis
else:
raise ValueError('Unexpected column.')
h5file = node.h5File
entry = node.entry
process = node.process
result = node.result
data = Qt.QByteArray()
stream = Qt.QDataStream(data, Qt.QIODevice.WriteOnly)
stream.writeString(h5file)
stream.writeString(entry)
stream.writeString(process)
stream.writeString(result)
stream.writeInt(q_axis)
mimeData = Qt.QMimeData()
mimeData.setData('application/FitModel', data)
if node.mimeData(index.column(), stream):
mimeData.setData('application/FitModel', data)
return mimeData
......
......@@ -59,16 +59,13 @@ class FitH5(XsocsH5Base):
for that entry)
- all arrays are 1D.
"""
# _axis_values = range(3)
# qx_axis, qy_axis, qz_axis = _axis_values
# axis_names = ('qx', 'qy', 'qz')
title_path = '{entry}/title'
start_time_path = '{entry}/start_time'
end_time_path = '{entry}/end_time'
date_path = '{entry}/{process}/date'
qspace_axis_path = '{entry}/qspace_axis/{axis}'
status_path = '{entry}/{process}/status/{axis}'
status_path = '{entry}/status/{axis}'
configuration_path = '{entry}/{process}/configuration'
result_grp_path = '{entry}/{process}/results'
result_path = '{entry}/{process}/results/{result}/{axis}'
......@@ -126,7 +123,34 @@ class FitH5(XsocsH5Base):
with self._get_file() as h5_file:
return sorted(h5_file[results_path].keys())
def get_status(self, entry, process, axis):
def get_qx_status(self, entry):
"""
Returns the Qx fit status for the given entry/process.
:param entry:
:param process:
:return:
"""
return self.get_status(entry, FitH5QAxis.qx_axis)
def get_qy_status(self, entry):
"""
Returns the Qy fit status for the given entry/process.
:param entry:
:param process:
:return:
"""
return self.get_status(entry, FitH5QAxis.qy_axis)
def get_qz_status(self, entry):
"""
Returns the Qz fit status for the given entry/process.
:param entry:
:param process:
:return:
"""
return self.get_status(entry, FitH5QAxis.qz_axis)
def get_status(self, entry, axis):
"""
Returns the fit status for the given entry/process/axis
:param entry:
......@@ -137,9 +161,25 @@ class FitH5(XsocsH5Base):
"""
axis_name = FitH5QAxis.axis_name(axis)
status_path = FitH5.status_path.format(entry=entry,
process=process,
axis=axis_name)
return self._get_array_data(status_path)
status = self._get_array_data(status_path)
# 30 Jan 2017.
# This is kept for compatibility with previous versions
# TODO : remove this sometime...
if status is None:
# only one process was supported at the time
processes = self.processes(entry)
if len(processes) == 0:
return None
process = processes[0]
status_path = '{entry}/{process}/status/{axis}'
status = self._get_array_data(status_path.format(entry=entry,
process=process,
axis=axis_name))
return status
def scan_x(self, entry):
"""
......@@ -317,7 +357,6 @@ class FitH5(XsocsH5Base):
results[:, col_idx] = result
col_idx += 1
results[:, col_idx] = self.get_status(entry,
process,
axis)
col_idx += 1
......@@ -371,10 +410,9 @@ class FitH5Writer(FitH5):
def set_title(self, entry, title):
self._set_scalar_data(FitH5.title_path.format(entry), title)
def set_status(self, entry, process, axis, data):
def set_status(self, entry, axis, data):
axis_name = FitH5QAxis.axis_name(axis)
status_path = FitH5.status_path.format(entry=entry,
process=process,
axis=axis_name)
self._set_array_data(status_path, data)
......
......@@ -50,7 +50,7 @@ class XsocsH5Base(object):
# opening the file the first time
# (creating it if necessary)
# all subsequent access will use the mode 'r' or 'a'
with self._get_file() as h5_f:
with self._get_file():
pass
# setting the mode to append if mode was 'w' (so we don't erase it
......@@ -137,8 +137,7 @@ class XsocsH5Base(object):
if dtype:
return h5_file[path].dtype
return h5_file[path][:]
except KeyError as ex:
print ex
except KeyError:
return None
def _set_scalar_data(self, path, value):
......
......@@ -33,6 +33,16 @@ __license__ = "MIT"
from collections import OrderedDict
import numpy as np
class FitStatus(object):
"""
Enum for the fit status
Starting at 1 for compatibility reasons.
"""
OK, FAILED = range(1, 3)
class FitResult(object):
"""
......@@ -57,6 +67,12 @@ class FitResult(object):
self._processes = OrderedDict()
n_pts = len(sample_x)
self._status = OrderedDict([('qx_status', np.zeros(n_pts)),
('qy_status', np.zeros(n_pts)),
('qz_status', np.zeros(n_pts))])
def processes(self):
"""
Returns the process names
......@@ -67,36 +83,36 @@ class FitResult(object):
def params(self, process):
return self._get_process(process, create=False)['params'].keys()
def status(self, process, axis):
def status(self, axis):
"""
Returns the status for the given axis.
:param axis:
:return:
"""
assert axis in self._AXIS
process = self._get_process(process, create=False)
return self._status[self._AXIS_NAMES[axis]][:]
return process['status'][self._AXIS_NAMES[axis]]
def qx_status(self, process):
def qx_status(self):
"""
Returns qx fit status the given process
:param process:
Returns qx fit status
:return:
"""
return self.status(process, self.QX_AXIS)
return self.status(self.QX_AXIS)
def qy_status(self, process):
def qy_status(self):
"""
Returns qy fit status the given process
:param process:
Returns qy fit status
:return:
"""
return self.status(process, self.QY_AXIS)
return self.status(self.QY_AXIS)
def qz_status(self, process):
def qz_status(self):
"""
Returns qz fit status the given process
:param process:
Returns qz fit status
:return:
"""
return self.status(process, self.QZ_AXIS)
return self.status(self.QZ_AXIS)
def results(self, process, param, axis=None):
"""
......@@ -156,33 +172,26 @@ class FitResult(object):
param_data = self._get_param(process, param)
param_data[self._AXIS_NAMES[axis]] = result
def set_qx_status(self, process, status):
self._set_axis_status(process, self.QX_AXIS, status)
def set_qx_status(self, status):
self._set_axis_status(self.QX_AXIS, status)
def set_qy_status(self, process, status):
self._set_axis_status(process, self.QY_AXIS, status)
def set_qy_status(self, status):
self._set_axis_status(self.QY_AXIS, status)
def set_qz_status(self, process, status):
self._set_axis_status(process, self.QZ_AXIS, status)
def set_qz_status(self, status):
self._set_axis_status(self.QZ_AXIS, status)
def _set_axis_status(self, process, axis, status):
def _set_axis_status(self, axis, status):
assert axis in self._AXIS
_process = self._get_process(process)
statuses = _process['status']
statuses[self._AXIS_NAMES[axis]] = status
self._status[self._AXIS_NAMES[axis]] = status
def _get_process(self, process, create=True):
if process not in self._processes:
if not create:
raise KeyError('Unknown process {0}.'.format(process))
status = OrderedDict([('qx_status', None),
('qy_status', None),
('qz_status', None)])
_process = OrderedDict([('params', OrderedDict()),
('status', status)])
_process = OrderedDict([('params', OrderedDict())])
self._processes[process] = _process
else:
_process = self._processes[process]
......
......@@ -42,6 +42,7 @@ import numpy as np
from ..io import QSpaceH5
from .fit_funcs import gaussian_fit, centroid
from .sharedresults import FitTypes, GaussianResults, CentroidResults
from .fitresults import FitStatus
disp_times = False
......@@ -265,7 +266,9 @@ class PeakFitter(Thread):
res_list = []
for th_idx in range(n_proc):
arg_list = (th_idx, roi_indices)
res = pool.apply_async(_fit_process, args=arg_list, callback=callback)
res = pool.apply_async(_fit_process,
args=arg_list,
callback=callback)
res_list.append(res)
# sending the image indices
......@@ -435,9 +438,9 @@ def _fit_process(th_idx, roiIndices=None):
t0 = time.time()
success_x = True
success_y = True
success_z = True
success_x = FitStatus.OK
success_y = FitStatus.OK
success_z = FitStatus.OK
z_sum = cube.sum(axis=0).sum(axis=0)
......@@ -449,10 +452,10 @@ def _fit_process(th_idx, roiIndices=None):
fit_z = fit_fn(q_z, z_sum, z_0)
z_0 = fit_z
except Exception as ex:
print('Z Failed', ex)
# print('Z Failed', ex)
z_0 = None
fit_z = [np.nan, np.nan, np.nan]
success_z = False
success_z = FitStatus.FAILED
l_shared_res.set_qz_results(i_cube, fit_z, success_z)
......@@ -470,10 +473,10 @@ def _fit_process(th_idx, roiIndices=None):
fit_y = fit_fn(q_y, y_sum, y_0)
y_0 = fit_y