process.py 12.5 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 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.
#
# ###########################################################################*/
"""module for process base class"""

__authors__ = ["H. Payno"]
__license__ = "MIT"
__date__ = "07/08/2019"


from .progress import Progress
payno's avatar
payno committed
33
from est.core.types import XASObject
34
from ..utils import extract_properties_from_dict
Henri Payno's avatar
Henri Payno committed
35
import logging
36
from collections import namedtuple
37
from ewokscore.taskwithprogress import TaskWithProgress as Task
payno's avatar
payno committed
38
from typing import Iterable
payno's avatar
payno committed
39

Henri Payno's avatar
Henri Payno committed
40
41
_logger = logging.getLogger(__name__)

42

43
44
45
46
_input_desc = namedtuple("_input_desc", ["name", "type", "handler", "doc"])

_output_desc = namedtuple("_output_desc", ["name", "type", "doc"])

47

48
49
class _NexusSpectrumDef:
    """Util function to define a Nexus plot"""
payno's avatar
payno committed
50

51
52
53
54
55
56
57
58
59
    def __init__(
        self,
        signal,
        axes,
        auxiliary_signals,
        silx_style=None,
        title=None,
        title_latex=None,
    ):
60
61
62
        self.__signal = None
        self.__axes = None
        self.__auxiliary_signals = None
63
64
        self.__title = None
        self.__title_latex = None
payno's avatar
payno committed
65
        self.__silx_style = silx_style
66
67
68
69

        self.signal = signal
        self.axes = axes
        self.auxiliary_signals = auxiliary_signals
70
        self.title = title
payno's avatar
payno committed
71
        self.title_latex = title_latex
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

    @property
    def signal(self):
        return self.__signal

    @signal.setter
    def signal(self, signal):
        if not isinstance(signal, str):
            raise TypeError("signal should be an instance of str")
        else:
            self.__signal = signal

    @property
    def axes(self):
        return self.__axes

    @axes.setter
    def axes(self, axes):
        if not isinstance(axes, tuple):
            raise TypeError("axes should be an instance of tuple")
        else:
            self.__axes = axes

    @property
    def auxiliary_signals(self):
        return self.__auxiliary_signals

    @auxiliary_signals.setter
    def auxiliary_signals(self, auxiliary_signals):
        if not isinstance(auxiliary_signals, (tuple, type(None))):
            raise TypeError("auxiliary_signals should be an instance of tuple")
        else:
            self.__auxiliary_signals = auxiliary_signals

payno's avatar
payno committed
106
107
108
109
110
111
112
    @property
    def silx_style(self):
        if self.__silx_style is None:
            return {}
        else:
            return self.__silx_style

113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    @property
    def title(self):
        return self.__title

    @title.setter
    def title(self, title):
        if not isinstance(title, (str, type(None))):
            raise TypeError("`title` should be an instance of str or None")
        else:
            self.__title = title

    @property
    def title_latex(self):
        return self.__title_latex

payno's avatar
payno committed
128
129
130
131
132
133
134
    @title_latex.setter
    def title_latex(self, title):
        if not isinstance(title, (str, type(None))):
            raise TypeError("`title` should be an instance of str or None")
        else:
            self.__title_latex = title

135

136
137
138
class _NexusDatasetDef:
    """Util function to define a Nexus plot"""

139
    def __init__(self, name: str, units=None, units_latex=None):
140
141
        self.__name = None
        self.__units = None
142
        self.__units_latex = None
143
144
145
146
147

        assert isinstance(name, str)

        self.name = name
        self.units = units
148
        self.units_latex = units_latex
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171

    @property
    def name(self):
        return self.__name

    @name.setter
    def name(self, name):
        if not isinstance(name, str):
            raise TypeError("name should be an instance of str")
        else:
            self.__name = name

    @property
    def units(self):
        return self.__units

    @units.setter
    def units(self, units):
        if not isinstance(units, (str, type(None))):
            raise TypeError("units should be an instance of str")
        else:
            self.__units = units

