source: sasview/DataLoader/data_info.py @ d6513cd

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

Data loader update (still working)

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