1 | """ |
---|
2 | CanSAS 2D data reader for reading HDF5 formatted CanSAS files. |
---|
3 | """ |
---|
4 | |
---|
5 | import numpy as np |
---|
6 | import re |
---|
7 | import os |
---|
8 | import sys |
---|
9 | |
---|
10 | from sas.sascalc.dataloader.readers.xml_reader import XMLreader |
---|
11 | from sas.sascalc.dataloader.data_info import plottable_1D, Data1D, Sample, Source |
---|
12 | from sas.sascalc.dataloader.data_info import Process, Aperture, Collimation, TransmissionSpectrum, Detector |
---|
13 | |
---|
14 | |
---|
15 | class Reader(XMLreader): |
---|
16 | """ |
---|
17 | A class for reading in CanSAS v2.0 data files. The existing iteration opens Mantid generated HDF5 formatted files |
---|
18 | with file extension .h5/.H5. Any number of data sets may be present within the file and any dimensionality of data |
---|
19 | may be used. Currently 1D and 2D SAS data sets are supported, but future implementations will include 1D and 2D |
---|
20 | SESANS data. This class assumes a single data set for each sasentry. |
---|
21 | |
---|
22 | :Dependencies: |
---|
23 | The CanSAS HDF5 reader requires h5py v2.5.0 or later. |
---|
24 | """ |
---|
25 | |
---|
26 | ## Logged warnings or messages |
---|
27 | logging = None |
---|
28 | ## List of errors for the current data set |
---|
29 | errors = None |
---|
30 | ## Raw file contents to be processed |
---|
31 | raw_data = None |
---|
32 | ## Data set being modified |
---|
33 | current_dataset = None |
---|
34 | ## For recursion and saving purposes, remember parent objects |
---|
35 | parent_list = None |
---|
36 | ## Data type name |
---|
37 | type_name = "Anton Paar SAXSess" |
---|
38 | ## Wildcards |
---|
39 | type = ["Anton Paar SAXSess Files (*.pdh)|*.pdh"] |
---|
40 | ## List of allowed extensions |
---|
41 | ext = ['.pdh', '.PDH'] |
---|
42 | ## Flag to bypass extension check |
---|
43 | allow_all = False |
---|
44 | ## List of files to return |
---|
45 | output = None |
---|
46 | |
---|
47 | def reset_state(self): |
---|
48 | self.current_dataset = Data1D(np.empty(0), np.empty(0), |
---|
49 | np.empty(0), np.empty(0)) |
---|
50 | self.datasets = [] |
---|
51 | self.raw_data = None |
---|
52 | self.errors = set() |
---|
53 | self.logging = [] |
---|
54 | self.output = [] |
---|
55 | self.detector = Detector() |
---|
56 | self.collimation = Collimation() |
---|
57 | self.aperture = Aperture() |
---|
58 | self.process = Process() |
---|
59 | self.source = Source() |
---|
60 | self.sample = Sample() |
---|
61 | self.trans_spectrum = TransmissionSpectrum() |
---|
62 | self.upper = 5 |
---|
63 | self.lower = 5 |
---|
64 | |
---|
65 | def read(self, filename): |
---|
66 | """ |
---|
67 | This is the general read method that all SasView data_loaders must have. |
---|
68 | |
---|
69 | :param filename: A path for an XML formatted Anton Paar SAXSess data file. |
---|
70 | :return: List of Data1D objects or a list of errors. |
---|
71 | """ |
---|
72 | |
---|
73 | ## Reinitialize the class when loading a new data file to reset all class variables |
---|
74 | self.reset_state() |
---|
75 | ## Check that the file exists |
---|
76 | if os.path.isfile(filename): |
---|
77 | basename = os.path.basename(filename) |
---|
78 | _, extension = os.path.splitext(basename) |
---|
79 | # If the file type is not allowed, return empty list |
---|
80 | if extension in self.ext or self.allow_all: |
---|
81 | ## Load the data file |
---|
82 | input_f = open(filename, 'r') |
---|
83 | buff = input_f.read() |
---|
84 | self.raw_data = buff.splitlines() |
---|
85 | self.read_data() |
---|
86 | return self.output |
---|
87 | |
---|
88 | def read_data(self): |
---|
89 | q_unit = "1/nm" |
---|
90 | i_unit = "1/um^2" |
---|
91 | self.current_dataset.title = self.raw_data[0] |
---|
92 | self.current_dataset.meta_data["Keywords"] = self.raw_data[1] |
---|
93 | line3 = self.raw_data[2].split() |
---|
94 | line4 = self.raw_data[3].split() |
---|
95 | line5 = self.raw_data[4].split() |
---|
96 | self.data_points = int(line3[0]) |
---|
97 | self.lower = 5 |
---|
98 | self.upper = self.lower + self.data_points |
---|
99 | self.source.radiation = 'x-ray' |
---|
100 | normal = float(line4[3]) |
---|
101 | self.current_dataset.source.radiation = "x-ray" |
---|
102 | self.current_dataset.source.name = "Anton Paar SAXSess Instrument" |
---|
103 | self.current_dataset.source.wavelength = float(line4[4]) |
---|
104 | xvals = [] |
---|
105 | yvals = [] |
---|
106 | dyvals = [] |
---|
107 | for i in range(self.lower, self.upper): |
---|
108 | index = i - self.lower |
---|
109 | data = self.raw_data[i].split() |
---|
110 | xvals.insert(index, normal * float(data[0])) |
---|
111 | yvals.insert(index, normal * float(data[1])) |
---|
112 | dyvals.insert(index, normal * float(data[2])) |
---|
113 | self.current_dataset.x = np.append(self.current_dataset.x, xvals) |
---|
114 | self.current_dataset.y = np.append(self.current_dataset.y, yvals) |
---|
115 | self.current_dataset.dy = np.append(self.current_dataset.dy, dyvals) |
---|
116 | if self.data_points != self.current_dataset.x.size: |
---|
117 | self.errors.add("Not all data was loaded properly.") |
---|
118 | if self.current_dataset.dx.size != self.current_dataset.x.size: |
---|
119 | dxvals = np.zeros(self.current_dataset.x.size) |
---|
120 | self.current_dataset.dx = dxvals |
---|
121 | if self.current_dataset.x.size != self.current_dataset.y.size: |
---|
122 | self.errors.add("The x and y data sets are not the same size.") |
---|
123 | if self.current_dataset.y.size != self.current_dataset.dy.size: |
---|
124 | self.errors.add("The y and dy datasets are not the same size.") |
---|
125 | self.current_dataset.errors = self.errors |
---|
126 | self.current_dataset.xaxis("Q", q_unit) |
---|
127 | self.current_dataset.yaxis("Intensity", i_unit) |
---|
128 | xml_intermediate = self.raw_data[self.upper:] |
---|
129 | xml = ''.join(xml_intermediate) |
---|
130 | self.set_xml_string(xml) |
---|
131 | dom = self.xmlroot.xpath('/fileinfo') |
---|
132 | self._parse_child(dom) |
---|
133 | self.output.append(self.current_dataset) |
---|
134 | |
---|
135 | def _parse_child(self, dom, parent=''): |
---|
136 | """ |
---|
137 | Recursive method for stepping through the embedded XML |
---|
138 | :param dom: XML node with or without children |
---|
139 | """ |
---|
140 | for node in dom: |
---|
141 | tagname = node.tag |
---|
142 | value = node.text |
---|
143 | attr = node.attrib |
---|
144 | key = attr.get("key", '') |
---|
145 | if len(node.getchildren()) > 1: |
---|
146 | self._parse_child(node, key) |
---|
147 | if key == "SampleDetector": |
---|
148 | self.current_dataset.detector.append(self.detector) |
---|
149 | self.detector = Detector() |
---|
150 | else: |
---|
151 | if key == "value": |
---|
152 | if parent == "Wavelength": |
---|
153 | self.current_dataset.source.wavelength = value |
---|
154 | elif parent == "SampleDetector": |
---|
155 | self.detector.distance = value |
---|
156 | elif parent == "Temperature": |
---|
157 | self.current_dataset.sample.temperature = value |
---|
158 | elif parent == "CounterSlitLength": |
---|
159 | self.detector.slit_length = value |
---|
160 | elif key == "unit": |
---|
161 | value = value.replace("_", "") |
---|
162 | if parent == "Wavelength": |
---|
163 | self.current_dataset.source.wavelength_unit = value |
---|
164 | elif parent == "SampleDetector": |
---|
165 | self.detector.distance_unit = value |
---|
166 | elif parent == "X": |
---|
167 | self.current_dataset.xaxis(self.current_dataset._xaxis, value) |
---|
168 | elif parent == "Y": |
---|
169 | self.current_dataset.yaxis(self.current_dataset._yaxis, value) |
---|
170 | elif parent == "Temperature": |
---|
171 | self.current_dataset.sample.temperature_unit = value |
---|
172 | elif parent == "CounterSlitLength": |
---|
173 | self.detector.slit_length_unit = value |
---|
174 | elif key == "quantity": |
---|
175 | if parent == "X": |
---|
176 | self.current_dataset.xaxis(value, self.current_dataset._xunit) |
---|
177 | elif parent == "Y": |
---|
178 | self.current_dataset.yaxis(value, self.current_dataset._yunit) |
---|