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

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

fixed the problem instead of removing the utest

  • Property mode set to 100644
File size: 37.2 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            for i in range(len(self.x)):
761                if self.x[i] != other.x[i]:
762                    msg = "Incompatible data sets: x-values do not match"
763                    raise ValueError, msg
764                """
765                if self.dxl != None and other.dxl == None:
766                    msg = "Incompatible data sets: dxl-values do not match"
767                    raise ValueError, msg
768                if self.dxl == None and other.dxl != None:
769                    msg = "Incompatible data sets: dxl-values do not match"
770                    raise ValueError, msg
771                if self.dxw != None and other.dxw == None:
772                    msg = "Incompatible data sets: dxw-values do not match"
773                    raise ValueError, msg
774                if self.dxw == None and other.dxw != None:
775                    msg = "Incompatible data sets: dxw-values do not match"
776                    raise ValueError, msg
777                """
778            # Check that the other data set has errors, otherwise
779            # create zero vector
780            dy_other = other.dy
781            if other.dy == None or (len(other.dy) != len(other.y)):
782                dy_other = numpy.zeros(len(other.y))
783           
784        # Check that we have errors, otherwise create zero vector
785        dy = self.dy
786        if self.dy == None or (len(self.dy) != len(self.y)):
787            dy = numpy.zeros(len(self.y))
788           
789        return dy, dy_other
790
791    def _perform_operation(self, other, operation):
792        """
793        """
794        # First, check the data compatibility
795        dy, dy_other = self._validity_check(other)
796        result = self.clone_without_data(len(self.x))
797        if self.dxw == None:
798            result.dxw = None
799        else:
800            result.dxw = numpy.zeros(len(self.x))
801        if self.dxl == None:
802            result.dxl = None
803        else:
804            result.dxl = numpy.zeros(len(self.x))
805
806        for i in range(len(self.x)):
807            result.x[i] = self.x[i]
808            if self.dx is not None and len(self.x) == len(self.dx):
809                result.dx[i] = self.dx[i]
810            if self.dxw is not None and len(self.x) == len(self.dxw):
811                result.dxw[i] = self.dxw[i]
812            if self.dxl is not None and len(self.x) == len(self.dxl):
813                result.dxl[i] = self.dxl[i]
814           
815            a = Uncertainty(self.y[i], dy[i]**2)
816            if isinstance(other, Data1D):
817                b = Uncertainty(other.y[i], dy_other[i]**2)
818                if other.dx is not None:
819                    result.dx[i] *= self.dx[i]
820                    result.dx[i] += (other.dx[i]**2)
821                    result.dx[i] /= 2
822                    result.dx[i] = math.sqrt(result.dx[i])
823                if result.dxl is not None and other.dxl is not None:
824                    result.dxl[i] *= self.dxl[i]
825                    result.dxl[i] += (other.dxl[i]**2)
826                    result.dxl[i] /= 2
827                    result.dxl[i] = math.sqrt(result.dxl[i])
828            else:
829                b = other
830           
831            output = operation(a, b)
832            result.y[i] = output.x
833            result.dy[i] = math.sqrt(math.fabs(output.variance))
834        return result
835   
836    def _validity_check_union(self, other):
837        """
838        Checks that the data lengths are compatible.
839        Checks that the x vectors are compatible.
840        Returns errors vectors equal to original
841        errors vectors if they were present or vectors
842        of zeros when none was found.
843       
844        :param other: other data set for operation
845       
846        :return: bool
847       
848        :raise ValueError: when data types are not compatible
849       
850        """
851        if not isinstance(other, Data1D):
852            msg = "Unable to perform operation: different types of data set"
853            raise ValueError, msg   
854        return True
855
856    def _perform_union(self, other):
857        """
858        """
859        # First, check the data compatibility
860        self._validity_check_union(other)
861        result = self.clone_without_data(len(self.x) + len(other.x))
862        if self.dy == None or other.dy is None:
863            result.dy = None
864        else:
865            result.dy = numpy.zeros(len(self.x) + len(other.x))
866        if self.dx == None or other.dx is None:
867            result.dx = None
868        else:
869            result.dx = numpy.zeros(len(self.x) + len(other.x))
870        if self.dxw == None or other.dxw is None:
871            result.dxw = None
872        else:
873            result.dxw = numpy.zeros(len(self.x) + len(other.x))
874        if self.dxl == None or other.dxl is None:
875            result.dxl = None
876        else:
877            result.dxl = numpy.zeros(len(self.x) + len(other.x))
878
879        result.x = numpy.append(self.x, other.x)
880        #argsorting
881        ind = numpy.argsort(result.x)
882        result.x = result.x[ind]
883        result.y = numpy.append(self.y, other.y)
884        result.y = result.y[ind]
885        if result.dy != None:
886            result.dy = numpy.append(self.dy, other.dy)
887            result.dy = result.dy[ind]
888        if result.dx is not None:
889            result.dx = numpy.append(self.dx, other.dx)
890            result.dx = result.dx[ind]
891        if result.dxw is not None:
892            result.dxw = numpy.append(self.dxw, other.dxw)
893            result.dxw = result.dxw[ind]
894        if result.dxl is not None:
895            result.dxl = numpy.append(self.dxl, other.dxl)
896            result.dxl = result.dxl[ind]
897        return result
898       
899       
900class Data2D(plottable_2D, DataInfo):
901    """
902    2D data class
903    """
904    ## Units for Q-values
905    Q_unit = '1/A'
906   
907    ## Units for I(Q) values
908    I_unit = '1/cm'
909   
910    ## Vector of Q-values at the center of each bin in x
911    x_bins = None
912   
913    ## Vector of Q-values at the center of each bin in y
914    y_bins = None
915   
916    def __init__(self, data=None, err_data=None, qx_data=None,
917                 qy_data=None, q_data=None, mask=None,
918                 dqx_data=None, dqy_data=None):
919        self.y_bins = []
920        self.x_bins = []
921        DataInfo.__init__(self)
922        plottable_2D.__init__(self, data, err_data, qx_data,
923                              qy_data, q_data, mask, dqx_data, dqy_data)
924        if len(self.detector) > 0:
925            raise RuntimeError, "Data2D: Detector bank already filled at init"
926
927    def __str__(self):
928        _str = "%s\n" % DataInfo.__str__(self)
929       
930        _str += "Data:\n"
931        _str += "   Type:         %s\n" % self.__class__.__name__
932        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
933        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
934        #leny = 0
935        #if len(self.data) > 0:
936        #    leny = len(self.data)
937        _str += "   Length:       %g \n" % (len(self.data))
938       
939        return _str
940 
941    def clone_without_data(self, length=0, clone=None):
942        """
943        Clone the current object, without copying the data (which
944        will be filled out by a subsequent operation).
945        The data arrays will be initialized to zero.
946       
947        :param length: length of the data array to be initialized
948        :param clone: if provided, the data will be copied to clone
949        """
950        from copy import deepcopy
951       
952        if clone is None or not issubclass(clone.__class__, Data2D):
953            data = numpy.zeros(length)
954            err_data = numpy.zeros(length)
955            qx_data = numpy.zeros(length)
956            qy_data = numpy.zeros(length)
957            q_data = numpy.zeros(length)
958            mask = numpy.zeros(length)
959            dqx_data = None
960            dqy_data = None
961            clone = Data2D(data=data, err_data=err_data, 
962                           qx_data=qx_data, qy_data=qy_data, 
963                           q_data=q_data, mask=mask)
964
965        clone.title       = self.title
966        clone.run         = self.run
967        clone.filename    = self.filename
968        clone.instrument  = self.instrument
969        clone.notes       = deepcopy(self.notes)
970        clone.process     = deepcopy(self.process)
971        clone.detector    = deepcopy(self.detector)
972        clone.sample      = deepcopy(self.sample)
973        clone.source      = deepcopy(self.source)
974        clone.collimation = deepcopy(self.collimation)
975        clone.meta_data   = deepcopy(self.meta_data)
976        clone.errors      = deepcopy(self.errors)
977       
978        return clone
979 
980    def _validity_check(self, other):
981        """
982        Checks that the data lengths are compatible.
983        Checks that the x vectors are compatible.
984        Returns errors vectors equal to original
985        errors vectors if they were present or vectors
986        of zeros when none was found.
987       
988        :param other: other data set for operation
989       
990        :return: dy for self, dy for other [numpy arrays]
991       
992        :raise ValueError: when lengths are not compatible
993       
994        """
995        err_other = None
996        if isinstance(other, Data2D):
997            # Check that data lengths are the same
998            if len(self.data) != len(other.data) or \
999                len(self.qx_data) != len(other.qx_data) or \
1000                len(self.qy_data) != len(other.qy_data):
1001                msg = "Unable to perform operation: data length are not equal"
1002                raise ValueError, msg
1003            #if len(self.data) < 1:
1004            #    msg = "Incompatible data sets: I-values do not match"
1005            #    raise ValueError, msg
1006            for ind in range(len(self.data)):
1007                if self.qx_data[ind] != other.qx_data[ind]:
1008                    msg = "Incompatible data sets: qx-values do not match"
1009                    raise ValueError, msg
1010                if self.qy_data[ind] != other.qy_data[ind]:
1011                    msg = "Incompatible data sets: qy-values do not match"
1012                    raise ValueError, msg
1013                   
1014            # Check that the scales match
1015            err_other = other.err_data
1016            if other.err_data == None or \
1017                (len(other.err_data) != len(other.data)):
1018                err_other = numpy.zeros(len(other.data))
1019           
1020        # Check that we have errors, otherwise create zero vector
1021        err = self.err_data
1022        if self.err_data == None or \
1023            (len(self.err_data) != len(self.data)):
1024            err = numpy.zeros(len(other.data))
1025           
1026        return err, err_other
1027 
1028    def _perform_operation(self, other, operation):
1029        """
1030        Perform 2D operations between data sets
1031       
1032        :param other: other data set
1033        :param operation: function defining the operation
1034       
1035        """
1036        # First, check the data compatibility
1037        dy, dy_other = self._validity_check(other)
1038        result = self.clone_without_data(numpy.size(self.data))
1039        if self.dqx_data == None or self.dqy_data == None:
1040            result.dqx_data = None
1041            result.dqy_data = None
1042        else:
1043            result.dqx_data = numpy.zeros(len(self.data))
1044            result.dqy_data = numpy.zeros(len(self.data))
1045        for i in range(numpy.size(self.data)):
1046            result.data[i] = self.data[i]
1047            if self.err_data is not None and \
1048                numpy.size(self.data) == numpy.size(self.err_data):
1049                result.err_data[i] = self.err_data[i]   
1050            if self.dqx_data is not None:
1051                result.dqx_data[i] = self.dqx_data[i]
1052            if self.dqy_data is not None:
1053                result.dqy_data[i] = self.dqy_data[i]
1054            result.qx_data[i] = self.qx_data[i]
1055            result.qy_data[i] = self.qy_data[i]
1056            result.q_data[i] = self.q_data[i]
1057            result.mask[i] = self.mask[i]
1058           
1059            a = Uncertainty(self.data[i], dy[i]**2)
1060            if isinstance(other, Data2D):
1061                b = Uncertainty(other.data[i], dy_other[i]**2)
1062                if other.dqx_data is not None and \
1063                        result.dqx_data is not None:
1064                    result.dqx_data[i] *= self.dqx_data[i]
1065                    result.dqx_data[i] += (other.dqx_data[i]**2)
1066                    result.dqx_data[i] /= 2
1067                    result.dqx_data[i] = math.sqrt(result.dqx_data[i])     
1068                if other.dqy_data is not None and \
1069                        result.dqy_data is not None:
1070                    result.dqy_data[i] *= self.dqy_data[i]
1071                    result.dqy_data[i] += (other.dqy_data[i]**2)
1072                    result.dqy_data[i] /= 2
1073                    result.dqy_data[i] = math.sqrt(result.dqy_data[i])
1074            else:
1075                b = other
1076           
1077            output = operation(a, b)
1078            result.data[i] = output.x
1079            result.err_data[i] = math.sqrt(math.fabs(output.variance))
1080        return result
1081   
1082    def _validity_check_union(self, other):
1083        """
1084        Checks that the data lengths are compatible.
1085        Checks that the x vectors are compatible.
1086        Returns errors vectors equal to original
1087        errors vectors if they were present or vectors
1088        of zeros when none was found.
1089       
1090        :param other: other data set for operation
1091       
1092        :return: bool
1093       
1094        :raise ValueError: when data types are not compatible
1095       
1096        """
1097        if not isinstance(other, Data2D):
1098            msg = "Unable to perform operation: different types of data set"
1099            raise ValueError, msg   
1100        return True
1101   
1102    def _perform_union(self, other):
1103        """
1104        Perform 2D operations between data sets
1105       
1106        :param other: other data set
1107        :param operation: function defining the operation
1108       
1109        """
1110        # First, check the data compatibility
1111        self._validity_check_union(other)
1112        result = self.clone_without_data(numpy.size(self.data) + \
1113                                         numpy.size(other.data))
1114        result.xmin = self.xmin
1115        result.xmax = self.xmax
1116        result.ymin = self.ymin
1117        result.ymax = self.ymax
1118        if self.dqx_data == None or self.dqy_data == None or \
1119                other.dqx_data == None or other.dqy_data == None :
1120            result.dqx_data = None
1121            result.dqy_data = None
1122        else:
1123            result.dqx_data = numpy.zeros(len(self.data) + \
1124                                         numpy.size(other.data))
1125            result.dqy_data = numpy.zeros(len(self.data) + \
1126                                         numpy.size(other.data))
1127       
1128        result.data = numpy.append(self.data, other.data)
1129        result.qx_data = numpy.append(self.qx_data, other.qx_data)
1130        result.qy_data = numpy.append(self.qy_data, other.qy_data)
1131        result.q_data = numpy.append(self.q_data, other.q_data)
1132        result.mask = numpy.append(self.mask, other.mask)
1133        if result.err_data is not None:
1134            result.err_data = numpy.append(self.err_data, other.err_data) 
1135        if self.dqx_data is not None:
1136            result.dqx_data = numpy.append(self.dqx_data, other.dqx_data)
1137        if self.dqy_data is not None:
1138            result.dqy_data = numpy.append(self.dqy_data, other.dqy_data)
1139
1140        return result
Note: See TracBrowser for help on using the repository browser.