172
173
174
175
176
177
178
179
180
181
182
    @property
    def units_latex(self):
        return self.__units_latex

    @units_latex.setter
    def units_latex(self, units):
        if not isinstance(units, (str, type(None))):
            raise TypeError("units should be an instance of str")
        else:
            self.__units_latex = units

183
184
185
186
187
    @property
    def attrs(self):
        attrs = {}
        if self.units is not None:
            attrs.update({"units": self.units})
188
189
        if self.units_latex is not None:
            attrs.update({"units_latex": self.units_latex})
190
191
192
193
194
195
        return attrs

    def __str__(self):
        return self.name


196
197
198
class Process(Task, register=False):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
199
        self._advancement = Progress(name=self.name)
200
201
        self.__stop = False
        """flag to notice when a end of process is required"""
202
203
        self._settings = {}
        # configuration
payno's avatar
payno committed
204
        self._callbacks = []
205

206
207
208
209
    def __init_subclass__(subclass, name="", **kwargs):
        super().__init_subclass__(**kwargs)
        subclass._NAME = name

210
    @property
payno's avatar
payno committed
211
    def name(self) -> str:
212
        return self._NAME
213
214
215

    def stop(self):
        self.__stop = True
216

payno's avatar
payno committed
217
218
219
220
221
222
223
224
225
226
227
228
229
    @property
    def advancement(self):
        return self._advancement

    @advancement.setter
    def advancement(self, advancement):
        assert isinstance(advancement, Progress)
        self._advancement = advancement

    @property
    def callbacks(self):
        return self._callbacks

230
    @staticmethod
payno's avatar
payno committed
231
    def getXasObject(xas_obj) -> XASObject:
232
        if isinstance(xas_obj, dict):
233
            _xas_obj = XASObject.from_dict(xas_obj)
234
        else:
235
236
237
            _xas_obj = xas_obj
        assert isinstance(_xas_obj, XASObject)
        if _xas_obj.n_spectrum > 0:
payno's avatar
payno committed
238
            _xas_obj.spectra.check_validity()
239
240
        assert isinstance(_xas_obj, XASObject)
        return _xas_obj
241

242
243
    @staticmethod
    def program_name():
244
245
        raise NotImplementedError("Base class")

246
247
    @staticmethod
    def program_version() -> str:
248
        """version of the program used for this processing"""
payno's avatar
payno committed
249
        raise NotImplementedError("Base class")
250

251
    @staticmethod
payno's avatar
payno committed
252
    def definition(self) -> str:
253
        """definition of the process"""
payno's avatar
payno committed
254
        raise NotImplementedError("Base class")
255

payno's avatar
payno committed
256
    def getConfiguration(self) -> dict:
257
258
259
260
261
        """

        :return: configuration of the process
        :rtype: dict
        """
262
263
264
265
266
        if len(self._settings) > 0:
            return self._settings
        else:
            return None

payno's avatar
payno committed
267
    def setConfiguration(self, configuration: dict):
268
269
270
271
272
273
274
275
276
277
        # filter configuration from orange widgets
        if "__version__" in configuration:
            del configuration["__version__"]
        if "savedWidgetGeometry" in configuration:
            del configuration["savedWidgetGeometry"]
        if "savedWidgetGeometry" in configuration:
            del configuration["savedWidgetGeometry"]
        if "controlAreaVisible" in configuration:
            del configuration["controlAreaVisible"]

278
279
        self._settings = configuration

payno's avatar
payno committed
280
281
282
    def register_process(
        self, xas_obj: XASObject, data_keys: Iterable, plots: Iterable = tuple()
    ):
283
284
285
        """
        Store the current process in the linked h5 file if any,
        output data stored will be the one defined by the data_keys
286
287
288

        :param xas_obj: object for which we want to save the treatment
        :type: :class:`.XASObject`
289
        :param tuple data_keys: keys of the id to save
290
        :param plots:
291
292
293
        """
        if xas_obj.has_linked_file():
            _data = {}
294
295
296
            for data_info in data_keys:
                # data_info can be a string or an instance of _NexusDatasetDef
                key = str(data_info)
297
                relative_to = None
