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

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 346d56e was 346d56e, checked in by lewis, 8 years ago

Allow adding metadata to 2d files and ensure it's written correctly

  • Property mode set to 100644
File size: 8.0 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 _h5_float(x):
47            if not (isinstance(x, list)):
48                x = [x]
49            return np.array(x, dtype=np.float32)
50
51        valid_data = all([issubclass(d.__class__, (Data1D, Data2D)) for d in dataset])
52        if not valid_data:
53            raise ValueError("All entries of dataset must be Data1D or Data2D objects")
54
55        # Get run name and number from first Data object
56        data_info = dataset[0]
57        run_number = ''
58        run_name = ''
59        if len(data_info.run) > 0:
60            run_number = data_info.run[0]
61            if len(data_info.run_name) > 0:
62                run_name = data_info.run_name[run_number]
63
64        import pdb; pdb.set_trace()
65
66        f = h5py.File(filename, 'w')
67        sasentry = f.create_group('sasentry01')
68        sasentry['definition'] = _h5_string('NXcanSAS')
69        sasentry['run'] = _h5_string(run_number)
70        sasentry['run'].attrs['name'] = run_name
71        sasentry['title'] = _h5_string(data_info.title)
72        sasentry.attrs['canSAS_class'] = 'SASentry'
73        sasentry.attrs['version'] = '1.0'
74
75        i = 1
76
77        for data_obj in dataset:
78            data_entry = sasentry.create_group("sasdata{0:0=2d}".format(i))
79            data_entry.attrs['canSAS_class'] = 'SASdata'
80            if isinstance(data_obj, Data1D):
81                self._write_1d_data(data_obj, data_entry)
82            elif isinstance(data_obj, Data2D):
83                self._write_2d_data(data_obj, data_entry)
84            i += 1
85
86        data_info = dataset[0]
87        sample_entry = sasentry.create_group('sassample')
88        sample_entry.attrs['canSAS_class'] = 'SASsample'
89        sample_entry['name'] = _h5_string(data_info.sample.name)
90        sample_attrs = ['thickness', 'temperature']
91        for key in sample_attrs:
92            if getattr(data_info.sample, key) is not None:
93                sample_entry.create_dataset(key,
94                    data=_h5_float(getattr(data_info.sample, key)))
95
96        instrument_entry = sasentry.create_group('sasinstrument')
97        instrument_entry.attrs['canSAS_class'] = 'SASinstrument'
98        instrument_entry['name'] = _h5_string(data_info.instrument)
99
100        source_entry = instrument_entry.create_group('sassource')
101        source_entry.attrs['canSAS_class'] = 'SASsource'
102        if data_info.source.radiation is None:
103            source_entry['radiation'] = _h5_string('neutron')
104        else:
105            source_entry['radiation'] = _h5_string(data_info.source.radiation)
106
107        if len(data_info.collimation) > 0:
108            i = 1
109            for coll_info in data_info.collimation:
110                collimation_entry = instrument_entry.create_group(
111                    'sascollimation{0:0=2d}'.format(i))
112                collimation_entry.attrs['canSAS_class'] = 'SAScollimation'
113                if coll_info.length is not None:
114                    collimation_entry['SDD'] = _h5_float(coll_info.length)
115                    collimation_entry['SDD'].attrs['units'] = coll_info.length_unit
116                if coll_info.name is not None:
117                    collimation_entry['name'] = _h5_string(coll_info.name)
118        else:
119            collimation_entry = instrument_entry.create_group('sascollimation01')
120
121        if len(data_info.detector) > 0:
122            i = 1
123            for det_info in data_info.detector:
124                detector_entry = instrument_entry.create_group(
125                    'sasdetector{0:0=2d}'.format(i))
126                detector_entry.attrs['canSAS_class'] = 'SASdetector'
127                if det_info.distance is not None:
128                    detector_entry['SDD'] = _h5_float(det_info.distance)
129                    detector_entry['SDD'].attrs['units'] = det_info.distance_unit
130                if det_info.name is not None:
131                    detector_entry['name'] = _h5_string(det_info.name)
132                else:
133                    detector_entry['name'] = _h5_string('')
134                i += 1
135        else:
136            detector_entry = instrument_entry.create_group('sasdetector01')
137            detector_entry.attrs['canSAS_class'] = 'SASdetector'
138            detector_entry.attrs['name'] = ''
139
140        # TODO: implement writing SASnote
141        i = 1
142        note_entry = sasentry.create_group('sasnote{0:0=2d}'.format(i))
143        note_entry.attrs['canSAS_class'] = 'SASnote'
144
145        f.close()
146
147    def _write_1d_data(self, data_obj, data_entry):
148        """
149        Writes the contents of a Data1D object to a SASdata h5py Group
150
151        :param data_obj: A Data1D object to write to the file
152        :param data_entry: A h5py Group object representing the SASdata
153        """
154        data_entry.attrs['signal'] = 'I'
155        data_entry.attrs['I_axes'] = 'Q'
156        data_entry.attrs['I_uncertainties'] = 'Idev'
157        data_entry.attrs['Q_indicies'] = 0
158        data_entry.create_dataset('Q', data=data_obj.x)
159        data_entry.create_dataset('I', data=data_obj.y)
160        data_entry.create_dataset('Idev', data=data_obj.dy)
161
162    def _write_2d_data(self, data, data_entry):
163        """
164        Writes the contents of a Data2D object to a SASdata h5py Group
165
166        :param data: A Data2D object to write to the file
167        :param data_entry: A h5py Group object representing the SASdata
168        """
169        data_entry.attrs['signal'] = 'I'
170        data_entry.attrs['I_axes'] = 'Q,Q'
171        data_entry.attrs['I_uncertainties'] = 'Idev'
172        data_entry.attrs['Q_indicies'] = [0,1]
173
174        (n_rows, n_cols) = (len(data.y_bins), len(data.x_bins))
175
176        if n_rows == 0 and n_cols == 0:
177            # Calculate rows and columns, assuming detector is square
178            # Same logic as used in PlotPanel.py _get_bins
179            n_cols = int(np.floor(np.sqrt(len(data.qy_data))))
180            n_rows = int(np.floor(len(data.qy_data) / n_cols))
181
182            if n_rows * n_cols != len(data.qy_data):
183                raise ValueError("Unable to calculate dimensions of 2D data")
184
185        I = np.reshape(data.data, (n_rows, n_cols))
186        dI = np.zeros((n_rows, n_cols))
187        if not all(data.err_data == [None]):
188            dI = np.reshape(data.err_data, (n_rows, n_cols))
189        qx =  np.reshape(data.qx_data, (n_rows, n_cols))
190        qy = np.reshape(data.qy_data, (n_rows, n_cols))
191
192        I_entry = data_entry.create_dataset('I', data=I)
193        I_entry.attrs['units'] = data.I_unit
194        Qx_entry = data_entry.create_dataset('Qx', data=qx)
195        Qx_entry.attrs['units'] = data.Q_unit
196        Qy_entry = data_entry.create_dataset('Qy', data=qy)
197        Qy_entry.attrs['units'] = data.Q_unit
198        Idev_entry = data_entry.create_dataset('Idev', data=dI)
199        Idev_entry.attrs['units'] = data.I_unit
Note: See TracBrowser for help on using the repository browser.