source: sasview/DataLoader/data_info.py @ 24120ab

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

Corrected units for slit length

  • Property mode set to 100644
File size: 25.4 KB
Line 
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
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."
21
22copyright 2008, University of Tennessee
23"""
24
25#TODO: Keep track of data manipulation in the 'process' data structure.
26#TODO: This module should be independent of plottables. We should write
27#        an adapter class for plottables when needed.
28
29#from sans.guitools.plottables import Data1D as plottable_1D
30from data_util.uncertainty import Uncertainty
31import numpy
32import math
33
34class plottable_1D:
35    """
36        Data1D is a place holder for 1D plottables.
37    """
38    # The presence of these should be mutually exclusive with the presence of Qdev (dx)
39    x = None
40    y = None
41    dx = None
42    dy = None
43    ## Slit smearing length
44    dxl = None
45    ## Slit smearing width
46    dxw = None
47   
48    # Units
49    _xaxis = ''
50    _xunit = ''
51    _yaxis = ''
52    _yunit = ''
53   
54    def __init__(self,x,y,dx=None,dy=None,dxl=None,dxw=None):
55        self.x = x
56        self.y = y
57        self.dx = dx
58        self.dy = dy
59        self.dxl = dxl
60        self.dxw = dxw
61
62    def xaxis(self, label, unit):
63        self._xaxis = label
64        self._xunit = unit
65       
66    def yaxis(self, label, unit):
67        self._yaxis = label
68        self._yunit = unit
69
70class plottable_2D:
71    """
72        Data2D is a place holder for 2D plottables.
73    """
74    xmin = None
75    xmax = None
76    ymin = None
77    ymax = None
78    data = None
79    err_data = None
80   
81    # Units
82    _xaxis = ''
83    _xunit = ''
84    _yaxis = ''
85    _yunit = ''
86    _zaxis = ''
87    _zunit = ''
88   
89    def __init__(self, data=None, err_data=None):
90        self.data = numpy.asarray(data)
91        self.err_data = numpy.asarray(err_data)
92       
93    def xaxis(self, label, unit):
94        self._xaxis = label
95        self._xunit = unit
96       
97    def yaxis(self, label, unit):
98        self._yaxis = label
99        self._yunit = unit
100           
101    def zaxis(self, label, unit):
102        self._zaxis = label
103        self._zunit = unit
104           
105class Vector:
106    """
107        Vector class to hold multi-dimensional objects
108    """
109    ## x component
110    x = None
111    ## y component
112    y = None
113    ## z component
114    z = None
115   
116    def __init__(self, x=None, y=None, z=None):
117        """
118            Initialization. Components that are not
119            set a set to None by default.
120           
121            @param x: x component
122            @param y: y component
123            @param z: z component
124        """
125        self.x = x
126        self.y = y
127        self.z = z
128       
129    def __str__(self):
130        return "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
131       
132
133class Detector:
134    """
135        Class to hold detector information
136    """
137    ## Name of the instrument [string]
138    name = ''
139    ## Sample to detector distance [float] [mm]
140    distance = None
141    distance_unit = 'mm'
142    ## Offset of this detector position in X, Y, (and Z if necessary) [Vector] [mm]
143    offset = None
144    offset_unit = 'm'
145    ## Orientation (rotation) of this detector in roll, pitch, and yaw [Vector] [degrees]
146    orientation = None
147    orientation_unit = 'degree'
148    ## Center of the beam on the detector in X and Y (and Z if necessary) [Vector] [mm]
149    beam_center = None
150    beam_center_unit = 'mm'
151    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
152    pixel_size = None
153    pixel_size_unit = 'mm'
154    ## Slit length of the instrument for this detector.[float] [mm]
155    slit_length = None
156    slit_length_unit = '1/A'
157   
158    def __init__(self):
159        """
160            Initialize class attribute that are objects...
161        """
162        self.offset      = Vector()
163        self.orientation = Vector()
164        self.beam_center = Vector()
165        self.pixel_size  = Vector()
166       
167   
168    def __str__(self):
169        _str  = "Detector:\n"
170        _str += "   Name:         %s\n" % self.name
171        _str += "   Distance:     %s [%s]\n" % \
172            (str(self.distance), str(self.distance_unit))
173        _str += "   Offset:       %s [%s]\n" % \
174            (str(self.offset), str(self.offset_unit))
175        _str += "   Orientation:  %s [%s]\n" % \
176            (str(self.orientation), str(self.orientation_unit))
177        _str += "   Beam center:  %s [%s]\n" % \
178            (str(self.beam_center), str(self.beam_center_unit))
179        _str += "   Pixel size:   %s [%s]\n" % \
180            (str(self.pixel_size), str(self.pixel_size_unit))
181        _str += "   Slit length:  %s [%s]\n" % \
182            (str(self.slit_length), str(self.slit_length_unit))
183        return _str
184
185class Aperture:
186    ## Name
187    name = None
188    ## Type
189    type = None
190    ## Size name
191    size_name = None
192    ## Aperture size [Vector]
193    size = None
194    size_unit = 'mm'
195    ## Aperture distance [float]
196    distance = None
197    distance_unit = 'mm'
198   
199    def __init__(self):
200        self.size = Vector()
201   
202class Collimation:
203    """
204        Class to hold collimation information
205    """
206    ## Name
207    name = ''
208    ## Length [float] [mm]
209    length = None
210    length_unit = 'mm'
211    ## Aperture
212    aperture = None
213   
214    def __init__(self):
215        self.aperture = []
216   
217    def __str__(self):
218        _str = "Collimation:\n"
219        _str += "   Length:       %s [%s]\n" % \
220            (str(self.length), str(self.length_unit))
221        for item in self.aperture:
222            _str += "   Aperture size:%s [%s]\n" % \
223                (str(item.size), str(item.size_unit))
224            _str += "   Aperture_dist:%s [%s]\n" % \
225                (str(item.distance), str(item.distance_unit))
226        return _str
227
228class Source:
229    """
230        Class to hold source information
231    """ 
232    ## Name
233    name = None
234    ## Radiation type [string]
235    radiation = None
236    ## Beam size name
237    beam_size_name = None
238    ## Beam size [Vector] [mm]
239    beam_size = None
240    beam_size_unit = 'mm'
241    ## Beam shape [string]
242    beam_shape = None
243    ## Wavelength [float] [Angstrom]
244    wavelength = None
245    wavelength_unit = 'A'
246    ## Minimum wavelength [float] [Angstrom]
247    wavelength_min = None
248    wavelength_min_unit = 'nm'
249    ## Maximum wavelength [float] [Angstrom]
250    wavelength_max = None
251    wavelength_max_unit = 'nm'
252    ## Wavelength spread [float] [Angstrom]
253    wavelength_spread = None
254    wavelength_spread_unit = 'percent'
255   
256    def __init__(self):
257        self.beam_size = Vector()
258       
259   
260    def __str__(self):
261        _str  = "Source:\n"
262        _str += "   Radiation:    %s\n" % str(self.radiation)
263        _str += "   Shape:        %s\n" % str(self.beam_shape)
264        _str += "   Wavelength:   %s [%s]\n" % \
265            (str(self.wavelength), str(self.wavelength_unit))
266        _str += "   Waveln_min:   %s [%s]\n" % \
267            (str(self.wavelength_min), str(self.wavelength_min_unit))
268        _str += "   Waveln_max:   %s [%s]\n" % \
269            (str(self.wavelength_max), str(self.wavelength_max_unit))
270        _str += "   Waveln_spread:%s [%s]\n" % \
271            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
272        _str += "   Beam_size:    %s [%s]\n" % \
273            (str(self.beam_size), str(self.beam_size_unit))
274        return _str
275   
276   
277"""
278    Definitions of radiation types
279"""
280NEUTRON  = 'neutron'
281XRAY     = 'x-ray'
282MUON     = 'muon'
283ELECTRON = 'electron'
284   
285class Sample:
286    """
287        Class to hold the sample description
288    """
289    ## Short name for sample
290    name = ''
291    ## ID
292    ID = ''
293    ## Thickness [float] [mm]
294    thickness = None
295    thickness_unit = 'mm'
296    ## Transmission [float] [fraction]
297    transmission = None
298    ## Temperature [float] [C]
299    temperature = None
300    temperature_unit = 'C'
301    ## Position [Vector] [mm]
302    position = None
303    position_unit = 'mm'
304    ## Orientation [Vector] [degrees]
305    orientation = None
306    orientation_unit = 'degree'
307    ## Details
308    details = None
309   
310    def __init__(self):
311        self.position    = Vector()
312        self.orientation = Vector()
313        self.details     = []
314   
315    def __str__(self):
316        _str  = "Sample:\n"
317        _str += "   ID:           %s\n" % str(self.ID)
318        _str += "   Transmission: %s\n" % str(self.transmission)
319        _str += "   Thickness:    %s [%s]\n" % \
320            (str(self.thickness), str(self.thickness_unit))
321        _str += "   Temperature:  %s [%s]\n" % \
322            (str(self.temperature), str(self.temperature_unit))
323        _str += "   Position:     %s [%s]\n" % \
324            (str(self.position), str(self.position_unit))
325        _str += "   Orientation:  %s [%s]\n" % \
326            (str(self.orientation), str(self.orientation_unit))
327       
328        _str += "   Details:\n"
329        for item in self.details:
330            _str += "      %s\n" % item
331           
332        return _str
333 
334class Process:
335    """
336        Class that holds information about the processes
337        performed on the data.
338    """
339    name = ''
340    date = ''
341    description= ''
342    term = None
343    notes = None
344   
345    def __init__(self):
346        self.term = []
347        self.notes = []
348   
349    def __str__(self):
350        _str  = "Process:\n"
351        _str += "   Name:         %s\n" % self.name
352        _str += "   Date:         %s\n" % self.date
353        _str += "   Description:  %s\n" % self.description
354        for item in self.term:
355            _str += "   Term:         %s\n" % item
356        for item in self.notes:
357            _str += "   Note:         %s\n" % item
358        return _str
359   
360 
361class DataInfo:
362    """
363        Class to hold the data read from a file.
364        It includes four blocks of data for the
365        instrument description, the sample description,
366        the data itself and any other meta data.
367    """
368    ## Title
369    title      = ''
370    ## Run number
371    run        = None
372    ## Run name
373    run_name   = None
374    ## File name
375    filename   = ''
376    ## Notes
377    notes      = None
378    ## Processes (Action on the data)
379    process    = None
380    ## Instrument name
381    instrument = ''
382    ## Detector information
383    detector   = None
384    ## Sample information
385    sample     = None
386    ## Source information
387    source     = None
388    ## Collimation information
389    collimation = None
390    ## Additional meta-data
391    meta_data  = None
392    ## Loading errors
393    errors = None
394           
395    def __init__(self):
396        """
397            Initialization
398        """
399        ## Title
400        self.title      = ''
401        ## Run number
402        self.run        = []
403        self.run_name   = {}
404        ## File name
405        self.filename   = ''
406        ## Notes
407        self.notes      = []
408        ## Processes (Action on the data)
409        self.process    = []
410        ## Instrument name
411        self.instrument = ''
412        ## Detector information
413        self.detector   = []
414        ## Sample information
415        self.sample     = Sample()
416        ## Source information
417        self.source     = Source()
418        ## Collimation information
419        self.collimation = []
420        ## Additional meta-data
421        self.meta_data  = {}
422        ## Loading errors
423        self.errors = []       
424       
425    def __str__(self):
426        """
427            Nice printout
428        """
429        _str =  "File:            %s\n" % self.filename
430        _str += "Title:           %s\n" % self.title
431        _str += "Run:             %s\n" % str(self.run)
432        _str += "Instrument:      %s\n" % str(self.instrument)
433        _str += "%s\n" % str(self.sample)
434        _str += "%s\n" % str(self.source)
435        for item in self.detector:
436            _str += "%s\n" % str(item)
437        for item in self.collimation:
438            _str += "%s\n" % str(item)
439        for item in self.process:
440            _str += "%s\n" % str(item)
441        for item in self.notes:
442            _str += "%s\n" % str(item)
443
444        return _str
445           
446    # Private method to perform operation. Not implemented for DataInfo,
447    # but should be implemented for each data class inherited from DataInfo
448    # that holds actual data (ex.: Data1D)
449    def _perform_operation(self, other, operation): return NotImplemented
450
451    def __add__(self, other):
452        """
453            Add two data sets
454           
455            @param other: data set to add to the current one
456            @return: new data set
457            @raise ValueError: raised when two data sets are incompatible
458        """
459        def operation(a, b): return a+b
460        return self._perform_operation(other, operation)
461       
462    def __radd__(self, other):
463        """
464            Add two data sets
465           
466            @param other: data set to add to the current one
467            @return: new data set
468            @raise ValueError: raised when two data sets are incompatible
469        """
470        def operation(a, b): return b+a
471        return self._perform_operation(other, operation)
472       
473    def __sub__(self, other):
474        """
475            Subtract two data sets
476           
477            @param other: data set to subtract from the current one
478            @return: new data set
479            @raise ValueError: raised when two data sets are incompatible
480        """
481        def operation(a, b): return a-b
482        return self._perform_operation(other, operation)
483       
484    def __rsub__(self, other):
485        """
486            Subtract two data sets
487           
488            @param other: data set to subtract from the current one
489            @return: new data set
490            @raise ValueError: raised when two data sets are incompatible
491        """
492        def operation(a, b): return b-a
493        return self._perform_operation(other, operation)
494       
495    def __mul__(self, other):
496        """
497            Multiply two data sets
498           
499            @param other: data set to subtract from the current one
500            @return: new data set
501            @raise ValueError: raised when two data sets are incompatible
502        """
503        def operation(a, b): return a*b
504        return self._perform_operation(other, operation)
505       
506    def __rmul__(self, other):
507        """
508            Multiply two data sets
509           
510            @param other: data set to subtract from the current one
511            @return: new data set
512            @raise ValueError: raised when two data sets are incompatible
513        """
514        def operation(a, b): return b*a
515        return self._perform_operation(other, operation)
516       
517    def __div__(self, other):
518        """
519            Divided a data set by another
520           
521            @param other: data set that the current one is divided by
522            @return: new data set
523            @raise ValueError: raised when two data sets are incompatible
524        """
525        def operation(a, b): return a/b
526        return self._perform_operation(other, operation)
527       
528    def __rdiv__(self, other):
529        """
530            Divided a data set by another
531           
532            @param other: data set that the current one is divided by
533            @return: new data set
534            @raise ValueError: raised when two data sets are incompatible
535        """
536        def operation(a, b): return b/a
537        return self._perform_operation(other, operation)           
538           
539class Data1D(plottable_1D, DataInfo):
540    """
541        1D data class
542    """
543    x_unit = '1/A'
544    y_unit = '1/cm'
545   
546    def __init__(self, x, y, dx=None, dy=None):
547        DataInfo.__init__(self)
548        plottable_1D.__init__(self, x, y, dx, dy)
549        if len(self.detector)>0:
550            raise RuntimeError, "Data1D: Detector bank already filled at init"
551       
552       
553    def __str__(self):
554        """
555            Nice printout
556        """
557        _str =  "%s\n" % DataInfo.__str__(self)
558   
559        _str += "Data:\n"
560        _str += "   Type:         %s\n" % self.__class__.__name__
561        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
562        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
563        _str += "   Length:       %g\n" % len(self.x)
564
565        return _str
566
567    def clone_without_data(self, length=0):
568        """
569            Clone the current object, without copying the data (which
570            will be filled out by a subsequent operation).
571            The data arrays will be initialized to zero.
572           
573            @param length: length of the data array to be initialized
574        """
575        from copy import deepcopy
576       
577        x  = numpy.zeros(length) 
578        dx = numpy.zeros(length) 
579        y  = numpy.zeros(length) 
580        dy = numpy.zeros(length) 
581       
582        clone = Data1D(x, y, dx=dx, dy=dy)
583        clone.title       = self.title
584        clone.run         = self.run
585        clone.filename    = self.filename
586        clone.notes       = deepcopy(self.notes) 
587        clone.process     = deepcopy(self.process) 
588        clone.detector    = deepcopy(self.detector) 
589        clone.sample      = deepcopy(self.sample) 
590        clone.source      = deepcopy(self.source) 
591        clone.collimation = deepcopy(self.collimation) 
592        clone.meta_data   = deepcopy(self.meta_data) 
593        clone.errors      = deepcopy(self.errors) 
594       
595        return clone
596
597    def _validity_check(self, other):
598        """
599            Checks that the data lengths are compatible.
600            Checks that the x vectors are compatible.
601            Returns errors vectors equal to original
602            errors vectors if they were present or vectors
603            of zeros when none was found.
604           
605            @param other: other data set for operation
606            @return: dy for self, dy for other [numpy arrays]
607            @raise ValueError: when lengths are not compatible
608        """
609        dy_other = None
610        if isinstance(other, Data1D):
611            # Check that data lengths are the same
612            if len(self.x) != len(other.x) or \
613                len(self.y) != len(other.y):
614                raise ValueError, "Unable to perform operation: data length are not equal"
615           
616            # Here we could also extrapolate between data points
617            for i in range(len(self.x)):
618                if self.x[i] != other.x[i]:
619                    raise ValueError, "Incompatible data sets: x-values do not match"
620           
621            # Check that the other data set has errors, otherwise
622            # create zero vector
623            dy_other = other.dy
624            if other.dy==None or (len(other.dy) != len(other.y)):
625                dy_other = numpy.zeros(len(other.y))
626           
627        # Check that we have errors, otherwise create zero vector
628        dy = self.dy
629        if self.dy==None or (len(self.dy) != len(self.y)):
630            dy = numpy.zeros(len(self.y))           
631           
632        return dy, dy_other
633
634    def _perform_operation(self, other, operation):
635        """
636        """
637        # First, check the data compatibility
638        dy, dy_other = self._validity_check(other)
639        result = self.clone_without_data(len(self.x))
640       
641        for i in range(len(self.x)):
642            result.x[i] = self.x[i]
643            if self.dx is not None and len(self.x)==len(self.dx):
644                result.dx[i] = self.dx[i]
645           
646            a = Uncertainty(self.y[i], dy[i]**2)
647            if isinstance(other, Data1D):
648                b = Uncertainty(other.y[i], dy_other[i]**2)
649            else:
650                b = other
651           
652            output = operation(a, b)
653            result.y[i] = output.x
654            result.dy[i] = math.sqrt(math.fabs(output.variance))
655        return result
656       
657class Data2D(plottable_2D, DataInfo):
658    """
659        2D data class
660    """
661    ## Units for Q-values
662    Q_unit = '1/A'
663   
664    ## Units for I(Q) values
665    I_unit = '1/cm'
666   
667    ## Vector of Q-values at the center of each bin in x
668    x_bins = None
669   
670    ## Vector of Q-values at the center of each bin in y
671    y_bins = None
672   
673   
674    def __init__(self, data=None, err_data=None):
675        self.y_bins = []
676        self.x_bins = []
677        DataInfo.__init__(self)
678        plottable_2D.__init__(self, data, err_data)
679        if len(self.detector)>0:
680            raise RuntimeError, "Data2D: Detector bank already filled at init"
681
682    def __str__(self):
683        _str =  "%s\n" % DataInfo.__str__(self)
684       
685        _str += "Data:\n"
686        _str += "   Type:         %s\n" % self.__class__.__name__
687        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
688        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
689        leny = 0
690        if len(self.data)>0:
691            leny = len(self.data[0])
692        _str += "   Length:       %g x %g\n" % (len(self.data), leny)
693       
694        return _str
695 
696    def clone_without_data(self, length=0):
697        """
698            Clone the current object, without copying the data (which
699            will be filled out by a subsequent operation).
700            The data arrays will be initialized to zero.
701           
702            @param length: length of the data array to be initialized
703        """
704        from copy import deepcopy
705       
706        data     = numpy.zeros(length) 
707        err_data = numpy.zeros(length) 
708       
709        clone = Data2D(data, err_data)
710        clone.title       = self.title
711        clone.run         = self.run
712        clone.filename    = self.filename
713        clone.notes       = deepcopy(self.notes) 
714        clone.process     = deepcopy(self.process) 
715        clone.detector    = deepcopy(self.detector) 
716        clone.sample      = deepcopy(self.sample) 
717        clone.source      = deepcopy(self.source) 
718        clone.collimation = deepcopy(self.collimation) 
719        clone.meta_data   = deepcopy(self.meta_data) 
720        clone.errors      = deepcopy(self.errors) 
721       
722        return clone
723 
724 
725    def _validity_check(self, other):
726        """
727            Checks that the data lengths are compatible.
728            Checks that the x vectors are compatible.
729            Returns errors vectors equal to original
730            errors vectors if they were present or vectors
731            of zeros when none was found.
732           
733            @param other: other data set for operation
734            @return: dy for self, dy for other [numpy arrays]
735            @raise ValueError: when lengths are not compatible
736        """
737        err_other = None
738        if isinstance(other, Data2D):
739            # Check that data lengths are the same
740            if numpy.size(self.data) != numpy.size(other.data):
741                raise ValueError, "Unable to perform operation: data length are not equal"
742               
743            # Check that the scales match
744            #TODO: matching scales?     
745           
746            # Check that the other data set has errors, otherwise
747            # create zero vector
748            #TODO: test this
749            err_other = other.err_data
750            if other.err_data==None or (numpy.size(other.err_data) != numpy.size(other.data)):
751                err_other = numpy.zeros([numpy.size(other.data,0), numpy.size(other.data,1)])
752           
753        # Check that we have errors, otherwise create zero vector
754        err = self.err_data
755        if self.err_data==None or (numpy.size(self.err_data) != numpy.size(self.data)):
756            err = numpy.zeros([numpy.size(self.data,0), numpy.size(self.data,1)])
757           
758        return err, err_other
759 
760 
761    def _perform_operation(self, other, operation):
762        """
763            Perform 2D operations between data sets
764           
765            @param other: other data set
766            @param operation: function defining the operation
767        """
768        # First, check the data compatibility
769        dy, dy_other = self._validity_check(other)
770   
771        result = self.clone_without_data([numpy.size(self.data,0), numpy.size(self.data,1)])
772       
773        for i in range(numpy.size(self.data,0)):
774            for j in range(numpy.size(self.data,1)):
775                result.data[i][j] = self.data[i][j]
776                if self.err_data is not None and numpy.size(self.data)==numpy.size(self.err_data):
777                    result.err_data[i][j] = self.err_data[i][j]
778               
779                a = Uncertainty(self.data[i][j], dy[i][j]**2)
780                if isinstance(other, Data2D):
781                    b = Uncertainty(other.data[i][j], dy_other[i][j]**2)
782                else:
783                    b = other
784               
785                output = operation(a, b)
786                result.data[i][j] = output.x
787                result.err_data[i][j] = math.sqrt(math.fabs(output.variance))
788        return result
789   
Note: See TracBrowser for help on using the repository browser.