source: sasview/sansdataloader/src/sans/dataloader/data_info.py @ 25a40ef0

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 25a40ef0 was a00bd02, checked in by Jae Cho <jhjcho@…>, 13 years ago

moving datainfo to dataloader

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