source: sasview/DataLoader/data_info.py @ 4c00964

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

Working on cansas writer

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