source: sasview/src/sas/sascalc/file_converter/nxcansas_writer.py @ ac3353d

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ac3353d was ac3353d, checked in by lewis, 8 years ago

Write more metadata to NXcanSAS files

  • Property mode set to 100644
File size: 10.4 KB
Line 
1"""
2    NXcanSAS 1/2D data reader for writing HDF5 formatted NXcanSAS files.
3"""
4
5import h5py
6import numpy as np
7import re
8import os
9
10from sas.sascalc.dataloader.readers.cansas_reader_HDF5 import Reader as Cansas2Reader
11from sas.sascalc.dataloader.data_info import Data1D, Data2D
12
13class NXcanSASWriter(Cansas2Reader):
14    """
15    A class for writing in NXcanSAS data files. Any number of data sets may be
16    written to the file. Currently 1D and 2D SAS data sets are supported
17
18    NXcanSAS spec: http://download.nexusformat.org/sphinx/classes/contributed_definitions/NXcanSAS.html
19
20    :Dependencies:
21        The NXcanSAS writer requires h5py => v2.5.0 or later.
22    """
23
24    def write(self, dataset, filename):
25        """
26        Write an array of Data1d or Data2D objects to an NXcanSAS file, as
27        one SASEntry with multiple SASData elements. The metadata of the first
28        elememt in the array will be written as the SASentry metadata
29        (detector, instrument, sample, etc).
30
31        :param dataset: A list of Data1D or Data2D objects to write
32        :param filename: Where to write the NXcanSAS file
33        """
34
35        def _h5_string(string):
36            """
37            Convert a string to a numpy string in a numpy array. This way it is
38            written to the HDF5 file as a fixed length ASCII string and is
39            compatible with the Reader read() method.
40            """
41            if not isinstance(string, str):
42                string = str(string)
43
44            return np.array([np.string_(string)])
45
46        def _write_h5_string(entry, value, key):
47            entry[key] = _h5_string(value)
48
49
50        def _h5_float(x):
51            if not (isinstance(x, list)):
52                x = [x]
53            return np.array(x, dtype=np.float32)
54
55        def _write_h5_float(entry, value, key):
56            entry.create_dataset(key, data=_h5_float(value))
57
58        def _write_h5_vector(entry, vector, names=['x_position', 'y_position'],
59            units=None, write_fn=_write_h5_string):
60            if len(names) < 2:
61                raise ValueError("Length of names must be >= 2.")
62
63            if vector.x is not None:
64                write_fn(entry, vector.x, names[0])
65                if units is not None:
66                    entry[names[0]].attrs['units'] = units
67            if vector.y is not None:
68                write_fn(entry, vector.y, names[1])
69                if units is not None:
70                    entry[names[1]].attrs['units'] = units
71            if len(names) == 3 and vector.z is not None:
72                write_fn(entry, vector.z, names[2])
73                if units is not None:
74                    entry[names[2]].attrs['units'] = units
75
76        valid_data = all([issubclass(d.__class__, (Data1D, Data2D)) for d in dataset])
77        if not valid_data:
78            raise ValueError("All entries of dataset must be Data1D or Data2D objects")
79
80        # Get run name and number from first Data object
81        data_info = dataset[0]
82        run_number = ''
83        run_name = ''
84        if len(data_info.run) > 0:
85            run_number = data_info.run[0]
86            if len(data_info.run_name) > 0:
87                run_name = data_info.run_name[run_number]
88
89        f = h5py.File(filename, 'w')
90        sasentry = f.create_group('sasentry01')
91        sasentry['definition'] = _h5_string('NXcanSAS')
92        sasentry['run'] = _h5_string(run_number)
93        sasentry['run'].attrs['name'] = run_name
94        sasentry['title'] = _h5_string(data_info.title)
95        sasentry.attrs['canSAS_class'] = 'SASentry'
96        sasentry.attrs['version'] = '1.0'
97
98        i = 1
99
100        for data_obj in dataset:
101            data_entry = sasentry.create_group("sasdata{0:0=2d}".format(i))
102            data_entry.attrs['canSAS_class'] = 'SASdata'
103            if isinstance(data_obj, Data1D):
104                self._write_1d_data(data_obj, data_entry)
105            elif isinstance(data_obj, Data2D):
106                self._write_2d_data(data_obj, data_entry)
107            i += 1
108
109        data_info = dataset[0]
110        # Sample metadata
111        sample_entry = sasentry.create_group('sassample')
112        sample_entry.attrs['canSAS_class'] = 'SASsample'
113        sample_entry['ID'] = _h5_string(data_info.sample.name)
114        sample_attrs = ['thickness', 'temperature', 'transmission']
115        for key in sample_attrs:
116            if getattr(data_info.sample, key) is not None:
117                sample_entry.create_dataset(key,
118                    data=_h5_float(getattr(data_info.sample, key)))
119        _write_h5_vector(sample_entry, data_info.sample.position)
120        _write_h5_vector(sample_entry, data_info.sample.orientation,
121            names=['polar_angle', 'azimuthal_angle'])
122
123        # Instrumment metadata
124        instrument_entry = sasentry.create_group('sasinstrument')
125        instrument_entry.attrs['canSAS_class'] = 'SASinstrument'
126        instrument_entry['name'] = _h5_string(data_info.instrument)
127
128        # Source metadata
129        source_entry = instrument_entry.create_group('sassource')
130        source_entry.attrs['canSAS_class'] = 'SASsource'
131        if data_info.source.radiation is None:
132            source_entry['radiation'] = _h5_string('neutron')
133        else:
134            source_entry['radiation'] = _h5_string(data_info.source.radiation)
135
136        # Collimation metadata
137        if len(data_info.collimation) > 0:
138            i = 1
139            for coll_info in data_info.collimation:
140                collimation_entry = instrument_entry.create_group(
141                    'sascollimation{0:0=2d}'.format(i))
142                collimation_entry.attrs['canSAS_class'] = 'SAScollimation'
143                if coll_info.length is not None:
144                    _write_h5_float(collimation_entry, coll_info.length, 'SDD')
145                    collimation_entry['SDD'].attrs['units'] = coll_info.length_unit
146                if coll_info.name is not None:
147                    collimation_entry['name'] = _h5_string(coll_info.name)
148        else:
149            # Create a blank one - at least 1 set of collimation metadata
150            # required by format
151            collimation_entry = instrument_entry.create_group('sascollimation01')
152
153        # Detector metadata
154        if len(data_info.detector) > 0:
155            i = 1
156            for det_info in data_info.detector:
157                detector_entry = instrument_entry.create_group(
158                    'sasdetector{0:0=2d}'.format(i))
159                detector_entry.attrs['canSAS_class'] = 'SASdetector'
160                if det_info.distance is not None:
161                    _write_h5_float(detector_entry, det_info.distance, 'SDD')
162                    detector_entry['SDD'].attrs['units'] = det_info.distance_unit
163                if det_info.name is not None:
164                    detector_entry['name'] = _h5_string(det_info.name)
165                else:
166                    detector_entry['name'] = _h5_string('')
167                if det_info.slit_length is not None:
168                    _write_h5_float(detector_entry, det_info.slit_length, 'slit_length')
169                    detector_entry['slit_length'].attrs['units'] = det_info.slit_length_unit
170                _write_h5_vector(detector_entry, det_info.offset)
171                _write_h5_vector(detector_entry, det_info.orientation,
172                    names=['polar_angle', 'azimuthal_angle'])
173                _write_h5_vector(detector_entry, det_info.beam_center,
174                    names=['beam_center_x', 'beam_center_y'],
175                    write_fn=_write_h5_float, units=det_info.beam_center_unit)
176                _write_h5_vector(detector_entry, det_info.pixel_size,
177                    names=['x_pixel_size', 'y_pixel_size'],
178                    write_fn=_write_h5_float, units=det_info.pixel_size_unit)
179
180                i += 1
181        else:
182            # Create a blank one - at least 1 detector required by format
183            detector_entry = instrument_entry.create_group('sasdetector01')
184            detector_entry.attrs['canSAS_class'] = 'SASdetector'
185            detector_entry.attrs['name'] = ''
186
187        # TODO: implement writing SASnote
188        i = 1
189        note_entry = sasentry.create_group('sasnote{0:0=2d}'.format(i))
190        note_entry.attrs['canSAS_class'] = 'SASnote'
191
192        f.close()
193
194    def _write_1d_data(self, data_obj, data_entry):
195        """
196        Writes the contents of a Data1D object to a SASdata h5py Group
197
198        :param data_obj: A Data1D object to write to the file
199        :param data_entry: A h5py Group object representing the SASdata
200        """
201        data_entry.attrs['signal'] = 'I'
202        data_entry.attrs['I_axes'] = 'Q'
203        data_entry.attrs['I_uncertainties'] = 'Idev'
204        data_entry.attrs['Q_indicies'] = 0
205
206        dI = data_obj.dy
207        if dI is None:
208            dI = np.zeros((data_obj.y.shape))
209
210        data_entry.create_dataset('Q', data=data_obj.x)
211        data_entry.create_dataset('I', data=data_obj.y)
212        data_entry.create_dataset('Idev', data=dI)
213
214    def _write_2d_data(self, data, data_entry):
215        """
216        Writes the contents of a Data2D object to a SASdata h5py Group
217
218        :param data: A Data2D object to write to the file
219        :param data_entry: A h5py Group object representing the SASdata
220        """
221        data_entry.attrs['signal'] = 'I'
222        data_entry.attrs['I_axes'] = 'Q,Q'
223        data_entry.attrs['I_uncertainties'] = 'Idev'
224        data_entry.attrs['Q_indicies'] = [0,1]
225
226        (n_rows, n_cols) = (len(data.y_bins), len(data.x_bins))
227
228        if n_rows == 0 and n_cols == 0:
229            # Calculate rows and columns, assuming detector is square
230            # Same logic as used in PlotPanel.py _get_bins
231            n_cols = int(np.floor(np.sqrt(len(data.qy_data))))
232            n_rows = int(np.floor(len(data.qy_data) / n_cols))
233
234            if n_rows * n_cols != len(data.qy_data):
235                raise ValueError("Unable to calculate dimensions of 2D data")
236
237        I = np.reshape(data.data, (n_rows, n_cols))
238        dI = np.zeros((n_rows, n_cols))
239        if not all(data.err_data == [None]):
240            dI = np.reshape(data.err_data, (n_rows, n_cols))
241        qx =  np.reshape(data.qx_data, (n_rows, n_cols))
242        qy = np.reshape(data.qy_data, (n_rows, n_cols))
243
244        I_entry = data_entry.create_dataset('I', data=I)
245        I_entry.attrs['units'] = data.I_unit
246        Qx_entry = data_entry.create_dataset('Qx', data=qx)
247        Qx_entry.attrs['units'] = data.Q_unit
248        Qy_entry = data_entry.create_dataset('Qy', data=qy)
249        Qy_entry.attrs['units'] = data.Q_unit
250        Idev_entry = data_entry.create_dataset('Idev', data=dI)
251        Idev_entry.attrs['units'] = data.I_unit
Note: See TracBrowser for help on using the repository browser.