source: sasview/sansdataloader/src/sans/dataloader/data_info.py @ 3cfa760

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 3cfa760 was c2e5898, checked in by Jae Cho <jhjcho@…>, 12 years ago

the data in the grid copied from datainfo can now be in data operation

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