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

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 20522e1 was 20522e1, checked in by jhbakker, 7 years ago

Fixes based on Jeff's comments (20170119). Tested with 1D SESANS data on
Windows 10 machine. Needs to be tested with 1D and 2D SANS data.

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