source: sasview/src/sans/dataloader/data_info.py @ 75eeb425

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 75eeb425 was 75eeb425, checked in by Jeff Krzywon <jeffery.krzywon@…>, 10 years ago

Pushing this here before I merge with trunk to be sure I don't lose anything.

  • Property mode set to 100644
File size: 38.7 KB
Line 
1"""
2    Module that contains classes to hold information read from
3    reduced data files.
4   
5    A good description of the data members can be found in
6    the CanSAS 1D XML data format:
7   
8    http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
9"""
10#####################################################################
11#This software was developed by the University of Tennessee as part of the
12#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
13#project funded by the US National Science Foundation.
14#See the license text in license.txt
15#copyright 2008, University of Tennessee
16######################################################################
17
18
19#TODO: Keep track of data manipulation in the 'process' data structure.
20#TODO: This module should be independent of plottables. We should write
21#        an adapter class for plottables when needed.
22
23#from sans.guitools.plottables import Data1D as plottable_1D
24from sans.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   
403class TransmissionSpectrum:
404    """
405    Class that holds information about transmission spectrum
406    for white beams and spallation sources.
407    """
408    name = ''
409    timestamp = ''
410    ## Wavelength (float) [A]
411    wavelength = None
412    wavelength_unit = 'A'
413    ## Transmission (float) [unit less]
414    transmission = None
415    transmission_unit = ''
416    ## Transmission Deviation (float) [unit less]
417    transmission_deviation = None
418    transmission_deviation_unit = ''
419   
420    def __init__(self):
421        self.wavelength = []
422        self.transmission = []
423        self.transmission_deviation = []
424   
425    def __str__(self):
426        _str  = "Transmission Spectrum:\n"
427        _str += "   Name:       {0}".format(self.name)
428        _str += "   Timestamp:  {1}".format(self.timestamp)
429        _str += "   Wavelength [{0}] | Transmission [{1}] | Trans Dev [{2}]\n".format(self.wavelength_unit, self.transmission_unit, self.transmission_deviation_unit)
430        for i in range(len(self.wavelength)):
431            _str += "   {0}, {1}".format(self.wavelength[i], self.transmission[i])
432            if len(self.transmission_deviation > i):
433                _str += ", {0}".format(self.transmission_deviation[i])
434            _str += "\n"
435        return _str
436   
437 
438class DataInfo:
439    """
440    Class to hold the data read from a file.
441    It includes four blocks of data for the
442    instrument description, the sample description,
443    the data itself and any other meta data.
444    """
445    ## Title
446    title      = ''
447    ## Run number
448    run        = None
449    ## Run name
450    run_name   = None
451    ## File name
452    filename   = ''
453    ## Notes
454    notes      = None
455    ## Processes (Action on the data)
456    process    = None
457    ## Instrument name
458    instrument = ''
459    ## Detector information
460    detector   = None
461    ## Sample information
462    sample     = None
463    ## Source information
464    source     = None
465    ## Collimation information
466    collimation = None
467    ## Transmission Spectrum INfo
468    trans_spectrum = None
469    ## Additional meta-data
470    meta_data  = None
471    ## Loading errors
472    errors = None
473           
474    def __init__(self):
475        """
476        Initialization
477        """
478        ## Title
479        self.title      = ''
480        ## Run number
481        self.run        = []
482        self.run_name   = {}
483        ## File name
484        self.filename   = ''
485        ## Notes
486        self.notes      = []
487        ## Processes (Action on the data)
488        self.process    = []
489        ## Instrument name
490        self.instrument = ''
491        ## Detector information
492        self.detector   = []
493        ## Sample information
494        self.sample     = Sample()
495        ## Source information
496        self.source     = Source()
497        ## Collimation information
498        self.collimation = []
499        ## Transmission Spectrum
500        self.trans_spectrum = TransmissionSpectrum()
501        ## Additional meta-data
502        self.meta_data  = {}
503        ## Loading errors
504        self.errors = []
505       
506    def append_empty_process(self):
507        """
508        """
509        self.process.append(Process())
510       
511    def add_notes(self, message=""):
512        """
513        Add notes to datainfo
514        """
515        self.notes.append(message)
516       
517    def __str__(self):
518        """
519        Nice printout
520        """
521        _str =  "File:            %s\n" % self.filename
522        _str += "Title:           %s\n" % self.title
523        _str += "Run:             %s\n" % str(self.run)
524        _str += "Instrument:      %s\n" % str(self.instrument)
525        _str += "%s\n" % str(self.sample)
526        _str += "%s\n" % str(self.source)
527        for item in self.detector:
528            _str += "%s\n" % str(item)
529        for item in self.collimation:
530            _str += "%s\n" % str(item)
531        for item in self.process:
532            _str += "%s\n" % str(item)
533        for item in self.notes:
534            _str += "%s\n" % str(item)
535
536        return _str
537           
538    # Private method to perform operation. Not implemented for DataInfo,
539    # but should be implemented for each data class inherited from DataInfo
540    # that holds actual data (ex.: Data1D)
541    def _perform_operation(self, other, operation):
542        """
543        Private method to perform operation. Not implemented for DataInfo,
544        but should be implemented for each data class inherited from DataInfo
545        that holds actual data (ex.: Data1D)
546        """
547        return NotImplemented
548   
549    def _perform_union(self, other):
550        """
551        Private method to perform union operation. Not implemented for DataInfo,
552        but should be implemented for each data class inherited from DataInfo
553        that holds actual data (ex.: Data1D)
554        """
555        return NotImplemented
556
557    def __add__(self, other):
558        """
559        Add two data sets
560       
561        :param other: data set to add to the current one
562        :return: new data set
563        :raise ValueError: raised when two data sets are incompatible
564        """
565        def operation(a, b):
566            return a + b
567        return self._perform_operation(other, operation)
568       
569    def __radd__(self, other):
570        """
571        Add two data sets
572       
573        :param other: data set to add to the current one
574       
575        :return: new data set
576       
577        :raise ValueError: raised when two data sets are incompatible
578       
579        """
580        def operation(a, b):
581            return b + a
582        return self._perform_operation(other, operation)
583       
584    def __sub__(self, other):
585        """
586        Subtract two data sets
587       
588        :param other: data set to subtract from the current one
589       
590        :return: new data set
591       
592        :raise ValueError: raised when two data sets are incompatible
593       
594        """
595        def operation(a, b):
596            return a - b
597        return self._perform_operation(other, operation)
598       
599    def __rsub__(self, other):
600        """
601        Subtract two data sets
602       
603        :param other: data set to subtract from the current one
604       
605        :return: new data set
606       
607        :raise ValueError: raised when two data sets are incompatible
608       
609        """
610        def operation(a, b):
611            return b - a
612        return self._perform_operation(other, operation)
613       
614    def __mul__(self, other):
615        """
616        Multiply two data sets
617       
618        :param other: data set to subtract from the current one
619       
620        :return: new data set
621       
622        :raise ValueError: raised when two data sets are incompatible
623       
624        """
625        def operation(a, b):
626            return a * b
627        return self._perform_operation(other, operation)
628       
629    def __rmul__(self, other):
630        """
631        Multiply two data sets
632       
633        :param other: data set to subtract from the current one
634       
635        :return: new data set
636       
637        :raise ValueError: raised when two data sets are incompatible
638        """
639        def operation(a, b):
640            return b * a
641        return self._perform_operation(other, operation)
642       
643    def __div__(self, other):
644        """
645        Divided a data set by another
646       
647        :param other: data set that the current one is divided by
648       
649        :return: new data set
650       
651        :raise ValueError: raised when two data sets are incompatible
652       
653        """
654        def operation(a, b):
655            return a/b
656        return self._perform_operation(other, operation)
657       
658    def __rdiv__(self, other):
659        """
660        Divided a data set by another
661       
662        :param other: data set that the current one is divided by
663       
664        :return: new data set
665       
666        :raise ValueError: raised when two data sets are incompatible
667       
668        """
669        def operation(a, b):
670            return b/a
671        return self._perform_operation(other, operation)
672           
673       
674    def __or__(self, other):
675        """
676        Union a data set with another
677       
678        :param other: data set to be unified
679       
680        :return: new data set
681       
682        :raise ValueError: raised when two data sets are incompatible
683       
684        """
685        return self._perform_union(other)
686       
687    def __ror__(self, other):
688        """
689        Union a data set with another
690       
691        :param other: data set to be unified
692       
693        :return: new data set
694       
695        :raise ValueError: raised when two data sets are incompatible
696       
697        """
698        return self._perform_union(other)
699               
700class Data1D(plottable_1D, DataInfo):
701    """
702    1D data class
703    """
704    x_unit = '1/A'
705    y_unit = '1/cm'
706   
707    def __init__(self, x, y, dx=None, dy=None):
708        DataInfo.__init__(self)
709        plottable_1D.__init__(self, x, y, dx, dy)
710       
711    def __str__(self):
712        """
713        Nice printout
714        """
715        _str =  "%s\n" % DataInfo.__str__(self)
716   
717        _str += "Data:\n"
718        _str += "   Type:         %s\n" % self.__class__.__name__
719        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
720        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
721        _str += "   Length:       %g\n" % len(self.x)
722
723        return _str
724
725    def is_slit_smeared(self):
726        """
727        Check whether the data has slit smearing information
728       
729        :return: True is slit smearing info is present, False otherwise
730       
731        """
732        def _check(v):
733            if (v.__class__ == list or v.__class__ == numpy.ndarray) \
734                and len(v) > 0 and min(v) > 0:
735                return True
736           
737            return False
738       
739        return _check(self.dxl) or _check(self.dxw)
740       
741    def clone_without_data(self, length=0, clone=None):
742        """
743        Clone the current object, without copying the data (which
744        will be filled out by a subsequent operation).
745        The data arrays will be initialized to zero.
746       
747        :param length: length of the data array to be initialized
748        :param clone: if provided, the data will be copied to clone
749        """
750        from copy import deepcopy
751       
752        if clone is None or not issubclass(clone.__class__, Data1D):
753            x  = numpy.zeros(length)
754            dx = numpy.zeros(length)
755            y  = numpy.zeros(length)
756            dy = numpy.zeros(length)
757            clone = Data1D(x, y, dx=dx, dy=dy)
758       
759        clone.title       = self.title
760        clone.run         = self.run
761        clone.filename    = self.filename
762        clone.instrument  = self.instrument
763        clone.notes       = deepcopy(self.notes)
764        clone.process     = deepcopy(self.process)
765        clone.detector    = deepcopy(self.detector)
766        clone.sample      = deepcopy(self.sample)
767        clone.source      = deepcopy(self.source)
768        clone.collimation = deepcopy(self.collimation)
769        clone.meta_data   = deepcopy(self.meta_data)
770        clone.errors      = deepcopy(self.errors)
771       
772        return clone
773
774    def _validity_check(self, other):
775        """
776        Checks that the data lengths are compatible.
777        Checks that the x vectors are compatible.
778        Returns errors vectors equal to original
779        errors vectors if they were present or vectors
780        of zeros when none was found.
781       
782        :param other: other data set for operation
783       
784        :return: dy for self, dy for other [numpy arrays]
785       
786        :raise ValueError: when lengths are not compatible
787       
788        """
789        dy_other = None
790        if isinstance(other, Data1D):
791            # Check that data lengths are the same
792            if len(self.x) != len(other.x) or \
793                len(self.y) != len(other.y):
794                msg = "Unable to perform operation: data length are not equal"
795                raise ValueError, msg
796           
797            # Here we could also extrapolate between data points
798            ZERO = 1.0e-12
799            for i in range(len(self.x)):
800                if math.fabs(self.x[i] - other.x[i]) > ZERO:
801                    msg = "Incompatible data sets: x-values do not match"
802                    raise ValueError, msg
803                """
804                if self.dxl != None and other.dxl == None:
805                    msg = "Incompatible data sets: dxl-values do not match"
806                    raise ValueError, msg
807                if self.dxl == None and other.dxl != None:
808                    msg = "Incompatible data sets: dxl-values do not match"
809                    raise ValueError, msg
810                if self.dxw != None and other.dxw == None:
811                    msg = "Incompatible data sets: dxw-values do not match"
812                    raise ValueError, msg
813                if self.dxw == None and other.dxw != None:
814                    msg = "Incompatible data sets: dxw-values do not match"
815                    raise ValueError, msg
816                """
817            # Check that the other data set has errors, otherwise
818            # create zero vector
819            dy_other = other.dy
820            if other.dy == None or (len(other.dy) != len(other.y)):
821                dy_other = numpy.zeros(len(other.y))
822           
823        # Check that we have errors, otherwise create zero vector
824        dy = self.dy
825        if self.dy == None or (len(self.dy) != len(self.y)):
826            dy = numpy.zeros(len(self.y))
827           
828        return dy, dy_other
829
830    def _perform_operation(self, other, operation):
831        """
832        """
833        # First, check the data compatibility
834        dy, dy_other = self._validity_check(other)
835        result = self.clone_without_data(len(self.x))
836        if self.dxw == None:
837            result.dxw = None
838        else:
839            result.dxw = numpy.zeros(len(self.x))
840        if self.dxl == None:
841            result.dxl = None
842        else:
843            result.dxl = numpy.zeros(len(self.x))
844
845        for i in range(len(self.x)):
846            result.x[i] = self.x[i]
847            if self.dx is not None and len(self.x) == len(self.dx):
848                result.dx[i] = self.dx[i]
849            if self.dxw is not None and len(self.x) == len(self.dxw):
850                result.dxw[i] = self.dxw[i]
851            if self.dxl is not None and len(self.x) == len(self.dxl):
852                result.dxl[i] = self.dxl[i]
853           
854            a = Uncertainty(self.y[i], dy[i]**2)
855            if isinstance(other, Data1D):
856                b = Uncertainty(other.y[i], dy_other[i]**2)
857                if other.dx is not None:
858                    result.dx[i] *= self.dx[i]
859                    result.dx[i] += (other.dx[i]**2)
860                    result.dx[i] /= 2
861                    result.dx[i] = math.sqrt(result.dx[i])
862                if result.dxl is not None and other.dxl is not None:
863                    result.dxl[i] *= self.dxl[i]
864                    result.dxl[i] += (other.dxl[i]**2)
865                    result.dxl[i] /= 2
866                    result.dxl[i] = math.sqrt(result.dxl[i])
867            else:
868                b = other
869           
870            output = operation(a, b)
871            result.y[i] = output.x
872            result.dy[i] = math.sqrt(math.fabs(output.variance))
873        return result
874   
875    def _validity_check_union(self, other):
876        """
877        Checks that the data lengths are compatible.
878        Checks that the x vectors are compatible.
879        Returns errors vectors equal to original
880        errors vectors if they were present or vectors
881        of zeros when none was found.
882       
883        :param other: other data set for operation
884       
885        :return: bool
886       
887        :raise ValueError: when data types are not compatible
888       
889        """
890        if not isinstance(other, Data1D):
891            msg = "Unable to perform operation: different types of data set"
892            raise ValueError, msg   
893        return True
894
895    def _perform_union(self, other):
896        """
897        """
898        # First, check the data compatibility
899        self._validity_check_union(other)
900        result = self.clone_without_data(len(self.x) + len(other.x))
901        if self.dy == None or other.dy is None:
902            result.dy = None
903        else:
904            result.dy = numpy.zeros(len(self.x) + len(other.x))
905        if self.dx == None or other.dx is None:
906            result.dx = None
907        else:
908            result.dx = numpy.zeros(len(self.x) + len(other.x))
909        if self.dxw == None or other.dxw is None:
910            result.dxw = None
911        else:
912            result.dxw = numpy.zeros(len(self.x) + len(other.x))
913        if self.dxl == None or other.dxl is None:
914            result.dxl = None
915        else:
916            result.dxl = numpy.zeros(len(self.x) + len(other.x))
917
918        result.x = numpy.append(self.x, other.x)
919        #argsorting
920        ind = numpy.argsort(result.x)
921        result.x = result.x[ind]
922        result.y = numpy.append(self.y, other.y)
923        result.y = result.y[ind]
924        if result.dy != None:
925            result.dy = numpy.append(self.dy, other.dy)
926            result.dy = result.dy[ind]
927        if result.dx is not None:
928            result.dx = numpy.append(self.dx, other.dx)
929            result.dx = result.dx[ind]
930        if result.dxw is not None:
931            result.dxw = numpy.append(self.dxw, other.dxw)
932            result.dxw = result.dxw[ind]
933        if result.dxl is not None:
934            result.dxl = numpy.append(self.dxl, other.dxl)
935            result.dxl = result.dxl[ind]
936        return result
937       
938       
939class Data2D(plottable_2D, DataInfo):
940    """
941    2D data class
942    """
943    ## Units for Q-values
944    Q_unit = '1/A'
945   
946    ## Units for I(Q) values
947    I_unit = '1/cm'
948   
949    ## Vector of Q-values at the center of each bin in x
950    x_bins = None
951   
952    ## Vector of Q-values at the center of each bin in y
953    y_bins = None
954   
955    def __init__(self, data=None, err_data=None, qx_data=None,
956                 qy_data=None, q_data=None, mask=None,
957                 dqx_data=None, dqy_data=None):
958        self.y_bins = []
959        self.x_bins = []
960        DataInfo.__init__(self)
961        plottable_2D.__init__(self, data, err_data, qx_data,
962                              qy_data, q_data, mask, dqx_data, dqy_data)
963        if len(self.detector) > 0:
964            raise RuntimeError, "Data2D: Detector bank already filled at init"
965
966    def __str__(self):
967        _str = "%s\n" % DataInfo.__str__(self)
968       
969        _str += "Data:\n"
970        _str += "   Type:         %s\n" % self.__class__.__name__
971        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
972        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
973        #leny = 0
974        #if len(self.data) > 0:
975        #    leny = len(self.data)
976        _str += "   Length:       %g \n" % (len(self.data))
977       
978        return _str
979 
980    def clone_without_data(self, length=0, clone=None):
981        """
982        Clone the current object, without copying the data (which
983        will be filled out by a subsequent operation).
984        The data arrays will be initialized to zero.
985       
986        :param length: length of the data array to be initialized
987        :param clone: if provided, the data will be copied to clone
988        """
989        from copy import deepcopy
990       
991        if clone is None or not issubclass(clone.__class__, Data2D):
992            data = numpy.zeros(length)
993            err_data = numpy.zeros(length)
994            qx_data = numpy.zeros(length)
995            qy_data = numpy.zeros(length)
996            q_data = numpy.zeros(length)
997            mask = numpy.zeros(length)
998            dqx_data = None
999            dqy_data = None
1000            clone = Data2D(data=data, err_data=err_data, 
1001                           qx_data=qx_data, qy_data=qy_data, 
1002                           q_data=q_data, mask=mask)
1003
1004        clone.title       = self.title
1005        clone.run         = self.run
1006        clone.filename    = self.filename
1007        clone.instrument  = self.instrument
1008        clone.notes       = deepcopy(self.notes)
1009        clone.process     = deepcopy(self.process)
1010        clone.detector    = deepcopy(self.detector)
1011        clone.sample      = deepcopy(self.sample)
1012        clone.source      = deepcopy(self.source)
1013        clone.collimation = deepcopy(self.collimation)
1014        clone.meta_data   = deepcopy(self.meta_data)
1015        clone.errors      = deepcopy(self.errors)
1016       
1017        return clone
1018 
1019    def _validity_check(self, other):
1020        """
1021        Checks that the data lengths are compatible.
1022        Checks that the x vectors are compatible.
1023        Returns errors vectors equal to original
1024        errors vectors if they were present or vectors
1025        of zeros when none was found.
1026       
1027        :param other: other data set for operation
1028       
1029        :return: dy for self, dy for other [numpy arrays]
1030       
1031        :raise ValueError: when lengths are not compatible
1032       
1033        """
1034        err_other = None
1035        if isinstance(other, Data2D):
1036            # Check that data lengths are the same
1037            if len(self.data) != len(other.data) or \
1038                len(self.qx_data) != len(other.qx_data) or \
1039                len(self.qy_data) != len(other.qy_data):
1040                msg = "Unable to perform operation: data length are not equal"
1041                raise ValueError, msg
1042            #if len(self.data) < 1:
1043            #    msg = "Incompatible data sets: I-values do not match"
1044            #    raise ValueError, msg
1045            for ind in range(len(self.data)):
1046                if self.qx_data[ind] != other.qx_data[ind]:
1047                    msg = "Incompatible data sets: qx-values do not match"
1048                    raise ValueError, msg
1049                if self.qy_data[ind] != other.qy_data[ind]:
1050                    msg = "Incompatible data sets: qy-values do not match"
1051                    raise ValueError, msg
1052                   
1053            # Check that the scales match
1054            err_other = other.err_data
1055            if other.err_data == None or \
1056                (len(other.err_data) != len(other.data)):
1057                err_other = numpy.zeros(len(other.data))
1058           
1059        # Check that we have errors, otherwise create zero vector
1060        err = self.err_data
1061        if self.err_data == None or \
1062            (len(self.err_data) != len(self.data)):
1063            err = numpy.zeros(len(other.data))
1064           
1065        return err, err_other
1066 
1067    def _perform_operation(self, other, operation):
1068        """
1069        Perform 2D operations between data sets
1070       
1071        :param other: other data set
1072        :param operation: function defining the operation
1073       
1074        """
1075        # First, check the data compatibility
1076        dy, dy_other = self._validity_check(other)
1077        result = self.clone_without_data(numpy.size(self.data))
1078        if self.dqx_data == None or self.dqy_data == None:
1079            result.dqx_data = None
1080            result.dqy_data = None
1081        else:
1082            result.dqx_data = numpy.zeros(len(self.data))
1083            result.dqy_data = numpy.zeros(len(self.data))
1084        for i in range(numpy.size(self.data)):
1085            result.data[i] = self.data[i]
1086            if self.err_data is not None and \
1087                numpy.size(self.data) == numpy.size(self.err_data):
1088                result.err_data[i] = self.err_data[i]   
1089            if self.dqx_data is not None:
1090                result.dqx_data[i] = self.dqx_data[i]
1091            if self.dqy_data is not None:
1092                result.dqy_data[i] = self.dqy_data[i]
1093            result.qx_data[i] = self.qx_data[i]
1094            result.qy_data[i] = self.qy_data[i]
1095            result.q_data[i] = self.q_data[i]
1096            result.mask[i] = self.mask[i]
1097           
1098            a = Uncertainty(self.data[i], dy[i]**2)
1099            if isinstance(other, Data2D):
1100                b = Uncertainty(other.data[i], dy_other[i]**2)
1101                if other.dqx_data is not None and \
1102                        result.dqx_data is not None:
1103                    result.dqx_data[i] *= self.dqx_data[i]
1104                    result.dqx_data[i] += (other.dqx_data[i]**2)
1105                    result.dqx_data[i] /= 2
1106                    result.dqx_data[i] = math.sqrt(result.dqx_data[i])     
1107                if other.dqy_data is not None and \
1108                        result.dqy_data is not None:
1109                    result.dqy_data[i] *= self.dqy_data[i]
1110                    result.dqy_data[i] += (other.dqy_data[i]**2)
1111                    result.dqy_data[i] /= 2
1112                    result.dqy_data[i] = math.sqrt(result.dqy_data[i])
1113            else:
1114                b = other
1115           
1116            output = operation(a, b)
1117            result.data[i] = output.x
1118            result.err_data[i] = math.sqrt(math.fabs(output.variance))
1119        return result
1120   
1121    def _validity_check_union(self, other):
1122        """
1123        Checks that the data lengths are compatible.
1124        Checks that the x vectors are compatible.
1125        Returns errors vectors equal to original
1126        errors vectors if they were present or vectors
1127        of zeros when none was found.
1128       
1129        :param other: other data set for operation
1130       
1131        :return: bool
1132       
1133        :raise ValueError: when data types are not compatible
1134       
1135        """
1136        if not isinstance(other, Data2D):
1137            msg = "Unable to perform operation: different types of data set"
1138            raise ValueError, msg   
1139        return True
1140   
1141    def _perform_union(self, other):
1142        """
1143        Perform 2D operations between data sets
1144       
1145        :param other: other data set
1146        :param operation: function defining the operation
1147       
1148        """
1149        # First, check the data compatibility
1150        self._validity_check_union(other)
1151        result = self.clone_without_data(numpy.size(self.data) + \
1152                                         numpy.size(other.data))
1153        result.xmin = self.xmin
1154        result.xmax = self.xmax
1155        result.ymin = self.ymin
1156        result.ymax = self.ymax
1157        if self.dqx_data == None or self.dqy_data == None or \
1158                other.dqx_data == None or other.dqy_data == None :
1159            result.dqx_data = None
1160            result.dqy_data = None
1161        else:
1162            result.dqx_data = numpy.zeros(len(self.data) + \
1163                                         numpy.size(other.data))
1164            result.dqy_data = numpy.zeros(len(self.data) + \
1165                                         numpy.size(other.data))
1166       
1167        result.data = numpy.append(self.data, other.data)
1168        result.qx_data = numpy.append(self.qx_data, other.qx_data)
1169        result.qy_data = numpy.append(self.qy_data, other.qy_data)
1170        result.q_data = numpy.append(self.q_data, other.q_data)
1171        result.mask = numpy.append(self.mask, other.mask)
1172        if result.err_data is not None:
1173            result.err_data = numpy.append(self.err_data, other.err_data) 
1174        if self.dqx_data is not None:
1175            result.dqx_data = numpy.append(self.dqx_data, other.dqx_data)
1176        if self.dqy_data is not None:
1177            result.dqy_data = numpy.append(self.dqy_data, other.dqy_data)
1178
1179        return result
Note: See TracBrowser for help on using the repository browser.