payno's avatar
payno committed
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
                use = "map_to"
                if key in (
                    "flat",
                    "fpp",
                    "f2",
                    "dmude",
                    "norm",
                    "norm_area",
                    "post_edge",
                    "bkg",
                    "energy",
                    "mback_mu",
                    "norm_mback",
                    "Energy",
                    "Mu",
313
                    "mu",
payno's avatar
payno committed
314
315
316
317
318
                    "NormalizedEnergy",
                    "NormalizedMu",
                    "NormalizedSignal",
                    "EXAFSKValues",
                    "EXAFSSignal",
payno's avatar
payno committed
319
320
321
322
                    "mu_ref",
                    "I0",
                    "I1",
                    "I2",
payno's avatar
payno committed
323
324
325
                ):
                    relative_to = "energy"
                    use = "map_to"
payno's avatar
payno committed
326
327
328
329
330
331
                elif key in (
                    "noise_savgol",
                    "noise_savgol_energy",
                ):
                    relative_to = "noise_savgol_energy"
                    use = "map_to"
payno's avatar
payno committed
332
                elif key in ("chir_re", "chir_im", "chir_mag", "r"):
payno's avatar
payno committed
333
                    relative_to = "r"
334
                    use = "map_to"
payno's avatar
payno committed
335
336
337
                elif key in ("masked_chir_mag", "masked_r"):
                    relative_to = "masked_r"
                    use = "map_to"
payno's avatar
payno committed
338
339
340
                elif key in ("ft.radius", "ft.intensity", "ft.imaginary"):
                    relative_to = "radius"
                    use = "_list_res_ft"
payno's avatar
payno committed
341
342
343
344
345
346
347
348
                elif key in ("masked_chi_weighted_k", "masked_k"):
                    relative_to = "masked_k"
                    use = "map_to"
                elif key in (
                    "chi",
                    "k",
                    "chi_weighted_k",
                ):
payno's avatar
payno committed
349
350
                    relative_to = "k"
                    use = "map_to"
payno's avatar
payno committed
351

352
                if use == "map_to":
payno's avatar
payno committed
353
354
355
                    if key == "norm_area":
                        continue
                    _data[key] = xas_obj.spectra.map_to(
356
                        data_info=key,
payno's avatar
payno committed
357
358
359
                        relative_to=relative_to,
                    )

360
361
                    # if we can display the result as a numpy.array 3d
                    try:
362
                        _data[key] = xas_obj.spectra.map_to(
363
                            data_info=key,
payno's avatar
payno committed
364
365
                            relative_to=relative_to,
                        )
366
                    except KeyError:
payno's avatar
payno committed
367
368
369
370
                        mess = "%s: unable to store %s, parameter not found" % (
                            self.name,
                            key,
                        )
371
372
                        _logger.error(mess)

payno's avatar
payno committed
373
                elif use == "_list_res_ft":
374
375
376
377
                    # for ft we are force to have a dictionary because it
                    # processing can end with a nan or with a numpy array.
                    # combining both would make us end up with a ndarray of
                    # object type
payno's avatar
payno committed
378
                    key_ = key.split(".")[-1]
379
                    res = {}
380
                    for spectrum in xas_obj.spectra:
381
                        x, y = spectrum.x, spectrum.y
payno's avatar
payno committed
382
                        res["_".join((str(x), str(y)))] = getattr(spectrum.ft, key_)
383
384
385
                    _data[key] = res
                else:
                    raise ValueError()
386
387
388
                if isinstance(data_info, _NexusDatasetDef):
                    for aname, avalue in data_info.attrs.items():
                        _data[(key, aname)] = avalue
payno's avatar
payno committed
389
            xas_obj.register_processing(process=self, results=_data, plots=plots)
payno's avatar
payno committed
390
391

    def addCallback(self, callback):
payno's avatar
payno committed
392
        self._callbacks.append(callback)
393
394
395
396
397
398
399

    def update_properties(self, properties):
        if properties is None:
            return
        if isinstance(properties, str):
            properties = extract_properties_from_dict(properties)
        self._settings.update(properties)