source: sasview/src/sas/dataloader/data_info.py @ 2b7d102

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 2b7d102 was 7eaf9f2, checked in by krzywon, 10 years ago

Fixed an error associated with the dataloader created when adding the
SESANS data object.

  • Property mode set to 100644
File size: 42.8 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_sesans1D:
30    """
31    SESANS is a place holder for 1D SESANS plottables.
32   
33    #TODO: This was directly copied from the plottables_1D.
34    #TODO: The class has not been updated from there.
35    """
36    # The presence of these should be mutually
37    # exclusive with the presence of Qdev (dx)
38    x = None
39    y = None
40    dx = None
41    dy = None
42    ## Slit smearing length
43    dxl = None
44    ## Slit smearing width
45    dxw = None
46   
47    # Units
48    _xaxis = ''
49    _xunit = ''
50    _yaxis = ''
51    _yunit = ''
52   
53    def __init__(self, x, y, dx=None, dy=None, dxl=None, dxw=None):
54        self.x = numpy.asarray(x)
55        self.y = numpy.asarray(y)
56        if dx is not None:
57            self.dx = numpy.asarray(dx)
58        if dy is not None:
59            self.dy = numpy.asarray(dy)
60        if dxl is not None:
61            self.dxl = numpy.asarray(dxl)
62        if dxw is not None: 
63            self.dxw = numpy.asarray(dxw)
64
65    def xaxis(self, label, unit):
66        """
67        set the x axis label and unit
68        """
69        self._xaxis = label
70        self._xunit = unit
71       
72    def yaxis(self, label, unit):
73        """
74        set the y axis label and unit
75        """
76        self._yaxis = label
77        self._yunit = unit
78
79
80class plottable_1D:
81    """
82    Data1D is a place holder for 1D plottables.
83    """
84    # The presence of these should be mutually
85    # exclusive with the presence of Qdev (dx)
86    x = None
87    y = None
88    dx = None
89    dy = None
90    ## Slit smearing length
91    dxl = None
92    ## Slit smearing width
93    dxw = None
94   
95    # Units
96    _xaxis = ''
97    _xunit = ''
98    _yaxis = ''
99    _yunit = ''
100   
101    def __init__(self, x, y, dx=None, dy=None, dxl=None, dxw=None):
102        self.x = numpy.asarray(x)
103        self.y = numpy.asarray(y)
104        if dx is not None:
105            self.dx = numpy.asarray(dx)
106        if dy is not None:
107            self.dy = numpy.asarray(dy)
108        if dxl is not None:
109            self.dxl = numpy.asarray(dxl)
110        if dxw is not None: 
111            self.dxw = numpy.asarray(dxw)
112
113    def xaxis(self, label, unit):
114        """
115        set the x axis label and unit
116        """
117        self._xaxis = label
118        self._xunit = unit
119       
120    def yaxis(self, label, unit):
121        """
122        set the y axis label and unit
123        """
124        self._yaxis = label
125        self._yunit = unit
126
127
128class plottable_2D:
129    """
130    Data2D is a place holder for 2D plottables.
131    """
132    xmin = None
133    xmax = None
134    ymin = None
135    ymax = None
136    data = None
137    qx_data = None
138    qy_data = None
139    q_data = None
140    err_data = None
141    dqx_data = None
142    dqy_data = None
143    mask = None
144   
145    # Units
146    _xaxis = ''
147    _xunit = ''
148    _yaxis = ''
149    _yunit = ''
150    _zaxis = ''
151    _zunit = ''
152   
153    def __init__(self, data=None, err_data=None, qx_data=None,
154                 qy_data=None, q_data=None, mask=None,
155                 dqx_data=None, dqy_data=None):
156        self.data = numpy.asarray(data)
157        self.qx_data = numpy.asarray(qx_data)
158        self.qy_data = numpy.asarray(qy_data)
159        self.q_data = numpy.asarray(q_data)
160        self.mask = numpy.asarray(mask)
161        self.err_data = numpy.asarray(err_data)
162        if dqx_data is not None:
163            self.dqx_data = numpy.asarray(dqx_data) 
164        if dqy_data is not None:
165            self.dqy_data = numpy.asarray(dqy_data) 
166               
167    def xaxis(self, label, unit):
168        """
169        set the x axis label and unit
170        """
171        self._xaxis = label
172        self._xunit = unit
173       
174    def yaxis(self, label, unit):
175        """
176        set the y axis label and unit
177        """
178        self._yaxis = label
179        self._yunit = unit
180           
181    def zaxis(self, label, unit):
182        """
183        set the z axis label and unit
184        """
185        self._zaxis = label
186        self._zunit = unit
187
188           
189class Vector:
190    """
191    Vector class to hold multi-dimensional objects
192    """
193    ## x component
194    x = None
195    ## y component
196    y = None
197    ## z component
198    z = None
199   
200    def __init__(self, x=None, y=None, z=None):
201        """
202        Initialization. Components that are not
203        set a set to None by default.
204       
205        :param x: x component
206        :param y: y component
207        :param z: z component
208       
209        """
210        self.x = x
211        self.y = y
212        self.z = z
213       
214    def __str__(self):
215        msg = "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
216        return msg
217       
218
219class Detector:
220    """
221    Class to hold detector information
222    """
223    ## Name of the instrument [string]
224    name = None
225    ## Sample to detector distance [float] [mm]
226    distance = None
227    distance_unit = 'mm'
228    ## Offset of this detector position in X, Y,
229    #(and Z if necessary) [Vector] [mm]
230    offset = None
231    offset_unit = 'm'
232    ## Orientation (rotation) of this detector in roll,
233    # pitch, and yaw [Vector] [degrees]
234    orientation = None
235    orientation_unit = 'degree'
236    ## Center of the beam on the detector in X and Y
237    #(and Z if necessary) [Vector] [mm]
238    beam_center = None
239    beam_center_unit = 'mm'
240    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
241    pixel_size = None
242    pixel_size_unit = 'mm'
243    ## Slit length of the instrument for this detector.[float] [mm]
244    slit_length = None
245    slit_length_unit = 'mm'
246   
247    def __init__(self):
248        """
249       
250        Initialize class attribute that are objects...
251       
252        """
253        self.offset      = Vector()
254        self.orientation = Vector()
255        self.beam_center = Vector()
256        self.pixel_size  = Vector()
257       
258    def __str__(self):
259        _str  = "Detector:\n"
260        _str += "   Name:         %s\n" % self.name
261        _str += "   Distance:     %s [%s]\n" % \
262            (str(self.distance), str(self.distance_unit))
263        _str += "   Offset:       %s [%s]\n" % \
264            (str(self.offset), str(self.offset_unit))
265        _str += "   Orientation:  %s [%s]\n" % \
266            (str(self.orientation), str(self.orientation_unit))
267        _str += "   Beam center:  %s [%s]\n" % \
268            (str(self.beam_center), str(self.beam_center_unit))
269        _str += "   Pixel size:   %s [%s]\n" % \
270            (str(self.pixel_size), str(self.pixel_size_unit))
271        _str += "   Slit length:  %s [%s]\n" % \
272            (str(self.slit_length), str(self.slit_length_unit))
273        return _str
274
275
276class Aperture:
277    ## Name
278    name = None
279    ## Type
280    type = None
281    ## Size name
282    size_name = None
283    ## Aperture size [Vector]
284    size = None
285    size_unit = 'mm'
286    ## Aperture distance [float]
287    distance = None
288    distance_unit = 'mm'
289   
290    def __init__(self):
291        self.size = Vector()
292   
293   
294class Collimation:
295    """
296    Class to hold collimation information
297    """
298    ## Name
299    name = None
300    ## Length [float] [mm]
301    length = None
302    length_unit = 'mm'
303    ## Aperture
304    aperture = None
305   
306    def __init__(self):
307        self.aperture = []
308   
309    def __str__(self):
310        _str = "Collimation:\n"
311        _str += "   Length:       %s [%s]\n" % \
312            (str(self.length), str(self.length_unit))
313        for item in self.aperture:
314            _str += "   Aperture size:%s [%s]\n" % \
315                (str(item.size), str(item.size_unit))
316            _str += "   Aperture_dist:%s [%s]\n" % \
317                (str(item.distance), str(item.distance_unit))
318        return _str
319
320
321class Source:
322    """
323    Class to hold source information
324    """
325    ## Name
326    name = None
327    ## Radiation type [string]
328    radiation = None
329    ## Beam size name
330    beam_size_name = None
331    ## Beam size [Vector] [mm]
332    beam_size = None
333    beam_size_unit = 'mm'
334    ## Beam shape [string]
335    beam_shape = None
336    ## Wavelength [float] [Angstrom]
337    wavelength = None
338    wavelength_unit = 'A'
339    ## Minimum wavelength [float] [Angstrom]
340    wavelength_min = None
341    wavelength_min_unit = 'nm'
342    ## Maximum wavelength [float] [Angstrom]
343    wavelength_max = None
344    wavelength_max_unit = 'nm'
345    ## Wavelength spread [float] [Angstrom]
346    wavelength_spread = None
347    wavelength_spread_unit = 'percent'
348   
349    def __init__(self):
350        self.beam_size = Vector()
351       
352    def __str__(self):
353        _str  = "Source:\n"
354        _str += "   Radiation:    %s\n" % str(self.radiation)
355        _str += "   Shape:        %s\n" % str(self.beam_shape)
356        _str += "   Wavelength:   %s [%s]\n" % \
357            (str(self.wavelength), str(self.wavelength_unit))
358        _str += "   Waveln_min:   %s [%s]\n" % \
359            (str(self.wavelength_min), str(self.wavelength_min_unit))
360        _str += "   Waveln_max:   %s [%s]\n" % \
361            (str(self.wavelength_max), str(self.wavelength_max_unit))
362        _str += "   Waveln_spread:%s [%s]\n" % \
363            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
364        _str += "   Beam_size:    %s [%s]\n" % \
365            (str(self.beam_size), str(self.beam_size_unit))
366        return _str
367   
368   
369"""
370Definitions of radiation types
371"""
372NEUTRON  = 'neutron'
373XRAY     = 'x-ray'
374MUON     = 'muon'
375ELECTRON = 'electron'
376   
377   
378class Sample:
379    """
380    Class to hold the sample description
381    """
382    ## Short name for sample
383    name = ''
384    ## ID
385    ID = ''
386    ## Thickness [float] [mm]
387    thickness = None
388    thickness_unit = 'mm'
389    ## Transmission [float] [fraction]
390    transmission = None
391    ## Temperature [float] [No Default]
392    temperature = None
393    temperature_unit = None
394    ## Position [Vector] [mm]
395    position = None
396    position_unit = 'mm'
397    ## Orientation [Vector] [degrees]
398    orientation = None
399    orientation_unit = 'degree'
400    ## Details
401    details = None
402   
403    def __init__(self):
404        self.position    = Vector()
405        self.orientation = Vector()
406        self.details     = []
407   
408    def __str__(self):
409        _str  = "Sample:\n"
410        _str += "   ID:           %s\n" % str(self.ID)
411        _str += "   Transmission: %s\n" % str(self.transmission)
412        _str += "   Thickness:    %s [%s]\n" % \
413            (str(self.thickness), str(self.thickness_unit))
414        _str += "   Temperature:  %s [%s]\n" % \
415            (str(self.temperature), str(self.temperature_unit))
416        _str += "   Position:     %s [%s]\n" % \
417            (str(self.position), str(self.position_unit))
418        _str += "   Orientation:  %s [%s]\n" % \
419            (str(self.orientation), str(self.orientation_unit))
420       
421        _str += "   Details:\n"
422        for item in self.details:
423            _str += "      %s\n" % item
424           
425        return _str
426 
427 
428class Process:
429    """
430    Class that holds information about the processes
431    performed on the data.
432    """
433    name = ''
434    date = ''
435    description = ''
436    term = None
437    notes = None
438   
439    def __init__(self):
440        self.term = []
441        self.notes = []
442   
443    def __str__(self):
444        _str  = "Process:\n"
445        _str += "   Name:         %s\n" % self.name
446        _str += "   Date:         %s\n" % self.date
447        _str += "   Description:  %s\n" % self.description
448        for item in self.term:
449            _str += "   Term:         %s\n" % item
450        for item in self.notes:
451            _str += "   Note:         %s\n" % item
452        return _str
453   
454class TransmissionSpectrum:
455    """
456    Class that holds information about transmission spectrum
457    for white beams and spallation sources.
458    """
459    name = ''
460    timestamp = ''
461    ## Wavelength (float) [A]
462    wavelength = None
463    wavelength_unit = 'A'
464    ## Transmission (float) [unit less]
465    transmission = None
466    transmission_unit = ''
467    ## Transmission Deviation (float) [unit less]
468    transmission_deviation = None
469    transmission_deviation_unit = ''
470   
471    def __init__(self):
472        self.wavelength = []
473        self.transmission = []
474        self.transmission_deviation = []
475   
476    def __str__(self):
477        _str  = "Transmission Spectrum:\n"
478        _str += "   Name:             \t{0}\n".format(self.name)
479        _str += "   Timestamp:        \t{0}\n".format(self.timestamp)
480        _str += "   Wavelength unit:  \t{0}\n".format(self.wavelength_unit)
481        _str += "   Transmission unit:\t{0}\n".format(self.transmission_unit)
482        _str += "   Trans. Dev. unit:  \t{0}\n".format(\
483                                            self.transmission_deviation_unit)
484        length_list = [len(self.wavelength), len(self.transmission), \
485                len(self.transmission_deviation)]
486        _str += "   Number of Pts:    \t{0}\n".format(max(length_list))
487        return _str
488   
489 
490class DataInfo:
491    """
492    Class to hold the data read from a file.
493    It includes four blocks of data for the
494    instrument description, the sample description,
495    the data itself and any other meta data.
496    """
497    ## Title
498    title      = ''
499    ## Run number
500    run        = None
501    ## Run name
502    run_name   = None
503    ## File name
504    filename   = ''
505    ## Notes
506    notes      = None
507    ## Processes (Action on the data)
508    process    = None
509    ## Instrument name
510    instrument = ''
511    ## Detector information
512    detector   = None
513    ## Sample information
514    sample     = None
515    ## Source information
516    source     = None
517    ## Collimation information
518    collimation = None
519    ## Transmission Spectrum INfo
520    trans_spectrum = None
521    ## Additional meta-data
522    meta_data  = None
523    ## Loading errors
524    errors = None
525           
526    def __init__(self):
527        """
528        Initialization
529        """
530        ## Title
531        self.title      = ''
532        ## Run number
533        self.run        = []
534        self.run_name   = {}
535        ## File name
536        self.filename   = ''
537        ## Notes
538        self.notes      = []
539        ## Processes (Action on the data)
540        self.process    = []
541        ## Instrument name
542        self.instrument = ''
543        ## Detector information
544        self.detector   = []
545        ## Sample information
546        self.sample     = Sample()
547        ## Source information
548        self.source     = Source()
549        ## Collimation information
550        self.collimation = []
551        ## Transmission Spectrum
552        self.trans_spectrum = []
553        ## Additional meta-data
554        self.meta_data  = {}
555        ## Loading errors
556        self.errors = []
557       
558    def append_empty_process(self):
559        """
560        """
561        self.process.append(Process())
562       
563    def add_notes(self, message=""):
564        """
565        Add notes to datainfo
566        """
567        self.notes.append(message)
568       
569    def __str__(self):
570        """
571        Nice printout
572        """
573        _str =  "File:            %s\n" % self.filename
574        _str += "Title:           %s\n" % self.title
575        _str += "Run:             %s\n" % str(self.run)
576        _str += "Instrument:      %s\n" % str(self.instrument)
577        _str += "%s\n" % str(self.sample)
578        _str += "%s\n" % str(self.source)
579        for item in self.detector:
580            _str += "%s\n" % str(item)
581        for item in self.collimation:
582            _str += "%s\n" % str(item)
583        for item in self.process:
584            _str += "%s\n" % str(item)
585        for item in self.notes:
586            _str += "%s\n" % str(item)
587        for item in self.trans_spectrum:
588            _str += "%s\n" % str(item)
589        return _str
590           
591    # Private method to perform operation. Not implemented for DataInfo,
592    # but should be implemented for each data class inherited from DataInfo
593    # that holds actual data (ex.: Data1D)
594    def _perform_operation(self, other, operation):
595        """
596        Private method to perform operation. Not implemented for DataInfo,
597        but should be implemented for each data class inherited from DataInfo
598        that holds actual data (ex.: Data1D)
599        """
600        return NotImplemented
601   
602    def _perform_union(self, other):
603        """
604        Private method to perform union operation. Not implemented for DataInfo,
605        but should be implemented for each data class inherited from DataInfo
606        that holds actual data (ex.: Data1D)
607        """
608        return NotImplemented
609
610    def __add__(self, other):
611        """
612        Add two data sets
613       
614        :param other: data set to add to the current one
615        :return: new data set
616        :raise ValueError: raised when two data sets are incompatible
617        """
618        def operation(a, b):
619            return a + b
620        return self._perform_operation(other, operation)
621       
622    def __radd__(self, other):
623        """
624        Add two data sets
625       
626        :param other: data set to add to the current one
627       
628        :return: new data set
629       
630        :raise ValueError: raised when two data sets are incompatible
631       
632        """
633        def operation(a, b):
634            return b + a
635        return self._perform_operation(other, operation)
636       
637    def __sub__(self, other):
638        """
639        Subtract two data sets
640       
641        :param other: data set to subtract from the current one
642       
643        :return: new data set
644       
645        :raise ValueError: raised when two data sets are incompatible
646       
647        """
648        def operation(a, b):
649            return a - b
650        return self._perform_operation(other, operation)
651       
652    def __rsub__(self, other):
653        """
654        Subtract two data sets
655       
656        :param other: data set to subtract from the current one
657       
658        :return: new data set
659       
660        :raise ValueError: raised when two data sets are incompatible
661       
662        """
663        def operation(a, b):
664            return b - a
665        return self._perform_operation(other, operation)
666       
667    def __mul__(self, other):
668        """
669        Multiply two data sets
670       
671        :param other: data set to subtract from the current one
672       
673        :return: new data set
674       
675        :raise ValueError: raised when two data sets are incompatible
676       
677        """
678        def operation(a, b):
679            return a * b
680        return self._perform_operation(other, operation)
681       
682    def __rmul__(self, other):
683        """
684        Multiply two data sets
685       
686        :param other: data set to subtract from the current one
687       
688        :return: new data set
689       
690        :raise ValueError: raised when two data sets are incompatible
691        """
692        def operation(a, b):
693            return b * a
694        return self._perform_operation(other, operation)
695       
696    def __div__(self, other):
697        """
698        Divided a data set by another
699       
700        :param other: data set that the current one is divided by
701       
702        :return: new data set
703       
704        :raise ValueError: raised when two data sets are incompatible
705       
706        """
707        def operation(a, b):
708            return a/b
709        return self._perform_operation(other, operation)
710       
711    def __rdiv__(self, other):
712        """
713        Divided a data set by another
714       
715        :param other: data set that the current one is divided by
716       
717        :return: new data set
718       
719        :raise ValueError: raised when two data sets are incompatible
720       
721        """
722        def operation(a, b):
723            return b/a
724        return self._perform_operation(other, operation)
725           
726       
727    def __or__(self, other):
728        """
729        Union a data set with another
730       
731        :param other: data set to be unified
732       
733        :return: new data set
734       
735        :raise ValueError: raised when two data sets are incompatible
736       
737        """
738        return self._perform_union(other)
739       
740    def __ror__(self, other):
741        """
742        Union a data set with another
743       
744        :param other: data set to be unified
745       
746        :return: new data set
747       
748        :raise ValueError: raised when two data sets are incompatible
749       
750        """
751        return self._perform_union(other)
752               
753class Data1D(plottable_1D, DataInfo):
754    """
755    1D data class
756    """
757    x_unit = '1/A'
758    y_unit = '1/cm'
759   
760    def __init__(self, x, y, dx=None, dy=None):
761        DataInfo.__init__(self)
762        plottable_1D.__init__(self, x, y, dx, dy)
763       
764    def __str__(self):
765        """
766        Nice printout
767        """
768        _str =  "%s\n" % DataInfo.__str__(self)
769   
770        _str += "Data:\n"
771        _str += "   Type:         %s\n" % self.__class__.__name__
772        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
773        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
774        _str += "   Length:       %g\n" % len(self.x)
775
776        return _str
777
778    def is_slit_smeared(self):
779        """
780        Check whether the data has slit smearing information
781       
782        :return: True is slit smearing info is present, False otherwise
783       
784        """
785        def _check(v):
786            if (v.__class__ == list or v.__class__ == numpy.ndarray) \
787                and len(v) > 0 and min(v) > 0:
788                return True
789           
790            return False
791       
792        return _check(self.dxl) or _check(self.dxw)
793       
794    def clone_without_data(self, length=0, clone=None):
795        """
796        Clone the current object, without copying the data (which
797        will be filled out by a subsequent operation).
798        The data arrays will be initialized to zero.
799       
800        :param length: length of the data array to be initialized
801        :param clone: if provided, the data will be copied to clone
802        """
803        from copy import deepcopy
804       
805        if clone is None or not issubclass(clone.__class__, Data1D):
806            x  = numpy.zeros(length)
807            dx = numpy.zeros(length)
808            y  = numpy.zeros(length)
809            dy = numpy.zeros(length)
810            clone = Data1D(x, y, dx=dx, dy=dy)
811       
812        clone.title          = self.title
813        clone.run            = self.run
814        clone.filename       = self.filename
815        clone.instrument     = self.instrument
816        clone.notes          = deepcopy(self.notes)
817        clone.process        = deepcopy(self.process)
818        clone.detector       = deepcopy(self.detector)
819        clone.sample         = deepcopy(self.sample)
820        clone.source         = deepcopy(self.source)
821        clone.collimation    = deepcopy(self.collimation)
822        clone.trans_spectrum = deepcopy(self.trans_spectrum)
823        clone.meta_data      = deepcopy(self.meta_data)
824        clone.errors         = deepcopy(self.errors)
825       
826        return clone
827
828    def _validity_check(self, other):
829        """
830        Checks that the data lengths are compatible.
831        Checks that the x vectors are compatible.
832        Returns errors vectors equal to original
833        errors vectors if they were present or vectors
834        of zeros when none was found.
835       
836        :param other: other data set for operation
837       
838        :return: dy for self, dy for other [numpy arrays]
839       
840        :raise ValueError: when lengths are not compatible
841       
842        """
843        dy_other = None
844        if isinstance(other, Data1D):
845            # Check that data lengths are the same
846            if len(self.x) != len(other.x) or \
847                len(self.y) != len(other.y):
848                msg = "Unable to perform operation: data length are not equal"
849                raise ValueError, msg
850           
851            # Here we could also extrapolate between data points
852            ZERO = 1.0e-12
853            for i in range(len(self.x)):
854                if math.fabs(self.x[i] - other.x[i]) > ZERO:
855                    msg = "Incompatible data sets: x-values do not match"
856                    raise ValueError, msg
857                """
858                if self.dxl != None and other.dxl == None:
859                    msg = "Incompatible data sets: dxl-values do not match"
860                    raise ValueError, msg
861                if self.dxl == None and other.dxl != None:
862                    msg = "Incompatible data sets: dxl-values do not match"
863                    raise ValueError, msg
864                if self.dxw != None and other.dxw == None:
865                    msg = "Incompatible data sets: dxw-values do not match"
866                    raise ValueError, msg
867                if self.dxw == None and other.dxw != None:
868                    msg = "Incompatible data sets: dxw-values do not match"
869                    raise ValueError, msg
870                """
871            # Check that the other data set has errors, otherwise
872            # create zero vector
873            dy_other = other.dy
874            if other.dy == None or (len(other.dy) != len(other.y)):
875                dy_other = numpy.zeros(len(other.y))
876           
877        # Check that we have errors, otherwise create zero vector
878        dy = self.dy
879        if self.dy == None or (len(self.dy) != len(self.y)):
880            dy = numpy.zeros(len(self.y))
881           
882        return dy, dy_other
883
884    def _perform_operation(self, other, operation):
885        """
886        """
887        # First, check the data compatibility
888        dy, dy_other = self._validity_check(other)
889        result = self.clone_without_data(len(self.x))
890        if self.dxw == None:
891            result.dxw = None
892        else:
893            result.dxw = numpy.zeros(len(self.x))
894        if self.dxl == None:
895            result.dxl = None
896        else:
897            result.dxl = numpy.zeros(len(self.x))
898
899        for i in range(len(self.x)):
900            result.x[i] = self.x[i]
901            if self.dx is not None and len(self.x) == len(self.dx):
902                result.dx[i] = self.dx[i]
903            if self.dxw is not None and len(self.x) == len(self.dxw):
904                result.dxw[i] = self.dxw[i]
905            if self.dxl is not None and len(self.x) == len(self.dxl):
906                result.dxl[i] = self.dxl[i]
907           
908            a = Uncertainty(self.y[i], dy[i]**2)
909            if isinstance(other, Data1D):
910                b = Uncertainty(other.y[i], dy_other[i]**2)
911                if other.dx is not None:
912                    result.dx[i] *= self.dx[i]
913                    result.dx[i] += (other.dx[i]**2)
914                    result.dx[i] /= 2
915                    result.dx[i] = math.sqrt(result.dx[i])
916                if result.dxl is not None and other.dxl is not None:
917                    result.dxl[i] *= self.dxl[i]
918                    result.dxl[i] += (other.dxl[i]**2)
919                    result.dxl[i] /= 2
920                    result.dxl[i] = math.sqrt(result.dxl[i])
921            else:
922                b = other
923           
924            output = operation(a, b)
925            result.y[i] = output.x
926            result.dy[i] = math.sqrt(math.fabs(output.variance))
927        return result
928   
929    def _validity_check_union(self, other):
930        """
931        Checks that the data lengths are compatible.
932        Checks that the x vectors are compatible.
933        Returns errors vectors equal to original
934        errors vectors if they were present or vectors
935        of zeros when none was found.
936       
937        :param other: other data set for operation
938       
939        :return: bool
940       
941        :raise ValueError: when data types are not compatible
942       
943        """
944        if not isinstance(other, Data1D):
945            msg = "Unable to perform operation: different types of data set"
946            raise ValueError, msg   
947        return True
948
949    def _perform_union(self, other):
950        """
951        """
952        # First, check the data compatibility
953        self._validity_check_union(other)
954        result = self.clone_without_data(len(self.x) + len(other.x))
955        if self.dy == None or other.dy is None:
956            result.dy = None
957        else:
958            result.dy = numpy.zeros(len(self.x) + len(other.x))
959        if self.dx == None or other.dx is None:
960            result.dx = None
961        else:
962            result.dx = numpy.zeros(len(self.x) + len(other.x))
963        if self.dxw == None or other.dxw is None:
964            result.dxw = None
965        else:
966            result.dxw = numpy.zeros(len(self.x) + len(other.x))
967        if self.dxl == None or other.dxl is None:
968            result.dxl = None
969        else:
970            result.dxl = numpy.zeros(len(self.x) + len(other.x))
971
972        result.x = numpy.append(self.x, other.x)
973        #argsorting
974        ind = numpy.argsort(result.x)
975        result.x = result.x[ind]
976        result.y = numpy.append(self.y, other.y)
977        result.y = result.y[ind]
978        if result.dy != None:
979            result.dy = numpy.append(self.dy, other.dy)
980            result.dy = result.dy[ind]
981        if result.dx is not None:
982            result.dx = numpy.append(self.dx, other.dx)
983            result.dx = result.dx[ind]
984        if result.dxw is not None:
985            result.dxw = numpy.append(self.dxw, other.dxw)
986            result.dxw = result.dxw[ind]
987        if result.dxl is not None:
988            result.dxl = numpy.append(self.dxl, other.dxl)
989            result.dxl = result.dxl[ind]
990        return result
991       
992       
993class SESANSData1D(plottable_sesans1D, DataInfo):
994    """
995    SESANS 1D data class
996    """
997    x_unit = '1/A'
998    y_unit = '1/cm'
999   
1000    def __init__(self, x, y, dx=None, dy=None):
1001        DataInfo.__init__(self)
1002        plottable_sesans1D.__init__(self, x, y, dx, dy)
1003       
1004    def __str__(self):
1005        """
1006        Nice printout
1007        """
1008        _str =  "%s\n" % DataInfo.__str__(self)
1009   
1010        _str += "Data:\n"
1011        _str += "   Type:         %s\n" % self.__class__.__name__
1012        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
1013        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
1014        _str += "   Length:       %g\n" % len(self.x)
1015
1016        return _str
1017
1018    def is_slit_smeared(self):
1019        """
1020        Check whether the data has slit smearing information
1021       
1022        :return: True is slit smearing info is present, False otherwise
1023       
1024        """
1025        def _check(v):
1026            if (v.__class__ == list or v.__class__ == numpy.ndarray) \
1027                and len(v) > 0 and min(v) > 0:
1028                return True
1029           
1030            return False
1031       
1032        return _check(self.dxl) or _check(self.dxw)
1033       
1034    def clone_without_data(self, length=0, clone=None):
1035        """
1036        Clone the current object, without copying the data (which
1037        will be filled out by a subsequent operation).
1038        The data arrays will be initialized to zero.
1039       
1040        :param length: length of the data array to be initialized
1041        :param clone: if provided, the data will be copied to clone
1042        """
1043        from copy import deepcopy
1044       
1045        if clone is None or not issubclass(clone.__class__, Data1D):
1046            x  = numpy.zeros(length)
1047            dx = numpy.zeros(length)
1048            y  = numpy.zeros(length)
1049            dy = numpy.zeros(length)
1050            clone = Data1D(x, y, dx=dx, dy=dy)
1051       
1052        clone.title          = self.title
1053        clone.run            = self.run
1054        clone.filename       = self.filename
1055        clone.instrument     = self.instrument
1056        clone.notes          = deepcopy(self.notes)
1057        clone.process        = deepcopy(self.process)
1058        clone.detector       = deepcopy(self.detector)
1059        clone.sample         = deepcopy(self.sample)
1060        clone.source         = deepcopy(self.source)
1061        clone.collimation    = deepcopy(self.collimation)
1062        clone.trans_spectrum = deepcopy(self.trans_spectrum)
1063        clone.meta_data      = deepcopy(self.meta_data)
1064        clone.errors         = deepcopy(self.errors)
1065       
1066        return clone
1067   
1068   
1069class Data2D(plottable_2D, DataInfo):
1070    """
1071    2D data class
1072    """
1073    ## Units for Q-values
1074    Q_unit = '1/A'
1075   
1076    ## Units for I(Q) values
1077    I_unit = '1/cm'
1078   
1079    ## Vector of Q-values at the center of each bin in x
1080    x_bins = None
1081   
1082    ## Vector of Q-values at the center of each bin in y
1083    y_bins = None
1084   
1085    def __init__(self, data=None, err_data=None, qx_data=None,
1086                 qy_data=None, q_data=None, mask=None,
1087                 dqx_data=None, dqy_data=None):
1088        self.y_bins = []
1089        self.x_bins = []
1090        DataInfo.__init__(self)
1091        plottable_2D.__init__(self, data, err_data, qx_data,
1092                              qy_data, q_data, mask, dqx_data, dqy_data)
1093        if len(self.detector) > 0:
1094            raise RuntimeError, "Data2D: Detector bank already filled at init"
1095
1096    def __str__(self):
1097        _str = "%s\n" % DataInfo.__str__(self)
1098       
1099        _str += "Data:\n"
1100        _str += "   Type:         %s\n" % self.__class__.__name__
1101        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
1102        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
1103        #leny = 0
1104        #if len(self.data) > 0:
1105        #    leny = len(self.data)
1106        _str += "   Length:       %g \n" % (len(self.data))
1107       
1108        return _str
1109 
1110    def clone_without_data(self, length=0, clone=None):
1111        """
1112        Clone the current object, without copying the data (which
1113        will be filled out by a subsequent operation).
1114        The data arrays will be initialized to zero.
1115       
1116        :param length: length of the data array to be initialized
1117        :param clone: if provided, the data will be copied to clone
1118        """
1119        from copy import deepcopy
1120       
1121        if clone is None or not issubclass(clone.__class__, Data2D):
1122            data = numpy.zeros(length)
1123            err_data = numpy.zeros(length)
1124            qx_data = numpy.zeros(length)
1125            qy_data = numpy.zeros(length)
1126            q_data = numpy.zeros(length)
1127            mask = numpy.zeros(length)
1128            dqx_data = None
1129            dqy_data = None
1130            clone = Data2D(data=data, err_data=err_data, 
1131                           qx_data=qx_data, qy_data=qy_data, 
1132                           q_data=q_data, mask=mask)
1133
1134        clone.title       = self.title
1135        clone.run         = self.run
1136        clone.filename    = self.filename
1137        clone.instrument  = self.instrument
1138        clone.notes       = deepcopy(self.notes)
1139        clone.process     = deepcopy(self.process)
1140        clone.detector    = deepcopy(self.detector)
1141        clone.sample      = deepcopy(self.sample)
1142        clone.source      = deepcopy(self.source)
1143        clone.collimation = deepcopy(self.collimation)
1144        clone.meta_data   = deepcopy(self.meta_data)
1145        clone.errors      = deepcopy(self.errors)
1146       
1147        return clone
1148 
1149    def _validity_check(self, other):
1150        """
1151        Checks that the data lengths are compatible.
1152        Checks that the x vectors are compatible.
1153        Returns errors vectors equal to original
1154        errors vectors if they were present or vectors
1155        of zeros when none was found.
1156       
1157        :param other: other data set for operation
1158       
1159        :return: dy for self, dy for other [numpy arrays]
1160       
1161        :raise ValueError: when lengths are not compatible
1162       
1163        """
1164        err_other = None
1165        if isinstance(other, Data2D):
1166            # Check that data lengths are the same
1167            if len(self.data) != len(other.data) or \
1168                len(self.qx_data) != len(other.qx_data) or \
1169                len(self.qy_data) != len(other.qy_data):
1170                msg = "Unable to perform operation: data length are not equal"
1171                raise ValueError, msg
1172            #if len(self.data) < 1:
1173            #    msg = "Incompatible data sets: I-values do not match"
1174            #    raise ValueError, msg
1175            for ind in range(len(self.data)):
1176                if self.qx_data[ind] != other.qx_data[ind]:
1177                    msg = "Incompatible data sets: qx-values do not match"
1178                    raise ValueError, msg
1179                if self.qy_data[ind] != other.qy_data[ind]:
1180                    msg = "Incompatible data sets: qy-values do not match"
1181                    raise ValueError, msg
1182                   
1183            # Check that the scales match
1184            err_other = other.err_data
1185            if other.err_data == None or \
1186                (len(other.err_data) != len(other.data)):
1187                err_other = numpy.zeros(len(other.data))
1188           
1189        # Check that we have errors, otherwise create zero vector
1190        err = self.err_data
1191        if self.err_data == None or \
1192            (len(self.err_data) != len(self.data)):
1193            err = numpy.zeros(len(other.data))
1194           
1195        return err, err_other
1196 
1197    def _perform_operation(self, other, operation):
1198        """
1199        Perform 2D operations between data sets
1200       
1201        :param other: other data set
1202        :param operation: function defining the operation
1203       
1204        """
1205        # First, check the data compatibility
1206        dy, dy_other = self._validity_check(other)
1207        result = self.clone_without_data(numpy.size(self.data))
1208        if self.dqx_data == None or self.dqy_data == None:
1209            result.dqx_data = None
1210            result.dqy_data = None
1211        else:
1212            result.dqx_data = numpy.zeros(len(self.data))
1213            result.dqy_data = numpy.zeros(len(self.data))
1214        for i in range(numpy.size(self.data)):
1215            result.data[i] = self.data[i]
1216            if self.err_data is not None and \
1217                numpy.size(self.data) == numpy.size(self.err_data):
1218                result.err_data[i] = self.err_data[i]   
1219            if self.dqx_data is not None:
1220                result.dqx_data[i] = self.dqx_data[i]
1221            if self.dqy_data is not None:
1222                result.dqy_data[i] = self.dqy_data[i]
1223            result.qx_data[i] = self.qx_data[i]
1224            result.qy_data[i] = self.qy_data[i]
1225            result.q_data[i] = self.q_data[i]
1226            result.mask[i] = self.mask[i]
1227           
1228            a = Uncertainty(self.data[i], dy[i]**2)
1229            if isinstance(other, Data2D):
1230                b = Uncertainty(other.data[i], dy_other[i]**2)
1231                if other.dqx_data is not None and \
1232                        result.dqx_data is not None:
1233                    result.dqx_data[i] *= self.dqx_data[i]
1234                    result.dqx_data[i] += (other.dqx_data[i]**2)
1235                    result.dqx_data[i] /= 2
1236                    result.dqx_data[i] = math.sqrt(result.dqx_data[i])     
1237                if other.dqy_data is not None and \
1238                        result.dqy_data is not None:
1239                    result.dqy_data[i] *= self.dqy_data[i]
1240                    result.dqy_data[i] += (other.dqy_data[i]**2)
1241                    result.dqy_data[i] /= 2
1242                    result.dqy_data[i] = math.sqrt(result.dqy_data[i])
1243            else:
1244                b = other
1245           
1246            output = operation(a, b)
1247            result.data[i] = output.x
1248            result.err_data[i] = math.sqrt(math.fabs(output.variance))
1249        return result
1250   
1251    def _validity_check_union(self, other):
1252        """
1253        Checks that the data lengths are compatible.
1254        Checks that the x vectors are compatible.
1255        Returns errors vectors equal to original
1256        errors vectors if they were present or vectors
1257        of zeros when none was found.
1258       
1259        :param other: other data set for operation
1260       
1261        :return: bool
1262       
1263        :raise ValueError: when data types are not compatible
1264       
1265        """
1266        if not isinstance(other, Data2D):
1267            msg = "Unable to perform operation: different types of data set"
1268            raise ValueError, msg   
1269        return True
1270   
1271    def _perform_union(self, other):
1272        """
1273        Perform 2D operations between data sets
1274       
1275        :param other: other data set
1276        :param operation: function defining the operation
1277       
1278        """
1279        # First, check the data compatibility
1280        self._validity_check_union(other)
1281        result = self.clone_without_data(numpy.size(self.data) + \
1282                                         numpy.size(other.data))
1283        result.xmin = self.xmin
1284        result.xmax = self.xmax
1285        result.ymin = self.ymin
1286        result.ymax = self.ymax
1287        if self.dqx_data == None or self.dqy_data == None or \
1288                other.dqx_data == None or other.dqy_data == None :
1289            result.dqx_data = None
1290            result.dqy_data = None
1291        else:
1292            result.dqx_data = numpy.zeros(len(self.data) + \
1293                                         numpy.size(other.data))
1294            result.dqy_data = numpy.zeros(len(self.data) + \
1295                                         numpy.size(other.data))
1296       
1297        result.data = numpy.append(self.data, other.data)
1298        result.qx_data = numpy.append(self.qx_data, other.qx_data)
1299        result.qy_data = numpy.append(self.qy_data, other.qy_data)
1300        result.q_data = numpy.append(self.q_data, other.q_data)
1301        result.mask = numpy.append(self.mask, other.mask)
1302        if result.err_data is not None:
1303            result.err_data = numpy.append(self.err_data, other.err_data) 
1304        if self.dqx_data is not None:
1305            result.dqx_data = numpy.append(self.dqx_data, other.dqx_data)
1306        if self.dqy_data is not None:
1307            result.dqy_data = numpy.append(self.dqy_data, other.dqy_data)
1308
1309        return result
Note: See TracBrowser for help on using the repository browser.