source: sasview/DataLoader/data_info.py @ de5c813

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 de5c813 was de5c813, checked in by Jae Cho <jhjcho@…>, 15 years ago

removed Imag2D class

  • Property mode set to 100644
File size: 25.3 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
105           
106class Vector:
107    """
108        Vector class to hold multi-dimensional objects
109    """
110    ## x component
111    x = None
112    ## y component
113    y = None
114    ## z component
115    z = None
116   
117    def __init__(self, x=None, y=None, z=None):
118        """
119            Initialization. Components that are not
120            set a set to None by default.
121           
122            @param x: x component
123            @param y: y component
124            @param z: z component
125        """
126        self.x = x
127        self.y = y
128        self.z = z
129       
130    def __str__(self):
131        return "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
132       
133
134class Detector:
135    """
136        Class to hold detector information
137    """
138    ## Name of the instrument [string]
139    name = ''
140    ## Sample to detector distance [float] [mm]
141    distance = None
142    distance_unit = 'mm'
143    ## Offset of this detector position in X, Y, (and Z if necessary) [Vector] [mm]
144    offset = None
145    offset_unit = 'm'
146    ## Orientation (rotation) of this detector in roll, pitch, and yaw [Vector] [degrees]
147    orientation = None
148    orientation_unit = 'degree'
149    ## Center of the beam on the detector in X and Y (and Z if necessary) [Vector] [mm]
150    beam_center = None
151    beam_center_unit = 'mm'
152    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
153    pixel_size = None
154    pixel_size_unit = 'mm'
155    ## Slit length of the instrument for this detector.[float] [mm]
156    slit_length = None
157    #slit_length_unit = '1/A'
158    slit_length_unit = 'mm'
159   
160    def __init__(self):
161        """
162            Initialize class attribute that are objects...
163        """
164        self.offset      = Vector()
165        self.orientation = Vector()
166        self.beam_center = Vector()
167        self.pixel_size  = Vector()
168       
169   
170    def __str__(self):
171        _str  = "Detector:\n"
172        _str += "   Name:         %s\n" % self.name
173        _str += "   Distance:     %s [%s]\n" % \
174            (str(self.distance), str(self.distance_unit))
175        _str += "   Offset:       %s [%s]\n" % \
176            (str(self.offset), str(self.offset_unit))
177        _str += "   Orientation:  %s [%s]\n" % \
178            (str(self.orientation), str(self.orientation_unit))
179        _str += "   Beam center:  %s [%s]\n" % \
180            (str(self.beam_center), str(self.beam_center_unit))
181        _str += "   Pixel size:   %s [%s]\n" % \
182            (str(self.pixel_size), str(self.pixel_size_unit))
183        _str += "   Slit length:  %s [%s]\n" % \
184            (str(self.slit_length), str(self.slit_length_unit))
185        return _str
186
187class Aperture:
188    ## Name
189    name = None
190    ## Type
191    type = None
192    ## Size name
193    size_name = None
194    ## Aperture size [Vector]
195    size = None
196    size_unit = 'mm'
197    ## Aperture distance [float]
198    distance = None
199    distance_unit = 'mm'
200   
201    def __init__(self):
202        self.size = Vector()
203   
204class Collimation:
205    """
206        Class to hold collimation information
207    """
208    ## Name
209    name = ''
210    ## Length [float] [mm]
211    length = None
212    length_unit = 'mm'
213    ## Aperture
214    aperture = None
215   
216    def __init__(self):
217        self.aperture = []
218   
219    def __str__(self):
220        _str = "Collimation:\n"
221        _str += "   Length:       %s [%s]\n" % \
222            (str(self.length), str(self.length_unit))
223        for item in self.aperture:
224            _str += "   Aperture size:%s [%s]\n" % \
225                (str(item.size), str(item.size_unit))
226            _str += "   Aperture_dist:%s [%s]\n" % \
227                (str(item.distance), str(item.distance_unit))
228        return _str
229
230class Source:
231    """
232        Class to hold source information
233    """ 
234    ## Name
235    name = None
236    ## Radiation type [string]
237    radiation = None
238    ## Beam size name
239    beam_size_name = None
240    ## Beam size [Vector] [mm]
241    beam_size = None
242    beam_size_unit = 'mm'
243    ## Beam shape [string]
244    beam_shape = None
245    ## Wavelength [float] [Angstrom]
246    wavelength = None
247    wavelength_unit = 'A'
248    ## Minimum wavelength [float] [Angstrom]
249    wavelength_min = None
250    wavelength_min_unit = 'nm'
251    ## Maximum wavelength [float] [Angstrom]
252    wavelength_max = None
253    wavelength_max_unit = 'nm'
254    ## Wavelength spread [float] [Angstrom]
255    wavelength_spread = None
256    wavelength_spread_unit = 'percent'
257   
258    def __init__(self):
259        self.beam_size = Vector()
260       
261   
262    def __str__(self):
263        _str  = "Source:\n"
264        _str += "   Radiation:    %s\n" % str(self.radiation)
265        _str += "   Shape:        %s\n" % str(self.beam_shape)
266        _str += "   Wavelength:   %s [%s]\n" % \
267            (str(self.wavelength), str(self.wavelength_unit))
268        _str += "   Waveln_min:   %s [%s]\n" % \
269            (str(self.wavelength_min), str(self.wavelength_min_unit))
270        _str += "   Waveln_max:   %s [%s]\n" % \
271            (str(self.wavelength_max), str(self.wavelength_max_unit))
272        _str += "   Waveln_spread:%s [%s]\n" % \
273            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
274        _str += "   Beam_size:    %s [%s]\n" % \
275            (str(self.beam_size), str(self.beam_size_unit))
276        return _str
277   
278   
279"""
280    Definitions of radiation types
281"""
282NEUTRON  = 'neutron'
283XRAY     = 'x-ray'
284MUON     = 'muon'
285ELECTRON = 'electron'
286   
287class Sample:
288    """
289        Class to hold the sample description
290    """
291    ## Short name for sample
292    name = ''
293    ## ID
294    ID = ''
295    ## Thickness [float] [mm]
296    thickness = None
297    thickness_unit = 'mm'
298    ## Transmission [float] [fraction]
299    transmission = None
300    ## Temperature [float] [C]
301    temperature = None
302    temperature_unit = 'C'
303    ## Position [Vector] [mm]
304    position = None
305    position_unit = 'mm'
306    ## Orientation [Vector] [degrees]
307    orientation = None
308    orientation_unit = 'degree'
309    ## Details
310    details = None
311   
312    def __init__(self):
313        self.position    = Vector()
314        self.orientation = Vector()
315        self.details     = []
316   
317    def __str__(self):
318        _str  = "Sample:\n"
319        _str += "   ID:           %s\n" % str(self.ID)
320        _str += "   Transmission: %s\n" % str(self.transmission)
321        _str += "   Thickness:    %s [%s]\n" % \
322            (str(self.thickness), str(self.thickness_unit))
323        _str += "   Temperature:  %s [%s]\n" % \
324            (str(self.temperature), str(self.temperature_unit))
325        _str += "   Position:     %s [%s]\n" % \
326            (str(self.position), str(self.position_unit))
327        _str += "   Orientation:  %s [%s]\n" % \
328            (str(self.orientation), str(self.orientation_unit))
329       
330        _str += "   Details:\n"
331        for item in self.details:
332            _str += "      %s\n" % item
333           
334        return _str
335 
336class Process:
337    """
338        Class that holds information about the processes
339        performed on the data.
340    """
341    name = ''
342    date = ''
343    description= ''
344    term = None
345    notes = None
346   
347    def __init__(self):
348        self.term = []
349        self.notes = []
350   
351    def __str__(self):
352        _str  = "Process:\n"
353        _str += "   Name:         %s\n" % self.name
354        _str += "   Date:         %s\n" % self.date
355        _str += "   Description:  %s\n" % self.description
356        for item in self.term:
357            _str += "   Term:         %s\n" % item
358        for item in self.notes:
359            _str += "   Note:         %s\n" % item
360        return _str
361   
362 
363class DataInfo:
364    """
365        Class to hold the data read from a file.
366        It includes four blocks of data for the
367        instrument description, the sample description,
368        the data itself and any other meta data.
369    """
370    ## Title
371    title      = ''
372    ## Run number
373    run        = None
374    ## Run name
375    run_name   = None
376    ## File name
377    filename   = ''
378    ## Notes
379    notes      = None
380    ## Processes (Action on the data)
381    process    = None
382    ## Instrument name
383    instrument = ''
384    ## Detector information
385    detector   = None
386    ## Sample information
387    sample     = None
388    ## Source information
389    source     = None
390    ## Collimation information
391    collimation = None
392    ## Additional meta-data
393    meta_data  = None
394    ## Loading errors
395    errors = None
396           
397    def __init__(self):
398        """
399            Initialization
400        """
401        ## Title
402        self.title      = ''
403        ## Run number
404        self.run        = []
405        self.run_name   = {}
406        ## File name
407        self.filename   = ''
408        ## Notes
409        self.notes      = []
410        ## Processes (Action on the data)
411        self.process    = []
412        ## Instrument name
413        self.instrument = ''
414        ## Detector information
415        self.detector   = []
416        ## Sample information
417        self.sample     = Sample()
418        ## Source information
419        self.source     = Source()
420        ## Collimation information
421        self.collimation = []
422        ## Additional meta-data
423        self.meta_data  = {}
424        ## Loading errors
425        self.errors = []       
426       
427    def __str__(self):
428        """
429            Nice printout
430        """
431        _str =  "File:            %s\n" % self.filename
432        _str += "Title:           %s\n" % self.title
433        _str += "Run:             %s\n" % str(self.run)
434        _str += "Instrument:      %s\n" % str(self.instrument)
435        _str += "%s\n" % str(self.sample)
436        _str += "%s\n" % str(self.source)
437        for item in self.detector:
438            _str += "%s\n" % str(item)
439        for item in self.collimation:
440            _str += "%s\n" % str(item)
441        for item in self.process:
442            _str += "%s\n" % str(item)
443        for item in self.notes:
444            _str += "%s\n" % str(item)
445
446        return _str
447           
448    # Private method to perform operation. Not implemented for DataInfo,
449    # but should be implemented for each data class inherited from DataInfo
450    # that holds actual data (ex.: Data1D)
451    def _perform_operation(self, other, operation): return NotImplemented
452
453    def __add__(self, other):
454        """
455            Add two data sets
456           
457            @param other: data set to add to the current one
458            @return: new data set
459            @raise ValueError: raised when two data sets are incompatible
460        """
461        def operation(a, b): return a+b
462        return self._perform_operation(other, operation)
463       
464    def __radd__(self, other):
465        """
466            Add two data sets
467           
468            @param other: data set to add to the current one
469            @return: new data set
470            @raise ValueError: raised when two data sets are incompatible
471        """
472        def operation(a, b): return b+a
473        return self._perform_operation(other, operation)
474       
475    def __sub__(self, other):
476        """
477            Subtract two data sets
478           
479            @param other: data set to subtract from the current one
480            @return: new data set
481            @raise ValueError: raised when two data sets are incompatible
482        """
483        def operation(a, b): return a-b
484        return self._perform_operation(other, operation)
485       
486    def __rsub__(self, other):
487        """
488            Subtract two data sets
489           
490            @param other: data set to subtract from the current one
491            @return: new data set
492            @raise ValueError: raised when two data sets are incompatible
493        """
494        def operation(a, b): return b-a
495        return self._perform_operation(other, operation)
496       
497    def __mul__(self, other):
498        """
499            Multiply two data sets
500           
501            @param other: data set to subtract from the current one
502            @return: new data set
503            @raise ValueError: raised when two data sets are incompatible
504        """
505        def operation(a, b): return a*b
506        return self._perform_operation(other, operation)
507       
508    def __rmul__(self, other):
509        """
510            Multiply two data sets
511           
512            @param other: data set to subtract from the current one
513            @return: new data set
514            @raise ValueError: raised when two data sets are incompatible
515        """
516        def operation(a, b): return b*a
517        return self._perform_operation(other, operation)
518       
519    def __div__(self, other):
520        """
521            Divided a data set by another
522           
523            @param other: data set that the current one is divided by
524            @return: new data set
525            @raise ValueError: raised when two data sets are incompatible
526        """
527        def operation(a, b): return a/b
528        return self._perform_operation(other, operation)
529       
530    def __rdiv__(self, other):
531        """
532            Divided a data set by another
533           
534            @param other: data set that the current one is divided by
535            @return: new data set
536            @raise ValueError: raised when two data sets are incompatible
537        """
538        def operation(a, b): return b/a
539        return self._perform_operation(other, operation)           
540           
541class Data1D(plottable_1D, DataInfo):
542    """
543        1D data class
544    """
545    x_unit = '1/A'
546    y_unit = '1/cm'
547   
548    def __init__(self, x, y, dx=None, dy=None):
549        DataInfo.__init__(self)
550        plottable_1D.__init__(self, x, y, dx, dy)
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   
790 
791 
Note: See TracBrowser for help on using the repository browser.