XsocsH5.py 12.2 KB
Newer Older
Damien Naudet's avatar
Damien Naudet 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 30 31
# 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"

32
import weakref
Damien Naudet's avatar
WIP  
Damien Naudet committed
33
from collections import OrderedDict, namedtuple
34 35 36 37 38
from contextlib import contextmanager

import h5py as _h5py
import numpy as _np

Damien Naudet's avatar
Damien Naudet committed
39
from .XsocsH5Base import XsocsH5Base
40 41


Damien Naudet's avatar
Damien Naudet committed
42 43
class InvalidEntryError(Exception):
    pass
44 45


Damien Naudet's avatar
WIP  
Damien Naudet committed
46 47 48 49
ScanPositions = namedtuple('ScanPositions',
                           ['motor_0', 'pos_0', 'motor_1', 'pos_1', 'shape'])


50 51 52 53 54 55 56
class XsocsH5(XsocsH5Base):

    TOP_ENTRY = 'global'
    positioners_tpl = '/{0}/instrument/positioners'
    img_data_tpl = '/{0}/measurement/image/data'
    measurement_tpl = '/{0}/measurement'
    detector_tpl = '/{0}/instrument/detector'
Damien Naudet's avatar
Damien Naudet committed
57
    scan_params_tpl = '/{0}/scan'
58 59 60 61 62 63 64 65 66

    def __init__(self, h5_f, mode='r'):
        super(XsocsH5, self).__init__(h5_f, mode=mode)

        self.__entries = None

    def title(self, entry):
        with self._get_file() as h5_file:
            path = entry + '/title'
67
            return h5_file[path][()]
68 69 70 71 72 73 74

    def entry_filename(self, entry):
        with self._get_file() as h5_file:
            return h5_file[entry].file.filename

    def _update_entries(self):
        with self._get_file() as h5_file:
75 76 77 78
            # TODO : this isnt pretty but for some reason the attrs.get() fails
            # when there is no attribute NX_class (should return the default
            # None)
            self.__entries = sorted([key for key in h5_file
Damien Naudet's avatar
Damien Naudet committed
79 80 81
                if ('NX_class' in h5_file[key].attrs and
                    h5_file[key].attrs[
                        'NX_class'].decode() == 'NXentry')])
82 83 84 85 86 87

    def entries(self):
        if self.__entries is None:
            self._update_entries()
        return self.__entries[:]

88 89 90 91
    def scan_angle(self, entry):
        # TODO : get the correct angle name
        return self.positioner(entry, 'eta')

92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    def get_entry_name(self, entry_idx):
        """
        Get the entry found at position *entry_idx* (entries names sorted
        alphabeticaly).
        Raises InvalidEntryError if the entry is not found.
        """
        try:
            return self.entries()[entry_idx]
        except IndexError:
            raise InvalidEntryError('Entry not found (entry_idx={0}).'
                                    ''.format(entry_idx))

    def __detector_params(self, entry, param_names):
        with self._get_file() as h5_file:
            path = self.detector_tpl.format(entry) + '/{0}'
            if isinstance(param_names, (list, set, tuple)):
                return [h5_file.get(path.format(param), _np.array(None))[()]
                        for param in param_names]
            return h5_file.get(path.format(param_names), _np.array(None))[()]

    def beam_energy(self, entry):
        return self.__detector_params(entry, 'beam_energy')

    def direct_beam(self, entry):
        return self.__detector_params(entry, ['center_chan_dim0',
                                              'center_chan_dim1'])

    def chan_per_deg(self, entry):
        return self.__detector_params(entry, ['chan_per_deg_dim0',
                                              'chan_per_deg_dim1'])

    def n_images(self, entry):
        # TODO : make sure that data.ndims = 3
        path = self.img_data_tpl.format(entry)
        return self._get_array_data(path, shape=True)[0]

    def image_size(self, entry):
        # TODO : make sure that data.ndims = 3
        path = self.img_data_tpl.format(entry)
        return self._get_array_data(path, shape=True)[1:3]

    def image_dtype(self, entry):
        path = self.img_data_tpl.format(entry)
        return self._get_array_data(path, dtype=True)

    def dset_shape(self, path):
        return self._get_array_data(path, shape=True)

