source: sasview/src/sas/sascalc/dataloader/data_info.py @ b699768

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 b699768 was b699768, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Initial commit of the refactored SasCalc? module.

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