roi.py 6.84 KB
Newer Older
payno's avatar
payno committed
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
# 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__ = "06/11/2019"

payno's avatar
payno committed
30
31
32
from est.core.types import XASObject
from est.core.types import Spectrum
from est.core.types import Spectra
payno's avatar
payno committed
33
34
35
from .process import Process
import logging
import numpy
payno's avatar
payno committed
36
import pint
payno's avatar
payno committed
37
from typing import Union
payno's avatar
payno committed
38
39
40
41
42
43
44
45
46
47
48
49
50

_logger = logging.getLogger(__name__)


def xas_roi(xas_obj):
    """
    apply roi on the XASObject.spectra

    :param xas_obj: object containing the configuration and spectra to process
    :type: Union[:class:`.XASObject`, dict]
    :return: spectra dict
    :rtype: :class:`.XASObject`
    """
payno's avatar
payno committed
51
52
    xas_roi_process = ROIProcess(inputs={"xas_obj": xas_obj})
    return xas_roi_process.run()
payno's avatar
payno committed
53
54
55
56
57
58
59


class _ROI(object):
    def __init__(self, origin, size):
        self.origin = origin
        self.size = size

payno's avatar
payno committed
60
    @property
payno's avatar
payno committed
61
    def origin(self) -> tuple:
payno's avatar
payno committed
62
63
64
        return self.__origin

    @origin.setter
payno's avatar
payno committed
65
    def origin(self, origin: Union[list, tuple]):
payno's avatar
payno committed
66
67
68
        self.__origin = int(origin[0]), int(origin[1])

    @property
payno's avatar
payno committed
69
    def size(self) -> tuple:
payno's avatar
payno committed
70
71
72
        return self.__size

    @size.setter
payno's avatar
payno committed
73
    def size(self, size: Union[list, tuple]):
payno's avatar
payno committed
74
75
        self.__size = int(size[0]), int(size[1])

payno's avatar
payno committed
76
    def to_dict(self) -> dict:
payno's avatar
payno committed
77
        return {"origin": self.origin, "size": self.size}
payno's avatar
payno committed
78

payno's avatar
payno committed
79
    @staticmethod
payno's avatar
payno committed
80
    def from_dict(ddict: dict):
payno's avatar
payno committed
81
        return _ROI(origin=ddict["origin"], size=ddict["size"])
payno's avatar
payno committed
82

payno's avatar
payno committed
83
84
85
86
87
88
    @staticmethod
    def from_silx_def(silx_roi):
        origin = silx_roi.getOrigin()
        size = silx_roi.getSize()
        return _ROI(origin=origin, size=size)

payno's avatar
payno committed
89

payno's avatar
payno committed
90
class ROIProcess(Process):
91

92
    _INPUT_NAMES = set(["xas_obj"])
93

94
    _OUTPUT_NAMES = set(["xas_obj"])
95

96
97
    def __init__(self, varinfo=None, **inputs):
        Process.__init__(self, name="roi", varinfo=varinfo, **inputs)
payno's avatar
payno committed
98
99
        self._roi = None

payno's avatar
payno committed
100
    def set_properties(self, properties: dict):
payno's avatar
payno committed
101
102
        if "roi" in properties:
            self._roi = _ROI.from_dict(properties["roi"])
payno's avatar
payno committed
103

payno's avatar
payno committed
104
    def setRoi(self, origin: Union[list, tuple], size: Union[list, tuple]):
payno's avatar
payno committed
105
        self._roi = _ROI(origin=origin, size=size)
payno's avatar
payno committed
106
107

    def _apply_roi(self, xas_obj, roi):
payno's avatar
payno committed
108
        _logger.warning("applying roi")
payno's avatar
payno committed
109
110
        assert isinstance(xas_obj, XASObject)
        assert isinstance(roi, _ROI)
payno's avatar
payno committed
111
112
113
114
115
116
117
118
        assert type(roi.origin[0]) is int
        assert type(roi.origin[1]) is int
        assert type(roi.size[0]) is int
        assert type(roi.size[1]) is int
        ymin, ymax = roi.origin[1], roi.origin[1] + roi.size[1]
        xmin, xmax = roi.origin[0], roi.origin[0] + roi.size[0]
        # clip roi
        ymin = max(ymin, 0)
