source: sasview/DataLoader/data_info.py @ cfe97ea

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.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since cfe97ea was 9198b83, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Added basic arithmetic for data class, with unit tests.

  • Property mode set to 100644
File size: 16.0 KB
RevLine 
[a3084ada]1"""
2    Module that contains classes to hold information read from
3    reduced data files.
4   
5    A good description of the data members can be found in
6    the CanSAS 1D XML data format:
7   
8    http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
9"""
10
11"""
12This software was developed by the University of Tennessee as part of the
13Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
14project funded by the US National Science Foundation.
15
16See the license text in license.txt
17
18copyright 2008, University of Tennessee
19"""
20
21from sans.guitools.plottables import Data1D as plottable_1D
[9198b83]22from data_util.uncertainty import Uncertainty
23import numpy
24import math
[a3084ada]25
[8780e9a]26class Data2D:
27    """
28        Data2D is a place holder for 2D plottables, which are
29        not yet implemented.
30    """
31    xmin = None
32    xmax = None
33    ymin = None
34    ymax = None
35    image = None
[a3084ada]36 
37class Vector:
38    """
39        Vector class to hold multi-dimensional objects
40    """
41    ## x component
42    x = None
43    ## y component
44    y = None
45    ## z component
46    z = None
47   
48    def __init__(self, x=None, y=None, z=None):
49        """
50            Initialization. Components that are not
51            set a set to None by default.
52           
53            @param x: x component
54            @param y: y component
55            @param z: z component
56        """
57        self.x = x
58        self.y = y
59        self.z = z
60       
61    def __str__(self):
62        return "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
63       
64
65class Detector:
66    """
67        Class to hold detector information
68    """
69    ## Name of the instrument [string]
70    name = ''
71    ## Sample to detector distance [float] [mm]
72    distance = None
[8780e9a]73    distance_unit = 'm'
[a3084ada]74    ## Offset of this detector position in X, Y, (and Z if necessary) [Vector] [mm]
75    offset = Vector()
[8780e9a]76    offset_unit = 'mm'
[a3084ada]77    ## Orientation (rotation) of this detector in roll, pitch, and yaw [Vector] [degrees]
78    orientation = Vector()
[8780e9a]79    orientation_unit = 'degree'
[a3084ada]80    ## Center of the beam on the detector in X and Y (and Z if necessary) [Vector] [pixel]
81    beam_center = Vector()
[8780e9a]82    beam_center_unit = 'mm'
[a3084ada]83    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
84    pixel_size = Vector()
[8780e9a]85    pixel_size_unit = 'mm'
[a3084ada]86    ## Slit length of the instrument for this detector.[float] [mm]
87    slit_length = None
[8780e9a]88    slit_length_unit = 'mm'
89   
90    def __str__(self):
91        _str  = "Detector:\n"
92        _str += "   Name:         %s\n" % self.name
93        _str += "   Distance:     %s [%s]\n" % \
94            (str(self.distance), str(self.distance_unit))
95        _str += "   Offset:       %s [%s]\n" % \
96            (str(self.offset), str(self.offset_unit))
97        _str += "   Orientation:  %s [%s]\n" % \
98            (str(self.orientation), str(self.orientation_unit))
99        _str += "   Beam center:  %s [%s]\n" % \
100            (str(self.beam_center), str(self.beam_center_unit))
101        _str += "   Pixel size:   %s [%s]\n" % \
102            (str(self.pixel_size), str(self.pixel_size_unit))
103        _str += "   Slit length:  %s [%s]\n" % \
104            (str(self.slit_length), str(self.slit_length_unit))
105        return _str
[a3084ada]106
107class Collimation:
108    """
109        Class to hold collimation information
110    """
[8780e9a]111    class Aperture:
112        # Aperture size [Vector]
113        size = Vector()
114        size_unit = 'mm'
115        # Aperture distance [float]
116        distance = None
117        distance_unit = 'mm'
118   
[a3084ada]119    ## Length [float] [mm]
120    length = None
[8780e9a]121    length_unit = 'mm'
122    ## Aperture
123    aperture = []
124   
125    def __str__(self):
126        _str = "Collimation:\n"
127        _str += "   Length:       %s [%s]\n" % \
128            (str(self.length), str(self.length_unit))
129        for item in self.aperture:
130            _str += "   Aperture size:%s [%s]\n" % \
131                (str(item.size), str(item.size_unit))
132            _str += "   Aperture_dist:%s [%s]\n" % \
133                (str(item.distance), str(item.distance_unit))
134        return _str
[a3084ada]135
136class Source:
137    """
138        Class to hold source information
139    """ 
140    ## Radiation type [string]
141    radiation = ''
142    ## Beam size [Vector] [mm]
143    beam_size = Vector()
[8780e9a]144    beam_size_unit = 'mm'
[a3084ada]145    ## Beam shape [string]
146    beam_shape = ''
147    ## Wavelength [float] [Angstrom]
148    wavelength = None
[8780e9a]149    wavelength_unit = 'A'
[a3084ada]150    ## Minimum wavelength [float] [Angstrom]
151    wavelength_min = None
[8780e9a]152    wavelength_min_unit = 'nm'
[a3084ada]153    ## Maximum wavelength [float] [Angstrom]
154    wavelength_max = None
[8780e9a]155    wavelength_max_unit = 'nm'
[a3084ada]156    ## Wavelength spread [float] [Angstrom]
157    wavelength_spread = None
[8780e9a]158    wavelength_spread_unit = 'percent'
159   
160    def __str__(self):
161        _str  = "Source:\n"
162        _str += "   Radiation:    %s\n" % str(self.radiation)
163        _str += "   Shape:        %s\n" % str(self.beam_shape)
164        _str += "   Wavelength:   %s [%s]\n" % \
165            (str(self.wavelength), str(self.wavelength_unit))
166        _str += "   Waveln_min:   %s [%s]\n" % \
167            (str(self.wavelength_min), str(self.wavelength_min_unit))
168        _str += "   Waveln_max:   %s [%s]\n" % \
169            (str(self.wavelength_max), str(self.wavelength_max_unit))
170        _str += "   Waveln_spread:%s [%s]\n" % \
171            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
172        _str += "   Beam_size:    %s [%s]\n" % \
173            (str(self.beam_size), str(self.beam_size_unit))
174        return _str
175   
[a3084ada]176   
177"""
178    Definitions of radiation types
179"""
180NEUTRON  = 'neutron'
181XRAY     = 'x-ray'
182MUON     = 'muon'
183ELECTRON = 'electron'
184   
185class Sample:
186    """
187        Class to hold the sample description
188    """
189    ## ID
190    ID = ''
191    ## Thickness [float] [mm]
192    thickness = None
[8780e9a]193    thickness_unit = 'mm'
194    ## Transmission [float] [fraction]
[a3084ada]195    transmission = None
196    ## Temperature [float] [C]
197    temperature = None
[8780e9a]198    temperature_unit = 'C'
[a3084ada]199    ## Position [Vector] [mm]
200    position = Vector()
[8780e9a]201    position_unit = 'mm'
[a3084ada]202    ## Orientation [Vector] [degrees]
203    orientation = Vector()
[8780e9a]204    orientation_unit = 'degree'
[a3084ada]205    ## Details
[8780e9a]206    details = []
207   
208    def __str__(self):
209        _str  = "Sample:\n"
210        _str += "   ID:           %s\n" % str(self.ID)
211        _str += "   Transmission: %s\n" % str(self.transmission)
212        _str += "   Thickness:    %s [%s]\n" % \
213            (str(self.thickness), str(self.thickness_unit))
214        _str += "   Temperature:  %s [%s]\n" % \
215            (str(self.temperature), str(self.temperature_unit))
216        _str += "   Position:     %s [%s]\n" % \
217            (str(self.position), str(self.position_unit))
218        _str += "   Orientation:  %s [%s]\n" % \
219            (str(self.orientation), str(self.orientation_unit))
220       
221        _str += "   Details:\n"
222        for item in self.details:
223            _str += "      %s\n" % item
224           
225        return _str
226 
227class Process:
228    """
229        Class that holds information about the processes
230        performed on the data.
231    """
232    name = ''
233    date = ''
234    description= ''
235    term = []
236    notes = []
237   
238    def __str__(self):
239        _str  = "Process:\n"
240        _str += "   Name:         %s\n" % self.name
241        _str += "   Date:         %s\n" % self.date
242        _str += "   Description:  %s\n" % self.description
243        for item in self.term:
244            _str += "   Term:         %s\n" % item
245        for item in self.notes:
246            _str += "   Note:         %s\n" % item
247        return _str
[a3084ada]248   
249 
250class DataInfo:
251    """
252        Class to hold the data read from a file.
253        It includes four blocks of data for the
254        instrument description, the sample description,
255        the data itself and any other meta data.
256    """
[8780e9a]257    ## Title
258    title      = ''
[a3084ada]259    ## Run number
260    run        = None
261    ## File name
262    filename   = ''
263    ## Notes
[8780e9a]264    notes      = []
[a3084ada]265    ## Processes (Action on the data)
266    process    = []
[8780e9a]267    ## Instrument name
268    instrument = ''
[a3084ada]269    ## Detector information
[8780e9a]270    detector   = []
[a3084ada]271    ## Sample information
272    sample     = Sample()
273    ## Source information
274    source     = Source()
[8780e9a]275    ## Collimation information
276    collimation = []
[a3084ada]277    ## Additional meta-data
278    meta_data  = {}
[8780e9a]279    ## Loading errors
280    errors = []
[a3084ada]281           
282class Data1D(plottable_1D, DataInfo):
283    """
284        1D data class
285    """
[8780e9a]286    x_unit = '1/A'
287    y_unit = '1/cm'
288   
[a3084ada]289    def __init__(self, x, y, dx=None, dy=None):
290        plottable_1D.__init__(self, x, y, dx, dy)
291       
292    def __str__(self):
293        """
294            Nice printout
295        """
[8780e9a]296        _str =  "File:            %s\n" % self.filename
297        _str += "Title:           %s\n" % self.title
298        _str += "Run:             %s\n" % str(self.run)
299        _str += "Instrument:      %s\n" % str(self.instrument)
300        _str += "%s\n" % str(self.sample)
301        _str += "%s\n" % str(self.source)
302        for item in self.detector:
303            _str += "%s\n" % str(item)
304        for item in self.collimation:
305            _str += "%s\n" % str(item)
306        for item in self.process:
307            _str += "%s\n" % str(item)
308        for item in self.notes:
309            _str += "%s\n" % str(item)
[a3084ada]310       
311       
312        _str += "Data:\n"
313        _str += "   Type:         %s\n" % self.__class__.__name__
314        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
315        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
316        _str += "   Length:       %g\n" % len(self.x)
317
318        return _str
319
[9198b83]320    def clone_without_data(self, length=0):
321        from copy import deepcopy
322       
323        x  = numpy.zeros(length) 
324        dx = numpy.zeros(length) 
325        y  = numpy.zeros(length) 
326        dy = numpy.zeros(length) 
327       
328        clone = Data1D(x, y, dx=dx, dy=dy)
329        clone.title       = self.title
330        clone.run         = self.run
331        clone.filename    = self.filename
332        clone.notes       = deepcopy(self.notes) 
333        clone.process     = deepcopy(self.process) 
334        clone.detector    = deepcopy(self.detector) 
335        clone.sample      = deepcopy(self.sample) 
336        clone.source      = deepcopy(self.source) 
337        clone.collimation = deepcopy(self.collimation) 
338        clone.meta_data   = deepcopy(self.meta_data) 
339        clone.errors      = deepcopy(self.errors) 
340       
341        return clone
342
343    def _validity_check(self, other):
344        """
345            Checks that the data lengths are compatible.
346            Checks that the x vectors are compatible.
347            Returns errors vectors equal to original
348            errors vectors if they were present or vectors
349            of zeros when none was found.
350           
351            @param other: other data set for operation
352            @return: dy for self, dy for other [numpy arrays]
353            @raise ValueError: when lengths are not compatible
354        """
355        dy_other = None
356        if isinstance(other, Data1D):
357            # Check that data lengths are the same
358            if len(self.x) != len(other.x) or \
359                len(self.y) != len(other.y):
360                raise ValueError, "Unable to perform operation: data length are not equal"
361           
362            # Here we could also extrapolate between data points
363            for i in range(len(self.x)):
364                if self.x[i] != other.x[i]:
365                    raise ValueError, "Incompatible data sets: x-values do not match"
366           
367            # Check that the other data set has errors, otherwise
368            # create zero vector
369            dy_other = other.dy
370            if other.dy==None or (len(other.dy) != len(other.y)):
371                dy_other = numpy.zeros(len(other.y))
372           
373        # Check that we have errors, otherwise create zero vector
374        dy = self.dy
375        if self.dy==None or (len(self.dy) != len(self.y)):
376            dy = numpy.zeros(len(self.y))           
377           
378        return dy, dy_other
[a3084ada]379
[9198b83]380    def _perform_operation(self, other, operation):
381        """
382        """
383        # First, check the data compatibility
384        dy, dy_other = self._validity_check(other)
385        result = self.clone_without_data(len(self.x))
386       
387        for i in range(len(self.x)):
388            result.x[i] = self.x[i]
389            if self.dx is not None and len(self.x)==len(self.dx):
390                result.dx[i] = self.dx[i]
391           
392            a = Uncertainty(self.y[i], dy[i]**2)
393            if isinstance(other, Data1D):
394                b = Uncertainty(other.y[i], dy_other[i]**2)
395            else:
396                b = other
397           
398            output = operation(a, b)
399            result.y[i] = output.x
400            result.dy[i] = math.sqrt(math.fabs(output.variance))
401        return result
402       
403
404    def __add__(self, other):
405        """
406            Add two data sets
407           
408            @param other: data set to add to the current one
409            @return: new data set
410            @raise ValueError: raised when two data sets are incompatible
411        """
412        def operation(a, b): return a+b
413        return self._perform_operation(other, operation)
414       
415    def __radd__(self, other):
416        """
417            Add two data sets
418           
419            @param other: data set to add to the current one
420            @return: new data set
421            @raise ValueError: raised when two data sets are incompatible
422        """
423        def operation(a, b): return b+a
424        return self._perform_operation(other, operation)
425       
426    def __sub__(self, other):
427        """
428            Subtract two data sets
429           
430            @param other: data set to subtract from the current one
431            @return: new data set
432            @raise ValueError: raised when two data sets are incompatible
433        """
434        def operation(a, b): return a-b
435        return self._perform_operation(other, operation)
436       
437    def __rsub__(self, other):
438        """
439            Subtract two data sets
440           
441            @param other: data set to subtract from the current one
442            @return: new data set
443            @raise ValueError: raised when two data sets are incompatible
444        """
445        def operation(a, b): return b-a
446        return self._perform_operation(other, operation)
447       
448    def __mul__(self, other):
449        """
450            Multiply two data sets
451           
452            @param other: data set to subtract from the current one
453            @return: new data set
454            @raise ValueError: raised when two data sets are incompatible
455        """
456        def operation(a, b): return a*b
457        return self._perform_operation(other, operation)
458       
459    def __rmul__(self, other):
460        """
461            Multiply two data sets
462           
463            @param other: data set to subtract from the current one
464            @return: new data set
465            @raise ValueError: raised when two data sets are incompatible
466        """
467        def operation(a, b): return b*a
468        return self._perform_operation(other, operation)
469       
470    def __div__(self, other):
471        """
472            Divided a data set by another
473           
474            @param other: data set that the current one is divided by
475            @return: new data set
476            @raise ValueError: raised when two data sets are incompatible
477        """
478        def operation(a, b): return a/b
479        return self._perform_operation(other, operation)
480       
481    def __rdiv__(self, other):
482        """
483            Divided a data set by another
484           
485            @param other: data set that the current one is divided by
486            @return: new data set
487            @raise ValueError: raised when two data sets are incompatible
488        """
489        def operation(a, b): return b/a
490        return self._perform_operation(other, operation)
491       
[a3084ada]492
493
Note: See TracBrowser for help on using the repository browser.