Damien Naudet's avatar
Damien Naudet committed
140
    def image_cumul(self, entry, dtype=None):
141 142
        """
        Returns the summed intensity for each image.
Damien Naudet's avatar
Damien Naudet committed
143 144 145
        :param dtype: dtype passed to the numpy.sum function.
            Default is numpy.double.
        :type dtype: numpy.dtype
146
        """
Damien Naudet's avatar
Damien Naudet committed
147 148 149 150 151
        if dtype is None:
            dtype = _np.double

        with self.image_dset_ctx(entry) as ctx:
            shape = ctx.shape
Damien Naudet's avatar
WIP  
Damien Naudet committed
152
            intensity = _np.zeros(shape=(shape[0],), dtype=dtype)
Damien Naudet's avatar
Damien Naudet committed
153
            img_buffer = _np.array(ctx[0], dtype=dtype)
154
            for idx in range(0, shape[0]):
Damien Naudet's avatar
Damien Naudet committed
155 156 157
                ctx.read_direct(img_buffer, idx)
                intensity[idx] = _np.sum(img_buffer)
        return intensity
158 159 160

    def scan_positions(self, entry):
        path = self.measurement_tpl.format(entry)
161
        params = self.scan_params(entry)
Damien Naudet's avatar
Damien Naudet committed
162 163
        m0 = '/adc{0}'.format(params['motor_0'].decode()[-1].upper())
        m1 = '/adc{0}'.format(params['motor_1'].decode()[-1].upper())
Damien Naudet's avatar
WIP  
Damien Naudet committed
164 165
        n_0 = params['motor_0_steps']
        n_1 = params['motor_1_steps']
166 167 168

        x_pos = self._get_array_data(path + m0)
        y_pos = self._get_array_data(path + m1)
Damien Naudet's avatar
WIP  
Damien Naudet committed
169 170 171 172 173
        return ScanPositions(motor_0=params['motor_0'],
                             pos_0=x_pos,
                             motor_1=params['motor_1'],
                             pos_1=y_pos,
                             shape=(n_0, n_1))
174

175 176 177 178 179 180 181 182 183 184 185 186
    def acquisition_params(self, entry):
        beam_energy = self.beam_energy(entry)
        direct_beam = self.direct_beam(entry)
        chan_per_deg = self.chan_per_deg(entry)

        result = OrderedDict()
        result['beam_energy'] = beam_energy
        result['direct_beam'] = direct_beam
        result['chan_per_deg'] = chan_per_deg

        return result

187
    def scan_params(self, entry):
Damien Naudet's avatar
Damien Naudet committed
188 189 190 191 192 193 194 195 196
        param_names = ['motor_0', 'motor_0_start',
                       'motor_0_end', 'motor_0_steps',
                       'motor_1', 'motor_1_start',
                       'motor_1_end', 'motor_1_steps',
                       'delay']
        with self._get_file() as h5_file:
            path = self.scan_params_tpl.format(entry) + '/{0}'
            if isinstance(param_names, (list, set, tuple)):
                return OrderedDict([(param, h5_file.get(path.format(param),
197
                                                        _np.array(None))[()])
Damien Naudet's avatar
Damien Naudet committed
198
                                    for param in param_names])
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231

    def positioner(self, entry, positioner):
        path = self.positioners_tpl.format(entry) + '/' + positioner
        return self._get_scalar_data(path)

    def measurement(self, entry, measurement):
        path = self.measurement_tpl.format(entry) + '/' + measurement
        return self._get_array_data(path)

    @contextmanager
    def image_dset_ctx(self,
                       entry,
                       create=False,
                       **kwargs):
        """
        Context manager for the image dataset.
        WARNING: only to be used as a context manager!
        """
        dset_path = self.img_data_tpl.format(entry)
        with self._get_file() as h5_file:
            if create:
                try:
                    image_dset = h5_file.require_dataset(dset_path,
                                                         **kwargs)
                except TypeError:
                    image_dset = h5_file.create_dataset(dset_path,
                                                        **kwargs)
            else:
                image_dset = h5_file[dset_path]
            yield weakref.proxy(image_dset)
            del image_dset


