source: sasview/src/sas/sascalc/dataloader/readers/sesans_reader.py @ 35cf5c0

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.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 35cf5c0 was 35cf5c0, checked in by Adam Washington <adam.washington@…>, 7 years ago

Test that SASView complains if SES columns do not match headers

  • Property mode set to 100644
File size: 6.7 KB
Line 
1"""
2    SESANS reader (based on ASCII reader)
3
4    Reader for .ses or .sesans file format
5
6    Jurrian Bakker
7"""
8import logging
9import numpy as np
10import os
11from sas.sascalc.dataloader.data_info import Data1D
12
13# Check whether we have a converter available
14has_converter = True
15try:
16    from sas.sascalc.data_util.nxsunit import Converter
17except:
18    has_converter = False
19_ZERO = 1e-16
20
21
22class Reader:
23    """
24    Class to load sesans files (6 columns).
25    """
26    # File type
27    type_name = "SESANS"
28
29    # Wildcards
30    type = ["SESANS files (*.ses)|*.ses",
31            "SESANS files (*..sesans)|*.sesans"]
32    # List of allowed extensions
33    ext = ['.ses', '.SES', '.sesans', '.SESANS']
34
35    # Flag to bypass extension check
36    allow_all = True
37
38    def read(self, path):
39        """
40        Load data file
41
42        :param path: file path
43
44        :return: SESANSData1D object, or None
45
46        :raise RuntimeError: when the file can't be opened
47        :raise ValueError: when the length of the data vectors are inconsistent
48        """
49        if os.path.isfile(path):
50            basename = os.path.basename(path)
51            _, extension = os.path.splitext(basename)
52            if not (self.allow_all or extension.lower() in self.ext):
53                raise RuntimeError(
54                    "{} has an unrecognized file extension".format(path))
55        else:
56            raise RuntimeError("{} is not a file".format(path))
57        with open(path, 'r') as input_f:
58            # Read in binary mode since GRASP frequently has no-ascii
59            # characters that brakes the open operation
60            line = input_f.readline()
61            params = {}
62            while not line.startswith("BEGIN_DATA"):
63                terms = line.split()
64                if len(terms) >= 2:
65                    params[terms[0]] = " ".join(terms[1:])
66                line = input_f.readline()
67            self.params = params
68
69            if "FileFormatVersion" not in self.params:
70                raise RuntimeError("SES file missing FileFormatVersion")
71            if float(self.params["FileFormatVersion"]) >= 2.0:
72                raise RuntimeError("SASView only supports SES version 1")
73
74            if "SpinEchoLength_unit" not in self.params:
75                raise RuntimeError("SpinEchoLength has no units")
76            if "Wavelength_unit" not in self.params:
77                raise RuntimeError("Wavelength has no units")
78            if params["SpinEchoLength_unit"] != params["Wavelength_unit"]:
79                raise RuntimeError("The spin echo data has rudely used "
80                                   "different units for the spin echo length "
81                                   "and the wavelength.  While sasview could "
82                                   "handle this instance, it is a violation "
83                                   "of the file format and will not be "
84                                   "handled by other software.")
85
86            headers = input_f.readline().split()
87
88            self._insist_header(headers, "SpinEchoLength")
89            self._insist_header(headers, "Depolarisation")
90            self._insist_header(headers, "Depolarisation_error")
91            self._insist_header(headers, "Wavelength")
92
93            data = np.loadtxt(input_f)
94
95            if data.shape[1] != len(headers):
96                raise RuntimeError(
97                    "File has {} headers, but {} columns".format(
98                        len(headers),
99                        data.shape[1]))
100
101            if data.size < 1:
102                raise RuntimeError("{} is empty".format(path))
103            x = data[:, headers.index("SpinEchoLength")]
104            if "SpinEchoLength_error" in headers:
105                dx = data[:, headers.index("SpinEchoLength_error")]
106            else:
107                dx = x*0.05
108            lam = data[:, headers.index("Wavelength")]
109            if "Wavelength_error" in headers:
110                dlam = data[:, headers.index("Wavelength_error")]
111            else:
112                dlam = lam*0.05
113            y = data[:, headers.index("Depolarisation")]
114            dy = data[:, headers.index("Depolarisation_error")]
115
116            lam_unit = self._unit_fetch("Wavelength")
117            x, x_unit = self._unit_conversion(x, "A", self._unit_fetch("SpinEchoLength"))
118            dx, dx_unit = self._unit_conversion(
119                dx, lam_unit,
120                self._unit_fetch("SpinEchoLength"))
121            dlam, dlam_unit = self._unit_conversion(
122                dlam, lam_unit,
123                self._unit_fetch("Wavelength"))
124            y_unit = self._unit_fetch("Depolarisation")
125
126            output = Data1D(x=x, y=y, lam=lam, dy=dy, dx=dx, dlam=dlam,
127                            isSesans=True)
128
129            output.y_unit = y_unit
130            output.x_unit = x_unit
131            output.source.wavelength_unit = lam_unit
132            output.source.wavelength = lam
133            self.filename = output.filename = basename
134            output.xaxis(r"\rm{z}", x_unit)
135            # Adjust label to ln P/(lam^2 t), remove lam column refs
136            output.yaxis(r"\rm{ln(P)/(t \lambda^2)}", y_unit)
137            # Store loading process information
138            output.meta_data['loader'] = self.type_name
139            output.sample.name = params["Sample"]
140            output.sample.ID = params["DataFileTitle"]
141            output.sample.thickness = self._unit_conversion(
142                float(params["Thickness"]), "cm",
143                self._unit_fetch("Thickness"))[0]
144
145            output.sample.zacceptance = (
146                float(params["Theta_zmax"]),
147                self._unit_fetch("Theta_zmax"))
148
149            output.sample.yacceptance = (
150                float(params["Theta_ymax"]),
151                self._unit_fetch("Theta_ymax"))
152            return output
153
154    @staticmethod
155    def _insist_header(headers, name):
156        if name not in headers:
157            raise RuntimeError(
158                "Missing {} column in spin echo data".format(name))
159
160    @staticmethod
161    def _unit_conversion(value, value_unit, default_unit):
162        """
163        Performs unit conversion on a measurement.
164
165        :param value: The magnitude of the measurement
166        :param value_unit: a string containing the final desired unit
167        :param default_unit: a string containing the units of the original measurement
168        :return: The magnitude of the measurement in the new units
169        """
170        # (float, string, string) -> float
171        if has_converter and value_unit != default_unit:
172            data_conv_q = Converter(default_unit)
173            value = data_conv_q(value, units=value_unit)
174            new_unit = default_unit
175        else:
176            new_unit = value_unit
177        return value, new_unit
178
179    def _unit_fetch(self, unit):
180        return self.params[unit+"_unit"]
Note: See TracBrowser for help on using the repository browser.