source: sasview/src/sas/sascalc/dataloader/readers/cansas_reader_HDF5.py @ 9220e89c

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249
Last change on this file since 9220e89c was 9220e89c, checked in by Jeff Krzywon <jkrzywon@…>, 5 years ago

Code cleanup and py3 compatibility fixes.

  • Property mode set to 100644
File size: 32.0 KB
RevLine 
[68aa210]1"""
[802fc18]2    NXcanSAS data reader for reading HDF5 formatted CanSAS files.
[68aa210]3"""
4
5import h5py
6import numpy as np
7import re
8import os
9import sys
10
[7b50f14]11from ..data_info import plottable_1D, plottable_2D,\
[082239e]12    Data1D, Data2D, DataInfo, Process, Aperture, Collimation, \
13    TransmissionSpectrum, Detector
[7b50f14]14from ..loader_exceptions import FileContentsException, DefaultReaderException
15from ..file_reader_base_class import FileReader, decode
[d72567e]16
[9220e89c]17try:
18  basestring
19except NameError:  # CRUFT: python 2 support
20  basestring = str
21
[4fdcc65]22
[5c5e7fd]23def h5attr(node, key, default=None):
24    return decode(node.attrs.get(key, default))
[68aa210]25
[4fdcc65]26
[9d786e5]27class Reader(FileReader):
[68aa210]28    """
[802fc18]29    A class for reading in NXcanSAS data files. The current implementation has
30    been tested to load data generated by multiple facilities, all of which are
31    known to produce NXcanSAS standards compliant data. Any number of data sets
32    may be present within the file and any dimensionality of data may be used.
33    Currently 1D and 2D SAS data sets are supported, but should be immediately
34    extensible to SESANS data.
[d72567e]35
[802fc18]36    Any number of SASdata groups  may be present in a SASentry and the data
37    within each SASdata group can be a single 1D I(Q), multi-framed 1D I(Q),
38    2D I(Qx, Qy) or multi-framed 2D I(Qx, Qy).
[5e906207]39
[68aa210]40    :Dependencies:
[802fc18]41        The NXcanSAS HDF5 reader requires h5py => v2.5.0 or later.
[68aa210]42    """
43
[082239e]44    # CanSAS version
[68aa210]45    cansas_version = 2.0
[082239e]46    # Data type name
[cf820f5]47    type_name = "NXcanSAS"
[082239e]48    # Wildcards
[cf820f5]49    type = ["NXcanSAS HDF5 Files (*.h5)|*.h5|"]
[082239e]50    # List of allowed extensions
[68aa210]51    ext = ['.h5', '.H5']
[082239e]52    # Flag to bypass extension check
[54544637]53    allow_all = True
[68aa210]54
[9d786e5]55    def get_file_contents(self):
[68aa210]56        """
[ad52d31]57        This is the general read method that all SasView data_loaders must have.
[68aa210]58
59        :param filename: A path for an HDF5 formatted CanSAS 2D data file.
[d72567e]60        :return: List of Data1D/2D objects and/or a list of errors.
[68aa210]61        """
[082239e]62        # Reinitialize when loading a new data file to reset all class variables
[61f329f0]63        self.reset_state()
[9d786e5]64
65        filename = self.f_open.name
66        self.f_open.close() # IO handled by h5py
67
[082239e]68        # Check that the file exists
[68aa210]69        if os.path.isfile(filename):
70            basename = os.path.basename(filename)
71            _, extension = os.path.splitext(basename)
72            # If the file type is not allowed, return empty list
73            if extension in self.ext or self.allow_all:
[082239e]74                # Load the data file
[7f75a3f]75                try:
76                    self.raw_data = h5py.File(filename, 'r')
77                except Exception as e:
[8dec7e7]78                    if extension not in self.ext:
[4fdcc65]79                        msg = "NXcanSAS Reader could not load file {}".format(
80                            basename + extension)
[8dec7e7]81                        raise DefaultReaderException(msg)
82                    raise FileContentsException(e.message)
[dcb91cf]83                try:
84                    # Read in all child elements of top level SASroot
85                    self.read_children(self.raw_data, [])
86                    # Add the last data set to the list of outputs
87                    self.add_data_set()
88                except Exception as exc:
89                    raise FileContentsException(exc.message)
90                finally:
91                    # Close the data file
92                    self.raw_data.close()
93
[4fdcc65]94                for data_set in self.output:
95                    if isinstance(data_set, Data1D):
96                        if data_set.x.size < 5:
97                            exception = FileContentsException(
98                                "Fewer than 5 data points found.")
99                            data_set.errors.append(exception)
[68aa210]100
[61f329f0]101    def reset_state(self):
[d72567e]102        """
103        Create the reader object and define initial states for class variables
104        """
[61f329f0]105        super(Reader, self).reset_state()
[d72567e]106        self.data1d = []
107        self.data2d = []
108        self.raw_data = None
[802fc18]109        self.multi_frame = False
110        self.data_frames = []
111        self.data_uncertainty_frames = []
[282bc3f]112        self.errors = []
[d72567e]113        self.logging = []
[b204004]114        self.q_names = []
[2651724]115        self.mask_name = u''
116        self.i_name = u''
117        self.i_node = u''
[b204004]118        self.i_uncertainties_name = u''
119        self.q_uncertainty_names = []
120        self.q_resolution_names = []
[d72567e]121        self.parent_class = u''
122        self.detector = Detector()
123        self.collimation = Collimation()
124        self.aperture = Aperture()
125        self.process = Process()
126        self.trans_spectrum = TransmissionSpectrum()
127
128    def read_children(self, data, parent_list):
[68aa210]129        """
[ad52d31]130        A recursive method for stepping through the hierarchical data file.
[68aa210]131
132        :param data: h5py Group object of any kind
133        :param parent: h5py Group parent name
134        """
135
[082239e]136        # Loop through each element of the parent and process accordingly
[68aa210]137        for key in data.keys():
[082239e]138            # Get all information for the current key
[68aa210]139            value = data.get(key)
[7b50f14]140            class_name = h5attr(value, u'canSAS_class')
[2ca5d57b]141            if isinstance(class_name, (list, tuple, np.ndarray)):
142                class_name = class_name[0]
[7b50f14]143            if class_name is None:
[5c5e7fd]144                class_name = h5attr(value, u'NX_class')
[68aa210]145            if class_name is not None:
146                class_prog = re.compile(class_name)
147            else:
148                class_prog = re.compile(value.name)
149
150            if isinstance(value, h5py.Group):
[c9ecd1b]151                # Set parent class before recursion
[8f882fe]152                last_parent_class = self.parent_class
[d72567e]153                self.parent_class = class_name
154                parent_list.append(key)
[082239e]155                # If a new sasentry, store the current data sets and create
156                # a fresh Data1D/2D object
[68aa210]157                if class_prog.match(u'SASentry'):
158                    self.add_data_set(key)
[d72567e]159                elif class_prog.match(u'SASdata'):
[9e0dd49]160                    self._find_data_attributes(value)
[802fc18]161                    self._initialize_new_data_set(value)
[082239e]162                # Recursion step to access data within the group
[d72567e]163                self.read_children(value, parent_list)
164                self.add_intermediate()
[8f882fe]165                # Reset parent class when returning from recursive method
166                self.parent_class = last_parent_class
[d72567e]167                parent_list.remove(key)
[68aa210]168
169            elif isinstance(value, h5py.Dataset):
[082239e]170                # If this is a dataset, store the data appropriately
[9dc1500]171                data_set = value.value
[7bd6860a]172                unit = self._get_unit(value)
[ac370c5]173
[68aa210]174                for data_point in data_set:
[2b538cd]175                    if isinstance(data_point, np.ndarray):
176                        if data_point.dtype.char == 'S':
177                            data_point = decode(bytes(data_point))
178                    else:
179                        data_point = decode(data_point)
[082239e]180                    # Top Level Meta Data
[68aa210]181                    if key == u'definition':
[c1dc994]182                        if isinstance(data_set, basestring):
183                            self.current_datainfo.meta_data['reader'] = data_set
184                            break
185                        else:
186                            self.current_datainfo.meta_data[
187                                'reader'] = data_point
[0d93464]188                    # Run
[68aa210]189                    elif key == u'run':
[be88076]190                        try:
[5c5e7fd]191                            run_name = h5attr(value, 'name')
[dfcdbf8]192                            run_dict = {data_set: run_name}
[be88076]193                            self.current_datainfo.run_name = run_dict
[7b50f14]194                        except Exception:
[be88076]195                            pass
[c1dc994]196                        if isinstance(data_set, basestring):
197                            self.current_datainfo.run.append(data_set)
198                            break
199                        else:
200                            self.current_datainfo.run.append(data_point)
[0d93464]201                    # Title
[68aa210]202                    elif key == u'title':
[c1dc994]203                        if isinstance(data_set, basestring):
204                            self.current_datainfo.title = data_set
205                            break
206                        else:
207                            self.current_datainfo.title = data_point
[0d93464]208                    # Note
[68aa210]209                    elif key == u'SASnote':
[dfcdbf8]210                        self.current_datainfo.notes.append(data_set)
211                        break
[082239e]212                    # Sample Information
[0d93464]213                    elif self.parent_class == u'SASsample':
214                        self.process_sample(data_point, key)
[082239e]215                    # Instrumental Information
[c94280c]216                    elif (key == u'name'
217                          and self.parent_class == u'SASinstrument'):
[d72567e]218                        self.current_datainfo.instrument = data_point
[0d93464]219                    # Detector
220                    elif self.parent_class == u'SASdetector':
221                        self.process_detector(data_point, key, unit)
222                    # Collimation
223                    elif self.parent_class == u'SAScollimation':
224                        self.process_collimation(data_point, key, unit)
225                    # Aperture
226                    elif self.parent_class == u'SASaperture':
227                        self.process_aperture(data_point, key)
[082239e]228                    # Process Information
[0d93464]229                    elif self.parent_class == u'SASprocess': # CanSAS 2.0
230                        self.process_process(data_point, key)
[082239e]231                    # Source
[0d93464]232                    elif self.parent_class == u'SASsource':
233                        self.process_source(data_point, key, unit)
[082239e]234                    # Everything else goes in meta_data
[0d93464]235                    elif self.parent_class == u'SASdata':
[96d06a4]236                        if isinstance(self.current_dataset, plottable_2D):
237                            self.process_2d_data_object(data_set, key, unit)
238                        else:
239                            self.process_1d_data_object(data_set, key, unit)
240
[0d93464]241                        break
242                    elif self.parent_class == u'SAStransmission_spectrum':
243                        self.process_trans_spectrum(data_set, key)
244                        break
[68aa210]245                    else:
[082239e]246                        new_key = self._create_unique_key(
247                            self.current_datainfo.meta_data, key)
[d72567e]248                        self.current_datainfo.meta_data[new_key] = data_point
[68aa210]249
250            else:
[082239e]251                # I don't know if this reachable code
[282bc3f]252                self.errors.append("ShouldNeverHappenException")
[68aa210]253
[96d06a4]254    def process_1d_data_object(self, data_set, key, unit):
[0d93464]255        """
[96d06a4]256        SASdata processor method for 1d data items
[0d93464]257        :param data_set: data from HDF5 file
258        :param key: canSAS_class attribute
259        :param unit: unit attribute
260        """
[2651724]261        if key == self.i_name:
[802fc18]262            if self.multi_frame:
263                for x in range(0, data_set.shape[0]):
264                    self.data_frames.append(data_set[x].flatten())
265            else:
266                self.current_dataset.y = data_set.flatten()
267                self.current_dataset.yaxis("Intensity", unit)
[b204004]268        elif key == self.i_uncertainties_name:
[802fc18]269            if self.multi_frame:
270                for x in range(0, data_set.shape[0]):
271                    self.data_uncertainty_frames.append(data_set[x].flatten())
[96d06a4]272            self.current_dataset.dy = data_set.flatten()
[b204004]273        elif key in self.q_names:
[0d93464]274            self.current_dataset.xaxis("Q", unit)
[96d06a4]275            self.current_dataset.x = data_set.flatten()
[b204004]276        elif key in self.q_resolution_names:
277            if (len(self.q_resolution_names) > 1
278                    and np.where(self.q_resolution_names == key)[0] == 0):
[2651724]279                self.current_dataset.dxw = data_set.flatten()
[b204004]280            elif (len(self.q_resolution_names) > 1
281                  and np.where(self.q_resolution_names == key)[0] == 1):
282                self.current_dataset.dxl = data_set.flatten()
283            else:
284                self.current_dataset.dx = data_set.flatten()
285        elif key in self.q_uncertainty_names:
286            if (len(self.q_uncertainty_names) > 1
287                    and np.where(self.q_uncertainty_names == key)[0] == 0):
288                self.current_dataset.dxw = data_set.flatten()
289            elif (len(self.q_uncertainty_names) > 1
290                  and np.where(self.q_uncertainty_names == key)[0] == 1):
[2651724]291                self.current_dataset.dxl = data_set.flatten()
292            else:
293                self.current_dataset.dx = data_set.flatten()
[96d06a4]294        elif key == self.mask_name:
295            self.current_dataset.mask = data_set.flatten()
296        elif key == u'wavelength':
297            self.current_datainfo.source.wavelength = data_set[0]
298            self.current_datainfo.source.wavelength_unit = unit
299
300    def process_2d_data_object(self, data_set, key, unit):
301        if key == self.i_name:
[4fdcc65]302            self.current_dataset.data = data_set
[96d06a4]303            self.current_dataset.zaxis("Intensity", unit)
[b204004]304        elif key == self.i_uncertainties_name:
[96d06a4]305            self.current_dataset.err_data = data_set.flatten()
[b204004]306        elif key in self.q_names:
[c2525bf]307            self.current_dataset.xaxis("Q_x", unit)
308            self.current_dataset.yaxis("Q_y", unit)
[b204004]309            if self.q_names[0] == self.q_names[1]:
[c2525bf]310                # All q data in a single array
[4fdcc65]311                self.current_dataset.qx_data = data_set[0]
312                self.current_dataset.qy_data = data_set[1]
[b204004]313            elif self.q_names.index(key) == 0:
[4fdcc65]314                self.current_dataset.qx_data = data_set
[b204004]315            elif self.q_names.index(key) == 1:
[4fdcc65]316                self.current_dataset.qy_data = data_set
[b204004]317        elif key in self.q_uncertainty_names or key in self.q_resolution_names:
318            if ((self.q_uncertainty_names[0] == self.q_uncertainty_names[1]) or
319                    (self.q_resolution_names[0] == self.q_resolution_names[1])):
[c2525bf]320                # All q data in a single array
321                self.current_dataset.dqx_data = data_set[0].flatten()
322                self.current_dataset.dqy_data = data_set[1].flatten()
[b204004]323            elif (self.q_uncertainty_names.index(key) == 0 or
324                  self.q_resolution_names.index(key) == 0):
[c2525bf]325                self.current_dataset.dqx_data = data_set.flatten()
[b204004]326            elif (self.q_uncertainty_names.index(key) == 1 or
327                  self.q_resolution_names.index(key) == 1):
[c2525bf]328                self.current_dataset.dqy_data = data_set.flatten()
329                self.current_dataset.yaxis("Q_y", unit)
330        elif key == self.mask_name:
331            self.current_dataset.mask = data_set.flatten()
[0d93464]332        elif key == u'Qy':
333            self.current_dataset.yaxis("Q_y", unit)
334            self.current_dataset.qy_data = data_set.flatten()
335        elif key == u'Qydev':
336            self.current_dataset.dqy_data = data_set.flatten()
337        elif key == u'Qx':
338            self.current_dataset.xaxis("Q_x", unit)
339            self.current_dataset.qx_data = data_set.flatten()
340        elif key == u'Qxdev':
341            self.current_dataset.dqx_data = data_set.flatten()
342
343    def process_trans_spectrum(self, data_set, key):
344        """
345        SAStransmission_spectrum processor
346        :param data_set: data from HDF5 file
347        :param key: canSAS_class attribute
348        """
349        if key == u'T':
350            self.trans_spectrum.transmission = data_set.flatten()
351        elif key == u'Tdev':
352            self.trans_spectrum.transmission_deviation = data_set.flatten()
353        elif key == u'lambda':
354            self.trans_spectrum.wavelength = data_set.flatten()
355
356    def process_sample(self, data_point, key):
357        """
358        SASsample processor
359        :param data_point: Single point from an HDF5 data file
360        :param key: class name data_point was taken from
361        """
362        if key == u'Title':
363            self.current_datainfo.sample.name = data_point
364        elif key == u'name':
365            self.current_datainfo.sample.name = data_point
366        elif key == u'ID':
367            self.current_datainfo.sample.name = data_point
368        elif key == u'thickness':
369            self.current_datainfo.sample.thickness = data_point
370        elif key == u'temperature':
371            self.current_datainfo.sample.temperature = data_point
372        elif key == u'transmission':
373            self.current_datainfo.sample.transmission = data_point
374        elif key == u'x_position':
375            self.current_datainfo.sample.position.x = data_point
376        elif key == u'y_position':
377            self.current_datainfo.sample.position.y = data_point
378        elif key == u'pitch':
379            self.current_datainfo.sample.orientation.x = data_point
380        elif key == u'yaw':
381            self.current_datainfo.sample.orientation.y = data_point
382        elif key == u'roll':
383            self.current_datainfo.sample.orientation.z = data_point
384        elif key == u'details':
385            self.current_datainfo.sample.details.append(data_point)
386
387    def process_detector(self, data_point, key, unit):
388        """
389        SASdetector processor
390        :param data_point: Single point from an HDF5 data file
391        :param key: class name data_point was taken from
392        :param unit: unit attribute from data set
393        """
394        if key == u'name':
395            self.detector.name = data_point
396        elif key == u'SDD':
397            self.detector.distance = float(data_point)
398            self.detector.distance_unit = unit
399        elif key == u'slit_length':
400            self.detector.slit_length = float(data_point)
401            self.detector.slit_length_unit = unit
402        elif key == u'x_position':
403            self.detector.offset.x = float(data_point)
404            self.detector.offset_unit = unit
405        elif key == u'y_position':
406            self.detector.offset.y = float(data_point)
407            self.detector.offset_unit = unit
408        elif key == u'pitch':
409            self.detector.orientation.x = float(data_point)
410            self.detector.orientation_unit = unit
411        elif key == u'roll':
412            self.detector.orientation.z = float(data_point)
413            self.detector.orientation_unit = unit
414        elif key == u'yaw':
415            self.detector.orientation.y = float(data_point)
416            self.detector.orientation_unit = unit
417        elif key == u'beam_center_x':
418            self.detector.beam_center.x = float(data_point)
419            self.detector.beam_center_unit = unit
420        elif key == u'beam_center_y':
421            self.detector.beam_center.y = float(data_point)
422            self.detector.beam_center_unit = unit
423        elif key == u'x_pixel_size':
424            self.detector.pixel_size.x = float(data_point)
425            self.detector.pixel_size_unit = unit
426        elif key == u'y_pixel_size':
427            self.detector.pixel_size.y = float(data_point)
428            self.detector.pixel_size_unit = unit
429
430    def process_collimation(self, data_point, key, unit):
431        """
432        SAScollimation processor
433        :param data_point: Single point from an HDF5 data file
434        :param key: class name data_point was taken from
435        :param unit: unit attribute from data set
436        """
437        if key == u'distance':
438            self.collimation.length = data_point
439            self.collimation.length_unit = unit
440        elif key == u'name':
441            self.collimation.name = data_point
442
443    def process_aperture(self, data_point, key):
444        """
445        SASaperture processor
446        :param data_point: Single point from an HDF5 data file
447        :param key: class name data_point was taken from
448        """
449        if key == u'shape':
450            self.aperture.shape = data_point
451        elif key == u'x_gap':
452            self.aperture.size.x = data_point
453        elif key == u'y_gap':
454            self.aperture.size.y = data_point
455
456    def process_source(self, data_point, key, unit):
457        """
458        SASsource processor
459        :param data_point: Single point from an HDF5 data file
460        :param key: class name data_point was taken from
461        :param unit: unit attribute from data set
462        """
463        if key == u'incident_wavelength':
464            self.current_datainfo.source.wavelength = data_point
465            self.current_datainfo.source.wavelength_unit = unit
466        elif key == u'wavelength_max':
467            self.current_datainfo.source.wavelength_max = data_point
468            self.current_datainfo.source.wavelength_max_unit = unit
469        elif key == u'wavelength_min':
470            self.current_datainfo.source.wavelength_min = data_point
471            self.current_datainfo.source.wavelength_min_unit = unit
472        elif key == u'incident_wavelength_spread':
473            self.current_datainfo.source.wavelength_spread = data_point
474            self.current_datainfo.source.wavelength_spread_unit = unit
475        elif key == u'beam_size_x':
476            self.current_datainfo.source.beam_size.x = data_point
477            self.current_datainfo.source.beam_size_unit = unit
478        elif key == u'beam_size_y':
479            self.current_datainfo.source.beam_size.y = data_point
480            self.current_datainfo.source.beam_size_unit = unit
481        elif key == u'beam_shape':
482            self.current_datainfo.source.beam_shape = data_point
483        elif key == u'radiation':
484            self.current_datainfo.source.radiation = data_point
485
486    def process_process(self, data_point, key):
487        """
488        SASprocess processor
489        :param data_point: Single point from an HDF5 data file
490        :param key: class name data_point was taken from
491        """
[ac38ab4]492        term_match = re.compile(u'^term[0-9]+$')
[0d93464]493        if key == u'Title':  # CanSAS 2.0
494            self.process.name = data_point
495        elif key == u'name':  # NXcanSAS
496            self.process.name = data_point
497        elif key == u'description':
498            self.process.description = data_point
499        elif key == u'date':
500            self.process.date = data_point
[ac38ab4]501        elif term_match.match(key):
502            self.process.term.append(data_point)
[0d93464]503        else:
504            self.process.notes.append(data_point)
505
[d72567e]506    def add_intermediate(self):
[ad52d31]507        """
[082239e]508        This method stores any intermediate objects within the final data set
509        after fully reading the set.
[ad52d31]510
[082239e]511        :param parent: The NXclass name for the h5py Group object that just
512                       finished being processed
[ad52d31]513        """
514
[d72567e]515        if self.parent_class == u'SASprocess':
516            self.current_datainfo.process.append(self.process)
[ad52d31]517            self.process = Process()
[d72567e]518        elif self.parent_class == u'SASdetector':
519            self.current_datainfo.detector.append(self.detector)
[ad52d31]520            self.detector = Detector()
[d72567e]521        elif self.parent_class == u'SAStransmission_spectrum':
522            self.current_datainfo.trans_spectrum.append(self.trans_spectrum)
[ad52d31]523            self.trans_spectrum = TransmissionSpectrum()
[d72567e]524        elif self.parent_class == u'SAScollimation':
525            self.current_datainfo.collimation.append(self.collimation)
[ad52d31]526            self.collimation = Collimation()
[d72567e]527        elif self.parent_class == u'SASaperture':
[ad52d31]528            self.collimation.aperture.append(self.aperture)
529            self.aperture = Aperture()
[d72567e]530        elif self.parent_class == u'SASdata':
[082239e]531            if isinstance(self.current_dataset, plottable_2D):
[d72567e]532                self.data2d.append(self.current_dataset)
[082239e]533            elif isinstance(self.current_dataset, plottable_1D):
[802fc18]534                if self.multi_frame:
[a165bee]535                    for x in range(0, len(self.data_frames)):
[802fc18]536                        self.current_dataset.y = self.data_frames[x]
537                        if len(self.data_uncertainty_frames) > x:
538                            self.current_dataset.dy = \
539                                self.data_uncertainty_frames[x]
540                        self.data1d.append(self.current_dataset)
541                else:
542                    self.data1d.append(self.current_dataset)
[68aa210]543
544    def final_data_cleanup(self):
545        """
[082239e]546        Does some final cleanup and formatting on self.current_datainfo and
547        all data1D and data2D objects and then combines the data and info into
548        Data1D and Data2D objects
[68aa210]549        """
[082239e]550        # Type cast data arrays to float64
[d72567e]551        if len(self.current_datainfo.trans_spectrum) > 0:
[ad52d31]552            spectrum_list = []
[d72567e]553            for spectrum in self.current_datainfo.trans_spectrum:
[ad52d31]554                spectrum.transmission = spectrum.transmission.astype(np.float64)
[082239e]555                spectrum.transmission_deviation = \
556                    spectrum.transmission_deviation.astype(np.float64)
[ad52d31]557                spectrum.wavelength = spectrum.wavelength.astype(np.float64)
[d72567e]558                if len(spectrum.transmission) > 0:
559                    spectrum_list.append(spectrum)
560            self.current_datainfo.trans_spectrum = spectrum_list
[68aa210]561
[082239e]562        # Append errors to dataset and reset class errors
[d72567e]563        self.current_datainfo.errors = self.errors
[282bc3f]564        self.errors = []
[68aa210]565
[082239e]566        # Combine all plottables with datainfo and append each to output
567        # Type cast data arrays to float64 and find min/max as appropriate
[d72567e]568        for dataset in self.data2d:
569            zeros = np.ones(dataset.data.size, dtype=bool)
570            try:
[082239e]571                for i in range(0, dataset.mask.size - 1):
[d72567e]572                    zeros[i] = dataset.mask[i]
573            except:
[282bc3f]574                self.errors.append(sys.exc_value)
[d72567e]575            dataset.mask = zeros
[082239e]576            # Calculate the actual Q matrix
[d72567e]577            try:
578                if dataset.q_data.size <= 1:
[54544637]579                    dataset.q_data = np.sqrt(dataset.qx_data
580                                             * dataset.qx_data
581                                             + dataset.qy_data
582                                             * dataset.qy_data)
[d72567e]583            except:
584                dataset.q_data = None
[ac370c5]585
586            if dataset.data.ndim == 2:
587                (n_rows, n_cols) = dataset.data.shape
[4fdcc65]588                flat_qy = dataset.qy_data[0::n_cols].flatten()
[b204004]589                # For 2D arrays of Qx and Qy, the Q value should be constant
590                # along each row -OR- each column. The direction is not
591                # specified in the NXcanSAS standard.
[4fdcc65]592                if flat_qy[0] == flat_qy[1]:
593                    flat_qy = np.transpose(dataset.qy_data)[0::n_cols].flatten()
594                dataset.y_bins = np.unique(flat_qy)
595                flat_qx = dataset.qx_data[0::n_rows].flatten()
[b204004]596                # For 2D arrays of Qx and Qy, the Q value should be constant
597                # along each row -OR- each column. The direction is not
598                # specified in the NXcanSAS standard.
[4fdcc65]599                if flat_qx[0] == flat_qx[1]:
600                    flat_qx = np.transpose(dataset.qx_data)[0::n_rows].flatten()
601                dataset.x_bins = np.unique(flat_qx)
[ac370c5]602                dataset.data = dataset.data.flatten()
[4fdcc65]603                dataset.qx_data = dataset.qx_data.flatten()
604                dataset.qy_data = dataset.qy_data.flatten()
[9d786e5]605            self.current_dataset = dataset
606            self.send_to_output()
[d72567e]607
608        for dataset in self.data1d:
[9d786e5]609            self.current_dataset = dataset
610            self.send_to_output()
[d72567e]611
[68aa210]612    def add_data_set(self, key=""):
613        """
[082239e]614        Adds the current_dataset to the list of outputs after preforming final
615        processing on the data and then calls a private method to generate a
616        new data set.
[68aa210]617
618        :param key: NeXus group name for current tree level
619        """
[d72567e]620
621        if self.current_datainfo and self.current_dataset:
[68aa210]622            self.final_data_cleanup()
[802fc18]623        self.data_frames = []
624        self.data_uncertainty_frames = []
[d72567e]625        self.data1d = []
626        self.data2d = []
627        self.current_datainfo = DataInfo()
[68aa210]628
[8f882fe]629    def _initialize_new_data_set(self, value=None):
[68aa210]630        """
[082239e]631        A private class method to generate a new 1D or 2D data object based on
632        the type of data within the set. Outside methods should call
633        add_data_set() to be sure any existing data is stored properly.
[68aa210]634
[d72567e]635        :param parent_list: List of names of parent elements
[68aa210]636        """
[9220e89c]637        if self._is_2d_not_multi_frame(value):
[d72567e]638            self.current_dataset = plottable_2D()
[68aa210]639        else:
640            x = np.array(0)
641            y = np.array(0)
[d72567e]642            self.current_dataset = plottable_1D(x, y)
643        self.current_datainfo.filename = self.raw_data.filename
[68aa210]644
[cf29187]645    @staticmethod
[9220e89c]646    def as_list_or_array(iterable):
647        """
648        Return value as a list if not already a list or array.
649        :param iterable:
650        :return:
651        """
652        if not (isinstance(iterable, np.ndarray) or isinstance(iterable, list)):
653            iterable = iterable.split(",") if isinstance(iterable, basestring)\
654                else [iterable]
[cf29187]655        return iterable
656
[9e0dd49]657    def _find_data_attributes(self, value):
[2651724]658        """
659        A class to find the indices for Q, the name of the Qdev and Idev, and
660        the name of the mask.
661        :param value: SASdata/NXdata HDF5 Group
662        """
[802fc18]663        # Initialize values to base types
664        self.mask_name = u''
665        self.i_name = u''
666        self.i_node = u''
667        self.i_uncertainties_name = u''
668        self.q_names = []
669        self.q_uncertainty_names = []
670        self.q_resolution_names = []
671        # Get attributes
[9e0dd49]672        attrs = value.attrs
[0bd8fac]673        signal = attrs.get("signal", "I")
674        i_axes = attrs.get("I_axes", ["Q"])
675        q_indices = attrs.get("Q_indices", [0])
[9220e89c]676        i_axes = self.as_list_or_array(i_axes)
[9e0dd49]677        keys = value.keys()
[802fc18]678        # Assign attributes to appropriate class variables
[9220e89c]679        self.q_names = [i_axes[int(v)] for v in self.as_list_or_array(q_indices)]
[18af6d2]680        self.mask_name = attrs.get("mask")
[9e0dd49]681        self.i_name = signal
[2651724]682        self.i_node = value.get(self.i_name)
[b204004]683        for item in self.q_names:
[2651724]684            if item in keys:
685                q_vals = value.get(item)
[0bd8fac]686                if q_vals.attrs.get("uncertainties") is not None:
[b204004]687                    self.q_uncertainty_names = q_vals.attrs.get("uncertainties")
[0bd8fac]688                elif q_vals.attrs.get("uncertainty") is not None:
[b204004]689                    self.q_uncertainty_names = q_vals.attrs.get("uncertainty")
690                if isinstance(self.q_uncertainty_names, basestring):
691                    self.q_uncertainty_names = self.q_uncertainty_names.split(",")
[0bd8fac]692                if q_vals.attrs.get("resolutions") is not None:
[b204004]693                    self.q_resolution_names = q_vals.attrs.get("resolutions")
694                if isinstance(self.q_resolution_names, basestring):
695                    self.q_resolution_names = self.q_resolution_names.split(",")
[9e0dd49]696        if self.i_name in keys:
697            i_vals = value.get(self.i_name)
[b204004]698            self.i_uncertainties_name = i_vals.attrs.get("uncertainties")
699            if self.i_uncertainties_name is None:
700                self.i_uncertainties_name = i_vals.attrs.get("uncertainty")
[9e0dd49]701
[9220e89c]702    def _is_2d_not_multi_frame(self, value, i_base="", q_base=""):
[ad52d31]703        """
[8f882fe]704        A private class to determine if the data set is 1d or 2d.
[ad52d31]705
[802fc18]706        :param value: Nexus/NXcanSAS data group
[d72567e]707        :param basename: Approximate name of an entry to search for
[8f882fe]708        :return: True if 2D, otherwise false
709        """
[802fc18]710        i_basename = i_base if i_base != "" else self.i_name
711        i_vals = value.get(i_basename)
[9220e89c]712        q_basename = q_base if q_base != "" else self.q_names
[802fc18]713        q_vals = value.get(q_basename[0])
[9220e89c]714        self.multi_frame = (i_vals is not None and q_vals is not None
715                            and len(i_vals.shape) != 1
716                            and len(q_vals.shape) == 1)
717        return (i_vals is not None and len(i_vals.shape) != 1
718                and not self.multi_frame)
[ad52d31]719
[68aa210]720    def _create_unique_key(self, dictionary, name, numb=0):
721        """
722        Create a unique key value for any dictionary to prevent overwriting
723        Recurses until a unique key value is found.
724
725        :param dictionary: A dictionary with any number of entries
726        :param name: The index of the item to be added to dictionary
727        :param numb: The number to be appended to the name, starts at 0
[d72567e]728        :return: The new name for the dictionary entry
[68aa210]729        """
730        if dictionary.get(name) is not None:
731            numb += 1
732            name = name.split("_")[0]
733            name += "_{0}".format(numb)
734            name = self._create_unique_key(dictionary, name, numb)
[d398285]735        return name
736
737    def _get_unit(self, value):
738        """
739        Find the unit for a particular value within the h5py dictionary
740
741        :param value: attribute dictionary for a particular value set
[d72567e]742        :return: unit for the value passed to the method
[d398285]743        """
[5c5e7fd]744        unit = h5attr(value, u'units')
[54544637]745        if unit is None:
[5c5e7fd]746            unit = h5attr(value, u'unit')
[54ba66e]747        return unit
Note: See TracBrowser for help on using the repository browser.