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

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 1b82623 was 1b82623, checked in by jhbakker, 8 years ago

Halfway removing SESANSdata1D mentions

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