Commit beefaba9 authored by Damien Naudet's avatar Damien Naudet

Squashed :

Added factory for project items.
Added 3d roi selection:
parent 313cb43e
......@@ -30,58 +30,6 @@ __license__ = "MIT"
__date__ = "15/09/2016"
import os
import h5py
# from silx.gui.hdf5 import Hdf5TreeView
#
# from .model.ModelDef import ModelRoles
# from .project.ProjectDef import ProcessId
# # from .project.HybridItem import HybridItem
# from .process.RecipSpaceWidget import (RecipSpaceWidget,
# RecipSpaceWidgetEvent)
# from .view.RealSpaceViewWidget import (RealSpaceViewWidget,
# RealSpaceViewWidgetEvent)
# from .view.QspaceViewWidget import (QSpaceViewWidget,
# QSpaceViewWidgetEvent)
# from .project.QSpaceItem import QSpaceItem
#
#
# # TODO : something a bit more... flexible
# def viewWidgetFromProjectEvent(project, event):
# index = event.index
# processId = index.data(ModelRoles.XsocsProcessId)
# eventData = event.data
#
# widgetCls = None
# xsocsType = index.data(ModelRoles.XsocsNodeType)
# if xsocsType == h5py.ExternalLink:
# widget = Hdf5TreeView()
# widget.findHdf5TreeModel().appendFile(event.data)
# return widget
#
# if xsocsType == 'HybridItem':
# if eventData.evtType == 'scatter':
# plotData = HybridItem(project.filename,
# eventData.path).getScatter()
# elif eventData.evtType == 'image':
# plotData = HybridItem(project.filename,
# eventData.path).getImage()
# else:
# plotData = None
#
# if processId == ProcessId.Input:
# widgetCls = RealSpaceViewWidget
# elif processId == ProcessId.QSpace:
# widgetCls = QSpaceViewWidget
#
# if widgetCls is not None and plotData is not None:
# widget = widgetCls(index)
# widget.setPlotData(*plotData)
# return widget
#
# print('Nothing to DO')
# return None
# TODO : something better!
def nextFileName(root, template, cntMax=10000):
......@@ -92,38 +40,3 @@ def nextFileName(root, template, cntMax=10000):
return nextFile
else:
raise ValueError('No available file names.')
#
# # TODO : cache the widget to reuse previous parameters?
# def processWidgetFromViewEvent(project, event, parent=None):
# widget = None
# index = event.index
#
# if isinstance(event, RealSpaceViewWidgetEvent):
# xsocsPrefix = os.path.basename(project.xsocsFile).rpartition('.')[0]
# template = '{0}_qspace_{{0:>04}}.h5'.format(xsocsPrefix)
# output_f = nextFileName(project.workdir, template)
# widget = RecipSpaceWidget(parent=parent,
# index=index,
# data_h5f=project.xsocsFile,
# output_f=output_f,
# qspace_size=None,
# image_binning=None,
# rect_roi=event.data)
# return widget
#
#
# # TODO : rework this
# def processDoneEvent(project, event):
# eventData = event.data
#
# if isinstance(event, RecipSpaceWidgetEvent):
# qspaceH5 = eventData.qspaceH5
# prefix = os.path.basename(qspaceH5).rpartition('.')[0]
# processLevel = ProcessId.QSpace
# itemPath = 'Qspace/' + prefix
# item = QSpaceItem(project.filename,
# itemPath,
# processLevel=processLevel)
# item.qspaceFile = qspaceH5
# project.reload()
......@@ -74,6 +74,8 @@ class XsocsGui(Qt.QMainWindow):
super(XsocsGui, self).__init__(parent)
setH5NodeFactory(XsocsH5Factory)
self.move(0, 0)
self.resize(300, 600)
self.__intensityView = None
self.__qspaceViews = {}
......@@ -124,15 +126,22 @@ class XsocsGui(Qt.QMainWindow):
self.__intensityView = view = IntensityView(self,
model=node.model,
node=node)
screen = Qt.QApplication.desktop()
size = screen.availableGeometry(view).size()
size.scale(size.width() * 0.6,
size.height() * 0.6,
Qt.Qt.IgnoreAspectRatio)
view.resize(size)
view.sigProcessApplied.connect(self.__intensityRoiApplied)
view.show()
view.raise_()
def __intensityRoiApplied(self, event):
xsocsFile = os.path.basename(self.__project.xsocsFile)
xsocsPrefix = xsocsFile.rpartition('.')[0]
template = '{0}_qspace_{{0:>04}}.h5'.format(xsocsPrefix)
output_f = nextFileName(self.__project.workdir, template)
widget = RecipSpaceWidget(parent=self,
widget = RecipSpaceWidget(parent=self.sender(),
data_h5f=self.__project.xsocsFile,
output_f=output_f,
qspace_size=None,
......@@ -157,16 +166,26 @@ class XsocsGui(Qt.QMainWindow):
if not view:
view = QSpaceView(self, model=node.model, node=node)
self.__qspaceViews[node] = view
screen = Qt.QApplication.desktop()
size = screen.availableGeometry(view).size()
size.scale(size.width() * 0.6,
size.height() * 0.6,
Qt.Qt.IgnoreAspectRatio)
view.resize(size)
view.sigProcessApplied.connect(self.__qspaceRoiApplied)
view.show()
view.raise_()
def __qspaceRoiApplied(self, node):
def __qspaceRoiApplied(self, node, roi):
item = h5NodeToProjectItem(node)
xsocsFile = os.path.basename(self.__project.xsocsFile)
xsocsPrefix = xsocsFile.rpartition('.')[0]
template = '{0}_fit_{{0:>04}}.h5'.format(xsocsPrefix)
output_f = nextFileName(self.__project.workdir, template)
fitWidget = FitWidget(item.qspaceFile, output_f)
fitWidget = FitWidget(item.qspaceFile,
output_f,
roi,
parent=self.sender())
fitWidget.exec_()
if fitWidget.status == FitWidget.StatusCompleted:
fitFile = fitWidget.fitFile
......
......@@ -29,15 +29,44 @@ __authors__ = ["D. Naudet"]
__license__ = "MIT"
__date__ = "15/09/2016"
import os
from collections import OrderedDict
from silx.gui import qt as Qt
from ...io.QSpaceH5 import QSpaceH5
from ...io.FitH5 import FitH5Writer
from ..widgets.Containers import GroupBox
from ..widgets.Input import StyledLineEdit
from ..widgets.FileChooser import FileChooser
from ...process.peak_fit import peak_fit, FitTypes
class RangeWidget(Qt.QWidget):
def __init__(self, left, right, **kwargs):
super(RangeWidget, self).__init__(**kwargs)
layout = Qt.QGridLayout(self)
leftEdit = StyledLineEdit(nChar=5)
rightEdit = StyledLineEdit(nChar=5)
leftEdit.setReadOnly(True)
rightEdit.setReadOnly(True)
layout.addWidget(leftEdit, 0, 1)
layout.addWidget(rightEdit, 0, 2)
if left is None:
leftTxt = 'N/A'
else:
leftTxt = '{0:6g}'.format(left)
if right is None:
rightTxt = 'N/A'
else:
rightTxt = '{0:6g}'.format(right)
leftEdit.setText(leftTxt)
rightEdit.setText(rightTxt)
class FitWidget(Qt.QDialog):
sigProcessDone = Qt.Signal(object)
......@@ -50,10 +79,19 @@ class FitWidget(Qt.QDialog):
__sigConvertDone = Qt.Signal()
# TODO : pass the actual roi values
def __init__(self,
qspaceFile,
outputFile=None,
roiIndices=None,
**kwargs):
"""
:param qspaceFile:
:param outputFile:
:param roi: array of 3 ranges, or None to fit the whole volume.
:param kwargs:
"""
super(FitWidget, self).__init__(**kwargs)
self.__status = FitWidget.StatusInit
......@@ -63,8 +101,39 @@ class FitWidget(Qt.QDialog):
self.__selectedFile = None
self.__fitFile = None
with QSpaceH5(qspaceFile) as qspaceH5:
qx = self.__qx = qspaceH5.qx
qy = self.__qy = qspaceH5.qy
qz = self.__qz = qspaceH5.qz
if roiIndices is None:
roiIndices = [[0, qx.shape[0]], [0, qy.shape[0]], [0, qz.shape[0]]]
self.__roiIndices = None
else:
self.__roiIndices = roiIndices
# TODO : check validity
xRoi = qx[[roiIndices[0][0], roiIndices[0][1] - 1]]
yRoi = qy[[roiIndices[1][0], roiIndices[1][1] - 1]]
zRoi = qz[[roiIndices[2][0], roiIndices[2][1] - 1]]
layout = Qt.QGridLayout(self)
xRangeWid = RangeWidget(*xRoi)
yRangeWid = RangeWidget(*yRoi)
zRangeWid = RangeWidget(*zRoi)
roiGroup = GroupBox('Selection')
roiLayout = Qt.QGridLayout(roiGroup)
roiLayout.addWidget(Qt.QLabel('X'), 0, 0)
roiLayout.addWidget(xRangeWid, 0, 1)
roiLayout.addWidget(Qt.QLabel('Y'), 1, 0)
roiLayout.addWidget(yRangeWid, 1, 1)
roiLayout.addWidget(Qt.QLabel('Z'), 2, 0)
roiLayout.addWidget(zRangeWid, 2, 1)
layout.addWidget(roiGroup, 0, 0)
fitTypeLayout = Qt.QHBoxLayout()
fitTypeCb = Qt.QComboBox()
fitTypeCb.currentIndexChanged[str].connect(self.__fitTypeChanged)
......@@ -73,23 +142,23 @@ class FitWidget(Qt.QDialog):
self.__fitTypeChanged(fitTypeCb.currentText())
fitTypeLayout.addWidget(Qt.QLabel('Fit :'))
fitTypeLayout.addWidget(fitTypeCb)
layout.addLayout(fitTypeLayout, 0, 0, alignment=Qt.Qt.AlignLeft)
layout.addLayout(fitTypeLayout, 1, 0, alignment=Qt.Qt.AlignLeft)
fChooser = FileChooser(fileMode=Qt.QFileDialog.AnyFile)
fChooser.sigSelectionChanged.connect(self.__fileChanged)
fChooser.label.setText('Output')
if outputFile:
fChooser.lineEdit.setText(outputFile)
layout.addWidget(fChooser, 1, 0)
layout.addWidget(fChooser, 2, 0)
bn_box = Qt.QDialogButtonBox(Qt.QDialogButtonBox.Ok |
Qt.QDialogButtonBox.Cancel)
bn_box.button(Qt.QDialogButtonBox.Ok).setText('Apply')
bn_box.accepted.connect(self.__onAccept)
bn_box.rejected.connect(self.reject)
layout.addWidget(bn_box, 3, 0)
layout.addWidget(bn_box, 4, 0)
layout.setRowStretch(2, 1)
layout.setRowStretch(3, 1)
self.__bn_box = bn_box
self.__fileChanged(outputFile)
......@@ -111,7 +180,7 @@ class FitWidget(Qt.QDialog):
results = peak_fit(self.__qspaceFile,
fit_type=self.__fitType,
n_proc=1)
roiIndices=self.__roiIndices)
with FitH5Writer(self.__selectedFile, mode='w') as fitH5:
fitH5.set_x_fit(results.x_height,
results.x_center,
......
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2015-2016 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 os
import time
import shutil
......@@ -8,6 +39,7 @@ from collections import namedtuple
from ...util.id01_spec import Id01DataMerger
from ..Widgets import (AcqParamsWidget,
AdjustedPushButton)
from ..widgets.Containers import GroupBox
from silx.gui import qt as Qt
......@@ -265,7 +297,7 @@ class _MergeProcessDialog(Qt.QDialog):
label.setTextFormat(Qt.Qt.RichText)
layout.addWidget(label, stretch=0, alignment=Qt.Qt.AlignHCenter)
grp_box = Qt.QGroupBox('Output directory :')
grp_box = GroupBox('Output directory :')
grp_box.setLayout(Qt.QVBoxLayout())
outdir_edit = Qt.QLineEdit(output_dir)
fm = outdir_edit.fontMetrics()
......@@ -273,7 +305,7 @@ class _MergeProcessDialog(Qt.QDialog):
grp_box.layout().addWidget(outdir_edit)
layout.addWidget(grp_box, stretch=0)
grp_box = Qt.QGroupBox('Files :')
grp_box = GroupBox('Files :')
grp_box.setLayout(Qt.QVBoxLayout())
tree_widget = Qt.QTreeWidget()
tree_widget.setColumnCount(3)
......@@ -419,7 +451,7 @@ class MergeWidget(Qt.QDialog):
# input QGroupBox
# ################
input_gbx = Qt.QGroupBox("Input")
input_gbx = GroupBox("Input")
layout = Qt.QGridLayout(input_gbx)
self.layout().addWidget(input_gbx,
0, 0,
......@@ -504,7 +536,7 @@ class MergeWidget(Qt.QDialog):
# ################
# scans + edf QGroupBox
# ################
scans_gbx = Qt.QGroupBox("Spec + EDF")
scans_gbx = GroupBox("Spec + EDF")
grp_layout = Qt.QHBoxLayout(scans_gbx)
self.layout().addWidget(scans_gbx,
1, 0,
......@@ -610,7 +642,7 @@ class MergeWidget(Qt.QDialog):
# ################
# parameters
# ################
params_gbx = Qt.QGroupBox("Acq. Parameters")
params_gbx = GroupBox("Acq. Parameters")
grp_layout = Qt.QVBoxLayout(params_gbx)
acq_params_wid = AcqParamsWidget(highlight_change=False)
......@@ -623,7 +655,7 @@ class MergeWidget(Qt.QDialog):
# output options
# ################
output_gbx = Qt.QGroupBox("Output")
output_gbx = GroupBox("Output")
layout = Qt.QGridLayout(output_gbx)
self.layout().addWidget(output_gbx,
3, 0,
......
......@@ -34,7 +34,7 @@ from collections import namedtuple
from silx.gui import qt as Qt
# from .ProcessWidget import ProcessWidget, ProcessWidgetEvent
from ..widgets.Containers import GroupBox
from ..Widgets import (AcqParamsWidget,
AdjustedLineEdit,
AdjustedPushButton)
......@@ -193,7 +193,7 @@ class RecipSpaceWidget(Qt.QDialog):
# input QGroupBox
# ################
input_gbx = Qt.QGroupBox("Input")
input_gbx = GroupBox("Input")
layout = Qt.QHBoxLayout(input_gbx)
topLayout.addWidget(input_gbx,
0, 0,
......@@ -219,7 +219,7 @@ class RecipSpaceWidget(Qt.QDialog):
# ################
# Scans
# ################
scans_gbx = Qt.QGroupBox("Scans")
scans_gbx = GroupBox("Scans")
topLayout.addWidget(scans_gbx, 1, 1, 2, 1)
topLayout.setRowStretch(2, 1000)
......@@ -265,7 +265,7 @@ class RecipSpaceWidget(Qt.QDialog):
# ################
# Acq. parameters
# ################
params_gbx = Qt.QGroupBox("Acq. Parameters")
params_gbx = GroupBox("Acq. Parameters")
grp_layout = Qt.QVBoxLayout(params_gbx)
topLayout.addWidget(params_gbx,
1, 0, alignment=Qt.Qt.AlignTop)
......@@ -294,7 +294,7 @@ class RecipSpaceWidget(Qt.QDialog):
# conversion params
# ################
conv_gbx = Qt.QGroupBox("Conversion parameters")
conv_gbx = GroupBox("Conversion parameters")
grp_layout = Qt.QVBoxLayout(conv_gbx)
topLayout.addWidget(conv_gbx, 2, 0, alignment=Qt.Qt.AlignTop)
......@@ -305,7 +305,7 @@ class RecipSpaceWidget(Qt.QDialog):
# output
# ################
output_gbx = Qt.QGroupBox("Output")
output_gbx = GroupBox("Output")
layout = Qt.QHBoxLayout(output_gbx)
topLayout.addWidget(output_gbx, 3, 0, 1, 2)
lab = Qt.QLabel('File :')
......
......@@ -167,6 +167,10 @@ class H5Base(Node):
count = 0
self.__h5Count = count
@staticmethod
def factory(h5File, h5Path):
return _H5NodeFactory(h5File, h5Path)
def _loadChildren(self):
base = self.h5Path.rstrip('/')
with h5py.File(self.__h5File, mode='r') as h5f:
......@@ -177,7 +181,7 @@ class H5Base(Node):
for key in h5f[self.__h5Path].keys()]
except AttributeError:
paths = []
newChildren = [_H5NodeFactory(self.__h5File, path)
newChildren = [H5Base.factory(self.__h5File, path)
for path in paths]
newChildren = newChildren
self.__h5Count = None
......
......@@ -103,6 +103,7 @@ def _getIntensity(entry, entry_f, projectLock, projectFile, pathTpl, queue):
dsetPath = pathTpl.format(str(entry))
projectLock.acquire()
# WARNING, make sure the file isn't opened in write mode elsewhere!!!
IntensityItem(projectFile, dsetPath, mode='r+', data=cumul)
projectLock.release()
except:
......
......@@ -34,11 +34,46 @@ import weakref
import h5py
import numpy as np
from .ProjectDef import getItemClass
from ...io.XsocsH5Base import XsocsH5Base
from ...io.XsocsH5 import XsocsH5
def _getIInfo(h5File, h5Path):
attrs = dict([(key, value) for key, value in h5File[h5Path].attrs.items()])
itemClass = h5File.get(h5Path, getclass=True)
itemLink = h5File.get(h5Path, getclass=True, getlink=True)
return itemClass, itemLink, attrs
def setProjectItemFactory(factory):
global _projectItemFactory
if factory is None:
_projectItemFactory = projectItemFactory
else:
_projectItemFactory= factory
def projectItemFactory(h5File, h5Path, mode=None):
klass = None
with h5py.File(h5File, mode='r') as h5f:
attrs = h5f[h5Path].attrs
# For some reason attrs.get sometimes fails,
# using "in" seems a bit more robust.
if 'XsocsClass' in attrs:
xsocsClass = attrs['XsocsClass']
klass = getItemClass(xsocsClass)
del attrs
if not klass:
klass = ProjectItem
return klass(h5File, h5Path, mode=mode)
_projectItemFactory = projectItemFactory
class ProjectItem(XsocsH5Base):
InputPath = '/_input'
......@@ -78,6 +113,10 @@ class ProjectItem(XsocsH5Base):
processLevel)
self._createItem()
@staticmethod
def factory(h5File, h5Path):
return _projectItemFactory(h5File, h5Path)
def __setXsocsAttributes(self, xsocsClass, processLevel):
if self.mode not in ('r',):
with self._get_file() as h5f:
......@@ -92,12 +131,26 @@ class ProjectItem(XsocsH5Base):
item.attrs['XsocsLevel'] = processLevel
del item
def children(self):
"""
Returns this items direct children.
:return:
"""
children = []
with self._get_file() as h5f:
try:
keys = h5f[self.path].keys()
except AttributeError:
return []
pathTpl = self.path.rstrip('/') + '/{0}'
for key in keys:
child = self.factory(self.filename,
pathTpl.format(key.lstrip('/')))
children.append(child)
return children
def cast(self):
className = self.xsocsClass
klass = getItemClass(className)
if klass:
return klass(self.filename, self.path, mode=self.mode)
return self
return ProjectItem.factory(self.filename, self.path)
def projectRoot(self):
return ProjectItem(self.filename, '/', mode=self.mode).cast()
......@@ -161,6 +214,15 @@ class ProjectItem(XsocsH5Base):
instance = klass(h5File, groupPath)
return instance
@property
def processLevel(self):
with self._get_file() as h5f:
return h5f[self.__nodePath].attrs.get('XsocsLevel')
@property
def xsocsClass(self):
return self.attribute(self.path, 'XsocsClass')
def _createItem(self):
"""
Called when the xsocsh5 file is succesfuly called. This should be used
......@@ -176,12 +238,3 @@ class ProjectItem(XsocsH5Base):
:return:
"""
pass
@property
def processLevel(self):
with self._get_file() as h5f:
return h5f[self.__nodePath].attrs.get('XsocsLevel')
@property
def xsocsClass(self):
return self.attribute(self.path, 'XsocsClass')
\ No newline at end of file
......@@ -33,8 +33,6 @@ import os
import numpy as np
# from .ProjectDef import ProcessId
# from .HybridItem import HybridItem
from ...io.QSpaceH5 import QSpaceH5
from .ProjectItem import ProjectItem
from .ProjectDef import ItemClassDef
......@@ -50,6 +48,12 @@ class QSpaceGroup(ProjectItem):
item.qspaceFile = qspaceFile
return item
def getQspaceItems(self):
children = [child for child in self.children()
if isinstance(child, QSpaceItem)]
return children
@ItemClassDef('QSpaceItem')
class QSpaceItem(ProjectItem):
......
......@@ -34,7 +34,7 @@ from .Hdf5Nodes import H5Base, H5NodeFactory
from .ProjectItem import ProjectItem
def h5NodeToProjectItem(h5Node, mode='r+', cast=True):
def h5NodeToProjectItem(h5Node, mode='r', cast=True):
if not isinstance(h5Node, H5Base):
return None
item = ProjectItem(h5Node.h5File, nodePath=h5Node.h5Path, mode=mode)
......
This diff is collapsed.
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2015-2016 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,