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

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

added qx_data, qy_data checking

  • Property mode set to 100644
File size: 32.2 KB
Line 
1"""
2    Module that contains classes to hold information read from
3    reduced data files.
4   
5    A good description of the data members can be found in
6    the CanSAS 1D XML data format:
7   
8    http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
9"""
10#####################################################################
11#This software was developed by the University of Tennessee as part of the
12#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
13#project funded by the US National Science Foundation.
14#See the license text in license.txt
15#copyright 2008, University of Tennessee
16######################################################################
17
18
19#TODO: Keep track of data manipulation in the 'process' data structure.
20#TODO: This module should be independent of plottables. We should write
21#        an adapter class for plottables when needed.
22
23#from sans.guitools.plottables import Data1D as plottable_1D
24from data_util.uncertainty import Uncertainty
25import numpy
26import math
27
28
29class plottable_1D:
30    """
31    Data1D is a place holder for 1D plottables.
32    """
33    # The presence of these should be mutually
34    # exclusive with the presence of Qdev (dx)
35    x = None
36    y = None
37    dx = None
38    dy = None
39    ## Slit smearing length
40    dxl = None
41    ## Slit smearing width
42    dxw = None
43   
44    # Units
45    _xaxis = ''
46    _xunit = ''
47    _yaxis = ''
48    _yunit = ''
49   
50    def __init__(self, x, y, dx=None, dy=None, dxl=None, dxw=None):
51        self.x = numpy.asarray(x)
52        self.y = numpy.asarray(y)
53        if dx is not None:
54            self.dx = numpy.asarray(dx)
55        if dy is not None:
56            self.dy = numpy.asarray(dy)
57        if dxl is not None:
58            self.dxl = numpy.asarray(dxl)
59        if dxw is not None: 
60            self.dxw = numpy.asarray(dxw)
61
62    def xaxis(self, label, unit):
63        """
64        set the x axis label and unit
65        """
66        self._xaxis = label
67        self._xunit = unit
68       
69    def yaxis(self, label, unit):
70        """
71        set the y axis label and unit
72        """
73        self._yaxis = label
74        self._yunit = unit
75
76
77class plottable_2D:
78    """
79    Data2D is a place holder for 2D plottables.
80    """
81    xmin = None
82    xmax = None
83    ymin = None
84    ymax = None
85    data = None
86    qx_data = None
87    qy_data = None
88    q_data = None
89    err_data = None
90    dqx_data = None
91    dqy_data = None
92    mask = None
93   
94    # Units
95    _xaxis = ''
96    _xunit = ''
97    _yaxis = ''
98    _yunit = ''
99    _zaxis = ''
100    _zunit = ''
101   
102    def __init__(self, data=None, err_data=None, qx_data=None,
103                 qy_data=None, q_data=None, mask=None,
104                 dqx_data=None, dqy_data=None):
105        self.data = numpy.asarray(data)
106        self.qx_data = numpy.asarray(qx_data)
107        self.qy_data = numpy.asarray(qy_data)
108        self.q_data = numpy.asarray(q_data)
109        self.mask = numpy.asarray(mask)
110        self.err_data = numpy.asarray(err_data)
111        if dqx_data is not None:
112            self.dqx_data = numpy.asarray(dqx_data) 
113        if dqy_data is not None:
114            self.dqy_data = numpy.asarray(dqy_data) 
115               
116    def xaxis(self, label, unit):
117        """
118        set the x axis label and unit
119        """
120        self._xaxis = label
121        self._xunit = unit
122       
123    def yaxis(self, label, unit):
124        """
125        set the y axis label and unit
126        """
127        self._yaxis = label
128        self._yunit = unit
129           
130    def zaxis(self, label, unit):
131        """
132        set the z axis label and unit
133        """
134        self._zaxis = label
135        self._zunit = unit
136
137           
138class Vector:
139    """
140    Vector class to hold multi-dimensional objects
141    """
142    ## x component
143    x = None
144    ## y component
145    y = None
146    ## z component
147    z = None
148   
149    def __init__(self, x=None, y=None, z=None):
150        """
151        Initialization. Components that are not
152        set a set to None by default.
153       
154        :param x: x component
155        :param y: y component
156        :param z: z component
157       
158        """
159        self.x = x
160        self.y = y
161        self.z = z
162       
163    def __str__(self):
164        msg = "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
165        return msg
166       
167
168class Detector:
169    """
170    Class to hold detector information
171    """
172    ## Name of the instrument [string]
173    name = None
174    ## Sample to detector distance [float] [mm]
175    distance = None
176    distance_unit = 'mm'
177    ## Offset of this detector position in X, Y,
178    #(and Z if necessary) [Vector] [mm]
179    offset = None
180    offset_unit = 'm'
181    ## Orientation (rotation) of this detector in roll,
182    # pitch, and yaw [Vector] [degrees]
183    orientation = None
184    orientation_unit = 'degree'
185    ## Center of the beam on the detector in X and Y
186    #(and Z if necessary) [Vector] [mm]
187    beam_center = None
188    beam_center_unit = 'mm'
189    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
190    pixel_size = None
191    pixel_size_unit = 'mm'
192    ## Slit length of the instrument for this detector.[float] [mm]
193    slit_length = None
194    slit_length_unit = 'mm'
195   
196    def __init__(self):
197        """
198       
199        Initialize class attribute that are objects...
200       
201        """
202        self.offset      = Vector()
203        self.orientation = Vector()
204        self.beam_center = Vector()
205        self.pixel_size  = Vector()
206       
207    def __str__(self):
208        _str  = "Detector:\n"
209        _str += "   Name:         %s\n" % self.name
210        _str += "   Distance:     %s [%s]\n" % \
211            (str(self.distance), str(self.distance_unit))
212        _str += "   Offset:       %s [%s]\n" % \
213            (str(self.offset), str(self.offset_unit))
214        _str += "   Orientation:  %s [%s]\n" % \
215            (str(self.orientation), str(self.orientation_unit))
216        _str += "   Beam center:  %s [%s]\n" % \
217            (str(self.beam_center), str(self.beam_center_unit))
218        _str += "   Pixel size:   %s [%s]\n" % \
219            (str(self.pixel_size), str(self.pixel_size_unit))
220        _str += "   Slit length:  %s [%s]\n" % \
221            (str(self.slit_length), str(self.slit_length_unit))
222        return _str
223
224
225class Aperture:
226    ## Name
227    name = None
228    ## Type
229    type = None
230    ## Size name
231    size_name = None
232    ## Aperture size [Vector]
233    size = None
234    size_unit = 'mm'
235    ## Aperture distance [float]
236    distance = None
237    distance_unit = 'mm'
238   
239    def __init__(self):
240        self.size = Vector()
241   
242   
243class Collimation:
244    """
245    Class to hold collimation information
246    """
247    ## Name
248    name = None
249    ## Length [float] [mm]
250    length = None
251    length_unit = 'mm'
252    ## Aperture
253    aperture = None
254   
255    def __init__(self):
256        self.aperture = []
257   
258    def __str__(self):
259        _str = "Collimation:\n"
260        _str += "   Length:       %s [%s]\n" % \
261            (str(self.length), str(self.length_unit))
262        for item in self.aperture:
263            _str += "   Aperture size:%s [%s]\n" % \
264                (str(item.size), str(item.size_unit))
265            _str += "   Aperture_dist:%s [%s]\n" % \
266                (str(item.distance), str(item.distance_unit))
267        return _str
268
269
270class Source:
271    """
272    Class to hold source information
273    """
274    ## Name
275    name = None
276    ## Radiation type [string]
277    radiation = None
278    ## Beam size name
279    beam_size_name = None
280    ## Beam size [Vector] [mm]
281    beam_size = None
282    beam_size_unit = 'mm'
283    ## Beam shape [string]
284    beam_shape = None
285    ## Wavelength [float] [Angstrom]
286    wavelength = None
287    wavelength_unit = 'A'
288    ## Minimum wavelength [float] [Angstrom]
289    wavelength_min = None
290    wavelength_min_unit = 'nm'
291    ## Maximum wavelength [float] [Angstrom]
292    wavelength_max = None
293    wavelength_max_unit = 'nm'
294    ## Wavelength spread [float] [Angstrom]
295    wavelength_spread = None
296    wavelength_spread_unit = 'percent'
297   
298    def __init__(self):
299        self.beam_size = Vector()
300       
301    def __str__(self):
302        _str  = "Source:\n"
303        _str += "   Radiation:    %s\n" % str(self.radiation)
304        _str += "   Shape:        %s\n" % str(self.beam_shape)
305        _str += "   Wavelength:   %s [%s]\n" % \
306            (str(self.wavelength), str(self.wavelength_unit))
307        _str += "   Waveln_min:   %s [%s]\n" % \
308            (str(self.wavelength_min), str(self.wavelength_min_unit))
309        _str += "   Waveln_max:   %s [%s]\n" % \
310            (str(self.wavelength_max), str(self.wavelength_max_unit))
311        _str += "   Waveln_spread:%s [%s]\n" % \
312            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
313        _str += "   Beam_size:    %s [%s]\n" % \
314            (str(self.beam_size), str(self.beam_size_unit))
315        return _str
316   
317   
318"""
319Definitions of radiation types
320"""
321NEUTRON  = 'neutron'
322XRAY     = 'x-ray'
323MUON     = 'muon'
324ELECTRON = 'electron'
325   
326   
327class Sample:
328    """
329    Class to hold the sample description
330    """
331    ## Short name for sample
332    name = ''
333    ## ID
334    ID = ''
335    ## Thickness [float] [mm]
336    thickness = None
337    thickness_unit = 'mm'
338    ## Transmission [float] [fraction]
339    transmission = None
340    ## Temperature [float] [C]
341    temperature = None
342    temperature_unit = 'C'
343    ## Position [Vector] [mm]
344    position = None
345    position_unit = 'mm'
346    ## Orientation [Vector] [degrees]
347    orientation = None
348    orientation_unit = 'degree'
349    ## Details
350    details = None
351   
352    def __init__(self):
353        self.position    = Vector()
354        self.orientation = Vector()
355        self.details     = []
356   
357    def __str__(self):
358        _str  = "Sample:\n"
359        _str += "   ID:           %s\n" % str(self.ID)
360        _str += "   Transmission: %s\n" % str(self.transmission)
361        _str += "   Thickness:    %s [%s]\n" % \
362            (str(self.thickness), str(self.thickness_unit))
363        _str += "   Temperature:  %s [%s]\n" % \
364            (str(self.temperature), str(self.temperature_unit))
365        _str += "   Position:     %s [%s]\n" % \
366            (str(self.position), str(self.position_unit))
367        _str += "   Orientation:  %s [%s]\n" % \
368            (str(self.orientation), str(self.orientation_unit))
369       
370        _str += "   Details:\n"
371        for item in self.details:
372            _str += "      %s\n" % item
373           
374        return _str
375 
376 
377class Process:
378    """
379    Class that holds information about the processes
380    performed on the data.
381    """
382    name = ''
383    date = ''
384    description = ''
385    term = None
386    notes = None
387   
388    def __init__(self):
389        self.term = []
390        self.notes = []
391   
392    def __str__(self):
393        _str  = "Process:\n"
394        _str += "   Name:         %s\n" % self.name
395        _str += "   Date:         %s\n" % self.date
396        _str += "   Description:  %s\n" % self.description
397        for item in self.term:
398            _str += "   Term:         %s\n" % item
399        for item in self.notes:
400            _str += "   Note:         %s\n" % item
401        return _str
402   
403 
404class DataInfo:
405    """
406    Class to hold the data read from a file.
407    It includes four blocks of data for the
408    instrument description, the sample description,
409    the data itself and any other meta data.
410    """
411    ## Title
412    title      = ''
413    ## Run number
414    run        = None
415    ## Run name
416    run_name   = None
417    ## File name
418    filename   = ''
419    ## Notes
420    notes      = None
421    ## Processes (Action on the data)
422    process    = None
423    ## Instrument name
424    instrument = ''
425    ## Detector information
426    detector   = None
427    ## Sample information
428    sample     = None
429    ## Source information
430    source     = None
431    ## Collimation information
432    collimation = None
433    ## Additional meta-data
434    meta_data  = None
435    ## Loading errors
436    errors = None
437           
438    def __init__(self):
439        """
440        Initialization
441        """
442        ## Title
443        self.title      = ''
444        ## Run number
445        self.run        = []
446        self.run_name   = {}
447        ## File name
448        self.filename   = ''
449        ## Notes
450        self.notes      = []
451        ## Processes (Action on the data)
452        self.process    = []
453        ## Instrument name
454        self.instrument = ''
455        ## Detector information
456        self.detector   = []
457        ## Sample information
458        self.sample     = Sample()
459        ## Source information
460        self.source     = Source()
461        ## Collimation information
462        self.collimation = []
463        ## Additional meta-data
464        self.meta_data  = {}
465        ## Loading errors
466        self.errors = []
467       
468    def append_empty_process(self):
469        """
470        """
471        self.process.append(Process())
472       
473    def add_notes(self, message=""):
474        """
475        Add notes to datainfo
476        """
477        self.notes.append(message)
478       
479    def __str__(self):
480        """
481        Nice printout
482        """
483        _str =  "File:            %s\n" % self.filename
484        _str += "Title:           %s\n" % self.title
485        _str += "Run:             %s\n" % str(self.run)
486        _str += "Instrument:      %s\n" % str(self.instrument)
487        _str += "%s\n" % str(self.sample)
488        _str += "%s\n" % str(self.source)
489        for item in self.detector:
490            _str += "%s\n" % str(item)
491        for item in self.collimation:
492            _str += "%s\n" % str(item)
493        for item in self.process:
494            _str += "%s\n" % str(item)
495        for item in self.notes:
496            _str += "%s\n" % str(item)
497
498        return _str
499           
500    # Private method to perform operation. Not implemented for DataInfo,
501    # but should be implemented for each data class inherited from DataInfo
502    # that holds actual data (ex.: Data1D)
503    def _perform_operation(self, other, operation):
504        """
505        Private method to perform operation. Not implemented for DataInfo,
506        but should be implemented for each data class inherited from DataInfo
507        that holds actual data (ex.: Data1D)
508        """
509        return NotImplemented
510
511    def __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                if self.dxl != None and other.dxl == None:
731                    msg = "Incompatible data sets: dxl-values do not match"
732                    raise ValueError, msg
733                if self.dxl == None and other.dxl != None:
734                    msg = "Incompatible data sets: dxl-values do not match"
735                    raise ValueError, msg
736                if self.dxw != None and other.dxw == None:
737                    msg = "Incompatible data sets: dxw-values do not match"
738                    raise ValueError, msg
739                if self.dxw == None and other.dxw != None:
740                    msg = "Incompatible data sets: dxw-values do not match"
741                    raise ValueError, msg
742           
743            # Check that the other data set has errors, otherwise
744            # create zero vector
745            dy_other = other.dy
746            if other.dy == None or (len(other.dy) != len(other.y)):
747                dy_other = numpy.zeros(len(other.y))
748           
749        # Check that we have errors, otherwise create zero vector
750        dy = self.dy
751        if self.dy == None or (len(self.dy) != len(self.y)):
752            dy = numpy.zeros(len(self.y))
753           
754        return dy, dy_other
755
756    def _perform_operation(self, other, operation):
757        """
758        """
759        # First, check the data compatibility
760        dy, dy_other = self._validity_check(other)
761        result = self.clone_without_data(len(self.x))
762        if self.dxw == None:
763            result.dxw = None
764        else:
765            result.dxw = numpy.zeros(len(self.x))
766        if self.dxl == None:
767            result.dxl = None
768        else:
769            result.dxl = numpy.zeros(len(self.x))
770
771        for i in range(len(self.x)):
772            result.x[i] = self.x[i]
773            if self.dx is not None and len(self.x) == len(self.dx):
774                result.dx[i] = self.dx[i]
775            if self.dxw is not None and len(self.x) == len(self.dxw):
776                result.dxw[i] = self.dxw[i]
777            if self.dxl is not None and len(self.x) == len(self.dxl):
778                result.dxl[i] = self.dxl[i]
779           
780            a = Uncertainty(self.y[i], dy[i]**2)
781            if isinstance(other, Data1D):
782                b = Uncertainty(other.y[i], dy_other[i]**2)
783                if other.dx is not None:
784                    result.dx[i] *= self.dx[i]
785                    result.dx[i] += (other.dx[i]**2)
786                    result.dx[i] /= 2
787                    result.dx[i] = math.sqrt(result.dx[i])
788                if result.dxl is not None and other.dxl is not None:
789                    result.dxl[i] *= self.dxl[i]
790                    other.dxl[i] += (other.dxl[i]**2)
791                    result.dxl[i] /= 2
792                    result.dxl[i] = math.sqrt(result.dxl[i])
793                if result.dxw is not None and self.dxw is not None:
794                    result.dxw[i] *= self.dxw[i]
795                    other.dxw[i] += (other.dxw[i]**2)
796                    result.dxw[i] /= 2
797                    result.dxw[i] = math.sqrt(result.dxw[i])
798            else:
799                b = other
800           
801            output = operation(a, b)
802            result.y[i] = output.x
803            result.dy[i] = math.sqrt(math.fabs(output.variance))
804        return result
805       
806       
807class Data2D(plottable_2D, DataInfo):
808    """
809    2D data class
810    """
811    ## Units for Q-values
812    Q_unit = '1/A'
813   
814    ## Units for I(Q) values
815    I_unit = '1/cm'
816   
817    ## Vector of Q-values at the center of each bin in x
818    x_bins = None
819   
820    ## Vector of Q-values at the center of each bin in y
821    y_bins = None
822   
823    def __init__(self, data=None, err_data=None, qx_data=None,
824                 qy_data=None, q_data=None, mask=None,
825                 dqx_data=None, dqy_data=None):
826        self.y_bins = []
827        self.x_bins = []
828        DataInfo.__init__(self)
829        plottable_2D.__init__(self, data, err_data, qx_data,
830                              qy_data, q_data, mask, dqx_data, dqy_data)
831        if len(self.detector) > 0:
832            raise RuntimeError, "Data2D: Detector bank already filled at init"
833
834    def __str__(self):
835        _str = "%s\n" % DataInfo.__str__(self)
836       
837        _str += "Data:\n"
838        _str += "   Type:         %s\n" % self.__class__.__name__
839        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
840        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
841        #leny = 0
842        #if len(self.data) > 0:
843        #    leny = len(self.data)
844        _str += "   Length:       %g \n" % (len(self.data))
845       
846        return _str
847 
848    def clone_without_data(self, length=0, clone=None):
849        """
850        Clone the current object, without copying the data (which
851        will be filled out by a subsequent operation).
852        The data arrays will be initialized to zero.
853       
854        :param length: length of the data array to be initialized
855        :param clone: if provided, the data will be copied to clone
856        """
857        from copy import deepcopy
858       
859        if clone is None or not issubclass(clone.__class__, Data2D):
860            data = numpy.zeros(length)
861            err_data = numpy.zeros(length)
862            qx_data = numpy.zeros(length)
863            qy_data = numpy.zeros(length)
864            q_data = numpy.zeros(length)
865            mask = numpy.zeros(length)
866            dqx_data = None
867            dqy_data = None
868            clone = Data2D(data, err_data, qx_data, qy_data,
869                            q_data, mask, dqx_data=dqx_data, dqy_data=dqy_data)
870
871        clone.title       = self.title
872        clone.run         = self.run
873        clone.filename    = self.filename
874        clone.instrument  = self.instrument
875        clone.notes       = deepcopy(self.notes)
876        clone.process     = deepcopy(self.process)
877        clone.detector    = deepcopy(self.detector)
878        clone.sample      = deepcopy(self.sample)
879        clone.source      = deepcopy(self.source)
880        clone.collimation = deepcopy(self.collimation)
881        clone.meta_data   = deepcopy(self.meta_data)
882        clone.errors      = deepcopy(self.errors)
883       
884        return clone
885 
886    def _validity_check(self, other):
887        """
888        Checks that the data lengths are compatible.
889        Checks that the x vectors are compatible.
890        Returns errors vectors equal to original
891        errors vectors if they were present or vectors
892        of zeros when none was found.
893       
894        :param other: other data set for operation
895       
896        :return: dy for self, dy for other [numpy arrays]
897       
898        :raise ValueError: when lengths are not compatible
899       
900        """
901        err_other = None
902        if isinstance(other, Data2D):
903            # Check that data lengths are the same
904            if numpy.size(self.data) != numpy.size(other.data):
905                msg = "Unable to perform operation: data length are not equal"
906                raise ValueError, msg
907            if numpy.size(self.qx_data) != numpy.size(other.qx_data):
908                msg = "Unable to perform operation: data length are not equal"
909                raise ValueError, msg
910            if numpy.size(self.qy_data) != numpy.size(other.qy_data):
911                msg = "Unable to perform operation: data length are not equal"
912                raise ValueError, msg
913            # Here we could also extrapolate between data points
914            if numpy.size(self.data) < 2:
915                msg = "Incompatible data sets: I-values do not match"
916                raise ValueError, msg
917            if self.qx_data.all() != other.qx_data.all():
918                msg = "Incompatible data sets: qx-values do not match"
919                raise ValueError, msg
920            if self.qy_data.all() != other.qy_data.all():
921                msg = "Incompatible data sets: qy-values do not match"
922                raise ValueError, msg
923                   
924            # Check that the scales match
925            err_other = other.err_data
926            if other.err_data == None or \
927                (numpy.size(other.err_data) != numpy.size(other.data)):
928                err_other = numpy.zeros(numpy.size(other.data))
929           
930        # Check that we have errors, otherwise create zero vector
931        err = self.err_data
932        if self.err_data == None or \
933            (numpy.size(self.err_data) != numpy.size(self.data)):
934            err = numpy.zeros(numpy.size(other.data))
935           
936        return err, err_other
937 
938    def _perform_operation(self, other, operation):
939        """
940        Perform 2D operations between data sets
941       
942        :param other: other data set
943        :param operation: function defining the operation
944       
945        """
946        # First, check the data compatibility
947        dy, dy_other = self._validity_check(other)
948        result = self.clone_without_data(numpy.size(self.data))
949        result.xmin = self.xmin
950        result.xmax = self.xmax
951        result.ymin = self.ymin
952        result.ymax = self.ymax
953        if self.dqx_data == None or self.dqy_data == None:
954            result.dqx_data = None
955            result.dqy_data = None
956        else:
957            result.dqx_data = numpy.zeros(len(self.data))
958            result.dqy_data = numpy.zeros(len(self.data))
959        for i in range(numpy.size(self.data)):
960            result.data[i] = self.data[i]
961            if self.err_data is not None and \
962                numpy.size(self.data) == numpy.size(self.err_data):
963                result.err_data[i] = self.err_data[i]   
964            if self.dqx_data is not None:
965                result.dqx_data[i] = self.dqx_data[i]
966            if self.dqy_data is not None:
967                result.dqy_data[i] = self.dqy_data[i]
968            result.qx_data[i] = self.qx_data[i]
969            result.qy_data[i] = self.qy_data[i]
970            result.q_data[i] = self.q_data[i]
971            result.mask[i] = self.mask[i]
972           
973            a = Uncertainty(self.data[i], dy[i]**2)
974            if isinstance(other, Data2D):
975                b = Uncertainty(other.data[i], dy_other[i]**2)
976                if other.dqx_data is not None and \
977                        result.dqx_data is not None:
978                    result.dqx_data[i] *= self.dqx_data[i]
979                    result.dqx_data[i] += (other.dqx_data[i]**2)
980                    result.dqx_data[i] /= 2
981                    result.dqx_data[i] = math.sqrt(result.dqx_data[i])     
982                if other.dqy_data is not None and \
983                        result.dqy_data is not None:
984                    result.dqy_data[i] *= self.dqy_data[i]
985                    result.dqy_data[i] += (other.dqy_data[i]**2)
986                    result.dqy_data[i] /= 2
987                    result.dqy_data[i] = math.sqrt(result.dqy_data[i])
988            else:
989                b = other
990           
991            output = operation(a, b)
992            result.data[i] = output.x
993            result.err_data[i] = math.sqrt(math.fabs(output.variance))
994        return result
Note: See TracBrowser for help on using the repository browser.