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

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 33add9e was f60a8c2, checked in by Mathieu Doucet <doucetm@…>, 13 years ago

Pep-8-ification

  • Property mode set to 100644
File size: 28.5 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           
628class Data1D(plottable_1D, DataInfo):
629    """
630    1D data class
631    """
632    x_unit = '1/A'
633    y_unit = '1/cm'
634   
635    def __init__(self, x, y, dx=None, dy=None):
636        DataInfo.__init__(self)
637        plottable_1D.__init__(self, x, y, dx, dy)
638       
639    def __str__(self):
640        """
641        Nice printout
642        """
643        _str =  "%s\n" % DataInfo.__str__(self)
644   
645        _str += "Data:\n"
646        _str += "   Type:         %s\n" % self.__class__.__name__
647        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
648        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
649        _str += "   Length:       %g\n" % len(self.x)
650
651        return _str
652
653    def is_slit_smeared(self):
654        """
655        Check whether the data has slit smearing information
656       
657        :return: True is slit smearing info is present, False otherwise
658       
659        """
660        def _check(v):
661            if (v.__class__ == list or v.__class__ == numpy.ndarray) \
662                and len(v) > 0 and min(v) > 0:
663                return True
664           
665            return False
666       
667        return _check(self.dxl) or _check(self.dxw)
668       
669    def clone_without_data(self, length=0, clone=None):
670        """
671        Clone the current object, without copying the data (which
672        will be filled out by a subsequent operation).
673        The data arrays will be initialized to zero.
674       
675        :param length: length of the data array to be initialized
676        :param clone: if provided, the data will be copied to clone
677        """
678        from copy import deepcopy
679       
680        if clone is None or not issubclass(clone.__class__, Data1D):
681            x  = numpy.zeros(length)
682            dx = numpy.zeros(length)
683            y  = numpy.zeros(length)
684            dy = numpy.zeros(length)
685            clone = Data1D(x, y, dx=dx, dy=dy)
686       
687        clone.title       = self.title
688        clone.run         = self.run
689        clone.filename    = self.filename
690        clone.instrument  = self.instrument
691        clone.notes       = deepcopy(self.notes)
692        clone.process     = deepcopy(self.process)
693        clone.detector    = deepcopy(self.detector)
694        clone.sample      = deepcopy(self.sample)
695        clone.source      = deepcopy(self.source)
696        clone.collimation = deepcopy(self.collimation)
697        clone.meta_data   = deepcopy(self.meta_data)
698        clone.errors      = deepcopy(self.errors)
699       
700        return clone
701
702    def _validity_check(self, other):
703        """
704        Checks that the data lengths are compatible.
705        Checks that the x vectors are compatible.
706        Returns errors vectors equal to original
707        errors vectors if they were present or vectors
708        of zeros when none was found.
709       
710        :param other: other data set for operation
711       
712        :return: dy for self, dy for other [numpy arrays]
713       
714        :raise ValueError: when lengths are not compatible
715       
716        """
717        dy_other = None
718        if isinstance(other, Data1D):
719            # Check that data lengths are the same
720            if len(self.x) != len(other.x) or \
721                len(self.y) != len(other.y):
722                msg = "Unable to perform operation: data length are not equal"
723                raise ValueError, msg
724           
725            # Here we could also extrapolate between data points
726            for i in range(len(self.x)):
727                if self.x[i] != other.x[i]:
728                    msg = "Incompatible data sets: x-values do not match"
729                    raise ValueError, msg
730           
731            # Check that the other data set has errors, otherwise
732            # create zero vector
733            dy_other = other.dy
734            if other.dy == None or (len(other.dy) != len(other.y)):
735                dy_other = numpy.zeros(len(other.y))
736           
737        # Check that we have errors, otherwise create zero vector
738        dy = self.dy
739        if self.dy == None or (len(self.dy) != len(self.y)):
740            dy = numpy.zeros(len(self.y))
741           
742        return dy, dy_other
743
744    def _perform_operation(self, other, operation):
745        """
746        """
747        # First, check the data compatibility
748        dy, dy_other = self._validity_check(other)
749        result = self.clone_without_data(len(self.x))
750       
751        for i in range(len(self.x)):
752            result.x[i] = self.x[i]
753            if self.dx is not None and len(self.x) == len(self.dx):
754                result.dx[i] = self.dx[i]
755           
756            a = Uncertainty(self.y[i], dy[i]**2)
757            if isinstance(other, Data1D):
758                b = Uncertainty(other.y[i], dy_other[i]**2)
759            else:
760                b = other
761           
762            output = operation(a, b)
763            result.y[i] = output.x
764            result.dy[i] = math.sqrt(math.fabs(output.variance))
765        return result
766       
767       
768class Data2D(plottable_2D, DataInfo):
769    """
770    2D data class
771    """
772    ## Units for Q-values
773    Q_unit = '1/A'
774   
775    ## Units for I(Q) values
776    I_unit = '1/cm'
777   
778    ## Vector of Q-values at the center of each bin in x
779    x_bins = None
780   
781    ## Vector of Q-values at the center of each bin in y
782    y_bins = None
783   
784    def __init__(self, data=None, err_data=None, qx_data=None,
785                 qy_data=None, q_data=None, mask=None,
786                 dqx_data=None, dqy_data=None):
787        self.y_bins = []
788        self.x_bins = []
789        DataInfo.__init__(self)
790        plottable_2D.__init__(self, data, err_data, qx_data,
791                              qy_data, q_data, mask, dqx_data, dqy_data)
792        if len(self.detector) > 0:
793            raise RuntimeError, "Data2D: Detector bank already filled at init"
794
795    def __str__(self):
796        _str = "%s\n" % DataInfo.__str__(self)
797       
798        _str += "Data:\n"
799        _str += "   Type:         %s\n" % self.__class__.__name__
800        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
801        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
802        #leny = 0
803        #if len(self.data) > 0:
804        #    leny = len(self.data)
805        _str += "   Length:       %g \n" % (len(self.data))
806       
807        return _str
808 
809    def clone_without_data(self, length=0, clone=None):
810        """
811        Clone the current object, without copying the data (which
812        will be filled out by a subsequent operation).
813        The data arrays will be initialized to zero.
814       
815        :param length: length of the data array to be initialized
816        :param clone: if provided, the data will be copied to clone
817        """
818        from copy import deepcopy
819       
820        if clone is None or not issubclass(clone.__class__, Data2D):
821            data = numpy.zeros(length)
822            err_data = numpy.zeros(length)
823            qx_data = numpy.zeros(length)
824            qy_data = numpy.zeros(length)
825            q_data = numpy.zeros(length)
826            mask = numpy.zeros(length)
827            dqx_data = None
828            dqy_data = None
829            clone = Data2D(data, err_data, qx_data, qy_data,
830                            q_data, mask, dqx_data=dqx_data, dqy_data=dqy_data)
831
832        clone.title       = self.title
833        clone.run         = self.run
834        clone.filename    = self.filename
835        clone.instrument  = self.instrument
836        clone.notes       = deepcopy(self.notes)
837        clone.process     = deepcopy(self.process)
838        clone.detector    = deepcopy(self.detector)
839        clone.sample      = deepcopy(self.sample)
840        clone.source      = deepcopy(self.source)
841        clone.collimation = deepcopy(self.collimation)
842        clone.meta_data   = deepcopy(self.meta_data)
843        clone.errors      = deepcopy(self.errors)
844       
845        return clone
846 
847    def _validity_check(self, other):
848        """
849        Checks that the data lengths are compatible.
850        Checks that the x vectors are compatible.
851        Returns errors vectors equal to original
852        errors vectors if they were present or vectors
853        of zeros when none was found.
854       
855        :param other: other data set for operation
856       
857        :return: dy for self, dy for other [numpy arrays]
858       
859        :raise ValueError: when lengths are not compatible
860       
861        """
862        err_other = None
863        if isinstance(other, Data2D):
864            # Check that data lengths are the same
865            if numpy.size(self.data) != numpy.size(other.data):
866                msg = "Unable to perform operation: data length are not equal"
867                raise ValueError, msg
868               
869            # Check that the scales match
870            #TODO: matching scales?
871           
872            # Check that the other data set has errors, otherwise
873            # create zero vector
874            #TODO: test this
875            err_other = other.err_data
876            if other.err_data == None or \
877                (numpy.size(other.err_data) != numpy.size(other.data)):
878                err_other = numpy.zeros([numpy.size(other.data, 0),
879                                          numpy.size(other.data, 1)])
880           
881        # Check that we have errors, otherwise create zero vector
882        err = self.err_data
883        if self.err_data == None or \
884            (numpy.size(self.err_data) != numpy.size(self.data)):
885            err = numpy.zeros([numpy.size(self.data, 0),
886                                numpy.size(self.data, 1)])
887           
888        return err, err_other
889 
890    def _perform_operation(self, other, operation):
891        """
892        Perform 2D operations between data sets
893       
894        :param other: other data set
895        :param operation: function defining the operation
896       
897        """
898        # First, check the data compatibility
899        dy, dy_other = self._validity_check(other)
900   
901        result = self.clone_without_data([numpy.size(self.data, 0),
902                                          numpy.size(self.data, 1)])
903       
904        for i in range(numpy.size(self.data, 0)):
905            for j in range(numpy.size(self.data, 1)):
906                result.data[i][j] = self.data[i][j]
907                if self.err_data is not None and \
908                    numpy.size(self.data) == numpy.size(self.err_data):
909                    result.err_data[i][j] = self.err_data[i][j]
910               
911                a = Uncertainty(self.data[i][j], dy[i][j]**2)
912                if isinstance(other, Data2D):
913                    b = Uncertainty(other.data[i][j], dy_other[i][j]**2)
914                else:
915                    b = other
916               
917                output = operation(a, b)
918                result.data[i][j] = output.x
919                result.err_data[i][j] = math.sqrt(math.fabs(output.variance))
920        return result
Note: See TracBrowser for help on using the repository browser.