payno's avatar
payno committed
119
        ymax = min(ymax, xas_obj.spectra.shape[0])
payno's avatar
payno committed
120
        xmin = max(xmin, 0)
payno's avatar
payno committed
121
        xmax = min(xmax, xas_obj.spectra.shape[1])
payno's avatar
payno committed
122
123
124
125
126
127
        assert type(ymin) is int
        assert type(ymax) is int
        assert type(xmin) is int
        assert type(xmax) is int
        assert ymax - ymin == roi.size[1]
        assert xmax - xmin == roi.size[0]
payno's avatar
payno committed
128
129
130
131
132

        # TODO: create a spectra object which deal automatically with
        # the following. Should be part of spectra object
        volumes = {}
        for key in xas_obj.spectra_keys():
payno's avatar
payno committed
133
134
135
            if isinstance(
                xas_obj.spectra.data.flat[0][key], (numpy.ndarray, pint.Quantity)
            ):
136
                # there is no processing for the _larch_grp_members case
payno's avatar
payno committed
137
                if key == "_larch_grp_members":
138
                    continue
139
                volume = xas_obj.spectra.map_to(data_info=key)
payno's avatar
payno committed
140
141
142
143
144
                volume_res = volume[:, ymin:ymax, xmin:xmax]
                volume_res = volume_res.reshape(volume_res.shape[0], -1)
                volumes[key] = volume_res

        # then rewrite spectrum list
payno's avatar
payno committed
145
146
147
148
        assert "Mu" in volumes
        assert "Energy" in volumes
        _mus = volumes["Mu"]
        _energies = volumes["Energy"]
payno's avatar
payno committed
149
150
151
152
153
154
        new_spectra = []
        assert _mus.shape == _energies.shape
        n_new_spectrum = roi.size[0] * roi.size[1]
        assert n_new_spectrum == _mus.shape[1]
        # create the new spectrum
        for i_new_spectrum in range(n_new_spectrum):
payno's avatar
payno committed
155
156
157
158
159
            new_spectra.append(
                Spectrum(
                    mu=_mus[:, i_new_spectrum], energy=_energies[:, i_new_spectrum]
                )
            )
payno's avatar
payno committed
160
161

        # update them from extra keys
payno's avatar
payno committed
162
163
        del volumes["Mu"]
        del volumes["Energy"]
payno's avatar
payno committed
164
165
166
167
168
        for key in volumes:
            for slice, new_spectrum in zip(volumes[key], new_spectra):
                new_spectrum[key] = slice

        # set the spectra
payno's avatar
payno committed
169
        xas_obj.spectra = Spectra(energy=_energies[:, 0], spectra=new_spectra)
payno's avatar
payno committed
170
171

        # update the dimensions
payno's avatar
payno committed
172
        xas_obj.spectra.reshape((roi.size[1], roi.size[0]))
payno's avatar
payno committed
173
174
        return xas_obj

175
    def run(self):
payno's avatar
payno committed
176
177
178
179
180
181
182
        """

        :param xas_obj: object containing the configuration and spectra to process
        :type: Union[:class:`.XASObject`, dict]
        :return: spectra dict
        :rtype: :class:`.XASObject`
        """
183
        xas_obj = self.inputs.xas_obj
184
        if xas_obj is None:
185
            raise ValueError("xas_obj should be provided")
payno's avatar
payno committed
186
        _xas_obj = self.getXasObject(xas_obj=xas_obj)
payno's avatar
payno committed
187

payno's avatar
payno committed
188
189
190
        # existing roi is priority. This is the case if called from pushworkflow
        # for example.
        if self._roi is not None:
191
            _xas_obj = self._apply_roi(xas_obj=_xas_obj, roi=self._roi)
payno's avatar
payno committed
192
193
194
        elif "roi" in _xas_obj.configuration:
            roi_dict = _xas_obj.configuration["roi"]
            origin, size = roi_dict["origin"], roi_dict["size"]
195
196
197
            _xas_obj = self._apply_roi(xas_obj=_xas_obj, roi=_ROI(origin, size))
        self.outputs.xas_obj = _xas_obj.to_dict()
        return _xas_obj
payno's avatar
payno committed
198

199
    @staticmethod
200
    def program_name():
201
202
        return "roi"

203
    __call__ = run