source: sasview/src/sas/dataloader/data_info.py @ 43a31fd

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 43a31fd was b3efb7d, checked in by krzywon, 10 years ago

Modified data loading error messages to be much more specific and
include information in the console log about the specific errors
generated.

The cansas reader is now loading non-standard units (ie counts), but
shows a specific error message about the issue.

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