Commit 64a74a10 authored by Payno's avatar Payno

[add some steps on the ROI selection]

parent 305f5e93
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-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.
#
# ###########################################################################*/
__authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "01/10/2018"
try:
from fabio.fabio import file_series
except ImportError:
from id06workflow.third_party.fabio.file_serie import FileSeries
import numpy
class Dataset(object):
"""
Class used to define a dataset
"""
def __init__(self, data_files=None, dark_files=None, ff_files=None):
assert isinstance(data_files, (type(None), list, str, set))
assert isinstance(dark_files, (type(None), list, str, set))
assert isinstance(ff_files, (type(None), list, str, set))
self._data_files = [] if data_files is None else data_files
"""data files"""
self._data = None
"""data"""
self._dark_files = [] if dark_files is None else dark_files
"""dark files"""
self._ff_files = [] if ff_files is None else ff_files
"""flat field files"""
self.__data_has_changed = data_files is not None
"""Flag to avoid loading data from files every time"""
self.__dark_has_changed = dark_files is not None
"""Flag to avoid loading darks from files every time"""
self.__ff_has_changed = ff_files is not None
"""Flag to avoid loading flat field from files every time"""
self._roi = None
"""Region Of Interest if any defined"""
self.__data = None
self.__darks = None
self.__ff = None
@property
def data_files(self):
return self._data_files
@data_files.setter
def data_files(self, data_files):
self._data_files = data_files
self.__data_has_changed = True
def addDataFile(self, data_file):
self._data_files.append(data_file)
self.__data_has_changed = True
@property
def dark_files(self):
return self._dark_files
@dark_files.setter
def dark_files(self, dark_files):
self._dark_files = dark_files
self.__dark_has_changed = True
def addDarkFile(self, dark_file):
self._dark_files.append(dark_file)
self.__dark_has_changed = True
@property
def flat_fields_files(self):
return self._ff_files
@flat_fields_files.setter
def flat_fields_files(self, flat_fields_files):
self._ff_files = flat_fields_files
self.__ff_has_changed = True
def addFlatFieldFile(self, flat_field_file):
self._ff_files.append(flat_field_file)
self.__ff_has_changed = True
def applyShiftCorrection(self, correction):
raise NotImplementedError('')
def is_valid(self):
return len(self._data_files) > 0 or self._data is not None
def __eq__(self, other):
if isinstance(other, Dataset) is False:
return False
return (sorted(list(self.data_files)) == sorted(list(other.data_files)) and
sorted(list(self.dark_files)) == sorted(list(other.dark_files)) and
sorted(list(self.flat_fields_files)) == sorted(list(other.flat_fields_files)))
def __str__(self):
return "\n".join(
("data_files: " + (str(self.data_files) or ""),
"dark_files: " + (str(self.dark_files) or ""),
"flat field images: " + (str(self.flat_fields_files) or "")))
def getBackground(self):
raise NotImplementedError()
def getData(self):
if self.__data_has_changed is True:
self.__data = self._loadFiles(self.data_files)
self.__data_has_changed = False
return self.__data
def _loadFiles(self, files_list):
# TODO: add information if the number of frame is fixed...
series = FileSeries(files_list)
framelist = []
for frame in series.iterframes():
framelist.append(frame.data)
return numpy.asarray(framelist)
......@@ -89,6 +89,9 @@ class TwoThetaGeometry(GeometryBase):
assert orientation.lower() in self._ALIASES
self._orientation = self._ALIASES[orientation]
def is_valid(self):
return True
def __eq__(self, other):
if isinstance(other, TwoThetaGeometry) is False:
return False
......@@ -97,3 +100,12 @@ class TwoThetaGeometry(GeometryBase):
self.xpixelsize == other.xpixelsize and
self.ypixelsize == other.ypixelsize and
self.orientation == other.orientation)
def __str__(self):
return "\n".join((
'2' + DELTA_CHAR +': ' + str(self.twotheta),
'xmag: ' + str(self.xmag),
'xpixelsize: ' + str(self.xpixelsize),
'ypixelsize: ' + str(self.ypixelsize),
'orientation: ' + self.orientation,
))
......@@ -55,3 +55,6 @@ class GeometryBase(object):
@ypixelsize.setter
def ypixelsize(self, value):
self._ypixelsize = value
def is_valid(self):
raise NotImplementedError('Base class')
......@@ -28,74 +28,68 @@ __authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "01/10/2018"
from id06workflow.core.Dataset import Dataset
from id06workflow.core.geometry import GeometryBase
class Dataset(object):
class Experiment(object):
"""
Class used to define a dataset
Define all component of an experiment:
- :class:`Dataset`
- :class:`Geometry`
"""
def __init__(self, data_files=None, dark_files=None, ff_files=None):
assert isinstance(data_files, (type(None), list, str, set))
assert isinstance(dark_files, (type(None), list, str, set))
assert isinstance(ff_files, (type(None), list, str, set))
self._data_files = [] if data_files is None else data_files
"""data files"""
self._data = None
"""data"""
self._dark_files = [] if dark_files is None else dark_files
"""dark files"""
self._ff_files = [] if ff_files is None else ff_files
"""flat field files"""
self._roi = None
"""Region Of Interest if any defined"""
def __init__(self, dataset=None, geometry=None):
self.dataset = dataset
self.geometry = geometry
self.roi = None
self.__data = None
"""current data. The one updated with treatment, roi...
Which is different from the raw data contained in the dataset"""
@property
def data_files(self):
return self._data_files
@data_files.setter
def data_files(self, data_files):
self.data_files = data_files
def dataset(self):
return self._dataset
def addDataFile(self, data_file):
self._data_files.append(data_file)
@dataset.setter
def dataset(self, dataset):
assert dataset is None or isinstance(dataset, Dataset)
self._dataset = dataset
@property
def dark_files(self):
return self._dark_files
@dark_files.setter
def dark_files(self, dark_files):
self._dark_files = dark_files
def geometry(self):
return self._geometry
def addDarkFile(self, dark_file):
self._dark_files.append(dark_file)
@dataset.setter
def geometry(self, geometry):
assert geometry is None or isinstance(geometry, GeometryBase)
self._geometry = geometry
@property
def flat_fields_files(self):
return self._ff_files
@flat_fields_files.setter
def flat_fields_files(self, flat_fields_files):
self._ff_files = flat_fields_files
def addFlatFieldFile(self, flat_field_file):
self._ff_files.append(flat_field_file)
def roi(self):
return self._roi
@roi.setter
def roi(self, roi):
assert isinstance(roi, (type(None), tuple, list))
self._roi = roi
def getRawData(self):
# TODO: cache should probably be used in the future to deal wuth data
if self.dataset is None:
return None
rawdata = self.dataset.getData()
if self._roi is not None:
return rawdata[self._roi]
else:
return rawdata
def applyShiftCorrection(self, correction):
raise NotImplementedError('')
def is_valid(self):
return len(self._data_files) > 0 or self._data is not None
def __eq__(self, other):
if isinstance(other, Dataset) is False:
return False
return (sorted(list(self.data_files)) == sorted(list(other.data_files)) and
sorted(list(self.dark_files)) == sorted(list(other.dark_files)) and
sorted(list(self.flat_fields_files)) == sorted(list(other.flat_fields_files)))
def __str__(self):
return "\n".join(
("data_files: " + (str(self.data_files) or ""),
"dark_files: " + (str(self.dark_files) or ""),
"flat field images: " + (str(self.flat_fields_files) or "")))
@property
def data(self):
if self.__data is None:
self.__data = self.getRawData()
return self.__data
@data.setter
def data(self, data):
self.__data = data
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-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.
#
# ###########################################################################*/
__authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "05/10/2018"
from silx.gui import qt
from silx.gui.plot.StackView import StackViewMainWindow
class NoiseRemovalWidget(qt.QWidget):
"""
"""
def __init__(self, parent):
qt.QTabWidget.__init__(self, parent)
# Here a lot of better stuff should be done, just two plots and
# synchronized sliders
self.setLayout(qt.QHBoxLayout())
self._rawData = StackViewMainWindow(parent=self)
self.layout().addWidget(self._rawData)
self._treatedData = StackViewMainWindow(parent=self)
self.layout().addWidget(self._treatedData)
# TODO: synchronized data
def getParameters(self):
raise NotImplementedError()
# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2016-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.
#
# ###########################################################################*/
__authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "04/10/2018"
from silx.gui import qt
from silx.gui.plot.tools.roi import RegionOfInterestManager
from silx.gui.plot.tools.roi import RegionOfInterestTableWidget
from silx.gui.plot.items.roi import RectangleROI
from silx.gui.plot.StackView import StackViewMainWindow
import numpy
class ROIOnStackView(qt.QWidget):
def __init__(self, parent):
qt.QWidget.__init__(self, parent)
self.setLayout(qt.QVBoxLayout())
# TODO: if data starts to be quiet large then we can load only part of
# if. There is some code to do this on tomwer.
self._stack = StackViewMainWindow()
self.layout().addWidget(self._stack)
self._plot = self._stack.getPlot()
# Create the object controlling the ROIs and set it up
self._roiManager = RegionOfInterestManager(self._plot)
self._roiManager.setColor('red') # Set the color of ROI
self._roiManager.sigRoiAdded.connect(self.updateAddedRegionOfInterest)
# Add a rectangular region of interest
self._roi = RectangleROI()
self._roi.setGeometry(origin=(0, 0), size=(2, 2))
self._roi.setLabel('Initial ROI')
self._roiManager.addRoi(self._roi)
# Create the table widget displaying
self._roiTable = RegionOfInterestTableWidget()
self._roiTable.setRegionOfInterestManager(self._roiManager)
# Add the region of interest table and the buttons to a dock widget
widget = qt.QWidget()
layout = qt.QVBoxLayout()
widget.setLayout(layout)
# layout.addWidget(roiToolbar)
layout.addWidget(self._roiTable)
def roiDockVisibilityChanged(visible):
"""Handle change of visibility of the roi dock widget
If dock becomes hidden, ROI interaction is stopped.
"""
if not visible:
self._roiManager.stop()
dock = qt.QDockWidget('Image ROI')
dock.setWidget(widget)
dock.visibilityChanged.connect(roiDockVisibilityChanged)
# expose some API
self.getStack = self._stack.getStack
# Set the name of each created region of interest
def updateAddedRegionOfInterest(self, roi):
"""Called for each added region of interest: set the name"""
if roi.getLabel() == '':
roi.setLabel('ROI %d' % len(self._roiManager.getRois()))
def setStack(self, stack, resetroi=False):
assert stack is not None
self._stack.setStack(stack)
if resetroi is True:
self._roi.setGeometry(origin=(0, 0),
size=stack.shape[-2:])
else:
self._clipROI()
def setROI(self, origin, size):
self._roi.setGeometry(origin=origin, size=size)
self._clipROI()
def _clipROI(self):
"""clip current roi"""
assert type(self._roi) is RectangleROI
# clip roi on the current stack
st = self.getStack(copy=False, returnNumpyArray=True)
if st is None:
return
else:
currentStack, params = st
# TODO to test and probably to redo
origin = (numpy.clip(self._roi.getOrigin()[0], 0, currentStack.shape[-1]),
numpy.clip(self._roi.getOrigin()[1], 0, currentStack.shape[-2]))
size = (numpy.clip(self._roi.getSize()[0], 0, currentStack.shape[-1]),
numpy.clip(self._roi.getSize()[0], 0, currentStack.shape[-1]))
self._roi.setGeometry(origin=origin, size=size)
This diff is collapsed.
......@@ -74,7 +74,7 @@ class TestFirstSetup(OrangeWorflowTest):
self._geometryWidget = self.getWidgetForNode(geometryNode)
self._roiSelectionWidget = self.getWidgetForNode(roiSelectionNode)
self._noiseRemovalWidget = self.getWidgetForNode(noiseRemovalNode)
self._shiftCorrectionwidget = self.getWidgetForNode(shiftCorrectionNode)
self._shiftCorrectionWidget = self.getWidgetForNode(shiftCorrectionNode)
self._dataset = utils.createRandomDataset(_dir=tempfile.mkdtemp(),
dims=(100, 100),
......@@ -108,6 +108,15 @@ class TestFirstSetup(OrangeWorflowTest):
app.processEvents()
self.assertTrue(self._geometryWidget.getSetupGeometry() == self._geometry)
self._selectionWidget._process()
app.processEvents()
self._roiSelectionWidget.setROI(origin=(10, 10), size=(20, 35))
app.processEvents()
self._roiSelectionWidget.validate()
app.processEvents()
self._roiSelectionWidget.hide()
app.exec_()
def suite():
test_suite = unittest.TestSuite()
......
......@@ -32,7 +32,7 @@ from Orange.widgets import gui
from Orange.widgets.widget import OWWidget
from Orange.canvas.registry.description import OutputSignal
from id06workflow.gui.datasetselection import DatasetSelection
from id06workflow.core.types import Dataset
from id06workflow.core.types import Experiment
from silx.gui import qt
import logging
_logger = logging.getLogger(__file__)
......@@ -50,7 +50,7 @@ class DataSelectionOW(OWWidget):
category = "esrfWidgets"
keywords = ["dataset", "data", "selection"]
outputs = [OutputSignal(name="data", type=Dataset)]
outputs = [OutputSignal(name="data", type=Experiment)]
want_main_area = True
resizing_enabled = True
......@@ -86,7 +86,7 @@ class DataSelectionOW(OWWidget):
_logger.warning('Defined dataset is invalid. Can\'t process')
else:
_logger.info('Dataset definition is valid, processing to next boxes')
self.send("data", dataset)
self.send("data", Experiment(dataset=dataset))
def sizeHint(self):
return qt.QSize(400, 400)
......@@ -33,7 +33,7 @@ from Orange.widgets import gui
from Orange.widgets.widget import OWWidget
from Orange.canvas.registry.description import InputSignal, OutputSignal
from id06workflow.gui.geometry import GeometryMainWidget
from id06workflow.core.types import Dataset
from id06workflow.core.types import Experiment
class GeometryOW(OWWidget):
......@@ -50,8 +50,8 @@ class GeometryOW(OWWidget):
category = "esrfWidgets"
keywords = ["dataset", "calibration", "motor", "angle", "geometry"]
inputs = [InputSignal(name="data", type=Dataset, handler='_process')]
outputs = [OutputSignal(name="data", type=Dataset)]
inputs = [InputSignal(name="data", type=Experiment, handler='_process')]
outputs = [OutputSignal(name="data", type=Experiment)]
want_main_area = True
resizing_enabled = True
......@@ -75,10 +75,10 @@ class GeometryOW(OWWidget):
self.setSetupGeometry = self._widget.setSetupGeometry
self.getSetupGeometry = self._widget.getSetupGeometry
def _process(self, dataset):
geometry = self._widget.getGeometry()
def _process(self, experiment):
geometry = self._widget.getSetupGeometry()
if geometry.is_valid() is True:
dataset.setGeometry(geometry)
self.send("data", dataset)
experiment.geometry = geometry
self.send("data", experiment)
else:
self.show()
<
......@@ -28,9 +28,14 @@ __license__ = "MIT"
__date__ = "01/10/2018"
from silx.gui import qt
from Orange.widgets import gui
from Orange.widgets.widget import OWWidget
from Orange.canvas.registry.description import InputSignal, OutputSignal
from id06workflow.core.types import Dataset
from id06workflow.core.types import Experiment
from id06workflow.gui.noiseremoval import NoiseRemovalWidget
import logging
_logger = logging.getLogger(__file__)
class NoiseRemovalOW(OWWidget):
"""
......@@ -44,8 +49,8 @@ class NoiseRemovalOW(OWWidget):
category = "esrfWidgets"
keywords = ["noise", "removal", "dataset", "filter"]
inputs = [InputSignal(name="data", handler='process', type=Dataset)]
outputs = [OutputSignal(name="data", type=Dataset)]
inputs = [InputSignal(name="data", handler='_process', type=Experiment)]
outputs = [OutputSignal(name="data", type=Experiment)]
want_main_area = True
resizing_enabled = True
......@@ -54,10 +59,33 @@ class NoiseRemovalOW(OWWidget):
def __init__(self):
super().__init__()
label = qt.QLabel("shift correction")
self.controlArea.layout().addWidget(label)
self._widget = NoiseRemovalWidget(parent=self)
layout = gui.vBox(self.mainArea, 'noise removal').layout()
layout.addWidget(self._widget)
# buttons
types = qt.QDialogButtonBox.Ok
self._buttons = qt.QDialogButtonBox(parent=self)
self._buttons.setStandardButtons(types)
layout.addWidget(self._buttons)
self._buttons.accepted.connect(self._process)
self._buttons.hide()
self.accepted.connect(self.validate)
def _process(self, experiment):
if experiment is None:
return
assert isinstance(experiment, Experiment)
def process(self, dataset):
assert isinstance(dataset, Dataset)
raise NotImplementedError()
if experiment.getRawData() is None:
_logger.warning('dataset is empty, won\'t apply noise removal')
else:
self._widget.setExperiment(experiment.getData())
self._buttons.show()
self.show()
def validate(self):