Damien Naudet's avatar
Damien Naudet committed
232
class XsocsH5Writer(XsocsH5):
233 234

    def __init__(self, h5_f, mode='a', **kwargs):
Damien Naudet's avatar
Damien Naudet committed
235
        super(XsocsH5Writer, self).__init__(h5_f, mode=mode, **kwargs)
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267

    def __set_detector_params(self, entry, params):
        with self._get_file() as h5_file:
            path = self.detector_tpl.format(entry) + '/{0}'
            for param_name, param_value in params.items():
                self._set_scalar_data(path.format(param_name), param_value)

    def set_beam_energy(self, beam_energy, entry):
        return self.__set_detector_params(entry, {'beam_energy': beam_energy})

    def set_direct_beam(self, direct_beam, entry):
        value = {'center_chan_dim0': direct_beam[0],
                 'center_chan_dim1': direct_beam[1]}
        return self.__set_detector_params(entry, value)

    def set_chan_per_deg(self, chan_per_deg, entry):
        value = {'chan_per_deg_dim0': chan_per_deg[0],
                 'chan_per_deg_dim1': chan_per_deg[1]}
        return self.__set_detector_params(entry, value)

    def set_scan_params(self,
                        entry,
                        motor_0,
                        motor_0_start,
                        motor_0_end,
                        motor_0_steps,
                        motor_1,
                        motor_1_start,
                        motor_1_end,
                        motor_1_steps,
                        delay,
                        **kwargs):
Damien Naudet's avatar
Damien Naudet committed
268 269 270 271 272 273 274 275 276 277 278 279 280 281

        params = OrderedDict([('motor_0', _np.string_(motor_0)),
                              ('motor_0_start', float(motor_0_start)),
                              ('motor_0_end', float(motor_0_end)),
                              ('motor_0_steps', int(motor_0_steps)),
                              ('motor_1', _np.string_(motor_1)),
                              ('motor_1_start', float(motor_1_start)),
                              ('motor_1_end', float(motor_1_end)),
                              ('motor_1_steps', int(motor_1_steps)),
                              ('delay', float(delay))])
        with self._get_file():
            path = self.scan_params_tpl.format(entry) + '/{0}'
            for param_name, param_value in params.items():
                self._set_scalar_data(path.format(param_name), param_value)
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309

    def create_entry(self, entry):
        with self._get_file() as h5_file:
            entry_grp = h5_file.require_group(entry)
            entry_grp.attrs['NX_class'] = _np.string_('NXentry')

            # creating mandatory groups and setting their Nexus attributes
            grp = entry_grp.require_group('measurement/image')
            grp.attrs['interpretation'] = _np.string_('image')

            # setting the nexus classes
            entry_grp.attrs['NX_class'] = _np.string_('NXentry')

            grp = entry_grp.require_group('instrument')
            grp.attrs['NX_class'] = _np.string_('NXinstrument')

            grp = entry_grp.require_group('instrument/detector')
            grp.attrs['NX_class'] = _np.string_('NXdetector')

            grp = entry_grp.require_group('instrument/positioners')
            grp.attrs['NX_class'] = _np.string_('NXcollection')

            grp = entry_grp.require_group('measurement')
            grp.attrs['NX_class'] = _np.string_('NXcollection')

            grp = entry_grp.require_group('measurement/image')
            grp.attrs['NX_class'] = _np.string_('NXcollection')

Damien Naudet's avatar
Damien Naudet committed
310
            # creating some links
311 312 313 314 315
            grp = entry_grp.require_group('measurement/image')
            det_grp = entry_grp.require_group('instrument/detector')
            grp['info'] = det_grp
            det_grp['data'] = _h5py.SoftLink(self.img_data_tpl.format(entry))

Damien Naudet's avatar
Damien Naudet committed
316
            self._update_entries()
317 318


Damien Naudet's avatar
Damien Naudet committed
319
class XsocsH5MasterWriter(XsocsH5Writer):
320 321 322 323

    def add_entry_file(self, entry, entry_file):
        with self._get_file() as h5_file:
            h5_file[entry] = _h5py.ExternalLink(entry_file, entry)