source: sasview/DataLoader/data_info.py @ 129284a

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 129284a was b99ac227, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Updates and tests for readers

  • Property mode set to 100644
File size: 19.6 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
[b99ac227]16If you use DANSE applications to do scientific research that leads to
17publication, we ask that you acknowledge the use of the software with the
18following sentence:
19
20"This work benefited from DANSE software developed under NSF award DMR-0520547."
[a3084ada]21
22copyright 2008, University of Tennessee
23"""
24
[b39c817]25#TODO: Keep track of data manipulation in the 'process' data structure.
26
[a3084ada]27from sans.guitools.plottables import Data1D as plottable_1D
[9198b83]28from data_util.uncertainty import Uncertainty
29import numpy
30import math
[a3084ada]31
[99d1af6]32class plottable_2D:
[8780e9a]33    """
34        Data2D is a place holder for 2D plottables, which are
35        not yet implemented.
36    """
37    xmin = None
38    xmax = None
39    ymin = None
40    ymax = None
[99d1af6]41    data = None
42    err_data = None
43   
44    # Units
45    _xaxis = ''
46    _xunit = ''
47    _yaxis = ''
48    _yunit = ''
49    _zaxis = ''
50    _zunit = ''
51   
52    def __init__(self, data=None, err_data=None):
53        self.data = data
54        self.err_data = err_data
55       
56    def xaxis(self, label, unit):
57        self._xaxis = label
58        self._xunit = unit
59       
60    def yaxis(self, label, unit):
61        self._yaxis = label
62        self._yunit = unit
63           
64    def zaxis(self, label, unit):
65        self._zaxis = label
66        self._zunit = unit
67           
[a3084ada]68class Vector:
69    """
70        Vector class to hold multi-dimensional objects
71    """
72    ## x component
73    x = None
74    ## y component
75    y = None
76    ## z component
77    z = None
78   
79    def __init__(self, x=None, y=None, z=None):
80        """
81            Initialization. Components that are not
82            set a set to None by default.
83           
84            @param x: x component
85            @param y: y component
86            @param z: z component
87        """
88        self.x = x
89        self.y = y
90        self.z = z
91       
92    def __str__(self):
93        return "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
94       
95
96class Detector:
97    """
98        Class to hold detector information
99    """
100    ## Name of the instrument [string]
101    name = ''
102    ## Sample to detector distance [float] [mm]
103    distance = None
[b39c817]104    distance_unit = 'mm'
[a3084ada]105    ## Offset of this detector position in X, Y, (and Z if necessary) [Vector] [mm]
106    offset = Vector()
[b39c817]107    offset_unit = 'm'
[a3084ada]108    ## Orientation (rotation) of this detector in roll, pitch, and yaw [Vector] [degrees]
109    orientation = Vector()
[8780e9a]110    orientation_unit = 'degree'
[99d1af6]111    ## Center of the beam on the detector in X and Y (and Z if necessary) [Vector] [mm]
[a3084ada]112    beam_center = Vector()
[8780e9a]113    beam_center_unit = 'mm'
[a3084ada]114    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
115    pixel_size = Vector()
[8780e9a]116    pixel_size_unit = 'mm'
[a3084ada]117    ## Slit length of the instrument for this detector.[float] [mm]
118    slit_length = None
[8780e9a]119    slit_length_unit = 'mm'
120   
121    def __str__(self):
122        _str  = "Detector:\n"
123        _str += "   Name:         %s\n" % self.name
124        _str += "   Distance:     %s [%s]\n" % \
125            (str(self.distance), str(self.distance_unit))
126        _str += "   Offset:       %s [%s]\n" % \
127            (str(self.offset), str(self.offset_unit))
128        _str += "   Orientation:  %s [%s]\n" % \
129            (str(self.orientation), str(self.orientation_unit))
130        _str += "   Beam center:  %s [%s]\n" % \
131            (str(self.beam_center), str(self.beam_center_unit))
132        _str += "   Pixel size:   %s [%s]\n" % \
133            (str(self.pixel_size), str(self.pixel_size_unit))
134        _str += "   Slit length:  %s [%s]\n" % \
135            (str(self.slit_length), str(self.slit_length_unit))
136        return _str
[a3084ada]137
138class Collimation:
139    """
140        Class to hold collimation information
141    """
[8780e9a]142    class Aperture:
143        # Aperture size [Vector]
144        size = Vector()
145        size_unit = 'mm'
146        # Aperture distance [float]
147        distance = None
148        distance_unit = 'mm'
149   
[a3084ada]150    ## Length [float] [mm]
151    length = None
[8780e9a]152    length_unit = 'mm'
153    ## Aperture
154    aperture = []
155   
156    def __str__(self):
157        _str = "Collimation:\n"
158        _str += "   Length:       %s [%s]\n" % \
159            (str(self.length), str(self.length_unit))
160        for item in self.aperture:
161            _str += "   Aperture size:%s [%s]\n" % \
162                (str(item.size), str(item.size_unit))
163            _str += "   Aperture_dist:%s [%s]\n" % \
164                (str(item.distance), str(item.distance_unit))
165        return _str
[a3084ada]166
167class Source:
168    """
169        Class to hold source information
170    """ 
171    ## Radiation type [string]
172    radiation = ''
173    ## Beam size [Vector] [mm]
174    beam_size = Vector()
[8780e9a]175    beam_size_unit = 'mm'
[a3084ada]176    ## Beam shape [string]
177    beam_shape = ''
178    ## Wavelength [float] [Angstrom]
179    wavelength = None
[8780e9a]180    wavelength_unit = 'A'
[a3084ada]181    ## Minimum wavelength [float] [Angstrom]
182    wavelength_min = None
[8780e9a]183    wavelength_min_unit = 'nm'
[a3084ada]184    ## Maximum wavelength [float] [Angstrom]
185    wavelength_max = None
[8780e9a]186    wavelength_max_unit = 'nm'
[a3084ada]187    ## Wavelength spread [float] [Angstrom]
188    wavelength_spread = None
[8780e9a]189    wavelength_spread_unit = 'percent'
190   
191    def __str__(self):
192        _str  = "Source:\n"
193        _str += "   Radiation:    %s\n" % str(self.radiation)
194        _str += "   Shape:        %s\n" % str(self.beam_shape)
195        _str += "   Wavelength:   %s [%s]\n" % \
196            (str(self.wavelength), str(self.wavelength_unit))
197        _str += "   Waveln_min:   %s [%s]\n" % \
198            (str(self.wavelength_min), str(self.wavelength_min_unit))
199        _str += "   Waveln_max:   %s [%s]\n" % \
200            (str(self.wavelength_max), str(self.wavelength_max_unit))
201        _str += "   Waveln_spread:%s [%s]\n" % \
202            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
203        _str += "   Beam_size:    %s [%s]\n" % \
204            (str(self.beam_size), str(self.beam_size_unit))
205        return _str
206   
[a3084ada]207   
208"""
209    Definitions of radiation types
210"""
211NEUTRON  = 'neutron'
212XRAY     = 'x-ray'
213MUON     = 'muon'
214ELECTRON = 'electron'
215   
216class Sample:
217    """
218        Class to hold the sample description
219    """
220    ## ID
221    ID = ''
222    ## Thickness [float] [mm]
223    thickness = None
[8780e9a]224    thickness_unit = 'mm'
225    ## Transmission [float] [fraction]
[a3084ada]226    transmission = None
227    ## Temperature [float] [C]
228    temperature = None
[8780e9a]229    temperature_unit = 'C'
[a3084ada]230    ## Position [Vector] [mm]
231    position = Vector()
[8780e9a]232    position_unit = 'mm'
[a3084ada]233    ## Orientation [Vector] [degrees]
234    orientation = Vector()
[8780e9a]235    orientation_unit = 'degree'
[a3084ada]236    ## Details
[8780e9a]237    details = []
238   
239    def __str__(self):
240        _str  = "Sample:\n"
241        _str += "   ID:           %s\n" % str(self.ID)
242        _str += "   Transmission: %s\n" % str(self.transmission)
243        _str += "   Thickness:    %s [%s]\n" % \
244            (str(self.thickness), str(self.thickness_unit))
245        _str += "   Temperature:  %s [%s]\n" % \
246            (str(self.temperature), str(self.temperature_unit))
247        _str += "   Position:     %s [%s]\n" % \
248            (str(self.position), str(self.position_unit))
249        _str += "   Orientation:  %s [%s]\n" % \
250            (str(self.orientation), str(self.orientation_unit))
251       
252        _str += "   Details:\n"
253        for item in self.details:
254            _str += "      %s\n" % item
255           
256        return _str
257 
258class Process:
259    """
260        Class that holds information about the processes
261        performed on the data.
262    """
263    name = ''
264    date = ''
265    description= ''
266    term = []
267    notes = []
268   
269    def __str__(self):
270        _str  = "Process:\n"
271        _str += "   Name:         %s\n" % self.name
272        _str += "   Date:         %s\n" % self.date
273        _str += "   Description:  %s\n" % self.description
274        for item in self.term:
275            _str += "   Term:         %s\n" % item
276        for item in self.notes:
277            _str += "   Note:         %s\n" % item
278        return _str
[a3084ada]279   
280 
281class DataInfo:
282    """
283        Class to hold the data read from a file.
284        It includes four blocks of data for the
285        instrument description, the sample description,
286        the data itself and any other meta data.
287    """
[8780e9a]288    ## Title
289    title      = ''
[a3084ada]290    ## Run number
291    run        = None
292    ## File name
293    filename   = ''
294    ## Notes
[8780e9a]295    notes      = []
[a3084ada]296    ## Processes (Action on the data)
297    process    = []
[8780e9a]298    ## Instrument name
299    instrument = ''
[a3084ada]300    ## Detector information
[8780e9a]301    detector   = []
[a3084ada]302    ## Sample information
303    sample     = Sample()
304    ## Source information
305    source     = Source()
[8780e9a]306    ## Collimation information
307    collimation = []
[a3084ada]308    ## Additional meta-data
309    meta_data  = {}
[8780e9a]310    ## Loading errors
311    errors = []
[a3084ada]312           
[b99ac227]313    def __init__(self):
314        """
315            Initialization
316        """
317        ## Title
318        self.title      = ''
319        ## Run number
320        self.run        = None
321        ## File name
322        self.filename   = ''
323        ## Notes
324        self.notes      = []
325        ## Processes (Action on the data)
326        self.process    = []
327        ## Instrument name
328        self.instrument = ''
329        ## Detector information
330        self.detector   = []
331        ## Sample information
332        self.sample     = Sample()
333        ## Source information
334        self.source     = Source()
335        ## Collimation information
336        self.collimation = []
337        ## Additional meta-data
338        self.meta_data  = {}
339        ## Loading errors
340        self.errors = []       
341       
[99d1af6]342    def __str__(self):
343        """
344            Nice printout
345        """
346        _str =  "File:            %s\n" % self.filename
347        _str += "Title:           %s\n" % self.title
348        _str += "Run:             %s\n" % str(self.run)
349        _str += "Instrument:      %s\n" % str(self.instrument)
350        _str += "%s\n" % str(self.sample)
351        _str += "%s\n" % str(self.source)
352        for item in self.detector:
353            _str += "%s\n" % str(item)
354        for item in self.collimation:
355            _str += "%s\n" % str(item)
356        for item in self.process:
357            _str += "%s\n" % str(item)
358        for item in self.notes:
359            _str += "%s\n" % str(item)
360
361        return _str
362           
[b39c817]363    # Private method to perform operation. Not implemented for DataInfo,
364    # but should be implemented for each data class inherited from DataInfo
365    # that holds actual data (ex.: Data1D)
366    def _perform_operation(self, other, operation): return NotImplemented
367
368    def __add__(self, other):
369        """
370            Add two data sets
371           
372            @param other: data set to add to the current one
373            @return: new data set
374            @raise ValueError: raised when two data sets are incompatible
375        """
376        def operation(a, b): return a+b
377        return self._perform_operation(other, operation)
378       
379    def __radd__(self, other):
380        """
381            Add two data sets
382           
383            @param other: data set to add to the current one
384            @return: new data set
385            @raise ValueError: raised when two data sets are incompatible
386        """
387        def operation(a, b): return b+a
388        return self._perform_operation(other, operation)
389       
390    def __sub__(self, other):
391        """
392            Subtract two data sets
393           
394            @param other: data set to subtract from the current one
395            @return: new data set
396            @raise ValueError: raised when two data sets are incompatible
397        """
398        def operation(a, b): return a-b
399        return self._perform_operation(other, operation)
400       
401    def __rsub__(self, other):
402        """
403            Subtract two data sets
404           
405            @param other: data set to subtract from the current one
406            @return: new data set
407            @raise ValueError: raised when two data sets are incompatible
408        """
409        def operation(a, b): return b-a
410        return self._perform_operation(other, operation)
411       
412    def __mul__(self, other):
413        """
414            Multiply two data sets
415           
416            @param other: data set to subtract from the current one
417            @return: new data set
418            @raise ValueError: raised when two data sets are incompatible
419        """
420        def operation(a, b): return a*b
421        return self._perform_operation(other, operation)
422       
423    def __rmul__(self, other):
424        """
425            Multiply two data sets
426           
427            @param other: data set to subtract from the current one
428            @return: new data set
429            @raise ValueError: raised when two data sets are incompatible
430        """
431        def operation(a, b): return b*a
432        return self._perform_operation(other, operation)
433       
434    def __div__(self, other):
435        """
436            Divided a data set by another
437           
438            @param other: data set that the current one is divided by
439            @return: new data set
440            @raise ValueError: raised when two data sets are incompatible
441        """
442        def operation(a, b): return a/b
443        return self._perform_operation(other, operation)
444       
445    def __rdiv__(self, other):
446        """
447            Divided a data set by another
448           
449            @param other: data set that the current one is divided by
450            @return: new data set
451            @raise ValueError: raised when two data sets are incompatible
452        """
453        def operation(a, b): return b/a
454        return self._perform_operation(other, operation)           
455           
[a3084ada]456class Data1D(plottable_1D, DataInfo):
457    """
458        1D data class
459    """
[8780e9a]460    x_unit = '1/A'
461    y_unit = '1/cm'
462   
[a3084ada]463    def __init__(self, x, y, dx=None, dy=None):
[b99ac227]464        DataInfo.__init__(self)
[a3084ada]465        plottable_1D.__init__(self, x, y, dx, dy)
[b99ac227]466        if len(self.detector)>0:
467            raise RuntimeError, "Data1D: Detector bank already filled at init"
468       
[a3084ada]469       
470    def __str__(self):
471        """
472            Nice printout
473        """
[99d1af6]474        _str =  "%s\n" % DataInfo.__str__(self)
475   
[a3084ada]476        _str += "Data:\n"
477        _str += "   Type:         %s\n" % self.__class__.__name__
478        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
479        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
480        _str += "   Length:       %g\n" % len(self.x)
481
482        return _str
483
[9198b83]484    def clone_without_data(self, length=0):
[b39c817]485        """
486            Clone the current object, without copying the data (which
487            will be filled out by a subsequent operation).
488            The data arrays will be initialized to zero.
489           
490            @param length: length of the data array to be initialized
491        """
[9198b83]492        from copy import deepcopy
493       
494        x  = numpy.zeros(length) 
495        dx = numpy.zeros(length) 
496        y  = numpy.zeros(length) 
497        dy = numpy.zeros(length) 
498       
499        clone = Data1D(x, y, dx=dx, dy=dy)
500        clone.title       = self.title
501        clone.run         = self.run
502        clone.filename    = self.filename
503        clone.notes       = deepcopy(self.notes) 
504        clone.process     = deepcopy(self.process) 
505        clone.detector    = deepcopy(self.detector) 
506        clone.sample      = deepcopy(self.sample) 
507        clone.source      = deepcopy(self.source) 
508        clone.collimation = deepcopy(self.collimation) 
509        clone.meta_data   = deepcopy(self.meta_data) 
510        clone.errors      = deepcopy(self.errors) 
511       
512        return clone
513
514    def _validity_check(self, other):
515        """
516            Checks that the data lengths are compatible.
517            Checks that the x vectors are compatible.
518            Returns errors vectors equal to original
519            errors vectors if they were present or vectors
520            of zeros when none was found.
521           
522            @param other: other data set for operation
523            @return: dy for self, dy for other [numpy arrays]
524            @raise ValueError: when lengths are not compatible
525        """
526        dy_other = None
527        if isinstance(other, Data1D):
528            # Check that data lengths are the same
529            if len(self.x) != len(other.x) or \
530                len(self.y) != len(other.y):
531                raise ValueError, "Unable to perform operation: data length are not equal"
532           
533            # Here we could also extrapolate between data points
534            for i in range(len(self.x)):
535                if self.x[i] != other.x[i]:
536                    raise ValueError, "Incompatible data sets: x-values do not match"
537           
538            # Check that the other data set has errors, otherwise
539            # create zero vector
540            dy_other = other.dy
541            if other.dy==None or (len(other.dy) != len(other.y)):
542                dy_other = numpy.zeros(len(other.y))
543           
544        # Check that we have errors, otherwise create zero vector
545        dy = self.dy
546        if self.dy==None or (len(self.dy) != len(self.y)):
547            dy = numpy.zeros(len(self.y))           
548           
549        return dy, dy_other
[a3084ada]550
[9198b83]551    def _perform_operation(self, other, operation):
552        """
553        """
554        # First, check the data compatibility
555        dy, dy_other = self._validity_check(other)
556        result = self.clone_without_data(len(self.x))
557       
558        for i in range(len(self.x)):
559            result.x[i] = self.x[i]
560            if self.dx is not None and len(self.x)==len(self.dx):
561                result.dx[i] = self.dx[i]
562           
563            a = Uncertainty(self.y[i], dy[i]**2)
564            if isinstance(other, Data1D):
565                b = Uncertainty(other.y[i], dy_other[i]**2)
566            else:
567                b = other
568           
569            output = operation(a, b)
570            result.y[i] = output.x
571            result.dy[i] = math.sqrt(math.fabs(output.variance))
572        return result
573       
[99d1af6]574class Data2D(plottable_2D, DataInfo):
575    """
576        2D data class
577    """
578    ## Units for Q-values
579    Q_unit = '1/A'
580   
581    ## Units for I(Q) values
582    I_unit = '1/cm'
583   
584    ## Vector of Q-values at the center of each bin in x
585    x_bins = []
586   
587    ## Vector of Q-values at the center of each bin in y
588    y_bins = []
589   
590   
591    def __init__(self, data=None, err_data=None):
[b99ac227]592        DataInfo.__init__(self)
[99d1af6]593        plottable_2D.__init__(self, data, err_data)
[b99ac227]594        if len(self.detector)>0:
595            raise RuntimeError, "Data2D: Detector bank already filled at init"
[99d1af6]596
597    def __str__(self):
598        _str =  "%s\n" % DataInfo.__str__(self)
599       
600        _str += "Data:\n"
601        _str += "   Type:         %s\n" % self.__class__.__name__
602        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
603        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
604        leny = 0
605        if len(self.data)>0:
606            leny = len(self.data[0])
607        _str += "   Length:       %g x %g\n" % (len(self.data), leny)
608       
609        return _str
610 
Note: See TracBrowser for help on using the repository browser.