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

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

added data operation tool

  • Property mode set to 100644
File size: 37.4 KB
Line 
1"""
2    Module that contains classes to hold information read from
3    reduced data files.
4   
5    A good description of the data members can be found in
6    the CanSAS 1D XML data format:
7   
8    http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
9"""
10#####################################################################
11#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 __add__(self, other):
512        """
513        Add two data sets
514       
515        :param other: data set to add to the current one
516        :return: new data set
517        :raise ValueError: raised when two data sets are incompatible
518        """
519        def operation(a, b):
520            return a + b
521        return self._perform_operation(other, operation)
522       
523    def __radd__(self, other):
524        """
525        Add two data sets
526       
527        :param other: data set to add to the current one
528       
529        :return: new data set
530       
531        :raise ValueError: raised when two data sets are incompatible
532       
533        """
534        def operation(a, b):
535            return b + a
536        return self._perform_operation(other, operation)
537       
538    def __sub__(self, other):
539        """
540        Subtract two data sets
541       
542        :param other: data set to subtract from the current one
543       
544        :return: new data set
545       
546        :raise ValueError: raised when two data sets are incompatible
547       
548        """
549        def operation(a, b):
550            return a - b
551        return self._perform_operation(other, operation)
552       
553    def __rsub__(self, other):
554        """
555        Subtract two data sets
556       
557        :param other: data set to subtract from the current one
558       
559        :return: new data set
560       
561        :raise ValueError: raised when two data sets are incompatible
562       
563        """
564        def operation(a, b):
565            return b - a
566        return self._perform_operation(other, operation)
567       
568    def __mul__(self, other):
569        """
570        Multiply two data sets
571       
572        :param other: data set to subtract from the current one
573       
574        :return: new data set
575       
576        :raise ValueError: raised when two data sets are incompatible
577       
578        """
579        def operation(a, b):
580            return a * b
581        return self._perform_operation(other, operation)
582       
583    def __rmul__(self, other):
584        """
585        Multiply two data sets
586       
587        :param other: data set to subtract from the current one
588       
589        :return: new data set
590       
591        :raise ValueError: raised when two data sets are incompatible
592        """
593        def operation(a, b):
594            return b * a
595        return self._perform_operation(other, operation)
596       
597    def __div__(self, other):
598        """
599        Divided a data set by another
600       
601        :param other: data set that the current one is divided by
602       
603        :return: new data set
604       
605        :raise ValueError: raised when two data sets are incompatible
606       
607        """
608        def operation(a, b):
609            return a/b
610        return self._perform_operation(other, operation)
611       
612    def __rdiv__(self, other):
613        """
614        Divided a data set by another
615       
616        :param other: data set that the current one is divided by
617       
618        :return: new data set
619       
620        :raise ValueError: raised when two data sets are incompatible
621       
622        """
623        def operation(a, b):
624            return b/a
625        return self._perform_operation(other, operation)
626           
627       
628    def __or__(self, other):
629        """
630        Union a data set with another
631       
632        :param other: data set to be unified
633       
634        :return: new data set
635       
636        :raise ValueError: raised when two data sets are incompatible
637       
638        """
639        return self._perform_union(other)
640       
641    def __ror__(self, other):
642        """
643        Union a data set with another
644       
645        :param other: data set to be unified
646       
647        :return: new data set
648       
649        :raise ValueError: raised when two data sets are incompatible
650       
651        """
652        return self._perform_union(other)
653               
654class Data1D(plottable_1D, DataInfo):
655    """
656    1D data class
657    """
658    x_unit = '1/A'
659    y_unit = '1/cm'
660   
661    def __init__(self, x, y, dx=None, dy=None):
662        DataInfo.__init__(self)
663        plottable_1D.__init__(self, x, y, dx, dy)
664       
665    def __str__(self):
666        """
667        Nice printout
668        """
669        _str =  "%s\n" % DataInfo.__str__(self)
670   
671        _str += "Data:\n"
672        _str += "   Type:         %s\n" % self.__class__.__name__
673        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
674        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
675        _str += "   Length:       %g\n" % len(self.x)
676
677        return _str
678
679    def is_slit_smeared(self):
680        """
681        Check whether the data has slit smearing information
682       
683        :return: True is slit smearing info is present, False otherwise
684       
685        """
686        def _check(v):
687            if (v.__class__ == list or v.__class__ == numpy.ndarray) \
688                and len(v) > 0 and min(v) > 0:
689                return True
690           
691            return False
692       
693        return _check(self.dxl) or _check(self.dxw)
694       
695    def clone_without_data(self, length=0, clone=None):
696        """
697        Clone the current object, without copying the data (which
698        will be filled out by a subsequent operation).
699        The data arrays will be initialized to zero.
700       
701        :param length: length of the data array to be initialized
702        :param clone: if provided, the data will be copied to clone
703        """
704        from copy import deepcopy
705       
706        if clone is None or not issubclass(clone.__class__, Data1D):
707            x  = numpy.zeros(length)
708            dx = numpy.zeros(length)
709            y  = numpy.zeros(length)
710            dy = numpy.zeros(length)
711            clone = Data1D(x, y, dx=dx, dy=dy)
712       
713        clone.title       = self.title
714        clone.run         = self.run
715        clone.filename    = self.filename
716        clone.instrument  = self.instrument
717        clone.notes       = deepcopy(self.notes)
718        clone.process     = deepcopy(self.process)
719        clone.detector    = deepcopy(self.detector)
720        clone.sample      = deepcopy(self.sample)
721        clone.source      = deepcopy(self.source)
722        clone.collimation = deepcopy(self.collimation)
723        clone.meta_data   = deepcopy(self.meta_data)
724        clone.errors      = deepcopy(self.errors)
725       
726        return clone
727
728    def _validity_check(self, other):
729        """
730        Checks that the data lengths are compatible.
731        Checks that the x vectors are compatible.
732        Returns errors vectors equal to original
733        errors vectors if they were present or vectors
734        of zeros when none was found.
735       
736        :param other: other data set for operation
737       
738        :return: dy for self, dy for other [numpy arrays]
739       
740        :raise ValueError: when lengths are not compatible
741       
742        """
743        dy_other = None
744        if isinstance(other, Data1D):
745            # Check that data lengths are the same
746            if len(self.x) != len(other.x) or \
747                len(self.y) != len(other.y):
748                msg = "Unable to perform operation: data length are not equal"
749                raise ValueError, msg
750           
751            # Here we could also extrapolate between data points
752            for i in range(len(self.x)):
753                if self.x[i] != other.x[i]:
754                    msg = "Incompatible data sets: x-values do not match"
755                    raise ValueError, msg
756                """
757                if self.dxl != None and other.dxl == None:
758                    msg = "Incompatible data sets: dxl-values do not match"
759                    raise ValueError, msg
760                if self.dxl == None and other.dxl != None:
761                    msg = "Incompatible data sets: dxl-values do not match"
762                    raise ValueError, msg
763                if self.dxw != None and other.dxw == None:
764                    msg = "Incompatible data sets: dxw-values do not match"
765                    raise ValueError, msg
766                if self.dxw == None and other.dxw != None:
767                    msg = "Incompatible data sets: dxw-values do not match"
768                    raise ValueError, msg
769                """
770            # Check that the other data set has errors, otherwise
771            # create zero vector
772            dy_other = other.dy
773            if other.dy == None or (len(other.dy) != len(other.y)):
774                dy_other = numpy.zeros(len(other.y))
775           
776        # Check that we have errors, otherwise create zero vector
777        dy = self.dy
778        if self.dy == None or (len(self.dy) != len(self.y)):
779            dy = numpy.zeros(len(self.y))
780           
781        return dy, dy_other
782
783    def _perform_operation(self, other, operation):
784        """
785        """
786        # First, check the data compatibility
787        dy, dy_other = self._validity_check(other)
788        result = self.clone_without_data(len(self.x))
789        if self.dxw == None:
790            result.dxw = None
791        else:
792            result.dxw = numpy.zeros(len(self.x))
793        if self.dxl == None:
794            result.dxl = None
795        else:
796            result.dxl = numpy.zeros(len(self.x))
797
798        for i in range(len(self.x)):
799            result.x[i] = self.x[i]
800            if self.dx is not None and len(self.x) == len(self.dx):
801                result.dx[i] = self.dx[i]
802            if self.dxw is not None and len(self.x) == len(self.dxw):
803                result.dxw[i] = self.dxw[i]
804            if self.dxl is not None and len(self.x) == len(self.dxl):
805                result.dxl[i] = self.dxl[i]
806           
807            a = Uncertainty(self.y[i], dy[i]**2)
808            if isinstance(other, Data1D):
809                b = Uncertainty(other.y[i], dy_other[i]**2)
810                if other.dx is not None:
811                    result.dx[i] *= self.dx[i]
812                    result.dx[i] += (other.dx[i]**2)
813                    result.dx[i] /= 2
814                    result.dx[i] = math.sqrt(result.dx[i])
815                if result.dxl is not None and other.dxl is not None:
816                    result.dxl[i] *= self.dxl[i]
817                    result.dxl[i] += (other.dxl[i]**2)
818                    result.dxl[i] /= 2
819                    result.dxl[i] = math.sqrt(result.dxl[i])
820            else:
821                b = other
822           
823            output = operation(a, b)
824            result.y[i] = output.x
825            result.dy[i] = math.sqrt(math.fabs(output.variance))
826        return result
827   
828    def _validity_check_union(self, other):
829        """
830        Checks that the data lengths are compatible.
831        Checks that the x vectors are compatible.
832        Returns errors vectors equal to original
833        errors vectors if they were present or vectors
834        of zeros when none was found.
835       
836        :param other: other data set for operation
837       
838        :return: bool
839       
840        :raise ValueError: when data types are not compatible
841       
842        """
843        if not isinstance(other, Data1D):
844            msg = "Unable to perform operation: different types of data set"
845            raise ValueError, msg   
846        return True
847
848    def _perform_union(self, other):
849        """
850        """
851        # First, check the data compatibility
852        self._validity_check_union(other)
853        result = self.clone_without_data(len(self.x) + len(other.x))
854        if self.dy == None or other.dy is None:
855            result.dy = None
856        else:
857            result.dy = numpy.zeros(len(self.x) + len(other.x))
858        if self.dx == None or other.dx is None:
859            result.dx = None
860        else:
861            result.dx = numpy.zeros(len(self.x) + len(other.x))
862        if self.dxw == None or other.dxw is None:
863            result.dxw = None
864        else:
865            result.dxw = numpy.zeros(len(self.x) + len(other.x))
866        if self.dxl == None or other.dxl is None:
867            result.dxl = None
868        else:
869            result.dxl = numpy.zeros(len(self.x) + len(other.x))
870
871        result.x = numpy.append(self.x, other.x)
872        #argsorting
873        ind = numpy.argsort(result.x)
874        result.x = result.x[ind]
875        result.y = numpy.append(self.y, other.y)
876        result.y = result.y[ind]
877        if result.dy != None:
878            result.dy = numpy.append(self.dy, other.dy)
879            result.dy = result.dy[ind]
880        if result.dx is not None:
881            result.dx = numpy.append(self.dx, other.dx)
882            result.dx = result.dx[ind]
883        if result.dxw is not None:
884            result.dxw = numpy.append(self.dxw, other.dxw)
885            result.dxw = result.dxw[ind]
886        if result.dxl is not None:
887            result.dxl = numpy.append(self.dxl, other.dxl)
888            result.dxl = result.dxl[ind]
889        return result
890       
891       
892class Data2D(plottable_2D, DataInfo):
893    """
894    2D data class
895    """
896    ## Units for Q-values
897    Q_unit = '1/A'
898   
899    ## Units for I(Q) values
900    I_unit = '1/cm'
901   
902    ## Vector of Q-values at the center of each bin in x
903    x_bins = None
904   
905    ## Vector of Q-values at the center of each bin in y
906    y_bins = None
907   
908    def __init__(self, data=None, err_data=None, qx_data=None,
909                 qy_data=None, q_data=None, mask=None,
910                 dqx_data=None, dqy_data=None):
911        self.y_bins = []
912        self.x_bins = []
913        DataInfo.__init__(self)
914        plottable_2D.__init__(self, data, err_data, qx_data,
915                              qy_data, q_data, mask, dqx_data, dqy_data)
916        if len(self.detector) > 0:
917            raise RuntimeError, "Data2D: Detector bank already filled at init"
918
919    def __str__(self):
920        _str = "%s\n" % DataInfo.__str__(self)
921       
922        _str += "Data:\n"
923        _str += "   Type:         %s\n" % self.__class__.__name__
924        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
925        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
926        #leny = 0
927        #if len(self.data) > 0:
928        #    leny = len(self.data)
929        _str += "   Length:       %g \n" % (len(self.data))
930       
931        return _str
932 
933    def clone_without_data(self, length=0, clone=None):
934        """
935        Clone the current object, without copying the data (which
936        will be filled out by a subsequent operation).
937        The data arrays will be initialized to zero.
938       
939        :param length: length of the data array to be initialized
940        :param clone: if provided, the data will be copied to clone
941        """
942        from copy import deepcopy
943       
944        if clone is None or not issubclass(clone.__class__, Data2D):
945            data = numpy.zeros(length)
946            err_data = numpy.zeros(length)
947            qx_data = numpy.zeros(length)
948            qy_data = numpy.zeros(length)
949            q_data = numpy.zeros(length)
950            mask = numpy.zeros(length)
951            dqx_data = None
952            dqy_data = None
953            clone = Data2D(data, err_data, qx_data, qy_data,
954                            q_data, mask, dqx_data=dqx_data, dqy_data=dqy_data)
955
956        clone.title       = self.title
957        clone.run         = self.run
958        clone.filename    = self.filename
959        clone.instrument  = self.instrument
960        clone.notes       = deepcopy(self.notes)
961        clone.process     = deepcopy(self.process)
962        clone.detector    = deepcopy(self.detector)
963        clone.sample      = deepcopy(self.sample)
964        clone.source      = deepcopy(self.source)
965        clone.collimation = deepcopy(self.collimation)
966        clone.meta_data   = deepcopy(self.meta_data)
967        clone.errors      = deepcopy(self.errors)
968       
969        return clone
970 
971    def _validity_check(self, other):
972        """
973        Checks that the data lengths are compatible.
974        Checks that the x vectors are compatible.
975        Returns errors vectors equal to original
976        errors vectors if they were present or vectors
977        of zeros when none was found.
978       
979        :param other: other data set for operation
980       
981        :return: dy for self, dy for other [numpy arrays]
982       
983        :raise ValueError: when lengths are not compatible
984       
985        """
986        err_other = None
987        if isinstance(other, Data2D):
988            # Check that data lengths are the same
989            if numpy.size(self.data) != numpy.size(other.data):
990                msg = "Unable to perform operation: data length are not equal"
991                raise ValueError, msg
992            if numpy.size(self.qx_data) != numpy.size(other.qx_data):
993                msg = "Unable to perform operation: data length are not equal"
994                raise ValueError, msg
995            if numpy.size(self.qy_data) != numpy.size(other.qy_data):
996                msg = "Unable to perform operation: data length are not equal"
997                raise ValueError, msg
998            # Here we could also extrapolate between data points
999            if numpy.size(self.data) < 2:
1000                msg = "Incompatible data sets: I-values do not match"
1001                raise ValueError, msg
1002            for ind in range(len(self.data)):
1003                if self.qx_data[ind] != other.qx_data[ind]:
1004                    msg = "Incompatible data sets: qx-values do not match"
1005                    raise ValueError, msg
1006                if self.qy_data[ind] != other.qy_data[ind]:
1007                    msg = "Incompatible data sets: qy-values do not match"
1008                    raise ValueError, msg
1009                   
1010            # Check that the scales match
1011            err_other = other.err_data
1012            if other.err_data == None or \
1013                (numpy.size(other.err_data) != numpy.size(other.data)):
1014                err_other = numpy.zeros(numpy.size(other.data))
1015           
1016        # Check that we have errors, otherwise create zero vector
1017        err = self.err_data
1018        if self.err_data == None or \
1019            (numpy.size(self.err_data) != numpy.size(self.data)):
1020            err = numpy.zeros(numpy.size(other.data))
1021           
1022        return err, err_other
1023 
1024    def _perform_operation(self, other, operation):
1025        """
1026        Perform 2D operations between data sets
1027       
1028        :param other: other data set
1029        :param operation: function defining the operation
1030       
1031        """
1032        # First, check the data compatibility
1033        dy, dy_other = self._validity_check(other)
1034        result = self.clone_without_data(numpy.size(self.data))
1035        result.xmin = self.xmin
1036        result.xmax = self.xmax
1037        result.ymin = self.ymin
1038        result.ymax = self.ymax
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.