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

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249unittest-saveload
Last change on this file since c2525bf was c2525bf, checked in by krzywon, 6 years ago

Properly load in 2D data using attributes from the NXcanSAS reader.

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