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

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

Don't throw out last data frame in multi frame data.

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