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

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

cleaning up

  • Property mode set to 100644
File size: 37.7 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, err_data, qx_data, qy_data,
962                            q_data, mask, dqx_data=dqx_data, dqy_data=dqy_data)
963
964        clone.title       = self.title
965        clone.run         = self.run
966        clone.filename    = self.filename
967        clone.instrument  = self.instrument
968        clone.notes       = deepcopy(self.notes)
969        clone.process     = deepcopy(self.process)
970        clone.detector    = deepcopy(self.detector)
971        clone.sample      = deepcopy(self.sample)
972        clone.source      = deepcopy(self.source)
973        clone.collimation = deepcopy(self.collimation)
974        clone.meta_data   = deepcopy(self.meta_data)
975        clone.errors      = deepcopy(self.errors)
976       
977        return clone
978 
979    def _validity_check(self, other):
980        """
981        Checks that the data lengths are compatible.
982        Checks that the x vectors are compatible.
983        Returns errors vectors equal to original
984        errors vectors if they were present or vectors
985        of zeros when none was found.
986       
987        :param other: other data set for operation
988       
989        :return: dy for self, dy for other [numpy arrays]
990       
991        :raise ValueError: when lengths are not compatible
992       
993        """
994        err_other = None
995        if isinstance(other, Data2D):
996            # Check that data lengths are the same
997            if numpy.size(self.data) != numpy.size(other.data):
998                msg = "Unable to perform operation: data length are not equal"
999                raise ValueError, msg
1000            if numpy.size(self.qx_data) != numpy.size(other.qx_data):
1001                msg = "Unable to perform operation: data length are not equal"
1002                raise ValueError, msg
1003            if numpy.size(self.qy_data) != numpy.size(other.qy_data):
1004                msg = "Unable to perform operation: data length are not equal"
1005                raise ValueError, msg
1006            # Here we could also extrapolate between data points
1007            if numpy.size(self.data) < 2:
1008                msg = "Incompatible data sets: I-values do not match"
1009                raise ValueError, msg
1010            for ind in range(len(self.data)):
1011                if self.qx_data[ind] != other.qx_data[ind]:
1012                    msg = "Incompatible data sets: qx-values do not match"
1013                    raise ValueError, msg
1014                if self.qy_data[ind] != other.qy_data[ind]:
1015                    msg = "Incompatible data sets: qy-values do not match"
1016                    raise ValueError, msg
1017                   
1018            # Check that the scales match
1019            err_other = other.err_data
1020            if other.err_data == None or \
1021                (numpy.size(other.err_data) != numpy.size(other.data)):
1022                err_other = numpy.zeros(numpy.size(other.data))
1023           
1024        # Check that we have errors, otherwise create zero vector
1025        err = self.err_data
1026        if self.err_data == None or \
1027            (numpy.size(self.err_data) != numpy.size(self.data)):
1028            err = numpy.zeros(numpy.size(other.data))
1029           
1030        return err, err_other
1031 
1032    def _perform_operation(self, other, operation):
1033        """
1034        Perform 2D operations between data sets
1035       
1036        :param other: other data set
1037        :param operation: function defining the operation
1038       
1039        """
1040        # First, check the data compatibility
1041        dy, dy_other = self._validity_check(other)
1042        result = self.clone_without_data(numpy.size(self.data))
1043        result.xmin = self.xmin
1044        result.xmax = self.xmax
1045        result.ymin = self.ymin
1046        result.ymax = self.ymax
1047        if self.dqx_data == None or self.dqy_data == None:
1048            result.dqx_data = None
1049            result.dqy_data = None
1050        else:
1051            result.dqx_data = numpy.zeros(len(self.data))
1052            result.dqy_data = numpy.zeros(len(self.data))
1053        for i in range(numpy.size(self.data)):
1054            result.data[i] = self.data[i]
1055            if self.err_data is not None and \
1056                numpy.size(self.data) == numpy.size(self.err_data):
1057                result.err_data[i] = self.err_data[i]   
1058            if self.dqx_data is not None:
1059                result.dqx_data[i] = self.dqx_data[i]
1060            if self.dqy_data is not None:
1061                result.dqy_data[i] = self.dqy_data[i]
1062            result.qx_data[i] = self.qx_data[i]
1063            result.qy_data[i] = self.qy_data[i]
1064            result.q_data[i] = self.q_data[i]
1065            result.mask[i] = self.mask[i]
1066           
1067            a = Uncertainty(self.data[i], dy[i]**2)
1068            if isinstance(other, Data2D):
1069                b = Uncertainty(other.data[i], dy_other[i]**2)
1070                if other.dqx_data is not None and \
1071                        result.dqx_data is not None:
1072                    result.dqx_data[i] *= self.dqx_data[i]
1073                    result.dqx_data[i] += (other.dqx_data[i]**2)
1074                    result.dqx_data[i] /= 2
1075                    result.dqx_data[i] = math.sqrt(result.dqx_data[i])     
1076                if other.dqy_data is not None and \
1077                        result.dqy_data is not None:
1078                    result.dqy_data[i] *= self.dqy_data[i]
1079                    result.dqy_data[i] += (other.dqy_data[i]**2)
1080                    result.dqy_data[i] /= 2
1081                    result.dqy_data[i] = math.sqrt(result.dqy_data[i])
1082            else:
1083                b = other
1084           
1085            output = operation(a, b)
1086            result.data[i] = output.x
1087            result.err_data[i] = math.sqrt(math.fabs(output.variance))
1088        return result
1089   
1090    def _validity_check_union(self, other):
1091        """
1092        Checks that the data lengths are compatible.
1093        Checks that the x vectors are compatible.
1094        Returns errors vectors equal to original
1095        errors vectors if they were present or vectors
1096        of zeros when none was found.
1097       
1098        :param other: other data set for operation
1099       
1100        :return: bool
1101       
1102        :raise ValueError: when data types are not compatible
1103       
1104        """
1105        if not isinstance(other, Data2D):
1106            msg = "Unable to perform operation: different types of data set"
1107            raise ValueError, msg   
1108        return True
1109   
1110    def _perform_union(self, other):
1111        """
1112        Perform 2D operations between data sets
1113       
1114        :param other: other data set
1115        :param operation: function defining the operation
1116       
1117        """
1118        # First, check the data compatibility
1119        self._validity_check_union(other)
1120        result = self.clone_without_data(numpy.size(self.data) + \
1121                                         numpy.size(other.data))
1122        result.xmin = self.xmin
1123        result.xmax = self.xmax
1124        result.ymin = self.ymin
1125        result.ymax = self.ymax
1126        if self.dqx_data == None or self.dqy_data == None or \
1127                other.dqx_data == None or other.dqy_data == None :
1128            result.dqx_data = None
1129            result.dqy_data = None
1130        else:
1131            result.dqx_data = numpy.zeros(len(self.data) + \
1132                                         numpy.size(other.data))
1133            result.dqy_data = numpy.zeros(len(self.data) + \
1134                                         numpy.size(other.data))
1135       
1136        result.data = numpy.append(self.data, other.data)
1137        result.qx_data = numpy.append(self.qx_data, other.qx_data)
1138        result.qy_data = numpy.append(self.qy_data, other.qy_data)
1139        result.q_data = numpy.append(self.q_data, other.q_data)
1140        result.mask = numpy.append(self.mask, other.mask)
1141        if result.err_data is not None:
1142            result.err_data = numpy.append(self.err_data, other.err_data) 
1143        if self.dqx_data is not None:
1144            result.dqx_data = numpy.append(self.dqx_data, other.dqx_data)
1145        if self.dqy_data is not None:
1146            result.dqy_data = numpy.append(self.dqy_data, other.dqy_data)
1147
1148        return result
Note: See TracBrowser for help on using the repository browser.