source: sasview/DataLoader/data_info.py @ ea290ee

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 ea290ee was ca10d8e, checked in by Gervaise Alina <gervyh@…>, 16 years ago

reverse code from version 1237 with modification on output axis unit

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