Index: src/sas/sascalc/dataloader/__init__.py
===================================================================
--- src/sas/sascalc/dataloader/__init__.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/__init__.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,3 @@
+from data_info import *
+from manipulations import *
+from readers import *
Index: src/sas/sascalc/dataloader/data_info.py
===================================================================
--- src/sas/sascalc/dataloader/data_info.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/data_info.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,1227 @@
+"""
+    Module that contains classes to hold information read from
+    reduced data files.
+
+    A good description of the data members can be found in
+    the CanSAS 1D XML data format:
+
+    http://www.smallangles.net/wgwiki/index.php/cansas1d_documentation
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+
+
+#TODO: Keep track of data manipulation in the 'process' data structure.
+#TODO: This module should be independent of plottables. We should write
+#        an adapter class for plottables when needed.
+
+#from sas.guitools.plottables import Data1D as plottable_1D
+from sas.sascalc.data_util.uncertainty import Uncertainty
+import numpy
+import math
+
+class plottable_sesans1D(object):
+    """
+    SESANS is a place holder for 1D SESANS plottables.
+
+    #TODO: This was directly copied from the plottables_1D. Modified Somewhat.
+    #Class has been updated.
+    """
+    # The presence of these should be mutually
+    # exclusive with the presence of Qdev (dx)
+    x = None
+    y = None
+    lam = None
+    dx = None
+    dy = None
+    dlam = None
+    ## Slit smearing length
+    dxl = None
+    ## Slit smearing width
+    dxw = None
+
+    # Units
+    _xaxis = ''
+    _xunit = ''
+    _yaxis = ''
+    _yunit = ''
+
+    def __init__(self, x, y, lam, dx=None, dy=None, dlam=None):
+#        print "SESANS plottable working"
+        self.x = numpy.asarray(x)
+        self.y = numpy.asarray(y)
+        self.lam = numpy.asarray(lam)
+        if dx is not None:
+            self.dx = numpy.asarray(dx)
+        if dy is not None:
+            self.dy = numpy.asarray(dy)
+        if dlam is not None:
+            self.dlam = numpy.asarray(dlam)
+
+    def xaxis(self, label, unit):
+        """
+        set the x axis label and unit
+        """
+        self._xaxis = label
+        self._xunit = unit
+
+    def yaxis(self, label, unit):
+        """
+        set the y axis label and unit
+        """
+        self._yaxis = label
+        self._yunit = unit
+
+
+class plottable_1D(object):
+    """
+    Data1D is a place holder for 1D plottables.
+    """
+    # The presence of these should be mutually
+    # exclusive with the presence of Qdev (dx)
+    x = None
+    y = None
+    dx = None
+    dy = None
+    ## Slit smearing length
+    dxl = None
+    ## Slit smearing width
+    dxw = None
+
+    # Units
+    _xaxis = ''
+    _xunit = ''
+    _yaxis = ''
+    _yunit = ''
+
+    def __init__(self, x, y, dx=None, dy=None, dxl=None, dxw=None):
+        self.x = numpy.asarray(x)
+        self.y = numpy.asarray(y)
+        if dx is not None:
+            self.dx = numpy.asarray(dx)
+        if dy is not None:
+            self.dy = numpy.asarray(dy)
+        if dxl is not None:
+            self.dxl = numpy.asarray(dxl)
+        if dxw is not None:
+            self.dxw = numpy.asarray(dxw)
+
+    def xaxis(self, label, unit):
+        """
+        set the x axis label and unit
+        """
+        self._xaxis = label
+        self._xunit = unit
+
+    def yaxis(self, label, unit):
+        """
+        set the y axis label and unit
+        """
+        self._yaxis = label
+        self._yunit = unit
+
+
+class plottable_2D(object):
+    """
+    Data2D is a place holder for 2D plottables.
+    """
+    xmin = None
+    xmax = None
+    ymin = None
+    ymax = None
+    data = None
+    qx_data = None
+    qy_data = None
+    q_data = None
+    err_data = None
+    dqx_data = None
+    dqy_data = None
+    mask = None
+
+    # Units
+    _xaxis = ''
+    _xunit = ''
+    _yaxis = ''
+    _yunit = ''
+    _zaxis = ''
+    _zunit = ''
+
+    def __init__(self, data=None, err_data=None, qx_data=None,
+                 qy_data=None, q_data=None, mask=None,
+                 dqx_data=None, dqy_data=None):
+        self.data = numpy.asarray(data)
+        self.qx_data = numpy.asarray(qx_data)
+        self.qy_data = numpy.asarray(qy_data)
+        self.q_data = numpy.asarray(q_data)
+        self.mask = numpy.asarray(mask)
+        self.err_data = numpy.asarray(err_data)
+        if dqx_data is not None:
+            self.dqx_data = numpy.asarray(dqx_data)
+        if dqy_data is not None:
+            self.dqy_data = numpy.asarray(dqy_data)
+
+    def xaxis(self, label, unit):
+        """
+        set the x axis label and unit
+        """
+        self._xaxis = label
+        self._xunit = unit
+
+    def yaxis(self, label, unit):
+        """
+        set the y axis label and unit
+        """
+        self._yaxis = label
+        self._yunit = unit
+
+    def zaxis(self, label, unit):
+        """
+        set the z axis label and unit
+        """
+        self._zaxis = label
+        self._zunit = unit
+
+
+class Vector(object):
+    """
+    Vector class to hold multi-dimensional objects
+    """
+    ## x component
+    x = None
+    ## y component
+    y = None
+    ## z component
+    z = None
+
+    def __init__(self, x=None, y=None, z=None):
+        """
+        Initialization. Components that are not
+        set a set to None by default.
+
+        :param x: x component
+        :param y: y component
+        :param z: z component
+        """
+        self.x = x
+        self.y = y
+        self.z = z
+
+    def __str__(self):
+        msg = "x = %s\ty = %s\tz = %s" % (str(self.x), str(self.y), str(self.z))
+        return msg
+
+
+class Detector(object):
+    """
+    Class to hold detector information
+    """
+    ## Name of the instrument [string]
+    name = None
+    ## Sample to detector distance [float] [mm]
+    distance = None
+    distance_unit = 'mm'
+    ## Offset of this detector position in X, Y,
+    #(and Z if necessary) [Vector] [mm]
+    offset = None
+    offset_unit = 'm'
+    ## Orientation (rotation) of this detector in roll,
+    # pitch, and yaw [Vector] [degrees]
+    orientation = None
+    orientation_unit = 'degree'
+    ## Center of the beam on the detector in X and Y
+    #(and Z if necessary) [Vector] [mm]
+    beam_center = None
+    beam_center_unit = 'mm'
+    ## Pixel size in X, Y, (and Z if necessary) [Vector] [mm]
+    pixel_size = None
+    pixel_size_unit = 'mm'
+    ## Slit length of the instrument for this detector.[float] [mm]
+    slit_length = None
+    slit_length_unit = 'mm'
+
+    def __init__(self):
+        """
+        Initialize class attribute that are objects...
+        """
+        self.offset = Vector()
+        self.orientation = Vector()
+        self.beam_center = Vector()
+        self.pixel_size = Vector()
+
+    def __str__(self):
+        _str = "Detector:\n"
+        _str += "   Name:         %s\n" % self.name
+        _str += "   Distance:     %s [%s]\n" % \
+            (str(self.distance), str(self.distance_unit))
+        _str += "   Offset:       %s [%s]\n" % \
+            (str(self.offset), str(self.offset_unit))
+        _str += "   Orientation:  %s [%s]\n" % \
+            (str(self.orientation), str(self.orientation_unit))
+        _str += "   Beam center:  %s [%s]\n" % \
+            (str(self.beam_center), str(self.beam_center_unit))
+        _str += "   Pixel size:   %s [%s]\n" % \
+            (str(self.pixel_size), str(self.pixel_size_unit))
+        _str += "   Slit length:  %s [%s]\n" % \
+            (str(self.slit_length), str(self.slit_length_unit))
+        return _str
+
+
+class Aperture(object):
+    ## Name
+    name = None
+    ## Type
+    type = None
+    ## Size name
+    size_name = None
+    ## Aperture size [Vector]
+    size = None
+    size_unit = 'mm'
+    ## Aperture distance [float]
+    distance = None
+    distance_unit = 'mm'
+
+    def __init__(self):
+        self.size = Vector()
+
+
+class Collimation(object):
+    """
+    Class to hold collimation information
+    """
+    ## Name
+    name = None
+    ## Length [float] [mm]
+    length = None
+    length_unit = 'mm'
+    ## Aperture
+    aperture = None
+
+    def __init__(self):
+        self.aperture = []
+
+    def __str__(self):
+        _str = "Collimation:\n"
+        _str += "   Length:       %s [%s]\n" % \
+            (str(self.length), str(self.length_unit))
+        for item in self.aperture:
+            _str += "   Aperture size:%s [%s]\n" % \
+                (str(item.size), str(item.size_unit))
+            _str += "   Aperture_dist:%s [%s]\n" % \
+                (str(item.distance), str(item.distance_unit))
+        return _str
+
+
+class Source(object):
+    """
+    Class to hold source information
+    """
+    ## Name
+    name = None
+    ## Radiation type [string]
+    radiation = None
+    ## Beam size name
+    beam_size_name = None
+    ## Beam size [Vector] [mm]
+    beam_size = None
+    beam_size_unit = 'mm'
+    ## Beam shape [string]
+    beam_shape = None
+    ## Wavelength [float] [Angstrom]
+    wavelength = None
+    wavelength_unit = 'A'
+    ## Minimum wavelength [float] [Angstrom]
+    wavelength_min = None
+    wavelength_min_unit = 'nm'
+    ## Maximum wavelength [float] [Angstrom]
+    wavelength_max = None
+    wavelength_max_unit = 'nm'
+    ## Wavelength spread [float] [Angstrom]
+    wavelength_spread = None
+    wavelength_spread_unit = 'percent'
+
+    def __init__(self):
+        self.beam_size = Vector()
+
+    def __str__(self):
+        _str = "Source:\n"
+        _str += "   Radiation:    %s\n" % str(self.radiation)
+        _str += "   Shape:        %s\n" % str(self.beam_shape)
+        _str += "   Wavelength:   %s [%s]\n" % \
+            (str(self.wavelength), str(self.wavelength_unit))
+        _str += "   Waveln_min:   %s [%s]\n" % \
+            (str(self.wavelength_min), str(self.wavelength_min_unit))
+        _str += "   Waveln_max:   %s [%s]\n" % \
+            (str(self.wavelength_max), str(self.wavelength_max_unit))
+        _str += "   Waveln_spread:%s [%s]\n" % \
+            (str(self.wavelength_spread), str(self.wavelength_spread_unit))
+        _str += "   Beam_size:    %s [%s]\n" % \
+            (str(self.beam_size), str(self.beam_size_unit))
+        return _str
+
+
+"""
+Definitions of radiation types
+"""
+NEUTRON = 'neutron'
+XRAY = 'x-ray'
+MUON = 'muon'
+ELECTRON = 'electron'
+
+
+class Sample(object):
+    """
+    Class to hold the sample description
+    """
+    ## Short name for sample
+    name = ''
+    ## ID
+    ID = ''
+    ## Thickness [float] [mm]
+    thickness = None
+    thickness_unit = 'mm'
+    ## Transmission [float] [fraction]
+    transmission = None
+    ## Temperature [float] [No Default]
+    temperature = None
+    temperature_unit = None
+    ## Position [Vector] [mm]
+    position = None
+    position_unit = 'mm'
+    ## Orientation [Vector] [degrees]
+    orientation = None
+    orientation_unit = 'degree'
+    ## Details
+    details = None
+
+    def __init__(self):
+        self.position = Vector()
+        self.orientation = Vector()
+        self.details = []
+
+    def __str__(self):
+        _str = "Sample:\n"
+        _str += "   ID:           %s\n" % str(self.ID)
+        _str += "   Transmission: %s\n" % str(self.transmission)
+        _str += "   Thickness:    %s [%s]\n" % \
+            (str(self.thickness), str(self.thickness_unit))
+        _str += "   Temperature:  %s [%s]\n" % \
+            (str(self.temperature), str(self.temperature_unit))
+        _str += "   Position:     %s [%s]\n" % \
+            (str(self.position), str(self.position_unit))
+        _str += "   Orientation:  %s [%s]\n" % \
+            (str(self.orientation), str(self.orientation_unit))
+
+        _str += "   Details:\n"
+        for item in self.details:
+            _str += "      %s\n" % item
+
+        return _str
+
+
+class Process(object):
+    """
+    Class that holds information about the processes
+    performed on the data.
+    """
+    name = ''
+    date = ''
+    description = ''
+    term = None
+    notes = None
+
+    def __init__(self):
+        self.term = []
+        self.notes = []
+
+    def is_empty(self):
+        """
+            Return True if the object is empty
+        """
+        return len(self.name) == 0 and len(self.date) == 0 and len(self.description) == 0 \
+            and len(self.term) == 0 and len(self.notes) == 0
+            
+    def single_line_desc(self):
+        """
+            Return a single line string representing the process
+        """
+        return "%s %s %s" % (self.name, self.date, self.description)
+     
+    def __str__(self):
+        _str = "Process:\n"
+        _str += "   Name:         %s\n" % self.name
+        _str += "   Date:         %s\n" % self.date
+        _str += "   Description:  %s\n" % self.description
+        for item in self.term:
+            _str += "   Term:         %s\n" % item
+        for item in self.notes:
+            _str += "   Note:         %s\n" % item
+        return _str
+
+
+class TransmissionSpectrum(object):
+    """
+    Class that holds information about transmission spectrum
+    for white beams and spallation sources.
+    """
+    name = ''
+    timestamp = ''
+    ## Wavelength (float) [A]
+    wavelength = None
+    wavelength_unit = 'A'
+    ## Transmission (float) [unit less]
+    transmission = None
+    transmission_unit = ''
+    ## Transmission Deviation (float) [unit less]
+    transmission_deviation = None
+    transmission_deviation_unit = ''
+
+    def __init__(self):
+        self.wavelength = []
+        self.transmission = []
+        self.transmission_deviation = []
+
+    def __str__(self):
+        _str = "Transmission Spectrum:\n"
+        _str += "   Name:             \t{0}\n".format(self.name)
+        _str += "   Timestamp:        \t{0}\n".format(self.timestamp)
+        _str += "   Wavelength unit:  \t{0}\n".format(self.wavelength_unit)
+        _str += "   Transmission unit:\t{0}\n".format(self.transmission_unit)
+        _str += "   Trans. Dev. unit:  \t{0}\n".format(\
+                                            self.transmission_deviation_unit)
+        length_list = [len(self.wavelength), len(self.transmission), \
+                len(self.transmission_deviation)]
+        _str += "   Number of Pts:    \t{0}\n".format(max(length_list))
+        return _str
+
+
+class DataInfo(object):
+    """
+    Class to hold the data read from a file.
+    It includes four blocks of data for the
+    instrument description, the sample description,
+    the data itself and any other meta data.
+    """
+    ## Title
+    title = ''
+    ## Run number
+    run = None
+    ## Run name
+    run_name = None
+    ## File name
+    filename = ''
+    ## Notes
+    notes = None
+    ## Processes (Action on the data)
+    process = None
+    ## Instrument name
+    instrument = ''
+    ## Detector information
+    detector = None
+    ## Sample information
+    sample = None
+    ## Source information
+    source = None
+    ## Collimation information
+    collimation = None
+    ## Transmission Spectrum INfo
+    trans_spectrum = None
+    ## Additional meta-data
+    meta_data = None
+    ## Loading errors
+    errors = None
+
+    def __init__(self):
+        """
+        Initialization
+        """
+        ## Title
+        self.title = ''
+        ## Run number
+        self.run = []
+        self.run_name = {}
+        ## File name
+        self.filename = ''
+        ## Notes
+        self.notes = []
+        ## Processes (Action on the data)
+        self.process = []
+        ## Instrument name
+        self.instrument = ''
+        ## Detector information
+        self.detector = []
+        ## Sample information
+        self.sample = Sample()
+        ## Source information
+        self.source = Source()
+        ## Collimation information
+        self.collimation = []
+        ## Transmission Spectrum
+        self.trans_spectrum = []
+        ## Additional meta-data
+        self.meta_data = {}
+        ## Loading errors
+        self.errors = []
+
+    def append_empty_process(self):
+        """
+        """
+        self.process.append(Process())
+
+    def add_notes(self, message=""):
+        """
+        Add notes to datainfo
+        """
+        self.notes.append(message)
+
+    def __str__(self):
+        """
+        Nice printout
+        """
+        _str = "File:            %s\n" % self.filename
+        _str += "Title:           %s\n" % self.title
+        _str += "Run:             %s\n" % str(self.run)
+        _str += "Instrument:      %s\n" % str(self.instrument)
+        _str += "%s\n" % str(self.sample)
+        _str += "%s\n" % str(self.source)
+        for item in self.detector:
+            _str += "%s\n" % str(item)
+        for item in self.collimation:
+            _str += "%s\n" % str(item)
+        for item in self.process:
+            _str += "%s\n" % str(item)
+        for item in self.notes:
+            _str += "%s\n" % str(item)
+        for item in self.trans_spectrum:
+            _str += "%s\n" % str(item)
+        return _str
+
+    # Private method to perform operation. Not implemented for DataInfo,
+    # but should be implemented for each data class inherited from DataInfo
+    # that holds actual data (ex.: Data1D)
+    def _perform_operation(self, other, operation):
+        """
+        Private method to perform operation. Not implemented for DataInfo,
+        but should be implemented for each data class inherited from DataInfo
+        that holds actual data (ex.: Data1D)
+        """
+        return NotImplemented
+
+    def _perform_union(self, other):
+        """
+        Private method to perform union operation. Not implemented for DataInfo,
+        but should be implemented for each data class inherited from DataInfo
+        that holds actual data (ex.: Data1D)
+        """
+        return NotImplemented
+
+    def __add__(self, other):
+        """
+        Add two data sets
+
+        :param other: data set to add to the current one
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return a + b
+        return self._perform_operation(other, operation)
+
+    def __radd__(self, other):
+        """
+        Add two data sets
+
+        :param other: data set to add to the current one
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return b + a
+        return self._perform_operation(other, operation)
+
+    def __sub__(self, other):
+        """
+        Subtract two data sets
+
+        :param other: data set to subtract from the current one
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return a - b
+        return self._perform_operation(other, operation)
+
+    def __rsub__(self, other):
+        """
+        Subtract two data sets
+
+        :param other: data set to subtract from the current one
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return b - a
+        return self._perform_operation(other, operation)
+
+    def __mul__(self, other):
+        """
+        Multiply two data sets
+
+        :param other: data set to subtract from the current one
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return a * b
+        return self._perform_operation(other, operation)
+
+    def __rmul__(self, other):
+        """
+        Multiply two data sets
+
+        :param other: data set to subtract from the current one
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return b * a
+        return self._perform_operation(other, operation)
+
+    def __div__(self, other):
+        """
+        Divided a data set by another
+
+        :param other: data set that the current one is divided by
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return a/b
+        return self._perform_operation(other, operation)
+
+    def __rdiv__(self, other):
+        """
+        Divided a data set by another
+
+        :param other: data set that the current one is divided by
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        def operation(a, b):
+            return b/a
+        return self._perform_operation(other, operation)
+
+    def __or__(self, other):
+        """
+        Union a data set with another
+
+        :param other: data set to be unified
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        return self._perform_union(other)
+
+    def __ror__(self, other):
+        """
+        Union a data set with another
+
+        :param other: data set to be unified
+        :return: new data set
+        :raise ValueError: raised when two data sets are incompatible
+        """
+        return self._perform_union(other)
+
+class SESANSData1D(plottable_sesans1D, DataInfo):
+    """
+    SESANS 1D data class
+    """
+    x_unit = 'nm'
+    y_unit = 'pol'
+
+    def __init__(self, x=None, y=None, lam=None, dx=None, dy=None, dlam=None):
+        DataInfo.__init__(self)
+        plottable_sesans1D.__init__(self, x, y, lam, dx, dy, dlam)
+
+    def __str__(self):
+        """
+        Nice printout
+        """
+        _str = "%s\n" % DataInfo.__str__(self)
+        _str += "Data:\n"
+        _str += "   Type:         %s\n" % self.__class__.__name__
+        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
+        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
+        _str += "   Length:       %g\n" % len(self.x)
+        return _str
+
+    def clone_without_data(self, length=0, clone=None):
+        """
+        Clone the current object, without copying the data (which
+        will be filled out by a subsequent operation).
+        The data arrays will be initialized to zero.
+
+        :param length: length of the data array to be initialized
+        :param clone: if provided, the data will be copied to clone
+        """
+        from copy import deepcopy
+        if clone is None or not issubclass(clone.__class__, Data1D):
+            x = numpy.zeros(length)
+            dx = numpy.zeros(length)
+            y = numpy.zeros(length)
+            dy = numpy.zeros(length)
+            clone = Data1D(x, y, dx=dx, dy=dy)
+
+        clone.title = self.title
+        clone.run = self.run
+        clone.filename = self.filename
+        clone.instrument = self.instrument
+        clone.notes = deepcopy(self.notes)
+        clone.process = deepcopy(self.process)
+        clone.detector = deepcopy(self.detector)
+        clone.sample = deepcopy(self.sample)
+        clone.source = deepcopy(self.source)
+        clone.collimation = deepcopy(self.collimation)
+        clone.trans_spectrum = deepcopy(self.trans_spectrum)
+        clone.meta_data = deepcopy(self.meta_data)
+        clone.errors = deepcopy(self.errors)
+
+        return clone
+
+class Data1D(plottable_1D, DataInfo):
+    """
+    1D data class
+    """
+    x_unit = '1/A'
+    y_unit = '1/cm'
+
+    def __init__(self, x, y, dx=None, dy=None):
+        DataInfo.__init__(self)
+        plottable_1D.__init__(self, x, y, dx, dy)
+
+    def __str__(self):
+        """
+        Nice printout
+        """
+        _str = "%s\n" % DataInfo.__str__(self)
+        _str += "Data:\n"
+        _str += "   Type:         %s\n" % self.__class__.__name__
+        _str += "   X-axis:       %s\t[%s]\n" % (self._xaxis, self._xunit)
+        _str += "   Y-axis:       %s\t[%s]\n" % (self._yaxis, self._yunit)
+        _str += "   Length:       %g\n" % len(self.x)
+        return _str
+
+    def is_slit_smeared(self):
+        """
+        Check whether the data has slit smearing information
+        :return: True is slit smearing info is present, False otherwise
+        """
+        def _check(v):
+            if (v.__class__ == list or v.__class__ == numpy.ndarray) \
+                and len(v) > 0 and min(v) > 0:
+                return True
+            return False
+        return _check(self.dxl) or _check(self.dxw)
+
+    def clone_without_data(self, length=0, clone=None):
+        """
+        Clone the current object, without copying the data (which
+        will be filled out by a subsequent operation).
+        The data arrays will be initialized to zero.
+
+        :param length: length of the data array to be initialized
+        :param clone: if provided, the data will be copied to clone
+        """
+        from copy import deepcopy
+
+        if clone is None or not issubclass(clone.__class__, Data1D):
+            x = numpy.zeros(length)
+            dx = numpy.zeros(length)
+            y = numpy.zeros(length)
+            dy = numpy.zeros(length)
+            clone = Data1D(x, y, dx=dx, dy=dy)
+
+        clone.title = self.title
+        clone.run = self.run
+        clone.filename = self.filename
+        clone.instrument = self.instrument
+        clone.notes = deepcopy(self.notes)
+        clone.process = deepcopy(self.process)
+        clone.detector = deepcopy(self.detector)
+        clone.sample = deepcopy(self.sample)
+        clone.source = deepcopy(self.source)
+        clone.collimation = deepcopy(self.collimation)
+        clone.trans_spectrum = deepcopy(self.trans_spectrum)
+        clone.meta_data = deepcopy(self.meta_data)
+        clone.errors = deepcopy(self.errors)
+
+        return clone
+
+    def _validity_check(self, other):
+        """
+        Checks that the data lengths are compatible.
+        Checks that the x vectors are compatible.
+        Returns errors vectors equal to original
+        errors vectors if they were present or vectors
+        of zeros when none was found.
+
+        :param other: other data set for operation
+        :return: dy for self, dy for other [numpy arrays]
+        :raise ValueError: when lengths are not compatible
+        """
+        dy_other = None
+        if isinstance(other, Data1D):
+            # Check that data lengths are the same
+            if len(self.x) != len(other.x) or \
+                len(self.y) != len(other.y):
+                msg = "Unable to perform operation: data length are not equal"
+                raise ValueError, msg
+            # Here we could also extrapolate between data points
+            ZERO = 1.0e-12
+            for i in range(len(self.x)):
+                if math.fabs(self.x[i] - other.x[i]) > ZERO:
+                    msg = "Incompatible data sets: x-values do not match"
+                    raise ValueError, msg
+
+            # Check that the other data set has errors, otherwise
+            # create zero vector
+            dy_other = other.dy
+            if other.dy == None or (len(other.dy) != len(other.y)):
+                dy_other = numpy.zeros(len(other.y))
+
+        # Check that we have errors, otherwise create zero vector
+        dy = self.dy
+        if self.dy == None or (len(self.dy) != len(self.y)):
+            dy = numpy.zeros(len(self.y))
+
+        return dy, dy_other
+
+    def _perform_operation(self, other, operation):
+        """
+        """
+        # First, check the data compatibility
+        dy, dy_other = self._validity_check(other)
+        result = self.clone_without_data(len(self.x))
+        if self.dxw == None:
+            result.dxw = None
+        else:
+            result.dxw = numpy.zeros(len(self.x))
+        if self.dxl == None:
+            result.dxl = None
+        else:
+            result.dxl = numpy.zeros(len(self.x))
+
+        for i in range(len(self.x)):
+            result.x[i] = self.x[i]
+            if self.dx is not None and len(self.x) == len(self.dx):
+                result.dx[i] = self.dx[i]
+            if self.dxw is not None and len(self.x) == len(self.dxw):
+                result.dxw[i] = self.dxw[i]
+            if self.dxl is not None and len(self.x) == len(self.dxl):
+                result.dxl[i] = self.dxl[i]
+
+            a = Uncertainty(self.y[i], dy[i]**2)
+            if isinstance(other, Data1D):
+                b = Uncertainty(other.y[i], dy_other[i]**2)
+                if other.dx is not None:
+                    result.dx[i] *= self.dx[i]
+                    result.dx[i] += (other.dx[i]**2)
+                    result.dx[i] /= 2
+                    result.dx[i] = math.sqrt(result.dx[i])
+                if result.dxl is not None and other.dxl is not None:
+                    result.dxl[i] *= self.dxl[i]
+                    result.dxl[i] += (other.dxl[i]**2)
+                    result.dxl[i] /= 2
+                    result.dxl[i] = math.sqrt(result.dxl[i])
+            else:
+                b = other
+
+            output = operation(a, b)
+            result.y[i] = output.x
+            result.dy[i] = math.sqrt(math.fabs(output.variance))
+        return result
+
+    def _validity_check_union(self, other):
+        """
+        Checks that the data lengths are compatible.
+        Checks that the x vectors are compatible.
+        Returns errors vectors equal to original
+        errors vectors if they were present or vectors
+        of zeros when none was found.
+
+        :param other: other data set for operation
+        :return: bool
+        :raise ValueError: when data types are not compatible
+        """
+        if not isinstance(other, Data1D):
+            msg = "Unable to perform operation: different types of data set"
+            raise ValueError, msg
+        return True
+
+    def _perform_union(self, other):
+        """
+        """
+        # First, check the data compatibility
+        self._validity_check_union(other)
+        result = self.clone_without_data(len(self.x) + len(other.x))
+        if self.dy == None or other.dy is None:
+            result.dy = None
+        else:
+            result.dy = numpy.zeros(len(self.x) + len(other.x))
+        if self.dx == None or other.dx is None:
+            result.dx = None
+        else:
+            result.dx = numpy.zeros(len(self.x) + len(other.x))
+        if self.dxw == None or other.dxw is None:
+            result.dxw = None
+        else:
+            result.dxw = numpy.zeros(len(self.x) + len(other.x))
+        if self.dxl == None or other.dxl is None:
+            result.dxl = None
+        else:
+            result.dxl = numpy.zeros(len(self.x) + len(other.x))
+
+        result.x = numpy.append(self.x, other.x)
+        #argsorting
+        ind = numpy.argsort(result.x)
+        result.x = result.x[ind]
+        result.y = numpy.append(self.y, other.y)
+        result.y = result.y[ind]
+        if result.dy != None:
+            result.dy = numpy.append(self.dy, other.dy)
+            result.dy = result.dy[ind]
+        if result.dx is not None:
+            result.dx = numpy.append(self.dx, other.dx)
+            result.dx = result.dx[ind]
+        if result.dxw is not None:
+            result.dxw = numpy.append(self.dxw, other.dxw)
+            result.dxw = result.dxw[ind]
+        if result.dxl is not None:
+            result.dxl = numpy.append(self.dxl, other.dxl)
+            result.dxl = result.dxl[ind]
+        return result
+
+
+class Data2D(plottable_2D, DataInfo):
+    """
+    2D data class
+    """
+    ## Units for Q-values
+    Q_unit = '1/A'
+    ## Units for I(Q) values
+    I_unit = '1/cm'
+    ## Vector of Q-values at the center of each bin in x
+    x_bins = None
+    ## Vector of Q-values at the center of each bin in y
+    y_bins = None
+
+    def __init__(self, data=None, err_data=None, qx_data=None,
+                 qy_data=None, q_data=None, mask=None,
+                 dqx_data=None, dqy_data=None):
+        self.y_bins = []
+        self.x_bins = []
+        DataInfo.__init__(self)
+        plottable_2D.__init__(self, data, err_data, qx_data,
+                              qy_data, q_data, mask, dqx_data, dqy_data)
+        if len(self.detector) > 0:
+            raise RuntimeError, "Data2D: Detector bank already filled at init"
+
+    def __str__(self):
+        _str = "%s\n" % DataInfo.__str__(self)
+        _str += "Data:\n"
+        _str += "   Type:         %s\n" % self.__class__.__name__
+        _str += "   X- & Y-axis:  %s\t[%s]\n" % (self._yaxis, self._yunit)
+        _str += "   Z-axis:       %s\t[%s]\n" % (self._zaxis, self._zunit)
+        _str += "   Length:       %g \n" % (len(self.data))
+        return _str
+
+    def clone_without_data(self, length=0, clone=None):
+        """
+        Clone the current object, without copying the data (which
+        will be filled out by a subsequent operation).
+        The data arrays will be initialized to zero.
+
+        :param length: length of the data array to be initialized
+        :param clone: if provided, the data will be copied to clone
+        """
+        from copy import deepcopy
+
+        if clone is None or not issubclass(clone.__class__, Data2D):
+            data = numpy.zeros(length)
+            err_data = numpy.zeros(length)
+            qx_data = numpy.zeros(length)
+            qy_data = numpy.zeros(length)
+            q_data = numpy.zeros(length)
+            mask = numpy.zeros(length)
+            dqx_data = None
+            dqy_data = None
+            clone = Data2D(data=data, err_data=err_data,
+                           qx_data=qx_data, qy_data=qy_data,
+                           q_data=q_data, mask=mask)
+
+        clone.title = self.title
+        clone.run = self.run
+        clone.filename = self.filename
+        clone.instrument = self.instrument
+        clone.notes = deepcopy(self.notes)
+        clone.process = deepcopy(self.process)
+        clone.detector = deepcopy(self.detector)
+        clone.sample = deepcopy(self.sample)
+        clone.source = deepcopy(self.source)
+        clone.collimation = deepcopy(self.collimation)
+        clone.meta_data = deepcopy(self.meta_data)
+        clone.errors = deepcopy(self.errors)
+
+        return clone
+
+    def _validity_check(self, other):
+        """
+        Checks that the data lengths are compatible.
+        Checks that the x vectors are compatible.
+        Returns errors vectors equal to original
+        errors vectors if they were present or vectors
+        of zeros when none was found.
+
+        :param other: other data set for operation
+        :return: dy for self, dy for other [numpy arrays]
+        :raise ValueError: when lengths are not compatible
+        """
+        err_other = None
+        if isinstance(other, Data2D):
+            # Check that data lengths are the same
+            if len(self.data) != len(other.data) or \
+                len(self.qx_data) != len(other.qx_data) or \
+                len(self.qy_data) != len(other.qy_data):
+                msg = "Unable to perform operation: data length are not equal"
+                raise ValueError, msg
+            for ind in range(len(self.data)):
+                if self.qx_data[ind] != other.qx_data[ind]:
+                    msg = "Incompatible data sets: qx-values do not match"
+                    raise ValueError, msg
+                if self.qy_data[ind] != other.qy_data[ind]:
+                    msg = "Incompatible data sets: qy-values do not match"
+                    raise ValueError, msg
+
+            # Check that the scales match
+            err_other = other.err_data
+            if other.err_data == None or \
+                (len(other.err_data) != len(other.data)):
+                err_other = numpy.zeros(len(other.data))
+
+        # Check that we have errors, otherwise create zero vector
+        err = self.err_data
+        if self.err_data == None or \
+            (len(self.err_data) != len(self.data)):
+            err = numpy.zeros(len(other.data))
+        return err, err_other
+
+    def _perform_operation(self, other, operation):
+        """
+        Perform 2D operations between data sets
+
+        :param other: other data set
+        :param operation: function defining the operation
+        """
+        # First, check the data compatibility
+        dy, dy_other = self._validity_check(other)
+        result = self.clone_without_data(numpy.size(self.data))
+        if self.dqx_data == None or self.dqy_data == None:
+            result.dqx_data = None
+            result.dqy_data = None
+        else:
+            result.dqx_data = numpy.zeros(len(self.data))
+            result.dqy_data = numpy.zeros(len(self.data))
+        for i in range(numpy.size(self.data)):
+            result.data[i] = self.data[i]
+            if self.err_data is not None and \
+                numpy.size(self.data) == numpy.size(self.err_data):
+                result.err_data[i] = self.err_data[i]
+            if self.dqx_data is not None:
+                result.dqx_data[i] = self.dqx_data[i]
+            if self.dqy_data is not None:
+                result.dqy_data[i] = self.dqy_data[i]
+            result.qx_data[i] = self.qx_data[i]
+            result.qy_data[i] = self.qy_data[i]
+            result.q_data[i] = self.q_data[i]
+            result.mask[i] = self.mask[i]
+
+            a = Uncertainty(self.data[i], dy[i]**2)
+            if isinstance(other, Data2D):
+                b = Uncertainty(other.data[i], dy_other[i]**2)
+                if other.dqx_data is not None and \
+                        result.dqx_data is not None:
+                    result.dqx_data[i] *= self.dqx_data[i]
+                    result.dqx_data[i] += (other.dqx_data[i]**2)
+                    result.dqx_data[i] /= 2
+                    result.dqx_data[i] = math.sqrt(result.dqx_data[i])
+                if other.dqy_data is not None and \
+                        result.dqy_data is not None:
+                    result.dqy_data[i] *= self.dqy_data[i]
+                    result.dqy_data[i] += (other.dqy_data[i]**2)
+                    result.dqy_data[i] /= 2
+                    result.dqy_data[i] = math.sqrt(result.dqy_data[i])
+            else:
+                b = other
+            output = operation(a, b)
+            result.data[i] = output.x
+            result.err_data[i] = math.sqrt(math.fabs(output.variance))
+        return result
+
+    def _validity_check_union(self, other):
+        """
+        Checks that the data lengths are compatible.
+        Checks that the x vectors are compatible.
+        Returns errors vectors equal to original
+        errors vectors if they were present or vectors
+        of zeros when none was found.
+
+        :param other: other data set for operation
+        :return: bool
+        :raise ValueError: when data types are not compatible
+        """
+        if not isinstance(other, Data2D):
+            msg = "Unable to perform operation: different types of data set"
+            raise ValueError, msg
+        return True
+
+    def _perform_union(self, other):
+        """
+        Perform 2D operations between data sets
+
+        :param other: other data set
+        :param operation: function defining the operation
+        """
+        # First, check the data compatibility
+        self._validity_check_union(other)
+        result = self.clone_without_data(numpy.size(self.data) + \
+                                         numpy.size(other.data))
+        result.xmin = self.xmin
+        result.xmax = self.xmax
+        result.ymin = self.ymin
+        result.ymax = self.ymax
+        if self.dqx_data == None or self.dqy_data == None or \
+                other.dqx_data == None or other.dqy_data == None:
+            result.dqx_data = None
+            result.dqy_data = None
+        else:
+            result.dqx_data = numpy.zeros(len(self.data) + \
+                                         numpy.size(other.data))
+            result.dqy_data = numpy.zeros(len(self.data) + \
+                                         numpy.size(other.data))
+
+        result.data = numpy.append(self.data, other.data)
+        result.qx_data = numpy.append(self.qx_data, other.qx_data)
+        result.qy_data = numpy.append(self.qy_data, other.qy_data)
+        result.q_data = numpy.append(self.q_data, other.q_data)
+        result.mask = numpy.append(self.mask, other.mask)
+        if result.err_data is not None:
+            result.err_data = numpy.append(self.err_data, other.err_data) 
+        if self.dqx_data is not None:
+            result.dqx_data = numpy.append(self.dqx_data, other.dqx_data)
+        if self.dqy_data is not None:
+            result.dqy_data = numpy.append(self.dqy_data, other.dqy_data)
+
+        return result
Index: src/sas/sascalc/dataloader/loader.py
===================================================================
--- src/sas/sascalc/dataloader/loader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/loader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,385 @@
+"""
+    File handler to support different file extensions.
+    Uses reflectometry's registry utility.
+
+    The default readers are found in the 'readers' sub-module
+    and registered by default at initialization time.
+
+    To add a new default reader, one must register it in
+    the register_readers method found in readers/__init__.py.
+
+    A utility method (find_plugins) is available to inspect
+    a directory (for instance, a user plug-in directory) and
+    look for new readers/writers.
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+
+import os
+import sys
+import logging
+import time
+from zipfile import ZipFile
+from sas.sascalc.data_util.registry import ExtensionRegistry
+# Default readers are defined in the readers sub-module
+import readers
+from readers import ascii_reader
+from readers import cansas_reader
+
+class Registry(ExtensionRegistry):
+    """
+    Registry class for file format extensions.
+    Readers and writers are supported.
+    """
+
+    def __init__(self):
+        super(Registry, self).__init__()
+
+        ## Writers
+        self.writers = {}
+
+        ## List of wildcards
+        self.wildcards = ['All (*.*)|*.*']
+
+        ## Creation time, for testing
+        self._created = time.time()
+
+        # Register default readers
+        readers.read_associations(self)
+
+    def load(self, path, format=None):
+        """
+        Call the loader for the file type of path.
+
+        :param path: file path
+        :param format: explicit extension, to force the use
+            of a particular reader
+
+        Defaults to the ascii (multi-column) reader
+        if no reader was registered for the file's
+        extension.
+        """
+        try:
+            return super(Registry, self).load(path, format=format)
+        except:
+            try:
+                # No reader was found. Default to the ascii reader.
+                ascii_loader = ascii_reader.Reader()
+                return ascii_loader.read(path)
+            except:
+                cansas_loader = cansas_reader.Reader()
+                return cansas_loader.read(path)
+
+    def find_plugins(self, dir):
+        """
+        Find readers in a given directory. This method
+        can be used to inspect user plug-in directories to
+        find new readers/writers.
+
+        :param dir: directory to search into
+        :return: number of readers found
+        """
+        readers_found = 0
+        temp_path = os.path.abspath(dir)
+        if not os.path.isdir(temp_path):
+            temp_path = os.path.join(os.getcwd(), dir)
+        if not os.path.isdir(temp_path):
+            temp_path = os.path.join(os.path.dirname(__file__), dir)
+        if not os.path.isdir(temp_path):
+            temp_path = os.path.join(os.path.dirname(sys.path[0]), dir)
+
+        dir = temp_path
+        # Check whether the directory exists
+        if not os.path.isdir(dir):
+            msg = "DataLoader couldn't locate DataLoader plugin folder."
+            msg += """ "%s" does not exist""" % dir
+            logging.warning(msg)
+            return readers_found
+
+        for item in os.listdir(dir):
+            full_path = os.path.join(dir, item)
+            if os.path.isfile(full_path):
+
+                # Process python files
+                if item.endswith('.py'):
+                    toks = os.path.splitext(os.path.basename(item))
+                    try:
+                        sys.path.insert(0, os.path.abspath(dir))
+                        module = __import__(toks[0], globals(), locals())
+                        if self._identify_plugin(module):
+                            readers_found += 1
+                    except:
+                        msg = "Loader: Error importing "
+                        msg += "%s\n  %s" % (item, sys.exc_value)
+                        logging.error(msg)
+
+                # Process zip files
+                elif item.endswith('.zip'):
+                    try:
+                        # Find the modules in the zip file
+                        zfile = ZipFile(item)
+                        nlist = zfile.namelist()
+
+                        sys.path.insert(0, item)
+                        for mfile in nlist:
+                            try:
+                                # Change OS path to python path
+                                fullname = mfile.replace('/', '.')
+                                fullname = os.path.splitext(fullname)[0]
+                                module = __import__(fullname, globals(),
+                                                    locals(), [""])
+                                if self._identify_plugin(module):
+                                    readers_found += 1
+                            except:
+                                msg = "Loader: Error importing"
+                                msg += " %s\n  %s" % (mfile, sys.exc_value)
+                                logging.error(msg)
+
+                    except:
+                        msg = "Loader: Error importing "
+                        msg += " %s\n  %s" % (item, sys.exc_value)
+                        logging.error(msg)
+
+        return readers_found
+
+    def associate_file_type(self, ext, module):
+        """
+        Look into a module to find whether it contains a
+        Reader class. If so, APPEND it to readers and (potentially)
+        to the list of writers for the given extension
+
+        :param ext: file extension [string]
+        :param module: module object
+        """
+        reader_found = False
+
+        if hasattr(module, "Reader"):
+            try:
+                # Find supported extensions
+                loader = module.Reader()
+                if ext not in self.loaders:
+                    self.loaders[ext] = []
+                # Append the new reader to the list
+                self.loaders[ext].append(loader.read)
+
+                reader_found = True
+
+                # Keep track of wildcards
+                type_name = module.__name__
+                if hasattr(loader, 'type_name'):
+                    type_name = loader.type_name
+
+                wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
+                                                ext.lower())
+                if wcard not in self.wildcards:
+                    self.wildcards.append(wcard)
+
+                # Check whether writing is supported
+                if hasattr(loader, 'write'):
+                    if ext not in self.writers:
+                        self.writers[ext] = []
+                    # Append the new writer to the list
+                    self.writers[ext].append(loader.write)
+
+            except:
+                msg = "Loader: Error accessing"
+                msg += " Reader in %s\n  %s" % (module.__name__, sys.exc_value)
+                logging.error(msg)
+        return reader_found
+
+    def associate_file_reader(self, ext, loader):
+        """
+        Append a reader object to readers
+
+        :param ext: file extension [string]
+        :param module: reader object
+        """
+        reader_found = False
+
+        try:
+            # Find supported extensions
+            if ext not in self.loaders:
+                self.loaders[ext] = []
+            # Append the new reader to the list
+            self.loaders[ext].append(loader.read)
+
+            reader_found = True
+
+            # Keep track of wildcards
+            if hasattr(loader, 'type_name'):
+                type_name = loader.type_name
+
+                wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
+                                                ext.lower())
+                if wcard not in self.wildcards:
+                    self.wildcards.append(wcard)
+
+        except:
+            msg = "Loader: Error accessing Reader "
+            msg += "in %s\n  %s" % (loader.__name__, sys.exc_value)
+            logging.error(msg)
+        return reader_found
+
+    def _identify_plugin(self, module):
+        """
+        Look into a module to find whether it contains a
+        Reader class. If so, add it to readers and (potentially)
+        to the list of writers.
+        :param module: module object
+
+        """
+        reader_found = False
+
+        if hasattr(module, "Reader"):
+            try:
+                # Find supported extensions
+                loader = module.Reader()
+                for ext in loader.ext:
+                    if ext not in self.loaders:
+                        self.loaders[ext] = []
+                    # When finding a reader at run time,
+                    # treat this reader as the new default
+                    self.loaders[ext].insert(0, loader.read)
+
+                    reader_found = True
+
+                    # Keep track of wildcards
+                    type_name = module.__name__
+                    if hasattr(loader, 'type_name'):
+                        type_name = loader.type_name
+                    wcard = "%s files (*%s)|*%s" % (type_name, ext.lower(),
+                                                    ext.lower())
+                    if wcard not in self.wildcards:
+                        self.wildcards.append(wcard)
+
+                # Check whether writing is supported
+                if hasattr(loader, 'write'):
+                    for ext in loader.ext:
+                        if ext not in self.writers:
+                            self.writers[ext] = []
+                        self.writers[ext].insert(0, loader.write)
+
+            except:
+                msg = "Loader: Error accessing Reader"
+                msg += " in %s\n  %s" % (module.__name__, sys.exc_value)
+                logging.error(msg)
+        return reader_found
+
+    def lookup_writers(self, path):
+        """
+        :return: the loader associated with the file type of path.
+        :Raises ValueError: if file type is not known.
+        """
+        # Find matching extensions
+        extlist = [ext for ext in self.extensions() if path.endswith(ext)]
+        # Sort matching extensions by decreasing order of length
+        extlist.sort(lambda a, b: len(a) < len(b))
+        # Combine loaders for matching extensions into one big list
+        writers = []
+        for L in [self.writers[ext] for ext in extlist]:
+            writers.extend(L)
+        # Remove duplicates if they exist
+        if len(writers) != len(set(writers)):
+            result = []
+            for L in writers:
+                if L not in result:
+                    result.append(L)
+            writers = L
+        # Raise an error if there are no matching extensions
+        if len(writers) == 0:
+            raise ValueError, "Unknown file type for " + path
+        # All done
+        return writers
+
+    def save(self, path, data, format=None):
+        """
+        Call the writer for the file type of path.
+
+        Raises ValueError if no writer is available.
+        Raises KeyError if format is not available.
+        May raise a writer-defined exception if writer fails.
+        """
+        if format is None:
+            writers = self.lookup_writers(path)
+        else:
+            writers = self.writers[format]
+        for fn in writers:
+            try:
+                return fn(path, data)
+            except:
+                pass  # give other loaders a chance to succeed
+        # If we get here it is because all loaders failed
+        raise  # reraises last exception
+
+
+class Loader(object):
+    """
+    Utility class to use the Registry as a singleton.
+    """
+    ## Registry instance
+    __registry = Registry()
+
+    def associate_file_type(self, ext, module):
+        """
+        Look into a module to find whether it contains a
+        Reader class. If so, append it to readers and (potentially)
+        to the list of writers for the given extension
+
+        :param ext: file extension [string]
+        :param module: module object
+        """
+        return self.__registry.associate_file_type(ext, module)
+
+    def associate_file_reader(self, ext, loader):
+        """
+        Append a reader object to readers
+
+        :param ext: file extension [string]
+        :param module: reader object
+        """
+        return self.__registry.associate_file_reader(ext, loader)
+
+    def load(self, file, format=None):
+        """
+        Load a file
+
+        :param file: file name (path)
+        :param format: specified format to use (optional)
+        :return: DataInfo object
+        """
+        return self.__registry.load(file, format)
+
+    def save(self, file, data, format):
+        """
+        Save a DataInfo object to file
+        :param file: file name (path)
+        :param data: DataInfo object
+        :param format: format to write the data in
+        """
+        return self.__registry.save(file, data, format)
+
+    def _get_registry_creation_time(self):
+        """
+        Internal method used to test the uniqueness
+        of the registry object
+        """
+        return self.__registry._created
+
+    def find_plugins(self, directory):
+        """
+        Find plugins in a given directory
+
+        :param dir: directory to look into to find new readers/writers
+        """
+        return self.__registry.find_plugins(directory)
+
+    def get_wildcards(self):
+        """
+        Return the list of wildcards
+        """
+        return self.__registry.wildcards
Index: src/sas/sascalc/dataloader/manipulations.py
===================================================================
--- src/sas/sascalc/dataloader/manipulations.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/manipulations.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,1140 @@
+"""
+Data manipulations for 2D data sets.
+Using the meta data information, various types of averaging
+are performed in Q-space
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+
+#TODO: copy the meta data from the 2D object to the resulting 1D object
+import math
+import numpy
+
+#from data_info import plottable_2D
+from data_info import Data1D
+
+
+def get_q(dx, dy, det_dist, wavelength):
+    """
+    :param dx: x-distance from beam center [mm]
+    :param dy: y-distance from beam center [mm]
+    :return: q-value at the given position
+    """
+    # Distance from beam center in the plane of detector
+    plane_dist = math.sqrt(dx * dx + dy * dy)
+    # Half of the scattering angle
+    theta = 0.5 * math.atan(plane_dist / det_dist)
+    return (4.0 * math.pi / wavelength) * math.sin(theta)
+
+
+def get_q_compo(dx, dy, det_dist, wavelength, compo=None):
+    """
+    This reduces tiny error at very large q.
+    Implementation of this func is not started yet.<--ToDo
+    """
+    if dy == 0:
+        if dx >= 0:
+            angle_xy = 0
+        else:
+            angle_xy = math.pi
+    else:
+        angle_xy = math.atan(dx / dy)
+
+    if compo == "x":
+        out = get_q(dx, dy, det_dist, wavelength) * math.cos(angle_xy)
+    elif compo == "y":
+        out = get_q(dx, dy, det_dist, wavelength) * math.sin(angle_xy)
+    else:
+        out = get_q(dx, dy, det_dist, wavelength)
+    return out
+
+
+def flip_phi(phi):
+    """
+    Correct phi to within the 0 <= to <= 2pi range
+
+    :return: phi in >=0 and <=2Pi
+    """
+    Pi = math.pi
+    if phi < 0:
+        phi_out = phi + (2 * Pi)
+    elif phi > (2 * Pi):
+        phi_out = phi - (2 * Pi)
+    else:
+        phi_out = phi
+    return phi_out
+
+
+def reader2D_converter(data2d=None):
+    """
+    convert old 2d format opened by IhorReader or danse_reader
+    to new Data2D format
+
+    :param data2d: 2d array of Data2D object
+    :return: 1d arrays of Data2D object
+
+    """
+    if data2d.data == None or data2d.x_bins == None or data2d.y_bins == None:
+        raise ValueError, "Can't convert this data: data=None..."
+    new_x = numpy.tile(data2d.x_bins, (len(data2d.y_bins), 1))
+    new_y = numpy.tile(data2d.y_bins, (len(data2d.x_bins), 1))
+    new_y = new_y.swapaxes(0, 1)
+
+    new_data = data2d.data.flatten()
+    qx_data = new_x.flatten()
+    qy_data = new_y.flatten()
+    q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
+    if data2d.err_data == None or numpy.any(data2d.err_data <= 0):
+        new_err_data = numpy.sqrt(numpy.abs(new_data))
+    else:
+        new_err_data = data2d.err_data.flatten()
+    mask = numpy.ones(len(new_data), dtype=bool)
+
+    #TODO: make sense of the following two lines...
+    #from sas.sascalc.dataloader.data_info import Data2D
+    #output = Data2D()
+    output = data2d
+    output.data = new_data
+    output.err_data = new_err_data
+    output.qx_data = qx_data
+    output.qy_data = qy_data
+    output.q_data = q_data
+    output.mask = mask
+
+    return output
+
+
+class _Slab(object):
+    """
+    Compute average I(Q) for a region of interest
+    """
+    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0,
+                 y_max=0.0, bin_width=0.001):
+        # Minimum Qx value [A-1]
+        self.x_min = x_min
+        # Maximum Qx value [A-1]
+        self.x_max = x_max
+        # Minimum Qy value [A-1]
+        self.y_min = y_min
+        # Maximum Qy value [A-1]
+        self.y_max = y_max
+        # Bin width (step size) [A-1]
+        self.bin_width = bin_width
+        # If True, I(|Q|) will be return, otherwise,
+        # negative q-values are allowed
+        self.fold = False
+
+    def __call__(self, data2D):
+        return NotImplemented
+
+    def _avg(self, data2D, maj):
+        """
+        Compute average I(Q_maj) for a region of interest.
+        The major axis is defined as the axis of Q_maj.
+        The minor axis is the axis that we average over.
+
+        :param data2D: Data2D object
+        :param maj_min: min value on the major axis
+        :return: Data1D object
+        """
+        if len(data2D.detector) != 1:
+            msg = "_Slab._avg: invalid number of "
+            msg += " detectors: %g" % len(data2D.detector)
+            raise RuntimeError, msg
+
+        # Get data
+        data = data2D.data[numpy.isfinite(data2D.data)]
+        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
+        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
+        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
+
+        # Build array of Q intervals
+        if maj == 'x':
+            if self.fold:
+                x_min = 0
+            else:
+                x_min = self.x_min
+            nbins = int(math.ceil((self.x_max - x_min) / self.bin_width))
+        elif maj == 'y':
+            if self.fold:
+                y_min = 0
+            else:
+                y_min = self.y_min
+            nbins = int(math.ceil((self.y_max - y_min) / self.bin_width))
+        else:
+            raise RuntimeError, "_Slab._avg: unrecognized axis %s" % str(maj)
+
+        x = numpy.zeros(nbins)
+        y = numpy.zeros(nbins)
+        err_y = numpy.zeros(nbins)
+        y_counts = numpy.zeros(nbins)
+
+        # Average pixelsize in q space
+        for npts in range(len(data)):
+            # default frac
+            frac_x = 0
+            frac_y = 0
+            # get ROI
+            if self.x_min <= qx_data[npts] and self.x_max > qx_data[npts]:
+                frac_x = 1
+            if self.y_min <= qy_data[npts] and self.y_max > qy_data[npts]:
+                frac_y = 1
+            frac = frac_x * frac_y
+
+            if frac == 0:
+                continue
+            # binning: find axis of q
+            if maj == 'x':
+                q_value = qx_data[npts]
+                min_value = x_min
+            if maj == 'y':
+                q_value = qy_data[npts]
+                min_value = y_min
+            if self.fold and q_value < 0:
+                q_value = -q_value
+            # bin
+            i_q = int(math.ceil((q_value - min_value) / self.bin_width)) - 1
+
+            # skip outside of max bins
+            if i_q < 0 or i_q >= nbins:
+                continue
+
+            #TODO: find better definition of x[i_q] based on q_data
+            # min_value + (i_q + 1) * self.bin_width / 2.0
+            x[i_q] += frac * q_value
+            y[i_q] += frac * data[npts]
+
+            if err_data == None or err_data[npts] == 0.0:
+                if data[npts] < 0:
+                    data[npts] = -data[npts]
+                err_y[i_q] += frac * frac * data[npts]
+            else:
+                err_y[i_q] += frac * frac * err_data[npts] * err_data[npts]
+            y_counts[i_q] += frac
+
+        # Average the sums
+        for n in range(nbins):
+            err_y[n] = math.sqrt(err_y[n])
+
+        err_y = err_y / y_counts
+        y = y / y_counts
+        x = x / y_counts
+        idx = (numpy.isfinite(y) & numpy.isfinite(x))
+
+        if not idx.any():
+            msg = "Average Error: No points inside ROI to average..."
+            raise ValueError, msg
+        return Data1D(x=x[idx], y=y[idx], dy=err_y[idx])
+
+
+class SlabY(_Slab):
+    """
+    Compute average I(Qy) for a region of interest
+    """
+    def __call__(self, data2D):
+        """
+        Compute average I(Qy) for a region of interest
+
+        :param data2D: Data2D object
+        :return: Data1D object
+        """
+        return self._avg(data2D, 'y')
+
+
+class SlabX(_Slab):
+    """
+    Compute average I(Qx) for a region of interest
+    """
+    def __call__(self, data2D):
+        """
+        Compute average I(Qx) for a region of interest
+        :param data2D: Data2D object
+        :return: Data1D object
+        """
+        return self._avg(data2D, 'x')
+
+
+class Boxsum(object):
+    """
+    Perform the sum of counts in a 2D region of interest.
+    """
+    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
+        # Minimum Qx value [A-1]
+        self.x_min = x_min
+        # Maximum Qx value [A-1]
+        self.x_max = x_max
+        # Minimum Qy value [A-1]
+        self.y_min = y_min
+        # Maximum Qy value [A-1]
+        self.y_max = y_max
+
+    def __call__(self, data2D):
+        """
+        Perform the sum in the region of interest
+
+        :param data2D: Data2D object
+        :return: number of counts, error on number of counts,
+            number of points summed
+        """
+        y, err_y, y_counts = self._sum(data2D)
+
+        # Average the sums
+        counts = 0 if y_counts == 0 else y
+        error = 0 if y_counts == 0 else math.sqrt(err_y)
+
+        # Added y_counts to return, SMK & PDB, 04/03/2013
+        return counts, error, y_counts
+
+    def _sum(self, data2D):
+        """
+        Perform the sum in the region of interest
+
+        :param data2D: Data2D object
+        :return: number of counts,
+            error on number of counts, number of entries summed
+        """
+        if len(data2D.detector) != 1:
+            msg = "Circular averaging: invalid number "
+            msg += "of detectors: %g" % len(data2D.detector)
+            raise RuntimeError, msg
+        # Get data
+        data = data2D.data[numpy.isfinite(data2D.data)]
+        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
+        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
+        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
+
+        y = 0.0
+        err_y = 0.0
+        y_counts = 0.0
+
+        # Average pixelsize in q space
+        for npts in range(len(data)):
+            # default frac
+            frac_x = 0
+            frac_y = 0
+
+            # get min and max at each points
+            qx = qx_data[npts]
+            qy = qy_data[npts]
+
+            # get the ROI
+            if self.x_min <= qx and self.x_max > qx:
+                frac_x = 1
+            if self.y_min <= qy and self.y_max > qy:
+                frac_y = 1
+            #Find the fraction along each directions
+            frac = frac_x * frac_y
+            if frac == 0:
+                continue
+            y += frac * data[npts]
+            if err_data == None or err_data[npts] == 0.0:
+                if data[npts] < 0:
+                    data[npts] = -data[npts]
+                err_y += frac * frac * data[npts]
+            else:
+                err_y += frac * frac * err_data[npts] * err_data[npts]
+            y_counts += frac
+        return y, err_y, y_counts
+
+
+class Boxavg(Boxsum):
+    """
+    Perform the average of counts in a 2D region of interest.
+    """
+    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
+        super(Boxavg, self).__init__(x_min=x_min, x_max=x_max,
+                                     y_min=y_min, y_max=y_max)
+
+    def __call__(self, data2D):
+        """
+        Perform the sum in the region of interest
+
+        :param data2D: Data2D object
+        :return: average counts, error on average counts
+
+        """
+        y, err_y, y_counts = self._sum(data2D)
+
+        # Average the sums
+        counts = 0 if y_counts == 0 else y / y_counts
+        error = 0 if y_counts == 0 else math.sqrt(err_y) / y_counts
+
+        return counts, error
+
+
+def get_pixel_fraction_square(x, xmin, xmax):
+    """
+    Return the fraction of the length
+    from xmin to x.::
+
+           A            B
+       +-----------+---------+
+       xmin        x         xmax
+
+    :param x: x-value
+    :param xmin: minimum x for the length considered
+    :param xmax: minimum x for the length considered
+    :return: (x-xmin)/(xmax-xmin) when xmin < x < xmax
+
+    """
+    if x <= xmin:
+        return 0.0
+    if x > xmin and x < xmax:
+        return (x - xmin) / (xmax - xmin)
+    else:
+        return 1.0
+
+
+class CircularAverage(object):
+    """
+    Perform circular averaging on 2D data
+
+    The data returned is the distribution of counts
+    as a function of Q
+    """
+    def __init__(self, r_min=0.0, r_max=0.0, bin_width=0.0005):
+        # Minimum radius included in the average [A-1]
+        self.r_min = r_min
+        # Maximum radius included in the average [A-1]
+        self.r_max = r_max
+        # Bin width (step size) [A-1]
+        self.bin_width = bin_width
+
+    def __call__(self, data2D, ismask=False):
+        """
+        Perform circular averaging on the data
+
+        :param data2D: Data2D object
+        :return: Data1D object
+        """
+        # Get data W/ finite values
+        data = data2D.data[numpy.isfinite(data2D.data)]
+        q_data = data2D.q_data[numpy.isfinite(data2D.data)]
+        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
+        mask_data = data2D.mask[numpy.isfinite(data2D.data)]
+
+        dq_data = None
+
+        # Get the dq for resolution averaging
+        if data2D.dqx_data != None and data2D.dqy_data != None:
+            # The pinholes and det. pix contribution present
+            # in both direction of the 2D which must be subtracted when
+            # converting to 1D: dq_overlap should calculated ideally at
+            # q = 0. Note This method works on only pinhole geometry.
+            # Extrapolate dqx(r) and dqy(phi) at q = 0, and take an average.
+            z_max = max(data2D.q_data)
+            z_min = min(data2D.q_data)
+            x_max = data2D.dqx_data[data2D.q_data[z_max]]
+            x_min = data2D.dqx_data[data2D.q_data[z_min]]
+            y_max = data2D.dqy_data[data2D.q_data[z_max]]
+            y_min = data2D.dqy_data[data2D.q_data[z_min]]
+            # Find qdx at q = 0
+            dq_overlap_x = (x_min * z_max - x_max * z_min) / (z_max - z_min)
+            # when extrapolation goes wrong
+            if dq_overlap_x > min(data2D.dqx_data):
+                dq_overlap_x = min(data2D.dqx_data)
+            dq_overlap_x *= dq_overlap_x
+            # Find qdx at q = 0
+            dq_overlap_y = (y_min * z_max - y_max * z_min) / (z_max - z_min)
+            # when extrapolation goes wrong
+            if dq_overlap_y > min(data2D.dqy_data):
+                dq_overlap_y = min(data2D.dqy_data)
+            # get dq at q=0.
+            dq_overlap_y *= dq_overlap_y
+
+            dq_overlap = numpy.sqrt((dq_overlap_x + dq_overlap_y) / 2.0)
+            # Final protection of dq
+            if dq_overlap < 0:
+                dq_overlap = y_min
+            dqx_data = data2D.dqx_data[numpy.isfinite(data2D.data)]
+            dqy_data = data2D.dqy_data[numpy.isfinite(data2D.data)] - dq_overlap
+            # def; dqx_data = dq_r dqy_data = dq_phi
+            # Convert dq 2D to 1D here
+            dqx = dqx_data * dqx_data
+            dqy = dqy_data * dqy_data
+            dq_data = numpy.add(dqx, dqy)
+            dq_data = numpy.sqrt(dq_data)
+
+        #q_data_max = numpy.max(q_data)
+        if len(data2D.q_data) == None:
+            msg = "Circular averaging: invalid q_data: %g" % data2D.q_data
+            raise RuntimeError, msg
+
+        # Build array of Q intervals
+        nbins = int(math.ceil((self.r_max - self.r_min) / self.bin_width))
+
+        x = numpy.zeros(nbins)
+        y = numpy.zeros(nbins)
+        err_y = numpy.zeros(nbins)
+        err_x = numpy.zeros(nbins)
+        y_counts = numpy.zeros(nbins)
+
+        for npt in range(len(data)):
+
+            if ismask and not mask_data[npt]:
+                continue
+
+            frac = 0
+
+            # q-value at the pixel (j,i)
+            q_value = q_data[npt]
+            data_n = data[npt]
+
+            ## No need to calculate the frac when all data are within range
+            if self.r_min >= self.r_max:
+                raise ValueError, "Limit Error: min > max"
+
+            if self.r_min <= q_value and q_value <= self.r_max:
+                frac = 1
+            if frac == 0:
+                continue
+            i_q = int(math.floor((q_value - self.r_min) / self.bin_width))
+
+            # Take care of the edge case at phi = 2pi.
+            if i_q == nbins:
+                i_q = nbins - 1
+            y[i_q] += frac * data_n
+            # Take dqs from data to get the q_average
+            x[i_q] += frac * q_value
+            if err_data == None or err_data[npt] == 0.0:
+                if data_n < 0:
+                    data_n = -data_n
+                err_y[i_q] += frac * frac * data_n
+            else:
+                err_y[i_q] += frac * frac * err_data[npt] * err_data[npt]
+            if dq_data != None:
+                # To be consistent with dq calculation in 1d reduction,
+                # we need just the averages (not quadratures) because
+                # it should not depend on the number of the q points
+                # in the qr bins.
+                err_x[i_q] += frac * dq_data[npt]
+            else:
+                err_x = None
+            y_counts[i_q] += frac
+
+        # Average the sums
+        for n in range(nbins):
+            if err_y[n] < 0:
+                err_y[n] = -err_y[n]
+            err_y[n] = math.sqrt(err_y[n])
+            #if err_x != None:
+            #    err_x[n] = math.sqrt(err_x[n])
+
+        err_y = err_y / y_counts
+        err_y[err_y == 0] = numpy.average(err_y)
+        y = y / y_counts
+        x = x / y_counts
+        idx = (numpy.isfinite(y)) & (numpy.isfinite(x))
+
+        if err_x != None:
+            d_x = err_x[idx] / y_counts[idx]
+        else:
+            d_x = None
+
+        if not idx.any():
+            msg = "Average Error: No points inside ROI to average..."
+            raise ValueError, msg
+
+        return Data1D(x=x[idx], y=y[idx], dy=err_y[idx], dx=d_x)
+
+
+class Ring(object):
+    """
+    Defines a ring on a 2D data set.
+    The ring is defined by r_min, r_max, and
+    the position of the center of the ring.
+
+    The data returned is the distribution of counts
+    around the ring as a function of phi.
+
+    Phi_min and phi_max should be defined between 0 and 2*pi
+    in anti-clockwise starting from the x- axis on the left-hand side
+    """
+    #Todo: remove center.
+    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0, nbins=36):
+        # Minimum radius
+        self.r_min = r_min
+        # Maximum radius
+        self.r_max = r_max
+        # Center of the ring in x
+        self.center_x = center_x
+        # Center of the ring in y
+        self.center_y = center_y
+        # Number of angular bins
+        self.nbins_phi = nbins
+
+
+    def __call__(self, data2D):
+        """
+        Apply the ring to the data set.
+        Returns the angular distribution for a given q range
+
+        :param data2D: Data2D object
+
+        :return: Data1D object
+        """
+        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
+            raise RuntimeError, "Ring averaging only take plottable_2D objects"
+
+        Pi = math.pi
+
+        # Get data
+        data = data2D.data[numpy.isfinite(data2D.data)]
+        q_data = data2D.q_data[numpy.isfinite(data2D.data)]
+        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
+        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
+        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
+
+        # Set space for 1d outputs
+        phi_bins = numpy.zeros(self.nbins_phi)
+        phi_counts = numpy.zeros(self.nbins_phi)
+        phi_values = numpy.zeros(self.nbins_phi)
+        phi_err = numpy.zeros(self.nbins_phi)
+
+        # Shift to apply to calculated phi values in order
+        # to center first bin at zero
+        phi_shift = Pi / self.nbins_phi
+
+        for npt in range(len(data)):
+            frac = 0
+            # q-value at the point (npt)
+            q_value = q_data[npt]
+            data_n = data[npt]
+
+            # phi-value at the point (npt)
+            phi_value = math.atan2(qy_data[npt], qx_data[npt]) + Pi
+
+            if self.r_min <= q_value and q_value <= self.r_max:
+                frac = 1
+            if frac == 0:
+                continue
+            # binning
+            i_phi = int(math.floor((self.nbins_phi) * \
+                                   (phi_value + phi_shift) / (2 * Pi)))
+
+            # Take care of the edge case at phi = 2pi.
+            if i_phi >= self.nbins_phi:
+                i_phi = 0
+            phi_bins[i_phi] += frac * data[npt]
+
+            if err_data == None or err_data[npt] == 0.0:
+                if data_n < 0:
+                    data_n = -data_n
+                phi_err[i_phi] += frac * frac * math.fabs(data_n)
+            else:
+                phi_err[i_phi] += frac * frac * err_data[npt] * err_data[npt]
+            phi_counts[i_phi] += frac
+
+        for i in range(self.nbins_phi):
+            phi_bins[i] = phi_bins[i] / phi_counts[i]
+            phi_err[i] = math.sqrt(phi_err[i]) / phi_counts[i]
+            phi_values[i] = 2.0 * math.pi / self.nbins_phi * (1.0 * i)
+
+        idx = (numpy.isfinite(phi_bins))
+
+        if not idx.any():
+            msg = "Average Error: No points inside ROI to average..."
+            raise ValueError, msg
+        #elif len(phi_bins[idx])!= self.nbins_phi:
+        #    print "resulted",self.nbins_phi- len(phi_bins[idx])
+        #,"empty bin(s) due to tight binning..."
+        return Data1D(x=phi_values[idx], y=phi_bins[idx], dy=phi_err[idx])
+
+
+def get_pixel_fraction(qmax, q_00, q_01, q_10, q_11):
+    """
+    Returns the fraction of the pixel defined by
+    the four corners (q_00, q_01, q_10, q_11) that
+    has q < qmax.::
+
+                q_01                q_11
+        y=1         +--------------+
+                    |              |
+                    |              |
+                    |              |
+        y=0         +--------------+
+                q_00                q_10
+
+                    x=0            x=1
+
+    """
+    # y side for x = minx
+    x_0 = get_intercept(qmax, q_00, q_01)
+    # y side for x = maxx
+    x_1 = get_intercept(qmax, q_10, q_11)
+
+    # x side for y = miny
+    y_0 = get_intercept(qmax, q_00, q_10)
+    # x side for y = maxy
+    y_1 = get_intercept(qmax, q_01, q_11)
+
+    # surface fraction for a 1x1 pixel
+    frac_max = 0
+
+    if x_0 and x_1:
+        frac_max = (x_0 + x_1) / 2.0
+    elif y_0 and y_1:
+        frac_max = (y_0 + y_1) / 2.0
+    elif x_0 and y_0:
+        if q_00 < q_10:
+            frac_max = x_0 * y_0 / 2.0
+        else:
+            frac_max = 1.0 - x_0 * y_0 / 2.0
+    elif x_0 and y_1:
+        if q_00 < q_10:
+            frac_max = x_0 * y_1 / 2.0
+        else:
+            frac_max = 1.0 - x_0 * y_1 / 2.0
+    elif x_1 and y_0:
+        if q_00 > q_10:
+            frac_max = x_1 * y_0 / 2.0
+        else:
+            frac_max = 1.0 - x_1 * y_0 / 2.0
+    elif x_1 and y_1:
+        if q_00 < q_10:
+            frac_max = 1.0 - (1.0 - x_1) * (1.0 - y_1) / 2.0
+        else:
+            frac_max = (1.0 - x_1) * (1.0 - y_1) / 2.0
+
+    # If we make it here, there is no intercept between
+    # this pixel and the constant-q ring. We only need
+    # to know if we have to include it or exclude it.
+    elif (q_00 + q_01 + q_10 + q_11) / 4.0 < qmax:
+        frac_max = 1.0
+
+    return frac_max
+
+
+def get_intercept(q, q_0, q_1):
+    """
+    Returns the fraction of the side at which the
+    q-value intercept the pixel, None otherwise.
+    The values returned is the fraction ON THE SIDE
+    OF THE LOWEST Q. ::
+
+            A           B
+        +-----------+--------+    <--- pixel size
+        0                    1
+        Q_0 -------- Q ----- Q_1   <--- equivalent Q range
+        if Q_1 > Q_0, A is returned
+        if Q_1 < Q_0, B is returned
+        if Q is outside the range of [Q_0, Q_1], None is returned
+
+    """
+    if q_1 > q_0:
+        if q > q_0 and q <= q_1:
+            return (q - q_0) / (q_1 - q_0)
+    else:
+        if q > q_1 and q <= q_0:
+            return (q - q_1) / (q_0 - q_1)
+    return None
+
+
+class _Sector(object):
+    """
+    Defines a sector region on a 2D data set.
+    The sector is defined by r_min, r_max, phi_min, phi_max,
+    and the position of the center of the ring
+    where phi_min and phi_max are defined by the right
+    and left lines wrt central line
+    and phi_max could be less than phi_min.
+
+    Phi is defined between 0 and 2*pi in anti-clockwise
+    starting from the x- axis on the left-hand side
+    """
+    def __init__(self, r_min, r_max, phi_min=0, phi_max=2 * math.pi, nbins=20):
+        self.r_min = r_min
+        self.r_max = r_max
+        self.phi_min = phi_min
+        self.phi_max = phi_max
+        self.nbins = nbins
+
+    def _agv(self, data2D, run='phi'):
+        """
+        Perform sector averaging.
+
+        :param data2D: Data2D object
+        :param run:  define the varying parameter ('phi' , 'q' , or 'q2')
+
+        :return: Data1D object
+        """
+        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
+            raise RuntimeError, "Ring averaging only take plottable_2D objects"
+        Pi = math.pi
+
+        # Get the all data & info
+        data = data2D.data[numpy.isfinite(data2D.data)]
+        q_data = data2D.q_data[numpy.isfinite(data2D.data)]
+        err_data = data2D.err_data[numpy.isfinite(data2D.data)]
+        qx_data = data2D.qx_data[numpy.isfinite(data2D.data)]
+        qy_data = data2D.qy_data[numpy.isfinite(data2D.data)]
+        dq_data = None
+
+        # Get the dq for resolution averaging
+        if data2D.dqx_data != None and data2D.dqy_data != None:
+            # The pinholes and det. pix contribution present
+            # in both direction of the 2D which must be subtracted when
+            # converting to 1D: dq_overlap should calculated ideally at
+            # q = 0.
+            # Extrapolate dqy(perp) at q = 0
+            z_max = max(data2D.q_data)
+            z_min = min(data2D.q_data)
+            x_max = data2D.dqx_data[data2D.q_data[z_max]]
+            x_min = data2D.dqx_data[data2D.q_data[z_min]]
+            y_max = data2D.dqy_data[data2D.q_data[z_max]]
+            y_min = data2D.dqy_data[data2D.q_data[z_min]]
+            # Find qdx at q = 0
+            dq_overlap_x = (x_min * z_max - x_max * z_min) / (z_max - z_min)
+            # when extrapolation goes wrong
+            if dq_overlap_x > min(data2D.dqx_data):
+                dq_overlap_x = min(data2D.dqx_data)
+            dq_overlap_x *= dq_overlap_x
+            # Find qdx at q = 0
+            dq_overlap_y = (y_min * z_max - y_max * z_min) / (z_max - z_min)
+            # when extrapolation goes wrong
+            if dq_overlap_y > min(data2D.dqy_data):
+                dq_overlap_y = min(data2D.dqy_data)
+            # get dq at q=0.
+            dq_overlap_y *= dq_overlap_y
+
+            dq_overlap = numpy.sqrt((dq_overlap_x + dq_overlap_y) / 2.0)
+            if dq_overlap < 0:
+                dq_overlap = y_min
+            dqx_data = data2D.dqx_data[numpy.isfinite(data2D.data)]
+            dqy_data = data2D.dqy_data[numpy.isfinite(data2D.data)] - dq_overlap
+            # def; dqx_data = dq_r dqy_data = dq_phi
+            # Convert dq 2D to 1D here
+            dqx = dqx_data * dqx_data
+            dqy = dqy_data * dqy_data
+            dq_data = numpy.add(dqx, dqy)
+            dq_data = numpy.sqrt(dq_data)
+
+        #set space for 1d outputs
+        x = numpy.zeros(self.nbins)
+        y = numpy.zeros(self.nbins)
+        y_err = numpy.zeros(self.nbins)
+        x_err = numpy.zeros(self.nbins)
+        y_counts = numpy.zeros(self.nbins)
+
+        # Get the min and max into the region: 0 <= phi < 2Pi
+        phi_min = flip_phi(self.phi_min)
+        phi_max = flip_phi(self.phi_max)
+
+        for n in range(len(data)):
+            frac = 0
+
+            # q-value at the pixel (j,i)
+            q_value = q_data[n]
+            data_n = data[n]
+
+            # Is pixel within range?
+            is_in = False
+
+            # phi-value of the pixel (j,i)
+            phi_value = math.atan2(qy_data[n], qx_data[n]) + Pi
+
+            ## No need to calculate the frac when all data are within range
+            if self.r_min <= q_value and q_value <= self.r_max:
+                frac = 1
+            if frac == 0:
+                continue
+            #In case of two ROIs (symmetric major and minor regions)(for 'q2')
+            if run.lower() == 'q2':
+                ## For minor sector wing
+                # Calculate the minor wing phis
+                phi_min_minor = flip_phi(phi_min - Pi)
+                phi_max_minor = flip_phi(phi_max - Pi)
+                # Check if phis of the minor ring is within 0 to 2pi
+                if phi_min_minor > phi_max_minor:
+                    is_in = (phi_value > phi_min_minor or \
+                              phi_value < phi_max_minor)
+                else:
+                    is_in = (phi_value > phi_min_minor and \
+                              phi_value < phi_max_minor)
+
+            #For all cases(i.e.,for 'q', 'q2', and 'phi')
+            #Find pixels within ROI
+            if phi_min > phi_max:
+                is_in = is_in or (phi_value > phi_min or \
+                                   phi_value < phi_max)
+            else:
+                is_in = is_in or (phi_value >= phi_min  and \
+                                    phi_value < phi_max)
+
+            if not is_in:
+                frac = 0
+            if frac == 0:
+                continue
+            # Check which type of averaging we need
+            if run.lower() == 'phi':
+                temp_x = (self.nbins) * (phi_value - self.phi_min)
+                temp_y = (self.phi_max - self.phi_min)
+                i_bin = int(math.floor(temp_x / temp_y))
+            else:
+                temp_x = (self.nbins) * (q_value - self.r_min)
+                temp_y = (self.r_max - self.r_min)
+                i_bin = int(math.floor(temp_x / temp_y))
+
+            # Take care of the edge case at phi = 2pi.
+            if i_bin == self.nbins:
+                i_bin = self.nbins - 1
+
+            ## Get the total y
+            y[i_bin] += frac * data_n
+            x[i_bin] += frac * q_value
+            if err_data[n] == None or err_data[n] == 0.0:
+                if data_n < 0:
+                    data_n = -data_n
+                y_err[i_bin] += frac * frac * data_n
+            else:
+                y_err[i_bin] += frac * frac * err_data[n] * err_data[n]
+
+            if dq_data != None:
+                # To be consistent with dq calculation in 1d reduction,
+                # we need just the averages (not quadratures) because
+                # it should not depend on the number of the q points
+                # in the qr bins.
+                x_err[i_bin] += frac * dq_data[n]
+            else:
+                x_err = None
+            y_counts[i_bin] += frac
+
+        # Organize the results
+        for i in range(self.nbins):
+            y[i] = y[i] / y_counts[i]
+            y_err[i] = math.sqrt(y_err[i]) / y_counts[i]
+
+            # The type of averaging: phi,q2, or q
+            # Calculate x[i]should be at the center of the bin
+            if run.lower() == 'phi':
+                x[i] = (self.phi_max - self.phi_min) / self.nbins * \
+                    (1.0 * i + 0.5) + self.phi_min
+            else:
+                # We take the center of ring area, not radius.
+                # This is more accurate than taking the radial center of ring.
+                #delta_r = (self.r_max - self.r_min) / self.nbins
+                #r_inner = self.r_min + delta_r * i
+                #r_outer = r_inner + delta_r
+                #x[i] = math.sqrt((r_inner * r_inner + r_outer * r_outer) / 2)
+                x[i] = x[i] / y_counts[i]
+        y_err[y_err == 0] = numpy.average(y_err)
+        idx = (numpy.isfinite(y) & numpy.isfinite(y_err))
+        if x_err != None:
+            d_x = x_err[idx] / y_counts[idx]
+        else:
+            d_x = None
+        if not idx.any():
+            msg = "Average Error: No points inside sector of ROI to average..."
+            raise ValueError, msg
+        #elif len(y[idx])!= self.nbins:
+        #    print "resulted",self.nbins- len(y[idx]),
+        #"empty bin(s) due to tight binning..."
+        return Data1D(x=x[idx], y=y[idx], dy=y_err[idx], dx=d_x)
+
+
+class SectorPhi(_Sector):
+    """
+    Sector average as a function of phi.
+    I(phi) is return and the data is averaged over Q.
+
+    A sector is defined by r_min, r_max, phi_min, phi_max.
+    The number of bin in phi also has to be defined.
+    """
+    def __call__(self, data2D):
+        """
+        Perform sector average and return I(phi).
+
+        :param data2D: Data2D object
+        :return: Data1D object
+        """
+        return self._agv(data2D, 'phi')
+
+
+class SectorQ(_Sector):
+    """
+    Sector average as a function of Q for both symatric wings.
+    I(Q) is return and the data is averaged over phi.
+
+    A sector is defined by r_min, r_max, phi_min, phi_max.
+    r_min, r_max, phi_min, phi_max >0.
+    The number of bin in Q also has to be defined.
+    """
+    def __call__(self, data2D):
+        """
+        Perform sector average and return I(Q).
+
+        :param data2D: Data2D object
+
+        :return: Data1D object
+        """
+        return self._agv(data2D, 'q2')
+
+
+class Ringcut(object):
+    """
+    Defines a ring on a 2D data set.
+    The ring is defined by r_min, r_max, and
+    the position of the center of the ring.
+
+    The data returned is the region inside the ring
+
+    Phi_min and phi_max should be defined between 0 and 2*pi
+    in anti-clockwise starting from the x- axis on the left-hand side
+    """
+    def __init__(self, r_min=0, r_max=0, center_x=0, center_y=0):
+        # Minimum radius
+        self.r_min = r_min
+        # Maximum radius
+        self.r_max = r_max
+        # Center of the ring in x
+        self.center_x = center_x
+        # Center of the ring in y
+        self.center_y = center_y
+
+    def __call__(self, data2D):
+        """
+        Apply the ring to the data set.
+        Returns the angular distribution for a given q range
+
+        :param data2D: Data2D object
+
+        :return: index array in the range
+        """
+        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
+            raise RuntimeError, "Ring cut only take plottable_2D objects"
+
+        # Get data
+        qx_data = data2D.qx_data
+        qy_data = data2D.qy_data
+        q_data = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
+
+        # check whether or not the data point is inside ROI
+        out = (self.r_min <= q_data) & (self.r_max >= q_data)
+        return out
+
+
+class Boxcut(object):
+    """
+    Find a rectangular 2D region of interest.
+    """
+    def __init__(self, x_min=0.0, x_max=0.0, y_min=0.0, y_max=0.0):
+        # Minimum Qx value [A-1]
+        self.x_min = x_min
+        # Maximum Qx value [A-1]
+        self.x_max = x_max
+        # Minimum Qy value [A-1]
+        self.y_min = y_min
+        # Maximum Qy value [A-1]
+        self.y_max = y_max
+
+    def __call__(self, data2D):
+        """
+       Find a rectangular 2D region of interest.
+
+       :param data2D: Data2D object
+       :return: mask, 1d array (len = len(data))
+           with Trues where the data points are inside ROI, otherwise False
+        """
+        mask = self._find(data2D)
+
+        return mask
+
+    def _find(self, data2D):
+        """
+        Find a rectangular 2D region of interest.
+
+        :param data2D: Data2D object
+
+        :return: out, 1d array (length = len(data))
+           with Trues where the data points are inside ROI, otherwise Falses
+        """
+        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
+            raise RuntimeError, "Boxcut take only plottable_2D objects"
+        # Get qx_ and qy_data
+        qx_data = data2D.qx_data
+        qy_data = data2D.qy_data
+
+        # check whether or not the data point is inside ROI
+        outx = (self.x_min <= qx_data) & (self.x_max > qx_data)
+        outy = (self.y_min <= qy_data) & (self.y_max > qy_data)
+
+        return outx & outy
+
+
+class Sectorcut(object):
+    """
+    Defines a sector (major + minor) region on a 2D data set.
+    The sector is defined by phi_min, phi_max,
+    where phi_min and phi_max are defined by the right
+    and left lines wrt central line.
+
+    Phi_min and phi_max are given in units of radian
+    and (phi_max-phi_min) should not be larger than pi
+    """
+    def __init__(self, phi_min=0, phi_max=math.pi):
+        self.phi_min = phi_min
+        self.phi_max = phi_max
+
+    def __call__(self, data2D):
+        """
+        Find a rectangular 2D region of interest.
+
+        :param data2D: Data2D object
+
+        :return: mask, 1d array (len = len(data))
+
+        with Trues where the data points are inside ROI, otherwise False
+        """
+        mask = self._find(data2D)
+
+        return mask
+
+    def _find(self, data2D):
+        """
+        Find a rectangular 2D region of interest.
+
+        :param data2D: Data2D object
+
+        :return: out, 1d array (length = len(data))
+
+        with Trues where the data points are inside ROI, otherwise Falses
+        """
+        if data2D.__class__.__name__ not in ["Data2D", "plottable_2D"]:
+            raise RuntimeError, "Sectorcut take only plottable_2D objects"
+        Pi = math.pi
+        # Get data
+        qx_data = data2D.qx_data
+        qy_data = data2D.qy_data
+
+        # get phi from data
+        phi_data = numpy.arctan2(qy_data, qx_data)
+
+        # Get the min and max into the region: -pi <= phi < Pi
+        phi_min_major = flip_phi(self.phi_min + Pi) - Pi
+        phi_max_major = flip_phi(self.phi_max + Pi) - Pi
+        # check for major sector
+        if phi_min_major > phi_max_major:
+            out_major = (phi_min_major <= phi_data) + (phi_max_major > phi_data)
+        else:
+            out_major = (phi_min_major <= phi_data) & (phi_max_major > phi_data)
+
+        # minor sector
+        # Get the min and max into the region: -pi <= phi < Pi
+        phi_min_minor = flip_phi(self.phi_min) - Pi
+        phi_max_minor = flip_phi(self.phi_max) - Pi
+
+        # check for minor sector
+        if phi_min_minor > phi_max_minor:
+            out_minor = (phi_min_minor <= phi_data) + \
+                            (phi_max_minor >= phi_data)
+        else:
+            out_minor = (phi_min_minor <= phi_data) & \
+                            (phi_max_minor >= phi_data)
+        out = out_major + out_minor
+
+        return out
Index: src/sas/sascalc/dataloader/readers/IgorReader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/IgorReader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/IgorReader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,304 @@
+"""
+    IGOR 2D reduced file reader
+"""
+############################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#If you use DANSE applications to do scientific research that leads to 
+#publication, we ask that you acknowledge the use of the software with the 
+#following sentence:
+#This work benefited from DANSE software developed under NSF award DMR-0520547. 
+#copyright 2008, University of Tennessee
+#############################################################################
+import os
+import numpy
+import math
+#import logging
+from sas.sascalc.dataloader.data_info import Data2D
+from sas.sascalc.dataloader.data_info import Detector
+from sas.sascalc.dataloader.manipulations import reader2D_converter
+
+# Look for unit converter
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+
+
+class Reader:
+    """ Simple data reader for Igor data files """
+    ## File type
+    type_name = "IGOR 2D"
+    ## Wildcards
+    type = ["IGOR 2D files (*.ASC)|*.ASC"]
+    ## Extension
+    ext=['.ASC', '.asc']
+
+    def read(self, filename=None):
+        """ Read file """
+        if not os.path.isfile(filename):
+            raise ValueError, \
+            "Specified file %s is not a regular file" % filename
+        
+        # Read file
+        f = open(filename, 'r')
+        buf = f.read()
+        
+        # Instantiate data object
+        output = Data2D()
+        output.filename = os.path.basename(filename)
+        detector = Detector()
+        if len(output.detector) > 0:
+            print str(output.detector[0])
+        output.detector.append(detector)
+                
+        # Get content
+        dataStarted = False
+        
+        lines = buf.split('\n')
+        itot = 0
+        x = []
+        y = []
+        
+        ncounts = 0
+        
+        xmin = None
+        xmax = None
+        ymin = None
+        ymax = None
+        
+        i_x = 0
+        i_y = -1
+        i_tot_row = 0
+        
+        isInfo = False
+        isCenter = False
+       
+        data_conv_q = None
+        data_conv_i = None
+        
+        if has_converter == True and output.Q_unit != '1/A':
+            data_conv_q = Converter('1/A')
+            # Test it
+            data_conv_q(1.0, output.Q_unit)
+            
+        if has_converter == True and output.I_unit != '1/cm':
+            data_conv_i = Converter('1/cm')
+            # Test it
+            data_conv_i(1.0, output.I_unit)
+         
+        for line in lines:
+            
+            # Find setup info line
+            if isInfo:
+                isInfo = False
+                line_toks = line.split()
+                # Wavelength in Angstrom
+                try:
+                    wavelength = float(line_toks[1])
+                except:
+                    msg = "IgorReader: can't read this file, missing wavelength"
+                    raise ValueError, msg
+                
+            #Find # of bins in a row assuming the detector is square.
+            if dataStarted == True:
+                try:
+                    value = float(line)
+                except:
+                    # Found a non-float entry, skip it
+                    continue
+                
+                # Get total bin number
+                
+            i_tot_row += 1
+        i_tot_row = math.ceil(math.sqrt(i_tot_row)) - 1
+        #print "i_tot", i_tot_row
+        size_x = i_tot_row  # 192#128
+        size_y = i_tot_row  # 192#128
+        output.data = numpy.zeros([size_x, size_y])
+        output.err_data = numpy.zeros([size_x, size_y])
+     
+        #Read Header and 2D data
+        for line in lines:
+            # Find setup info line
+            if isInfo:
+                isInfo = False
+                line_toks = line.split()
+                # Wavelength in Angstrom
+                try:
+                    wavelength = float(line_toks[1])
+                except:
+                    msg = "IgorReader: can't read this file, missing wavelength"
+                    raise ValueError, msg
+                # Distance in meters
+                try:
+                    distance = float(line_toks[3])
+                except:
+                    msg = "IgorReader: can't read this file, missing distance"
+                    raise ValueError, msg
+                
+                # Distance in meters
+                try:
+                    transmission = float(line_toks[4])
+                except:
+                    msg = "IgorReader: can't read this file, "
+                    msg += "missing transmission"
+                    raise ValueError, msg
+                                            
+            if line.count("LAMBDA") > 0:
+                isInfo = True
+                
+            # Find center info line
+            if isCenter:
+                isCenter = False
+                line_toks = line.split()
+                
+                # Center in bin number: Must substrate 1 because
+                #the index starts from 1
+                center_x = float(line_toks[0]) - 1
+                center_y = float(line_toks[1]) - 1
+
+            if line.count("BCENT") > 0:
+                isCenter = True
+                
+            # Find data start
+            if line.count("***")>0:
+                dataStarted = True
+                
+                # Check that we have all the info
+                if wavelength == None \
+                    or distance == None \
+                    or center_x == None \
+                    or center_y == None:
+                    msg = "IgorReader:Missing information in data file"
+                    raise ValueError, msg
+                
+            if dataStarted == True:
+                try:
+                    value = float(line)
+                except:
+                    # Found a non-float entry, skip it
+                    continue
+                
+                # Get bin number
+                if math.fmod(itot, i_tot_row) == 0:
+                    i_x = 0
+                    i_y += 1
+                else:
+                    i_x += 1
+                    
+                output.data[i_y][i_x] = value
+                ncounts += 1
+                
+                # Det 640 x 640 mm
+                # Q = 4pi/lambda sin(theta/2)
+                # Bin size is 0.5 cm 
+                #REmoved +1 from theta = (i_x-center_x+1)*0.5 / distance
+                # / 100.0 and 
+                #REmoved +1 from theta = (i_y-center_y+1)*0.5 /
+                # distance / 100.0
+                #ToDo: Need  complete check if the following
+                # covert process is consistent with fitting.py.
+                theta = (i_x - center_x) * 0.5 / distance / 100.0
+                qx = 4.0 * math.pi / wavelength * math.sin(theta/2.0)
+
+                if has_converter == True and output.Q_unit != '1/A':
+                    qx = data_conv_q(qx, units=output.Q_unit)
+
+                if xmin == None or qx < xmin:
+                    xmin = qx
+                if xmax == None or qx > xmax:
+                    xmax = qx
+                
+                theta = (i_y - center_y) * 0.5 / distance / 100.0
+                qy = 4.0 * math.pi / wavelength * math.sin(theta / 2.0)
+
+                if has_converter == True and output.Q_unit != '1/A':
+                    qy = data_conv_q(qy, units=output.Q_unit)
+                
+                if ymin == None or qy < ymin:
+                    ymin = qy
+                if ymax == None or qy > ymax:
+                    ymax = qy
+                
+                if not qx in x:
+                    x.append(qx)
+                if not qy in y:
+                    y.append(qy)
+                
+                itot += 1
+                  
+                  
+        theta = 0.25 / distance / 100.0
+        xstep = 4.0 * math.pi / wavelength * math.sin(theta / 2.0)
+        
+        theta = 0.25 / distance / 100.0
+        ystep = 4.0 * math.pi/ wavelength * math.sin(theta / 2.0)
+        
+        # Store all data ######################################
+        # Store wavelength
+        if has_converter == True and output.source.wavelength_unit != 'A':
+            conv = Converter('A')
+            wavelength = conv(wavelength, units=output.source.wavelength_unit)
+        output.source.wavelength = wavelength
+
+        # Store distance
+        if has_converter == True and detector.distance_unit != 'm':
+            conv = Converter('m')
+            distance = conv(distance, units=detector.distance_unit)
+        detector.distance = distance
+  
+        # Store transmission
+        output.sample.transmission = transmission
+        
+        # Store pixel size
+        pixel = 5.0
+        if has_converter == True and detector.pixel_size_unit != 'mm':
+            conv = Converter('mm')
+            pixel = conv(pixel, units=detector.pixel_size_unit)
+        detector.pixel_size.x = pixel
+        detector.pixel_size.y = pixel
+  
+        # Store beam center in distance units
+        detector.beam_center.x = center_x * pixel
+        detector.beam_center.y = center_y * pixel
+        
+        # Store limits of the image (2D array)
+        xmin = xmin - xstep / 2.0
+        xmax = xmax + xstep / 2.0
+        ymin = ymin - ystep / 2.0
+        ymax = ymax + ystep / 2.0
+        if has_converter == True and output.Q_unit != '1/A':
+            xmin = data_conv_q(xmin, units=output.Q_unit)
+            xmax = data_conv_q(xmax, units=output.Q_unit)
+            ymin = data_conv_q(ymin, units=output.Q_unit)
+            ymax = data_conv_q(ymax, units=output.Q_unit)
+        output.xmin = xmin
+        output.xmax = xmax
+        output.ymin = ymin
+        output.ymax = ymax
+        
+        # Store x and y axis bin centers
+        output.x_bins = x
+        output.y_bins = y
+        
+        # Units
+        if data_conv_q is not None:
+            output.xaxis("\\rm{Q_{x}}", output.Q_unit)
+            output.yaxis("\\rm{Q_{y}}", output.Q_unit)
+        else:
+            output.xaxis("\\rm{Q_{x}}", 'A^{-1}')
+            output.yaxis("\\rm{Q_{y}}", 'A^{-1}')
+            
+        if data_conv_i is not None:
+            output.zaxis("\\rm{Intensity}", output.I_unit)
+        else:
+            output.zaxis("\\rm{Intensity}", "cm^{-1}")
+    
+        # Store loading process information
+        output.meta_data['loader'] = self.type_name
+        output = reader2D_converter(output)
+
+        return output
Index: src/sas/sascalc/dataloader/readers/__init__.py
===================================================================
--- src/sas/sascalc/dataloader/readers/__init__.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/__init__.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,13 @@
+# Backward compatibility with the previous implementation of the default readers
+from associations import register_readers
+
+# Method to associate extensions to default readers 
+from associations import read_associations
+
+# Method to return the location of the XML settings file
+def get_data_path():
+    """
+        Return the location of the settings file for the data readers.
+    """
+    import os
+    return os.path.dirname(__file__)
Index: src/sas/sascalc/dataloader/readers/abs_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/abs_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/abs_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,249 @@
+"""
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+
+import numpy
+import os
+from sas.sascalc.dataloader.data_info import Data1D
+from sas.sascalc.dataloader.data_info import Detector
+
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+    
+    
+class Reader:
+    """
+    Class to load IGOR reduced .ABS files
+    """
+    ## File type
+    type_name = "IGOR 1D"
+    ## Wildcards
+    type = ["IGOR 1D files (*.abs)|*.abs"]
+    ## List of allowed extensions
+    ext = ['.abs', '.ABS']
+    
+    def read(self, path):
+        """ 
+        Load data file.
+        
+        :param path: file path
+        
+        :return: Data1D object, or None
+        
+        :raise RuntimeError: when the file can't be opened
+        :raise ValueError: when the length of the data vectors are inconsistent
+        """
+        if os.path.isfile(path):
+            basename = os.path.basename(path)
+            root, extension = os.path.splitext(basename)
+            if extension.lower() in self.ext:
+                try:
+                    input_f = open(path,'r')
+                except:
+                    raise  RuntimeError, "abs_reader: cannot open %s" % path
+                buff = input_f.read()
+                lines = buff.split('\n')
+                x  = numpy.zeros(0)
+                y  = numpy.zeros(0)
+                dy = numpy.zeros(0)
+                dx = numpy.zeros(0)
+                output = Data1D(x, y, dy=dy, dx=dx)
+                detector = Detector()
+                output.detector.append(detector)
+                output.filename = basename
+                
+                is_info = False
+                is_center = False
+                is_data_started = False
+                
+                data_conv_q = None
+                data_conv_i = None
+                
+                if has_converter == True and output.x_unit != '1/A':
+                    data_conv_q = Converter('1/A')
+                    # Test it
+                    data_conv_q(1.0, output.x_unit)
+                    
+                if has_converter == True and output.y_unit != '1/cm':
+                    data_conv_i = Converter('1/cm')
+                    # Test it
+                    data_conv_i(1.0, output.y_unit)
+                
+                for line in lines:
+                    
+                    # Information line 1
+                    if is_info == True:
+                        is_info = False
+                        line_toks = line.split()
+                        
+                        # Wavelength in Angstrom
+                        try:
+                            value = float(line_toks[1])
+                            if has_converter == True and \
+                                output.source.wavelength_unit != 'A':
+                                conv = Converter('A')
+                                output.source.wavelength = conv(value,
+                                        units=output.source.wavelength_unit)
+                            else:
+                                output.source.wavelength = value
+                        except:
+                            #goes to ASC reader
+                            msg = "abs_reader: cannot open %s" % path
+                            raise  RuntimeError, msg
+                        
+                        # Distance in meters
+                        try:
+                            value = float(line_toks[3])
+                            if has_converter == True and \
+                                detector.distance_unit != 'm':
+                                conv = Converter('m')
+                                detector.distance = conv(value,
+                                                units=detector.distance_unit)
+                            else:
+                                detector.distance = value
+                        except:
+                            #goes to ASC reader
+                            msg = "abs_reader: cannot open %s" % path
+                            raise  RuntimeError, msg
+                        # Transmission
+                        try:
+                            output.sample.transmission = float(line_toks[4])
+                        except:
+                            # Transmission is not a mandatory entry
+                            pass
+                    
+                        # Thickness in mm
+                        try:
+                            value = float(line_toks[5])
+                            if has_converter == True and \
+                                output.sample.thickness_unit != 'cm':
+                                conv = Converter('cm')
+                                output.sample.thickness = conv(value,
+                                            units=output.sample.thickness_unit)
+                            else:
+                                output.sample.thickness = value
+                        except:
+                            # Thickness is not a mandatory entry
+                            pass
+                    
+                    #MON CNT   LAMBDA   DET ANG   DET DIST   TRANS   THICK 
+                    #  AVE   STEP
+                    if line.count("LAMBDA") > 0:
+                        is_info = True
+                        
+                    # Find center info line
+                    if is_center == True:
+                        is_center = False
+                        line_toks = line.split()
+                        # Center in bin number
+                        center_x = float(line_toks[0])
+                        center_y = float(line_toks[1])
+                        
+                        # Bin size
+                        if has_converter == True and \
+                            detector.pixel_size_unit != 'mm':
+                            conv = Converter('mm')
+                            detector.pixel_size.x = conv(5.0,
+                                                units=detector.pixel_size_unit)
+                            detector.pixel_size.y = conv(5.0,
+                                                units=detector.pixel_size_unit)
+                        else:
+                            detector.pixel_size.x = 5.0
+                            detector.pixel_size.y = 5.0
+                        
+                        # Store beam center in distance units
+                        # Det 640 x 640 mm
+                        if has_converter == True and \
+                            detector.beam_center_unit != 'mm':
+                            conv = Converter('mm')
+                            detector.beam_center.x = conv(center_x * 5.0,
+                                             units=detector.beam_center_unit)
+                            detector.beam_center.y = conv(center_y * 5.0,
+                                            units=detector.beam_center_unit)
+                        else:
+                            detector.beam_center.x = center_x * 5.0
+                            detector.beam_center.y = center_y * 5.0
+                        
+                        # Detector type
+                        try:
+                            detector.name = line_toks[7]
+                        except:
+                            # Detector name is not a mandatory entry
+                            pass
+                    
+                    #BCENT(X,Y)   A1(mm)   A2(mm)   A1A2DIST(m)   DL/L
+                    #  BSTOP(mm)   DET_TYP
+                    if line.count("BCENT") > 0:
+                        is_center = True
+                        
+                    # Parse the data
+                    if is_data_started == True:
+                        toks = line.split()
+
+                        try:
+                            _x  = float(toks[0])
+                            _y  = float(toks[1])
+                            _dy = float(toks[2])
+                            _dx = float(toks[3])
+                            
+                            if data_conv_q is not None:
+                                _x = data_conv_q(_x, units=output.x_unit)
+                                _dx = data_conv_i(_dx, units=output.x_unit)
+                                
+                            if data_conv_i is not None:
+                                _y = data_conv_i(_y, units=output.y_unit)
+                                _dy = data_conv_i(_dy, units=output.y_unit)
+                           
+                            x = numpy.append(x, _x)
+                            y = numpy.append(y, _y)
+                            dy = numpy.append(dy, _dy)
+                            dx = numpy.append(dx, _dx)
+                            
+                        except:
+                            # Could not read this data line. If we are here
+                            # it is because we are in the data section. Just
+                            # skip it.
+                            pass
+                            
+                    #The 6 columns are | Q (1/A) | I(Q) (1/cm) | std. dev.
+                    # I(Q) (1/cm) | sigmaQ | meanQ | ShadowFactor|
+                    if line.count("The 6 columns") > 0:
+                        is_data_started = True
+            
+                # Sanity check
+                if not len(y) == len(dy):
+                    msg = "abs_reader: y and dy have different length"
+                    raise ValueError, msg
+                # If the data length is zero, consider this as
+                # though we were not able to read the file.
+                if len(x) == 0:
+                    raise ValueError, "ascii_reader: could not load file"
+                
+                output.x = x[x != 0]
+                output.y = y[x != 0]
+                output.dy = dy[x != 0]
+                output.dx = dx[x != 0]
+                if data_conv_q is not None:
+                    output.xaxis("\\rm{Q}", output.x_unit)
+                else:
+                    output.xaxis("\\rm{Q}", 'A^{-1}')
+                if data_conv_i is not None:
+                    output.yaxis("\\rm{Intensity}", output.y_unit)
+                else:
+                    output.yaxis("\\rm{Intensity}", "cm^{-1}")
+                    
+                # Store loading process information
+                output.meta_data['loader'] = self.type_name
+                return output
+        else:
+            raise RuntimeError, "%s is not a file" % path
+        return None
Index: src/sas/sascalc/dataloader/readers/ascii_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/ascii_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/ascii_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,358 @@
+"""
+    ASCII reader
+"""
+############################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#If you use DANSE applications to do scientific research that leads to
+#publication, we ask that you acknowledge the use of the software with the
+#following sentence:
+#This work benefited from DANSE software developed under NSF award DMR-0520547.
+#copyright 2008, University of Tennessee
+#############################################################################
+
+
+import numpy
+import os
+from sas.sascalc.dataloader.data_info import Data1D
+
+# Check whether we have a converter available
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+_ZERO = 1e-16
+
+
+class Reader:
+    """
+    Class to load ascii files (2, 3 or 4 columns).
+    """
+    ## File type
+    type_name = "ASCII"
+    
+    ## Wildcards
+    type = ["ASCII files (*.txt)|*.txt",
+            "ASCII files (*.dat)|*.dat",
+            "ASCII files (*.abs)|*.abs",
+            "CSV files (*.csv)|*.csv"]
+    ## List of allowed extensions
+    ext = ['.txt', '.TXT', '.dat', '.DAT', '.abs', '.ABS', 'csv', 'CSV']
+    
+    ## Flag to bypass extension check
+    allow_all = True
+    
+    def read(self, path):
+        """
+        Load data file
+        
+        :param path: file path
+        
+        :return: Data1D object, or None
+        
+        :raise RuntimeError: when the file can't be opened
+        :raise ValueError: when the length of the data vectors are inconsistent
+        """
+        if os.path.isfile(path):
+            basename = os.path.basename(path)
+            _, extension = os.path.splitext(basename)
+            if self.allow_all or extension.lower() in self.ext:
+                try:
+                    # Read in binary mode since GRASP frequently has no-ascii
+                    # characters that brakes the open operation
+                    input_f = open(path,'rb')
+                except:
+                    raise  RuntimeError, "ascii_reader: cannot open %s" % path
+                buff = input_f.read()
+                lines = buff.splitlines()
+                 
+                x  = numpy.zeros(0)
+                y  = numpy.zeros(0)
+                dy = numpy.zeros(0)
+                dx = numpy.zeros(0)
+                
+                #temp. space to sort data
+                tx  = numpy.zeros(0)
+                ty  = numpy.zeros(0)
+                tdy = numpy.zeros(0)
+                tdx = numpy.zeros(0)
+                
+                output = Data1D(x, y, dy=dy, dx=dx)
+                self.filename = output.filename = basename
+           
+                data_conv_q = None
+                data_conv_i = None
+                
+                if has_converter == True and output.x_unit != '1/A':
+                    data_conv_q = Converter('1/A')
+                    # Test it
+                    data_conv_q(1.0, output.x_unit)
+                    
+                if has_converter == True and output.y_unit != '1/cm':
+                    data_conv_i = Converter('1/cm')
+                    # Test it
+                    data_conv_i(1.0, output.y_unit)
+           
+                # The first good line of data will define whether
+                # we have 2-column or 3-column ascii
+                has_error_dx = None
+                has_error_dy = None
+                
+                #Initialize counters for data lines and header lines.
+                is_data = False  # Has more than 5 lines
+                # More than "5" lines of data is considered as actual
+                # data unless that is the only data
+                mum_data_lines = 5
+                # To count # of current data candidate lines
+                i = -1
+                # To count total # of previous data candidate lines
+                i1 = -1
+                # To count # of header lines
+                j = -1
+                # Helps to count # of header lines
+                j1 = -1
+                #minimum required number of columns of data; ( <= 4).
+                lentoks = 2
+                for line in lines:
+                    # Initial try for CSV (split on ,)
+                    toks = line.split(',')
+                    # Now try SCSV (split on ;)
+                    if len(toks) < 2:
+                        toks = line.split(';')
+                    # Now go for whitespace                    
+                    if len(toks) < 2:
+                        toks = line.split()
+                    try:
+                        #Make sure that all columns are numbers.
+                        for colnum in range(len(toks)):
+                            float(toks[colnum])
+                            
+                        _x = float(toks[0])
+                        _y = float(toks[1])
+                        
+                        #Reset the header line counters
+                        if j == j1:
+                            j = 0
+                            j1 = 0
+                            
+                        if i > 1:
+                            is_data = True
+                        
+                        if data_conv_q is not None:
+                            _x = data_conv_q(_x, units=output.x_unit)
+                            
+                        if data_conv_i is not None:
+                            _y = data_conv_i(_y, units=output.y_unit)
+                        
+                        # If we have an extra token, check
+                        # whether it can be interpreted as a
+                        # third column.
+                        _dy = None
+                        if len(toks) > 2:
+                            try:
+                                _dy = float(toks[2])
+                                
+                                if data_conv_i is not None:
+                                    _dy = data_conv_i(_dy, units=output.y_unit)
+                                
+                            except:
+                                # The third column is not a float, skip it.
+                                pass
+                            
+                        # If we haven't set the 3rd column
+                        # flag, set it now.
+                        if has_error_dy == None:
+                            has_error_dy = False if _dy == None else True
+                            
+                        #Check for dx
+                        _dx = None
+                        if len(toks) > 3:
+                            try:
+                                _dx = float(toks[3])
+                                
+                                if data_conv_i is not None:
+                                    _dx = data_conv_i(_dx, units=output.x_unit)
+                                
+                            except:
+                                # The 4th column is not a float, skip it.
+                                pass
+                            
+                        # If we haven't set the 3rd column
+                        # flag, set it now.
+                        if has_error_dx == None:
+                            has_error_dx = False if _dx == None else True
+                        
+                        #After talked with PB, we decided to take care of only
+                        # 4 columns of data for now.
+                        #number of columns in the current line
+                        #To remember the # of columns in the current
+                        #line of data
+                        new_lentoks = len(toks)
+                        
+                        #If the previous columns not equal to the current,
+                        #mark the previous as non-data and reset the dependents.
+                        if lentoks != new_lentoks:
+                            if is_data == True:
+                                break
+                            else:
+                                i = -1
+                                i1 = 0
+                                j = -1
+                                j1 = -1
+                            
+                        #Delete the previously stored lines of data candidates
+                        # if is not data.
+                        if i < 0 and -1 < i1 < mum_data_lines and \
+                            is_data == False:
+                            try:
+                                x = numpy.zeros(0)
+                                y = numpy.zeros(0)
+                            except:
+                                pass
+                            
+                        x = numpy.append(x, _x)
+                        y = numpy.append(y, _y)
+                        
+                        if has_error_dy == True:
+                            #Delete the previously stored lines of
+                            # data candidates if is not data.
+                            if i < 0 and -1 < i1 < mum_data_lines and \
+                                is_data == False:
+                                try:
+                                    dy = numpy.zeros(0)
+                                except:
+                                    pass
+                            dy = numpy.append(dy, _dy)
+                            
+                        if has_error_dx == True:
+                            #Delete the previously stored lines of
+                            # data candidates if is not data.
+                            if i < 0 and -1 < i1 < mum_data_lines and \
+                                is_data == False:
+                                try:
+                                    dx = numpy.zeros(0)
+                                except:
+                                    pass
+                            dx = numpy.append(dx, _dx)
+                            
+                        #Same for temp.
+                        #Delete the previously stored lines of data candidates
+                        # if is not data.
+                        if i < 0 and -1 < i1 < mum_data_lines and\
+                            is_data == False:
+                            try:
+                                tx = numpy.zeros(0)
+                                ty = numpy.zeros(0)
+                            except:
+                                pass
+
+                        tx = numpy.append(tx, _x)
+                        ty = numpy.append(ty, _y)
+                        
+                        if has_error_dy == True:
+                            #Delete the previously stored lines of
+                            # data candidates if is not data.
+                            if i < 0 and -1 < i1 < mum_data_lines and \
+                                is_data == False:
+                                try:
+                                    tdy = numpy.zeros(0)
+                                except:
+                                    pass
+                            tdy = numpy.append(tdy, _dy)
+                        if has_error_dx == True:
+                            #Delete the previously stored lines of
+                            # data candidates if is not data.
+                            if i < 0 and -1 < i1 < mum_data_lines and \
+                                is_data == False:
+                                try:
+                                    tdx = numpy.zeros(0)
+                                except:
+                                    pass
+                            tdx = numpy.append(tdx, _dx)
+
+                        #reset i1 and flag lentoks for the next
+                        if lentoks < new_lentoks:
+                            if is_data == False:
+                                i1 = -1
+                        #To remember the # of columns on the current line
+                        # for the next line of data
+                        lentoks = len(toks)
+                        
+                        #Reset # of header lines and counts #
+                        # of data candidate lines
+                        if j == 0 and j1 == 0:
+                            i1 = i + 1
+                        i += 1
+                    except:
+                        # It is data and meet non - number, then stop reading
+                        if is_data == True:
+                            break
+                        lentoks = 2
+                        #Counting # of header lines
+                        j += 1
+                        if j == j1 + 1:
+                            j1 = j
+                        else:
+                            j = -1
+                        #Reset # of lines of data candidates
+                        i = -1
+                        
+                        # Couldn't parse this line, skip it
+                        pass
+                    
+                input_f.close()
+                # Sanity check
+                if has_error_dy == True and not len(y) == len(dy):
+                    msg = "ascii_reader: y and dy have different length"
+                    raise RuntimeError, msg
+                if has_error_dx == True and not len(x) == len(dx):
+                    msg = "ascii_reader: y and dy have different length"
+                    raise RuntimeError, msg
+                # If the data length is zero, consider this as
+                # though we were not able to read the file.
+                if len(x) == 0:
+                    raise RuntimeError, "ascii_reader: could not load file"
+                
+                #Let's re-order the data to make cal.
+                # curve look better some cases
+                ind = numpy.lexsort((ty, tx))
+                for i in ind:
+                    x[i] = tx[ind[i]]
+                    y[i] = ty[ind[i]]
+                    if has_error_dy == True:
+                        dy[i] = tdy[ind[i]]
+                    if has_error_dx == True:
+                        dx[i] = tdx[ind[i]]
+                # Zeros in dx, dy
+                if has_error_dx:
+                    dx[dx == 0] = _ZERO
+                if has_error_dy:
+                    dy[dy == 0] = _ZERO
+                #Data
+                output.x = x[x != 0]
+                output.y = y[x != 0]
+                output.dy = dy[x != 0] if has_error_dy == True\
+                    else numpy.zeros(len(output.y))
+                output.dx = dx[x != 0] if has_error_dx == True\
+                    else numpy.zeros(len(output.x))
+                                
+                if data_conv_q is not None:
+                    output.xaxis("\\rm{Q}", output.x_unit)
+                else:
+                    output.xaxis("\\rm{Q}", 'A^{-1}')
+                if data_conv_i is not None:
+                    output.yaxis("\\rm{Intensity}", output.y_unit)
+                else:
+                    output.yaxis("\\rm{Intensity}", "cm^{-1}")
+                    
+                # Store loading process information
+                output.meta_data['loader'] = self.type_name
+                if len(output.x) < 1:
+                    raise RuntimeError, "%s is empty" % path
+                return output
+            
+        else:
+            raise RuntimeError, "%s is not a file" % path
+        return None
Index: src/sas/sascalc/dataloader/readers/associations.py
===================================================================
--- src/sas/sascalc/dataloader/readers/associations.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/associations.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,105 @@
+"""
+Module to associate default readers to file extensions.
+The module reads an xml file to get the readers for each file extension.
+The readers are tried in order they appear when reading a file.
+"""
+############################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#If you use DANSE applications to do scientific research that leads to
+#publication, we ask that you acknowledge the use of the software with the
+#following sentence:
+#This work benefited from DANSE software developed under NSF award DMR-0520547.
+#copyright 2009, University of Tennessee
+#############################################################################
+import os
+import sys
+import logging
+import json
+
+FILE_NAME = 'defaults.json'
+
+def read_associations(loader, settings=FILE_NAME):
+    """
+    Read the specified settings file to associate
+    default readers to file extension.
+    
+    :param loader: Loader object
+    :param settings: path to the json settings file [string]
+    """
+    reader_dir = os.path.dirname(__file__)
+    path = os.path.join(reader_dir, settings)
+    
+    # If we can't find the file in the installation
+    # directory, look into the execution directory.
+    if not os.path.isfile(path):
+        path = os.path.join(os.getcwd(), settings)
+    if not os.path.isfile(path):
+        path = os.path.join(sys.path[0], settings)
+    if not os.path.isfile(path):
+        path = settings
+    if not os.path.isfile(path):
+        path = "./%s" % settings
+    if os.path.isfile(path):
+        with open(path) as fh:
+            json_tree = json.load(fh)
+        
+        # Read in the file extension associations
+        entry_list = json_tree['SasLoader']['FileType']
+
+        # For each FileType entry, get the associated reader and extension
+        for entry in entry_list:
+            reader = entry['-reader']
+            ext = entry['-extension']
+            
+            if reader is not None and ext is not None:
+                # Associate the extension with a particular reader
+                # TODO: Modify the Register code to be case-insensitive
+                # and remove the extra line below.
+                try:
+                    exec "import %s" % reader
+                    exec "loader.associate_file_type('%s', %s)" % (ext.lower(),
+                                                                    reader)
+                    exec "loader.associate_file_type('%s', %s)" % (ext.upper(),
+                                                                    reader)
+                except:
+                    msg = "read_associations: skipping association"
+                    msg += " for %s\n  %s" % (ext.lower(), sys.exc_value)
+                    logging.error(msg)
+    else:
+        print "Could not find reader association settings\n  %s [%s]" % (__file__, os.getcwd())
+         
+         
+def register_readers(registry_function):
+    """
+    Function called by the registry/loader object to register
+    all default readers using a call back function.
+    
+    :WARNING: this method is now obsolete
+
+    :param registry_function: function to be called to register each reader
+    """
+    logging.info("register_readers is now obsolete: use read_associations()")
+    import abs_reader
+    import ascii_reader
+    import cansas_reader
+    import danse_reader
+    import hfir1d_reader
+    import IgorReader
+    import red2d_reader
+    #import tiff_reader
+    import nexus_reader
+    import sesans_reader
+    registry_function(sesans_reader)
+    registry_function(abs_reader)
+    registry_function(ascii_reader)
+    registry_function(cansas_reader)
+    registry_function(danse_reader)
+    registry_function(hfir1d_reader)
+    registry_function(IgorReader)
+    registry_function(red2d_reader)
+    #registry_function(tiff_reader)
+    registry_function(nexus_reader)
+    
+    return True
Index: src/sas/sascalc/dataloader/readers/cansas_constants.py
===================================================================
--- src/sas/sascalc/dataloader/readers/cansas_constants.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/cansas_constants.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,672 @@
+"""
+Information relating to the CanSAS data format. These constants are used in
+the cansas_reader.py file to read in any version of the cansas format.
+"""
+class CansasConstants(object):
+    """
+    The base class to define where all of the data is to be saved by
+    cansas_reader.py.
+    """
+    names = ''
+    format = ''
+
+    def __init__(self):
+        self.names = self.CANSAS_NS
+        self.format = self.CANSAS_FORMAT
+
+    def iterate_namespace(self, namespace):
+        """
+        Method to iterate through a cansas constants tree based on a list of
+        names
+
+        :param namespace: A list of names that match the tree structure of
+            cansas_constants
+        """
+        # The current level to look through in cansas_constants.
+        return_me = CurrentLevel()
+        return_me.current_level = self.CANSAS_FORMAT.get("SASentry")
+        # Defaults for variable and datatype
+        return_me.ns_variable = "{0}.meta_data[\"{2}\"] = \"{1}\""
+        return_me.ns_datatype = "content"
+        return_me.ns_optional = True
+        for name in namespace:
+            try:
+                if name != "SASentry":
+                    return_me.current_level = \
+                        return_me.current_level.get("children").get(name, "")
+                    if return_me.current_level == "":
+                        return_me.current_level = \
+                                return_me.current_level.get("<any>", "")
+                    cl_variable = return_me.current_level.get("variable", "")
+                    cl_datatype = return_me.current_level.get("storeas", "")
+                    cl_units_optional = \
+                             return_me.current_level.get("units_required", "")
+                    # Where are how to store the variable for the given
+                    # namespace CANSAS_CONSTANTS tree is hierarchical, so
+                    # is no value, inherit
+                    return_me.ns_variable = cl_variable if cl_variable != "" \
+                        else return_me.ns_variable
+                    return_me.ns_datatype = cl_datatype if cl_datatype != "" \
+                        else return_me.ns_datatype
+                    return_me.ns_optional = cl_units_optional if \
+                        cl_units_optional != return_me.ns_optional \
+                                        else return_me.ns_optional
+            except AttributeError:
+                return_me.ns_variable = "{0}.meta_data[\"{2}\"] = \"{1}\""
+                return_me.ns_datatype = "content"
+                return_me.ns_optional = True
+        return return_me
+
+    def get_namespace_map(self):
+        """
+        Helper method to get the names namespace list
+        """
+        return self.names
+
+    # CANSAS_NS holds the base namespace and default schema file information
+    CANSAS_NS = {"1.0" : {"ns" : "cansas1d/1.0",
+                          "schema" : "cansas1d_v1_0.xsd"
+                         },
+                 "1.1" : {"ns" : "urn:cansas1d:1.1",
+                          "schema" : "cansas1d_v1_1.xsd"
+                         }
+                }
+
+    # The constants below hold information on where to store the CanSAS data
+    # when loaded in using sasview
+    META_DATA = "{0}.meta_data[\"{2}\"] = \"{1}\""
+    ANY = {"variable" : "{0}.meta_data[\"{2}\"] = \'{1}\'",
+           "storeas" : "content",
+          }
+    TITLE = {"variable" : "{0}.title = \"{1}\""}
+    SASNOTE = {"variable" : "{0}.notes.append(\'{1}\')"}
+    SASPROCESS_TERM = {"variable" : None,
+                       "attributes" : {"unit" : {"variable" : None},
+                                       "name" : {"variable" : None}
+                                      }
+                      }
+    SASPROCESS_SASPROCESSNOTE = {"variable" : None,
+                                 "children" : {"<any>" : ANY}
+                                }
+    SASPROCESS = {"variable" : None,
+                  "children" : {"name" : {"variable" : "{0}.name = \'{1}\'"},
+                                "date" : {"variable" : "{0}.date = \'{1}\'"},
+                                "description" :
+                                {"variable" : "{0}.description = \'{1}\'"},
+                                "term" : SASPROCESS_TERM,
+                                "SASprocessnote" : SASPROCESS_SASPROCESSNOTE,
+                                "<any>" : ANY
+                               },
+                 }
+    RUN = {"variable" : "{0}.run.append(\"{1}\")",
+           "attributes" : {"name" :
+                           {"variable" : "{0}.run_name[\"{3}\"] = \"{1}\""}}
+          }
+    SASDATA_IDATA_Q = {"variable" : "{0}.x = numpy.append({0}.x, {1})",
+                       "unit" : "x_unit",
+                       "attributes" : {"unit" :
+                                       {"variable" : "{0}._xunit = \"{1}\"",
+                                        "storeas" : "content"
+                                       }
+                                      },
+                      }
+    SASDATA_IDATA_I = {"variable" : "{0}.y = numpy.append({0}.y, {1})",
+                       "unit" : "y_unit",
+                       "attributes" : {"unit" :
+                                       {"variable" : "{0}._yunit = \"{1}\"",
+                                        "storeas" : "content"
+                                       }
+                                      },
+                      }
+    SASDATA_IDATA_IDEV = {"variable" : "{0}.dy = numpy.append({0}.dy, {1})",
+                          "unit" : "y_unit",
+                          "attributes" : {"unit" :
+                                          {"variable" : META_DATA,
+                                           "storeas" : "content"
+                                          }
+                                         },
+                         }
+    SASDATA_IDATA_QDEV = {"variable" : "{0}.dx = numpy.append({0}.dx, {1})",
+                          "unit" : "x_unit",
+                          "attributes" : {"unit" :
+                                          {"variable" : META_DATA,
+                                           "storeas" : "content"
+                                          }
+                                         },
+                         }
+    SASDATA_IDATA_DQL = {"variable" : "{0}.dxl = numpy.append({0}.dxl, {1})",
+                         "unit" : "x_unit",
+                         "attributes" : {"unit" :
+                                         {"variable" : META_DATA,
+                                          "storeas" : "content"
+                                         }
+                                        },
+                        }
+    SASDATA_IDATA_DQW = {"variable" : "{0}.dxw = numpy.append({0}.dxw, {1})",
+                         "unit" : "x_unit",
+                         "attributes" : {"unit" :
+                                         {"variable" : META_DATA,
+                                          "storeas" : "content"
+                                         }
+                                        },
+                        }
+    SASDATA_IDATA_QMEAN = {"storeas" : "content",
+                           "unit" : "x_unit",
+                           "variable" : META_DATA,
+                           "attributes" : {"unit" : {"variable" : META_DATA}},
+                          }
+    SASDATA_IDATA_SHADOWFACTOR = {"variable" : META_DATA,
+                                  "storeas" : "content",
+                                 }
+    SASDATA_IDATA = {"storeas" : "float",
+                     "units_optional" : False,
+                     "variable" : None,
+                     "attributes" : {"name" : {"variable" : META_DATA,
+                                               "storeas" : "content",
+                                              },
+                                     "timestamp" : {"variable" : META_DATA,
+                                                    "storeas" : "timestamp",
+                                                   }
+                                    },
+                     "children" : {"Q" : SASDATA_IDATA_Q,
+                                   "I" : SASDATA_IDATA_I,
+                                   "Idev" : SASDATA_IDATA_IDEV,
+                                   "Qdev" : SASDATA_IDATA_QDEV,
+                                   "dQw" : SASDATA_IDATA_DQW,
+                                   "dQl" : SASDATA_IDATA_DQL,
+                                   "Qmean" : SASDATA_IDATA_QMEAN,
+                                   "Shadowfactor" : SASDATA_IDATA_SHADOWFACTOR,
+                                   "<any>" : ANY
+                                  }
+                    }
+    SASDATA = {"attributes" : {"name" : {"variable" : META_DATA,}},
+               "variable" : None,
+               "children" : {"Idata" : SASDATA_IDATA,
+                             "<any>" : ANY
+                            }
+              }
+    SASTRANSSPEC_TDATA_LAMDBA = {"variable" : "{0}.wavelength.append({1})",
+                                 "unit" : "wavelength_unit",
+                                 "attributes" :
+                                 {"unit" :
+                                  {"variable" : "{0}.wavelength_unit = \"{1}\"",
+                                   "storeas" : "content"
+                                  }
+                                 }
+                                }
+    SASTRANSSPEC_TDATA_T = {"variable" : "{0}.transmission.append({1})",
+                            "unit" : "transmission_unit",
+                            "attributes" :
+                            {"unit" :
+                             {"variable" : "{0}.transmission_unit = \"{1}\"",
+                              "storeas" : "content"
+                             }
+                            }
+                           }
+    SASTRANSSPEC_TDATA_TDEV = {"variable" :
+                               "{0}.transmission_deviation.append({1})",
+                               "unit" : "transmission_deviation_unit",
+                               "attributes" :
+                               {"unit" :
+                                {"variable" :
+                                 "{0}.transmission_deviation_unit = \"{1}\"",
+                                 "storeas" : "content"
+                                }
+                               }
+                              }
+    SASTRANSSPEC_TDATA = {"storeas" : "float",
+                          "variable" : None,
+                          "children" : {"Lambda" : SASTRANSSPEC_TDATA_LAMDBA,
+                                        "T" : SASTRANSSPEC_TDATA_T,
+                                        "Tdev" : SASTRANSSPEC_TDATA_TDEV,
+                                        "<any>" : ANY,
+                                       }
+                         }
+    SASTRANSSPEC = {"variable" : None,
+                    "children" : {"Tdata" : SASTRANSSPEC_TDATA,
+                                  "<any>" : ANY,
+                                 },
+                    "attributes" :
+                    {"name" :
+                     {"variable" : "{0}.name = \"{1}\""},
+                     "timestamp" :
+                     {"variable" : "{0}.timestamp = \"{1}\""},
+                    }
+                   }
+    SASSAMPLE_THICK = {"variable" : "{0}.sample.thickness = {1}",
+                       "unit" : "sample.thickness_unit",
+                       "storeas" : "float",
+                       "attributes" :
+                       {"unit" :
+                        {"variable" : "{0}.sample.thickness_unit = \"{1}\"",
+                         "storeas" : "content"
+                        }
+                       },
+                      }
+    SASSAMPLE_TRANS = {"variable" : "{0}.sample.transmission = {1}",
+                       "storeas" : "float",
+                      }
+    SASSAMPLE_TEMP = {"variable" : "{0}.sample.temperature = {1}",
+                      "unit" : "sample.temperature_unit",
+                      "storeas" : "float",
+                      "attributes" :
+                      {"unit" :
+                       {"variable" : "{0}.sample.temperature_unit = \"{1}\"",
+                        "storeas" : "content"
+                       }
+                      },
+                     }
+    SASSAMPLE_POS_ATTR = {"unit" : {"variable" :
+                                    "{0}.sample.position_unit = \"{1}\"",
+                                    "storeas" : "content"
+                                   }
+                         }
+    SASSAMPLE_POS_X = {"variable" : "{0}.sample.position.x = {1}",
+                       "unit" : "sample.position_unit",
+                       "storeas" : "float",
+                       "attributes" : SASSAMPLE_POS_ATTR
+                      }
+    SASSAMPLE_POS_Y = {"variable" : "{0}.sample.position.y = {1}",
+                       "unit" : "sample.position_unit",
+                       "storeas" : "float",
+                       "attributes" : SASSAMPLE_POS_ATTR
+                      }
+    SASSAMPLE_POS_Z = {"variable" : "{0}.sample.position.z = {1}",
+                       "unit" : "sample.position_unit",
+                       "storeas" : "float",
+                       "attributes" : SASSAMPLE_POS_ATTR
+                      }
+    SASSAMPLE_POS = {"children" : {"variable" : None,
+                                   "x" : SASSAMPLE_POS_X,
+                                   "y" : SASSAMPLE_POS_Y,
+                                   "z" : SASSAMPLE_POS_Z,
+                                  },
+                    }
+    SASSAMPLE_ORIENT_ATTR = {"unit" :
+                             {"variable" :
+                              "{0}.sample.orientation_unit = \"{1}\"",
+                              "storeas" : "content"
+                             }
+                            }
+    SASSAMPLE_ORIENT_ROLL = {"variable" : "{0}.sample.orientation.x = {1}",
+                             "unit" : "sample.orientation_unit",
+                             "storeas" : "float",
+                             "attributes" : SASSAMPLE_ORIENT_ATTR
+                            }
+    SASSAMPLE_ORIENT_PITCH = {"variable" : "{0}.sample.orientation.y = {1}",
+                              "unit" : "sample.orientation_unit",
+                              "storeas" : "float",
+                              "attributes" : SASSAMPLE_ORIENT_ATTR
+                             }
+    SASSAMPLE_ORIENT_YAW = {"variable" : "{0}.sample.orientation.z = {1}",
+                            "unit" : "sample.orientation_unit",
+                            "storeas" : "float",
+                            "attributes" : SASSAMPLE_ORIENT_ATTR
+                           }
+    SASSAMPLE_ORIENT = {"variable" : None,
+                        "children" : {"roll" : SASSAMPLE_ORIENT_ROLL,
+                                      "pitch" : SASSAMPLE_ORIENT_PITCH,
+                                      "yaw" : SASSAMPLE_ORIENT_YAW,
+                                     },
+                       }
+    SASSAMPLE = {"attributes" :
+                 {"name" : {"variable" : "{0}.sample.name = \"{1}\""},},
+                 "variable" : None,
+                 "children" : {"ID" : {"variable" : "{0}.sample.ID = \"{1}\""},
+                               "thickness" : SASSAMPLE_THICK,
+                               "transmission" : SASSAMPLE_TRANS,
+                               "temperature" : SASSAMPLE_TEMP,
+                               "position" : SASSAMPLE_POS,
+                               "orientation" : SASSAMPLE_ORIENT,
+                               "details" :
+                               {"variable" :
+                                "{0}.sample.details.append(\"{1}\")"},
+                               "<any>" : ANY
+                              },
+                }
+    SASINSTR_SRC_BEAMSIZE_ATTR = {"unit" :
+                                  "{0}.source.beam_size_unit = \"{1}\"",
+                                  "storeas" : "content"
+                                 }
+    SASINSTR_SRC_BEAMSIZE_X = {"variable" : "{0}.source.beam_size.x = {1}",
+                               "unit" : "source.beam_size_unit",
+                               "storeas" : "float",
+                               "attributes" : SASINSTR_SRC_BEAMSIZE_ATTR
+                              }
+    SASINSTR_SRC_BEAMSIZE_Y = {"variable" : "{0}.source.beam_size.y = {1}",
+                               "unit" : "source.beam_size_unit",
+                               "storeas" : "float",
+                               "attributes" : SASINSTR_SRC_BEAMSIZE_ATTR
+                              }
+    SASINSTR_SRC_BEAMSIZE_Z = {"variable" : "{0}.source.beam_size.z = {1}",
+                               "unit" : "source.beam_size_unit",
+                               "storeas" : "float",
+                               "attributes" : SASINSTR_SRC_BEAMSIZE_ATTR
+                              }
+    SASINSTR_SRC_BEAMSIZE = {"attributes" :
+                             {"name" : {"variable" :
+                                        "{0}.source.beam_size_name = \"{1}\""}},
+                             "variable" : None,
+                             "children" : {"x" : SASINSTR_SRC_BEAMSIZE_X,
+                                           "y" : SASINSTR_SRC_BEAMSIZE_Y,
+                                           "z" : SASINSTR_SRC_BEAMSIZE_Z,
+                                          }
+                            }
+    SASINSTR_SRC_WL = {"variable" : "{0}.source.wavelength = {1}",
+                       "unit" : "source.wavelength_unit",
+                       "storeas" : "float",
+                       "attributes" :
+                       {"unit" :
+                        {"variable" : "{0}.source.wavelength_unit = \"{1}\"",
+                         "storeas" : "content"
+                        },
+                       }
+                      }
+    SASINSTR_SRC_WL_MIN = {"variable" : "{0}.source.wavelength_min = {1}",
+                           "unit" : "source.wavelength_min_unit",
+                           "storeas" : "float",
+                           "attributes" :
+                           {"unit" :
+                            {"variable" :
+                             "{0}.source.wavelength_min_unit = \"{1}\"",
+                             "storeas" : "content"
+                            },
+                           }
+                          }
+    SASINSTR_SRC_WL_MAX = {"variable" : "{0}.source.wavelength_max = {1}",
+                           "unit" : "source.wavelength_max_unit",
+                           "storeas" : "float",
+                           "attributes" :
+                           {"unit" :
+                            {"variable" :
+                             "{0}.source.wavelength_max_unit = \"{1}\"",
+                             "storeas" : "content"
+                            },
+                           }
+                          }
+    SASINSTR_SRC_WL_SPR = {"variable" : "{0}.source.wavelength_spread = {1}",
+                           "unit" : "source.wavelength_spread_unit",
+                           "storeas" : "float",
+                           "attributes" :
+                           {"unit" :
+                            {"variable" :
+                             "{0}.source.wavelength_spread_unit = \"{1}\"",
+                             "storeas" : "content"
+                            },
+                           }
+                          }
+    SASINSTR_SRC = {"attributes" : {"name" : {"variable" :
+                                              "{0}.source.name = \"{1}\""}},
+                    "variable" : None,
+                    "children" : {"radiation" :
+                                  {"variable" :
+                                   "{0}.source.radiation = \"{1}\""},
+                                  "beam_size" : SASINSTR_SRC_BEAMSIZE,
+                                  "beam_shape" :
+                                  {"variable" :
+                                   "{0}.source.beam_shape = \"{1}\""},
+                                  "wavelength" : SASINSTR_SRC_WL,
+                                  "wavelength_min" : SASINSTR_SRC_WL_MIN,
+                                  "wavelength_max" : SASINSTR_SRC_WL_MAX,
+                                  "wavelength_spread" : SASINSTR_SRC_WL_SPR,
+                                 },
+                   }
+    SASINSTR_COLL_APER_ATTR = {"unit" : {"variable" : "{0}.size_unit = \"{1}\"",
+                                         "storeas" : "content"
+                                        },
+                              }
+    SASINSTR_COLL_APER_X = {"variable" : "{0}.size.x = {1}",
+                            "unit" : "size_unit",
+                            "storeas" : "float",
+                            "attributes" : SASINSTR_COLL_APER_ATTR
+                           }
+    SASINSTR_COLL_APER_Y = {"variable" : "{0}.size.y = {1}",
+                            "unit" : "size_unit",
+                            "storeas" : "float",
+                            "attributes" : SASINSTR_COLL_APER_ATTR
+                           }
+    SASINSTR_COLL_APER_Z = {"variable" : "{0}.size.z = {1}",
+                            "unit" : "size_unit",
+                            "storeas" : "float",
+                            "attributes" : SASINSTR_COLL_APER_ATTR
+                           }
+    SASINSTR_COLL_APER_SIZE = {"attributes" :
+                               {"unit" : {"variable" :
+                                          "{0}.size_unit = \"{1}\""}},
+                               "children" : {"storeas" : "float",
+                                             "x" : SASINSTR_COLL_APER_X,
+                                             "y" : SASINSTR_COLL_APER_Y,
+                                             "z" : SASINSTR_COLL_APER_Z,
+                                            }
+                              }
+    SASINSTR_COLL_APER_DIST = {"storeas" : "float",
+                               "attributes" :
+                               {"storeas" : "content",
+                                "unit" : {"variable" :
+                                          "{0}.distance_unit = \"{1}\""}
+                               },
+                               "variable" : "{0}.distance = {1}",
+                               "unit" : "distance_unit",
+                              }
+    SASINSTR_COLL_APER = {"variable" : None,
+                          "attributes" : {"name" : {"variable" :
+                                                    "{0}.name = \"{1}\""},
+                                          "type" : {"variable" :
+                                                    "{0}.type = \"{1}\""},
+                                         },
+                          "children" : {"size" : SASINSTR_COLL_APER_SIZE,
+                                        "distance" : SASINSTR_COLL_APER_DIST
+                                       }
+                         }
+    SASINSTR_COLL = {"attributes" :
+                     {"name" : {"variable" : "{0}.name = \"{1}\""}},
+                     "variable" : None,
+                     "children" :
+                     {"length" :
+                      {"variable" : "{0}.length = {1}",
+                       "unit" : "length_unit",
+                       "storeas" : "float",
+                       "attributes" :
+                       {"storeas" : "content",
+                        "unit" : {"variable" : "{0}.length_unit = \"{1}\""}
+                       },
+                      },
+                      "aperture" : SASINSTR_COLL_APER,
+                     },
+                    }
+    SASINSTR_DET_SDD = {"variable" : "{0}.distance = {1}",
+                        "unit" : "distance_unit",
+                        "attributes" :
+                        {"unit" :
+                         {"variable" : "{0}.distance_unit = \"{1}\"",
+                          "storeas" : "content"
+                         }
+                        },
+                       }
+    SASINSTR_DET_OFF_ATTR = {"unit" : {"variable" : "{0}.offset_unit = \"{1}\"",
+                                       "storeas" : "content"
+                                      },
+                            }
+    SASINSTR_DET_OFF_X = {"variable" : "{0}.offset.x = {1}",
+                          "unit" : "offset_unit",
+                          "attributes" : SASINSTR_DET_OFF_ATTR
+                         }
+    SASINSTR_DET_OFF_Y = {"variable" : "{0}.offset.y = {1}",
+                          "unit" : "offset_unit",
+                          "attributes" : SASINSTR_DET_OFF_ATTR
+                         }
+    SASINSTR_DET_OFF_Z = {"variable" : "{0}.offset.z = {1}",
+                          "unit" : "offset_unit",
+                          "attributes" : SASINSTR_DET_OFF_ATTR
+                         }
+    SASINSTR_DET_OFF = {"variable" : None,
+                        "children" : {"x" : SASINSTR_DET_OFF_X,
+                                      "y" : SASINSTR_DET_OFF_Y,
+                                      "z" : SASINSTR_DET_OFF_Z,
+                                     }
+                       }
+    SASINSTR_DET_OR_ATTR = {"unit" : "{0}.orientation_unit = \"{1}\"",
+                            "storeas" : "content"
+                           }
+    SASINSTR_DET_OR_ROLL = {"variable" : "{0}.orientation.x = {1}",
+                            "unit" : "orientation_unit",
+                            "attributes" : SASINSTR_DET_OR_ATTR
+                           }
+    SASINSTR_DET_OR_PITCH = {"variable" : "{0}.orientation.y = {1}",
+                             "unit" : "orientation_unit",
+                             "attributes" : SASINSTR_DET_OR_ATTR
+                            }
+    SASINSTR_DET_OR_YAW = {"variable" : "{0}.orientation.z = {1}",
+                           "unit" : "orientation_unit",
+                           "attributes" : SASINSTR_DET_OR_ATTR
+                          }
+    SASINSTR_DET_OR = {"variable" : None,
+                       "children" : {"roll" : SASINSTR_DET_OR_ROLL,
+                                     "pitch" : SASINSTR_DET_OR_PITCH,
+                                     "yaw" : SASINSTR_DET_OR_YAW,
+                                    }
+                      }
+    SASINSTR_DET_BC_X = {"variable" : "{0}.beam_center.x = {1}",
+                         "unit" : "beam_center_unit",
+                         "attributes" :
+                         {"unit" : "{0}.beam_center_unit = \"{1}\"",
+                          "storeas" : "content"
+                         }
+                        }
+    SASINSTR_DET_BC_Y = {"variable" : "{0}.beam_center.y = {1}",
+                         "unit" : "beam_center_unit",
+                         "attributes" :
+                         {"unit" : "{0}.beam_center_unit = \"{1}\"",
+                          "storeas" : "content"
+                         }
+                        }
+    SASINSTR_DET_BC_Z = {"variable" : "{0}.beam_center.z = {1}",
+                         "unit" : "beam_center_unit",
+                         "attributes" :
+                         {"unit" : "{0}.beam_center_unit = \"{1}\"",
+                          "storeas" : "content"
+                         }
+                        }
+    SASINSTR_DET_BC = {"variable" : None,
+                       "children" : {"x" : SASINSTR_DET_BC_X,
+                                     "y" : SASINSTR_DET_BC_Y,
+                                     "z" : SASINSTR_DET_BC_Z,
+                                    }
+                      }
+    SASINSTR_DET_PIXEL_X = {"variable" : "{0}.pixel_size.x = {1}",
+                            "unit" : "pixel_size_unit",
+                            "attributes" :
+                            {"unit" : "{0}.pixel_size_unit = \"{1}\"",
+                             "storeas" : "content"
+                            }
+                           }
+    SASINSTR_DET_PIXEL_Y = {"variable" : "{0}.pixel_size.y = {1}",
+                            "unit" : "pixel_size_unit",
+                            "attributes" :
+                            {"unit" : "{0}.pixel_size_unit = \"{1}\"",
+                             "storeas" : "content"
+                            }
+                           }
+    SASINSTR_DET_PIXEL_Z = {"variable" : "{0}.pixel_size.z = {1}",
+                            "unit" : "pixel_size_unit",
+                            "attributes" :
+                            {"unit" : "{0}.pixel_size_unit = \"{1}\"",
+                             "storeas" : "content"
+                            }
+                           }
+    SASINSTR_DET_PIXEL = {"variable" : None,
+                          "children" : {"x" : SASINSTR_DET_PIXEL_X,
+                                        "y" : SASINSTR_DET_PIXEL_Y,
+                                        "z" : SASINSTR_DET_PIXEL_Z,
+                                       }
+                         }
+    SASINSTR_DET_SLIT = {"variable" : "{0}.slit_length = {1}",
+                         "unit" : "slit_length_unit",
+                         "attributes" :
+                         {"unit" :
+                          {"variable" : "{0}.slit_length_unit = \"{1}\"",
+                           "storeas" : "content"
+                          }
+                         }
+                        }
+    SASINSTR_DET = {"storeas" : "float",
+                    "variable" : None,
+                    "attributes" : {"name" :
+                                    {"storeas" : "content",
+                                     "variable" : "{0}.name = \"{1}\"",
+                                    }
+                                   },
+                    "children" : {"name" : {"storeas" : "content",
+                                            "variable" : "{0}.name = \"{1}\"",
+                                           },
+                                  "SDD" : SASINSTR_DET_SDD,
+                                  "offset" : SASINSTR_DET_OFF,
+                                  "orientation" : SASINSTR_DET_OR,
+                                  "beam_center" : SASINSTR_DET_BC,
+                                  "pixel_size" : SASINSTR_DET_PIXEL,
+                                  "slit_length" : SASINSTR_DET_SLIT,
+                                 }
+                   }
+    SASINSTR = {"variable" : None,
+                "children" :
+                {"variable" : None,
+                 "name" : {"variable" : "{0}.instrument = \"{1}\""},
+                 "SASsource" : SASINSTR_SRC,
+                 "SAScollimation" : SASINSTR_COLL,
+                 "SASdetector" : SASINSTR_DET,
+                },
+               }
+    CANSAS_FORMAT = {"SASentry" :
+                     {"units_optional" : True,
+                      "variable" : None,
+                      "storeas" : "content",
+                      "attributes" : {"name" :
+                                      {"variable" :
+                                       "{0}.run_name[\"{3}\"] = \"{1}\""}},
+                      "children" : {"Title" : TITLE,
+                                    "Run" : RUN,
+                                    "SASdata" : SASDATA,
+                                    "SAStransmission_spectrum" : SASTRANSSPEC,
+                                    "SASsample" : SASSAMPLE,
+                                    "SASinstrument" : SASINSTR,
+                                    "SASprocess" : SASPROCESS,
+                                    "SASnote" : SASNOTE,
+                                    "<any>" : ANY,
+                                   }
+                     }
+                    }
+
+
+class CurrentLevel(object):
+    """
+    A helper class to hold information on where you are in the constants tree
+    """
+
+    current_level = ''
+    ns_variable = ''
+    ns_datatype = ''
+    ns_optional = True
+
+    def __init__(self):
+        self.current_level = {}
+        self.ns_variable = ''
+        self.ns_datatype = "content"
+        self.ns_optional = True
+
+    def get_current_level(self):
+        """
+        Helper method to get the current_level map
+        """
+        return self.current_level
+
+    def get_data_type(self):
+        """
+        Helper method to get the ns_datatype label
+        """
+        return self.ns_datatype
+
+    def get_variable(self):
+        """
+        Helper method to get the ns_variable label
+        """
+        return self.ns_variable
Index: src/sas/sascalc/dataloader/readers/cansas_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/cansas_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/cansas_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,1181 @@
+"""
+    CanSAS data reader - new recursive cansas_version.
+"""
+############################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#If you use DANSE applications to do scientific research that leads to
+#publication, we ask that you acknowledge the use of the software with the
+#following sentence:
+#This work benefited from DANSE software developed under NSF award DMR-0520547.
+#copyright 2008,2009 University of Tennessee
+#############################################################################
+
+import logging
+import numpy
+import os
+import sys
+import datetime
+import inspect
+# For saving individual sections of data
+from sas.sascalc.dataloader.data_info import Data1D
+from sas.sascalc.dataloader.data_info import Collimation
+from sas.sascalc.dataloader.data_info import TransmissionSpectrum
+from sas.sascalc.dataloader.data_info import Detector
+from sas.sascalc.dataloader.data_info import Process
+from sas.sascalc.dataloader.data_info import Aperture
+import sas.sascalc.dataloader.readers.xml_reader as xml_reader
+from sas.sascalc.dataloader.readers.xml_reader import XMLreader
+from sas.sascalc.dataloader.readers.cansas_constants import CansasConstants
+
+# The following 2 imports *ARE* used. Do not remove either.
+import xml.dom.minidom
+from xml.dom.minidom import parseString
+
+_ZERO = 1e-16
+PREPROCESS = "xmlpreprocess"
+ENCODING = "encoding"
+RUN_NAME_DEFAULT = "None"
+HAS_CONVERTER = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except ImportError:
+    HAS_CONVERTER = False
+
+CONSTANTS = CansasConstants()
+CANSAS_FORMAT = CONSTANTS.format
+CANSAS_NS = CONSTANTS.names
+ALLOW_ALL = True
+
+# DO NOT REMOVE Called by outside packages:
+#    sas.perspectives.invariant.invariant_state
+#    sas.perspectives.fitting.pagestate
+def get_content(location, node):
+    """
+    Get the first instance of the content of a xpath location.
+
+    :param location: xpath location
+    :param node: node to start at
+
+    :return: Element, or None
+    """
+    nodes = node.xpath(location,
+                       namespaces={'ns': CANSAS_NS.get("1.0").get("ns")})
+    if len(nodes) > 0:
+        return nodes[0]
+    else:
+        return None
+
+# DO NOT REMOVE Called by outside packages:
+#    sas.perspectives.fitting.pagestate
+def write_node(doc, parent, name, value, attr=None):
+    """
+    :param doc: document DOM
+    :param parent: parent node
+    :param name: tag of the element
+    :param value: value of the child text node
+    :param attr: attribute dictionary
+
+    :return: True if something was appended, otherwise False
+    """
+    if attr is None:
+        attr = {}
+    if value is not None:
+        node = doc.createElement(name)
+        node.appendChild(doc.createTextNode(str(value)))
+        for item in attr:
+            node.setAttribute(item, attr[item])
+        parent.appendChild(node)
+        return True
+    return False
+
+class Reader(XMLreader):
+    """
+    Class to load cansas 1D XML files
+
+    :Dependencies:
+        The CanSAS reader requires PyXML 0.8.4 or later.
+    """
+    ##CanSAS version - defaults to version 1.0
+    cansas_version = "1.0"
+    base_ns = "{cansas1d/1.0}"
+    logging = None
+    errors = None
+    type_name = "canSAS"
+    ## Wildcards
+    type = ["XML files (*.xml)|*.xml", "SasView Save Files (*.svs)|*.svs"]
+    ## List of allowed extensions
+    ext = ['.xml', '.XML', '.svs', '.SVS']
+    ## Flag to bypass extension check
+    allow_all = True
+
+    def __init__(self):
+        ## List of errors
+        self.errors = set()
+        self.logging = []
+        self.encoding = None
+
+    def is_cansas(self, ext="xml"):
+        """
+        Checks to see if the xml file is a CanSAS file
+
+        :param ext: The file extension of the data file
+        """
+        if self.validate_xml():
+            name = "{http://www.w3.org/2001/XMLSchema-instance}schemaLocation"
+            value = self.xmlroot.get(name)
+            if CANSAS_NS.get(self.cansas_version).get("ns") == \
+                    value.rsplit(" ")[0]:
+                return True
+        if ext == "svs":
+            return True
+        return False
+
+    def load_file_and_schema(self, xml_file):
+        """
+        Loads the file and associates a schema, if a known schema exists
+
+        :param xml_file: The xml file path sent to Reader.read
+        """
+        base_name = xml_reader.__file__
+        base_name = base_name.replace("\\", "/")
+        base = base_name.split("/sas/")[0]
+
+        # Load in xml file and get the cansas version from the header
+        self.set_xml_file(xml_file)
+        self.cansas_version = self.xmlroot.get("version", "1.0")
+
+        # Generic values for the cansas file based on the version
+        cansas_defaults = CANSAS_NS.get(self.cansas_version, "1.0")
+        schema_path = "{0}/sas/dataloader/readers/schema/{1}".format\
+                (base, cansas_defaults.get("schema")).replace("\\", "/")
+
+        # Link a schema to the XML file.
+        self.set_schema(schema_path)
+        return cansas_defaults
+
+    def read(self, xml_file):
+        """
+        Validate and read in an xml_file file in the canSAS format.
+
+        :param xml_file: A canSAS file path in proper XML format
+        """
+        # output - Final list of Data1D objects
+        output = []
+        # ns - Namespace hierarchy for current xml_file object
+        ns_list = []
+
+        # Check that the file exists
+        if os.path.isfile(xml_file):
+            basename = os.path.basename(xml_file)
+            _, extension = os.path.splitext(basename)
+            # If the file type is not allowed, return nothing
+            if extension in self.ext or self.allow_all:
+                # Get the file location of
+                cansas_defaults = self.load_file_and_schema(xml_file)
+
+                # Try to load the file, but raise an error if unable to.
+                # Check the file matches the XML schema
+                try:
+                    if self.is_cansas(extension):
+                        # Get each SASentry from XML file and add it to a list.
+                        entry_list = self.xmlroot.xpath(
+                                '/ns:SASroot/ns:SASentry',
+                                namespaces={'ns': cansas_defaults.get("ns")})
+                        ns_list.append("SASentry")
+
+                        # If multiple files, modify the name for each is unique
+                        increment = 0
+                        # Parse each SASentry item
+                        for entry in entry_list:
+                            # Define a new Data1D object with zeroes for
+                            # x_vals and y_vals
+                            data1d = Data1D(numpy.empty(0), numpy.empty(0),
+                                            numpy.empty(0), numpy.empty(0))
+                            data1d.dxl = numpy.empty(0)
+                            data1d.dxw = numpy.empty(0)
+
+                            # If more than one SASentry, increment each in order
+                            name = basename
+                            if len(entry_list) - 1 > 0:
+                                name += "_{0}".format(increment)
+                                increment += 1
+
+                            # Set the Data1D name and then parse the entry.
+                            # The entry is appended to a list of entry values
+                            data1d.filename = name
+                            data1d.meta_data["loader"] = "CanSAS 1D"
+
+                            # Get all preprocessing events and encoding
+                            self.set_processing_instructions()
+                            data1d.meta_data[PREPROCESS] = \
+                                    self.processing_instructions
+
+                            # Parse the XML file
+                            return_value, extras = \
+                                self._parse_entry(entry, ns_list, data1d)
+                            del extras[:]
+
+                            return_value = self._final_cleanup(return_value)
+                            output.append(return_value)
+                    else:
+                        output.append("Invalid XML at: {0}".format(\
+                                                    self.find_invalid_xml()))
+                except:
+                    # If the file does not match the schema, raise this error
+                    raise RuntimeError, "%s cannot be read" % xml_file
+                return output
+        # Return a list of parsed entries that dataloader can manage
+        return None
+
+    def _final_cleanup(self, data1d):
+        """
+        Final cleanup of the Data1D object to be sure it has all the
+        appropriate information needed for perspectives
+
+        :param data1d: Data1D object that has been populated
+        """
+        # Final cleanup
+        # Remove empty nodes, verify array sizes are correct
+        for error in self.errors:
+            data1d.errors.append(error)
+        self.errors.clear()
+        numpy.trim_zeros(data1d.x)
+        numpy.trim_zeros(data1d.y)
+        numpy.trim_zeros(data1d.dy)
+        size_dx = data1d.dx.size
+        size_dxl = data1d.dxl.size
+        size_dxw = data1d.dxw.size
+        if data1d._xunit != data1d.x_unit:
+            data1d.x_unit = data1d._xunit
+        if data1d._yunit != data1d.y_unit:
+            data1d.y_unit = data1d._yunit
+        if size_dxl == 0 and size_dxw == 0:
+            data1d.dxl = None
+            data1d.dxw = None
+            numpy.trim_zeros(data1d.dx)
+        elif size_dx == 0:
+            data1d.dx = None
+            size_dx = size_dxl
+            numpy.trim_zeros(data1d.dxl)
+            numpy.trim_zeros(data1d.dxw)
+        return data1d
+
+    def _create_unique_key(self, dictionary, name, numb=0):
+        """
+        Create a unique key value for any dictionary to prevent overwriting
+        Recurses until a unique key value is found.
+
+        :param dictionary: A dictionary with any number of entries
+        :param name: The index of the item to be added to dictionary
+        :param numb: The number to be appended to the name, starts at 0
+        """
+        if dictionary.get(name) is not None:
+            numb += 1
+            name = name.split("_")[0]
+            name += "_{0}".format(numb)
+            name = self._create_unique_key(dictionary, name, numb)
+        return name
+
+    def _unit_conversion(self, node, new_current_level, data1d, \
+                                                tagname, node_value):
+        """
+        A unit converter method used to convert the data included in the file
+        to the default units listed in data_info
+
+        :param new_current_level: cansas_constants level as returned by
+            iterate_namespace
+        :param attr: The attributes of the node
+        :param data1d: Where the values will be saved
+        :param node_value: The value of the current dom node
+        """
+        attr = node.attrib
+        value_unit = ''
+        err_msg = None
+        default_unit = None
+        if 'unit' in attr and new_current_level.get('unit') is not None:
+            try:
+                local_unit = attr['unit']
+                if isinstance(node_value, float) is False:
+                    exec("node_value = float({0})".format(node_value))
+                unitname = new_current_level.get("unit")
+                exec "default_unit = data1d.{0}".format(unitname)
+                if local_unit is not None and default_unit is not None and \
+                        local_unit.lower() != default_unit.lower() \
+                        and local_unit.lower() != "none":
+                    if HAS_CONVERTER == True:
+                        ## Check local units - bad units raise KeyError
+                        data_conv_q = Converter(local_unit)
+                        value_unit = default_unit
+                        node_value = data_conv_q(node_value, units=default_unit)
+                    else:
+                        value_unit = local_unit
+                        err_msg = "Unit converter is not available.\n"
+                else:
+                    value_unit = local_unit
+            except KeyError:
+                err_msg = "CanSAS reader: unexpected "
+                err_msg += "\"{0}\" unit [{1}]; "
+                err_msg = err_msg.format(tagname, local_unit)
+                err_msg += "expecting [{0}]".format(default_unit)
+                value_unit = local_unit
+            except:
+                err_msg = "CanSAS reader: unknown error converting "
+                err_msg += "\"{0}\" unit [{1}]"
+                err_msg = err_msg.format(tagname, local_unit)
+                value_unit = local_unit
+        elif 'unit' in attr:
+            value_unit = attr['unit']
+        if err_msg:
+            self.errors.add(err_msg)
+        node_value = "float({0})".format(node_value)
+        return node_value, value_unit
+
+    def _check_for_empty_data(self, data1d):
+        """
+        Creates an empty data set if no data is passed to the reader
+
+        :param data1d: presumably a Data1D object
+        """
+        if data1d == None:
+            self.errors = set()
+            x_vals = numpy.empty(0)
+            y_vals = numpy.empty(0)
+            dx_vals = numpy.empty(0)
+            dy_vals = numpy.empty(0)
+            dxl = numpy.empty(0)
+            dxw = numpy.empty(0)
+            data1d = Data1D(x_vals, y_vals, dx_vals, dy_vals)
+            data1d.dxl = dxl
+            data1d.dxw = dxw
+        return data1d
+
+    def _handle_special_cases(self, tagname, data1d, children):
+        """
+        Handle cases where the data type in Data1D is a dictionary or list
+
+        :param tagname: XML tagname in use
+        :param data1d: The original Data1D object
+        :param children: Child nodes of node
+        :param node: existing node with tag name 'tagname'
+        """
+        if tagname == "SASdetector":
+            data1d = Detector()
+        elif tagname == "SAScollimation":
+            data1d = Collimation()
+        elif tagname == "SAStransmission_spectrum":
+            data1d = TransmissionSpectrum()
+        elif tagname == "SASprocess":
+            data1d = Process()
+            for child in children:
+                if child.tag.replace(self.base_ns, "") == "term":
+                    term_attr = {}
+                    for attr in child.keys():
+                        term_attr[attr] = \
+                            ' '.join(child.get(attr).split())
+                    if child.text is not None:
+                        term_attr['value'] = \
+                            ' '.join(child.text.split())
+                    data1d.term.append(term_attr)
+        elif tagname == "aperture":
+            data1d = Aperture()
+        if tagname == "Idata" and children is not None:
+            data1d = self._check_for_empty_resolution(data1d, children)
+        return data1d
+
+    def _check_for_empty_resolution(self, data1d, children):
+        """
+        A method to check all resolution data sets are the same size as I and Q
+        """
+        dql_exists = False
+        dqw_exists = False
+        dq_exists = False
+        di_exists = False
+        for child in children:
+            tag = child.tag.replace(self.base_ns, "")
+            if tag == "dQl":
+                dql_exists = True
+            if tag == "dQw":
+                dqw_exists = True
+            if tag == "Qdev":
+                dq_exists = True
+            if tag == "Idev":
+                di_exists = True
+        if dqw_exists and dql_exists == False:
+            data1d.dxl = numpy.append(data1d.dxl, 0.0)
+        elif dql_exists and dqw_exists == False:
+            data1d.dxw = numpy.append(data1d.dxw, 0.0)
+        elif dql_exists == False and dqw_exists == False \
+                                            and dq_exists == False:
+            data1d.dx = numpy.append(data1d.dx, 0.0)
+        if di_exists == False:
+            data1d.dy = numpy.append(data1d.dy, 0.0)
+        return data1d
+
+    def _restore_original_case(self,
+                               tagname_original,
+                               tagname,
+                               save_data1d,
+                               data1d):
+        """
+        Save the special case data to the appropriate location and restore
+        the original Data1D object
+
+        :param tagname_original: Unmodified tagname for the node
+        :param tagname: modified tagname for the node
+        :param save_data1d: The original Data1D object
+        :param data1d: If a special case was handled, an object of that type
+        """
+        if tagname_original == "SASdetector":
+            save_data1d.detector.append(data1d)
+        elif tagname_original == "SAScollimation":
+            save_data1d.collimation.append(data1d)
+        elif tagname == "SAStransmission_spectrum":
+            save_data1d.trans_spectrum.append(data1d)
+        elif tagname_original == "SASprocess":
+            save_data1d.process.append(data1d)
+        elif tagname_original == "aperture":
+            save_data1d.aperture.append(data1d)
+        else:
+            save_data1d = data1d
+        return save_data1d
+
+    def _handle_attributes(self, node, data1d, cs_values, tagname):
+        """
+        Process all of the attributes for a node
+        """
+        attr = node.attrib
+        if attr is not None:
+            for key in node.keys():
+                try:
+                    node_value, unit = self._get_node_value(node, cs_values, \
+                                                   data1d, tagname)
+                    cansas_attrib = \
+                        cs_values.current_level.get("attributes").get(key)
+                    attrib_variable = cansas_attrib.get("variable")
+                    if key == 'unit' and unit != '':
+                        attrib_value = unit
+                    else:
+                        attrib_value = node.attrib[key]
+                    store_attr = attrib_variable.format("data1d",
+                                                        attrib_value,
+                                                        key,
+                                                        node_value)
+                    exec store_attr
+                except AttributeError:
+                    pass
+        return data1d
+
+    def _get_node_value(self, node, cs_values, data1d, tagname):
+        """
+        Get the value of a node and any applicable units
+
+        :param node: The XML node to get the value of
+        :param cs_values: A CansasConstants.CurrentLevel object
+        :param attr: The node attributes
+        :param dataid: The working object to be modified
+        :param tagname: The tagname of the node
+        """
+        #Get the text from the node and convert all whitespace to spaces
+        units = ''
+        node_value = node.text
+        if node_value == "":
+            node_value = None
+        if node_value is not None:
+            node_value = ' '.join(node_value.split())
+
+        # If the value is a float, compile with units.
+        if cs_values.ns_datatype == "float":
+            # If an empty value is given, set as zero.
+            if node_value is None or node_value.isspace() \
+                                    or node_value.lower() == "nan":
+                node_value = "0.0"
+            #Convert the value to the base units
+            node_value, units = self._unit_conversion(node, \
+                        cs_values.current_level, data1d, tagname, node_value)
+
+        # If the value is a timestamp, convert to a datetime object
+        elif cs_values.ns_datatype == "timestamp":
+            if node_value is None or node_value.isspace():
+                pass
+            else:
+                try:
+                    node_value = \
+                        datetime.datetime.fromtimestamp(node_value)
+                except ValueError:
+                    node_value = None
+        return node_value, units
+
+    def _parse_entry(self, dom, names=None, data1d=None, extras=None):
+        """
+        Parse a SASEntry - new recursive method for parsing the dom of
+            the CanSAS data format. This will allow multiple data files
+            and extra nodes to be read in simultaneously.
+
+        :param dom: dom object with a namespace base of names
+        :param names: A list of element names that lead up to the dom object
+        :param data1d: The data1d object that will be modified
+        :param extras: Any values that should go into meta_data when data1d
+            is not a Data1D object
+        """
+
+        if extras is None:
+            extras = []
+        if names is None or names == []:
+            names = ["SASentry"]
+
+        data1d = self._check_for_empty_data(data1d)
+
+        self.base_ns = "{0}{1}{2}".format("{", \
+                            CANSAS_NS.get(self.cansas_version).get("ns"), "}")
+        tagname = ''
+        tagname_original = ''
+
+        # Go through each child in the parent element
+        for node in dom:
+            try:
+                # Get the element name and set the current names level
+                tagname = node.tag.replace(self.base_ns, "")
+                tagname_original = tagname
+                if tagname == "fitting_plug_in" or tagname == "pr_inversion" or\
+                    tagname == "invariant":
+                    continue
+                names.append(tagname)
+                children = node.getchildren()
+                if len(children) == 0:
+                    children = None
+                save_data1d = data1d
+
+                # Look for special cases
+                data1d = self._handle_special_cases(tagname, data1d, children)
+
+                # Get where to store content
+                cs_values = CONSTANTS.iterate_namespace(names)
+                # If the element is a child element, recurse
+                if children is not None:
+                    # Returned value is new Data1D object with all previous and
+                    # new values in it.
+                    data1d, extras = self._parse_entry(node,
+                                                       names, data1d, extras)
+
+                #Get the information from the node
+                node_value, _ = self._get_node_value(node, cs_values, \
+                                                            data1d, tagname)
+
+                # If appending to a dictionary (meta_data | run_name)
+                # make sure the key is unique
+                if cs_values.ns_variable == "{0}.meta_data[\"{2}\"] = \"{1}\"":
+                    # If we are within a Process, Detector, Collimation or
+                    # Aperture instance, pull out old data1d
+                    tagname = self._create_unique_key(data1d.meta_data, \
+                                                      tagname, 0)
+                    if isinstance(data1d, Data1D) == False:
+                        store_me = cs_values.ns_variable.format("data1d", \
+                                                            node_value, tagname)
+                        extras.append(store_me)
+                        cs_values.ns_variable = None
+                if cs_values.ns_variable == "{0}.run_name[\"{2}\"] = \"{1}\"":
+                    tagname = self._create_unique_key(data1d.run_name, \
+                                                      tagname, 0)
+
+                # Check for Data1D object and any extra commands to save
+                if isinstance(data1d, Data1D):
+                    for item in extras:
+                        exec item
+                # Don't bother saving empty information unless it is a float
+                if cs_values.ns_variable is not None and \
+                            node_value is not None and \
+                            node_value.isspace() == False:
+                    # Format a string and then execute it.
+                    store_me = cs_values.ns_variable.format("data1d", \
+                                                            node_value, tagname)
+                    exec store_me
+                # Get attributes and process them
+                data1d = self._handle_attributes(node, data1d, cs_values, \
+                                                 tagname)
+
+            except TypeError:
+                pass
+            except Exception as excep:
+                exc_type, exc_obj, exc_tb = sys.exc_info()
+                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
+                print(excep, exc_type, fname, exc_tb.tb_lineno, \
+                      tagname, exc_obj)
+            finally:
+                # Save special cases in original data1d object
+                # then restore the data1d
+                save_data1d = self._restore_original_case(tagname_original, \
+                                                tagname, save_data1d, data1d)
+                if tagname_original == "fitting_plug_in" or \
+                    tagname_original == "invariant" or \
+                    tagname_original == "pr_inversion":
+                    pass
+                else:
+                    data1d = save_data1d
+                    # Remove tagname from names to restore original base
+                    names.remove(tagname_original)
+        return data1d, extras
+
+    def _get_pi_string(self):
+        """
+        Creates the processing instructions header for writing to file
+        """
+        pis = self.return_processing_instructions()
+        if len(pis) > 0:
+            pi_tree = self.create_tree(pis[0])
+            i = 1
+            for i in range(1, len(pis) - 1):
+                pi_tree = self.append(pis[i], pi_tree)
+            pi_string = self.to_string(pi_tree)
+        else:
+            pi_string = ""
+        return pi_string
+
+    def _create_main_node(self):
+        """
+        Creates the primary xml header used when writing to file
+        """
+        xsi = "http://www.w3.org/2001/XMLSchema-instance"
+        version = self.cansas_version
+        n_s = CANSAS_NS.get(version).get("ns")
+        if version == "1.1":
+            url = "http://www.cansas.org/formats/1.1/"
+        else:
+            url = "http://svn.smallangles.net/svn/canSAS/1dwg/trunk/"
+        schema_location = "{0} {1}cansas1d.xsd".format(n_s, url)
+        attrib = {"{" + xsi + "}schemaLocation" : schema_location,
+                  "version" : version}
+        nsmap = {'xsi' : xsi, None: n_s}
+
+        main_node = self.create_element("{" + n_s + "}SASroot",
+                                        attrib=attrib, nsmap=nsmap)
+        return main_node
+
+    def _write_run_names(self, datainfo, entry_node):
+        """
+        Writes the run names to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+        """
+        if datainfo.run == None or datainfo.run == []:
+            datainfo.run.append(RUN_NAME_DEFAULT)
+            datainfo.run_name[RUN_NAME_DEFAULT] = RUN_NAME_DEFAULT
+        for item in datainfo.run:
+            runname = {}
+            if item in datainfo.run_name and \
+            len(str(datainfo.run_name[item])) > 1:
+                runname = {'name': datainfo.run_name[item]}
+            self.write_node(entry_node, "Run", item, runname)
+
+    def _write_data(self, datainfo, entry_node):
+        """
+        Writes the I and Q data to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+        """
+        node = self.create_element("SASdata")
+        self.append(node, entry_node)
+
+        for i in range(len(datainfo.x)):
+            point = self.create_element("Idata")
+            node.append(point)
+            self.write_node(point, "Q", datainfo.x[i],
+                            {'unit': datainfo.x_unit})
+            if len(datainfo.y) >= i:
+                self.write_node(point, "I", datainfo.y[i],
+                                {'unit': datainfo.y_unit})
+            if datainfo.dy != None and len(datainfo.dy) > i:
+                self.write_node(point, "Idev", datainfo.dy[i],
+                                {'unit': datainfo.y_unit})
+            if datainfo.dx != None and len(datainfo.dx) > i:
+                self.write_node(point, "Qdev", datainfo.dx[i],
+                                {'unit': datainfo.x_unit})
+            if datainfo.dxw != None and len(datainfo.dxw) > i:
+                self.write_node(point, "dQw", datainfo.dxw[i],
+                                {'unit': datainfo.x_unit})
+            if datainfo.dxl != None and len(datainfo.dxl) > i:
+                self.write_node(point, "dQl", datainfo.dxl[i],
+                                {'unit': datainfo.x_unit})
+
+    def _write_trans_spectrum(self, datainfo, entry_node):
+        """
+        Writes the transmission spectrum data to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+        """
+        for i in range(len(datainfo.trans_spectrum)):
+            spectrum = datainfo.trans_spectrum[i]
+            node = self.create_element("SAStransmission_spectrum",
+                                       {"name" : spectrum.name})
+            self.append(node, entry_node)
+            if isinstance(spectrum.timestamp, datetime.datetime):
+                node.setAttribute("timestamp", spectrum.timestamp)
+            for i in range(len(spectrum.wavelength)):
+                point = self.create_element("Tdata")
+                node.append(point)
+                self.write_node(point, "Lambda", spectrum.wavelength[i],
+                                {'unit': spectrum.wavelength_unit})
+                self.write_node(point, "T", spectrum.transmission[i],
+                                {'unit': spectrum.transmission_unit})
+                if spectrum.transmission_deviation != None \
+                and len(spectrum.transmission_deviation) >= i:
+                    self.write_node(point, "Tdev",
+                                    spectrum.transmission_deviation[i],
+                                    {'unit':
+                                     spectrum.transmission_deviation_unit})
+
+    def _write_sample_info(self, datainfo, entry_node):
+        """
+        Writes the sample information to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+        """
+        sample = self.create_element("SASsample")
+        if datainfo.sample.name is not None:
+            self.write_attribute(sample, "name",
+                                 str(datainfo.sample.name))
+        self.append(sample, entry_node)
+        self.write_node(sample, "ID", str(datainfo.sample.ID))
+        self.write_node(sample, "thickness", datainfo.sample.thickness,
+                        {"unit": datainfo.sample.thickness_unit})
+        self.write_node(sample, "transmission", datainfo.sample.transmission)
+        self.write_node(sample, "temperature", datainfo.sample.temperature,
+                        {"unit": datainfo.sample.temperature_unit})
+
+        pos = self.create_element("position")
+        written = self.write_node(pos,
+                                  "x",
+                                  datainfo.sample.position.x,
+                                  {"unit": datainfo.sample.position_unit})
+        written = written | self.write_node( \
+            pos, "y", datainfo.sample.position.y,
+            {"unit": datainfo.sample.position_unit})
+        written = written | self.write_node( \
+            pos, "z", datainfo.sample.position.z,
+            {"unit": datainfo.sample.position_unit})
+        if written == True:
+            self.append(pos, sample)
+
+        ori = self.create_element("orientation")
+        written = self.write_node(ori, "roll",
+                                  datainfo.sample.orientation.x,
+                                  {"unit": datainfo.sample.orientation_unit})
+        written = written | self.write_node( \
+            ori, "pitch", datainfo.sample.orientation.y,
+            {"unit": datainfo.sample.orientation_unit})
+        written = written | self.write_node( \
+            ori, "yaw", datainfo.sample.orientation.z,
+            {"unit": datainfo.sample.orientation_unit})
+        if written == True:
+            self.append(ori, sample)
+
+        for item in datainfo.sample.details:
+            self.write_node(sample, "details", item)
+
+    def _write_instrument(self, datainfo, entry_node):
+        """
+        Writes the instrumental information to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+        """
+        instr = self.create_element("SASinstrument")
+        self.append(instr, entry_node)
+        self.write_node(instr, "name", datainfo.instrument)
+        return instr
+
+    def _write_source(self, datainfo, instr):
+        """
+        Writes the source information to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param instr: instrument node  to be appended to
+        """
+        source = self.create_element("SASsource")
+        if datainfo.source.name is not None:
+            self.write_attribute(source, "name",
+                                 str(datainfo.source.name))
+        self.append(source, instr)
+        if datainfo.source.radiation == None or datainfo.source.radiation == '':
+            datainfo.source.radiation = "neutron"
+        self.write_node(source, "radiation", datainfo.source.radiation)
+
+        size = self.create_element("beam_size")
+        if datainfo.source.beam_size_name is not None:
+            self.write_attribute(size, "name",
+                                 str(datainfo.source.beam_size_name))
+        written = self.write_node( \
+            size, "x", datainfo.source.beam_size.x,
+            {"unit": datainfo.source.beam_size_unit})
+        written = written | self.write_node( \
+            size, "y", datainfo.source.beam_size.y,
+            {"unit": datainfo.source.beam_size_unit})
+        written = written | self.write_node( \
+            size, "z", datainfo.source.beam_size.z,
+            {"unit": datainfo.source.beam_size_unit})
+        if written == True:
+            self.append(size, source)
+
+        self.write_node(source, "beam_shape", datainfo.source.beam_shape)
+        self.write_node(source, "wavelength",
+                        datainfo.source.wavelength,
+                        {"unit": datainfo.source.wavelength_unit})
+        self.write_node(source, "wavelength_min",
+                        datainfo.source.wavelength_min,
+                        {"unit": datainfo.source.wavelength_min_unit})
+        self.write_node(source, "wavelength_max",
+                        datainfo.source.wavelength_max,
+                        {"unit": datainfo.source.wavelength_max_unit})
+        self.write_node(source, "wavelength_spread",
+                        datainfo.source.wavelength_spread,
+                        {"unit": datainfo.source.wavelength_spread_unit})
+
+    def _write_collimation(self, datainfo, instr):
+        """
+        Writes the collimation information to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param instr: lxml node ElementTree object to be appended to
+        """
+        if datainfo.collimation == [] or datainfo.collimation == None:
+            coll = Collimation()
+            datainfo.collimation.append(coll)
+        for item in datainfo.collimation:
+            coll = self.create_element("SAScollimation")
+            if item.name is not None:
+                self.write_attribute(coll, "name", str(item.name))
+            self.append(coll, instr)
+
+            self.write_node(coll, "length", item.length,
+                            {"unit": item.length_unit})
+
+            for aperture in item.aperture:
+                apert = self.create_element("aperture")
+                if aperture.name is not None:
+                    self.write_attribute(apert, "name", str(aperture.name))
+                if aperture.type is not None:
+                    self.write_attribute(apert, "type", str(aperture.type))
+                self.append(apert, coll)
+
+                size = self.create_element("size")
+                if aperture.size_name is not None:
+                    self.write_attribute(size, "name",
+                                         str(aperture.size_name))
+                written = self.write_node(size, "x", aperture.size.x,
+                                          {"unit": aperture.size_unit})
+                written = written | self.write_node( \
+                    size, "y", aperture.size.y,
+                    {"unit": aperture.size_unit})
+                written = written | self.write_node( \
+                    size, "z", aperture.size.z,
+                    {"unit": aperture.size_unit})
+                if written == True:
+                    self.append(size, apert)
+
+                self.write_node(apert, "distance", aperture.distance,
+                                {"unit": aperture.distance_unit})
+
+    def _write_detectors(self, datainfo, instr):
+        """
+        Writes the detector information to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param inst: lxml instrument node to be appended to
+        """
+        if datainfo.detector == None or datainfo.detector == []:
+            det = Detector()
+            det.name = ""
+            datainfo.detector.append(det)
+
+        for item in datainfo.detector:
+            det = self.create_element("SASdetector")
+            written = self.write_node(det, "name", item.name)
+            written = written | self.write_node(det, "SDD", item.distance,
+                                                {"unit": item.distance_unit})
+            if written == True:
+                self.append(det, instr)
+
+            off = self.create_element("offset")
+            written = self.write_node(off, "x", item.offset.x,
+                                      {"unit": item.offset_unit})
+            written = written | self.write_node(off, "y", item.offset.y,
+                                                {"unit": item.offset_unit})
+            written = written | self.write_node(off, "z", item.offset.z,
+                                                {"unit": item.offset_unit})
+            if written == True:
+                self.append(off, det)
+
+            ori = self.create_element("orientation")
+            written = self.write_node(ori, "roll", item.orientation.x,
+                                      {"unit": item.orientation_unit})
+            written = written | self.write_node(ori, "pitch",
+                                                item.orientation.y,
+                                                {"unit": item.orientation_unit})
+            written = written | self.write_node(ori, "yaw",
+                                                item.orientation.z,
+                                                {"unit": item.orientation_unit})
+            if written == True:
+                self.append(ori, det)
+
+            center = self.create_element("beam_center")
+            written = self.write_node(center, "x", item.beam_center.x,
+                                      {"unit": item.beam_center_unit})
+            written = written | self.write_node(center, "y",
+                                                item.beam_center.y,
+                                                {"unit": item.beam_center_unit})
+            written = written | self.write_node(center, "z",
+                                                item.beam_center.z,
+                                                {"unit": item.beam_center_unit})
+            if written == True:
+                self.append(center, det)
+
+            pix = self.create_element("pixel_size")
+            written = self.write_node(pix, "x", item.pixel_size.x,
+                                      {"unit": item.pixel_size_unit})
+            written = written | self.write_node(pix, "y", item.pixel_size.y,
+                                                {"unit": item.pixel_size_unit})
+            written = written | self.write_node(pix, "z", item.pixel_size.z,
+                                                {"unit": item.pixel_size_unit})
+            written = written | self.write_node(det, "slit_length",
+                                                item.slit_length,
+                                                {"unit": item.slit_length_unit})
+            if written == True:
+                self.append(pix, det)
+
+    def _write_process_notes(self, datainfo, entry_node):
+        """
+        Writes the process notes to the XML file
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+
+        """
+        for item in datainfo.process:
+            node = self.create_element("SASprocess")
+            self.append(node, entry_node)
+            self.write_node(node, "name", item.name)
+            self.write_node(node, "date", item.date)
+            self.write_node(node, "description", item.description)
+            for term in item.term:
+                value = term['value']
+                del term['value']
+                self.write_node(node, "term", value, term)
+            for note in item.notes:
+                self.write_node(node, "SASprocessnote", note)
+            if len(item.notes) == 0:
+                self.write_node(node, "SASprocessnote", "")
+
+    def _write_notes(self, datainfo, entry_node):
+        """
+        Writes the notes to the XML file and creates an empty note if none
+        exist
+
+        :param datainfo: The Data1D object the information is coming from
+        :param entry_node: lxml node ElementTree object to be appended to
+
+        """
+        if len(datainfo.notes) == 0:
+            node = self.create_element("SASnote")
+            self.append(node, entry_node)
+        else:
+            for item in datainfo.notes:
+                node = self.create_element("SASnote")
+                self.write_text(node, item)
+                self.append(node, entry_node)
+
+    def _check_origin(self, entry_node, doc, frm):
+        """
+        Return the document, and the SASentry node associated with
+        the data we just wrote.
+        If the calling function was not the cansas reader, return a minidom
+        object rather than an lxml object.
+
+        :param entry_node: lxml node ElementTree object to be appended to
+        :param doc: entire xml tree
+        """
+        if not frm:
+            frm = inspect.stack()[1]
+        mod_name = frm[1].replace("\\", "/").replace(".pyc", "")
+        mod_name = mod_name.replace(".py", "")
+        mod = mod_name.split("sas/")
+        mod_name = mod[1]
+        if mod_name != "dataloader/readers/cansas_reader":
+            string = self.to_string(doc, pretty_print=False)
+            doc = parseString(string)
+            node_name = entry_node.tag
+            node_list = doc.getElementsByTagName(node_name)
+            entry_node = node_list.item(0)
+        return doc, entry_node
+
+    def _to_xml_doc(self, datainfo):
+        """
+        Create an XML document to contain the content of a Data1D
+
+        :param datainfo: Data1D object
+        """
+        if not issubclass(datainfo.__class__, Data1D):
+            raise RuntimeError, "The cansas writer expects a Data1D instance"
+
+        # Get PIs and create root element
+        pi_string = self._get_pi_string()
+        # Define namespaces and create SASroot object
+        main_node = self._create_main_node()
+        # Create ElementTree, append SASroot and apply processing instructions
+        base_string = pi_string + self.to_string(main_node)
+        base_element = self.create_element_from_string(base_string)
+        doc = self.create_tree(base_element)
+        # Create SASentry Element
+        entry_node = self.create_element("SASentry")
+        root = doc.getroot()
+        root.append(entry_node)
+
+        # Add Title to SASentry
+        self.write_node(entry_node, "Title", datainfo.title)
+        # Add Run to SASentry
+        self._write_run_names(datainfo, entry_node)
+        # Add Data info to SASEntry
+        self._write_data(datainfo, entry_node)
+        # Transmission Spectrum Info
+        self._write_trans_spectrum(datainfo, entry_node)
+        # Sample info
+        self._write_sample_info(datainfo, entry_node)
+        # Instrument info
+        instr = self._write_instrument(datainfo, entry_node)
+        #   Source
+        self._write_source(datainfo, instr)
+        #   Collimation
+        self._write_collimation(datainfo, instr)
+        #   Detectors
+        self._write_detectors(datainfo, instr)
+        # Processes info
+        self._write_process_notes(datainfo, entry_node)
+        # Note info
+        self._write_notes(datainfo, entry_node)
+        # Return the document, and the SASentry node associated with
+        #      the data we just wrote
+        # If the calling function was not the cansas reader, return a minidom
+        #      object rather than an lxml object.
+        frm = inspect.stack()[1]
+        doc, entry_node = self._check_origin(entry_node, doc, frm)
+        return doc, entry_node
+
+    def write_node(self, parent, name, value, attr=None):
+        """
+        :param doc: document DOM
+        :param parent: parent node
+        :param name: tag of the element
+        :param value: value of the child text node
+        :param attr: attribute dictionary
+
+        :return: True if something was appended, otherwise False
+        """
+        if value is not None:
+            parent = self.ebuilder(parent, name, value, attr)
+            return True
+        return False
+
+    def write(self, filename, datainfo):
+        """
+        Write the content of a Data1D as a CanSAS XML file
+
+        :param filename: name of the file to write
+        :param datainfo: Data1D object
+        """
+        # Create XML document
+        doc, _ = self._to_xml_doc(datainfo)
+        # Write the file
+        file_ref = open(filename, 'w')
+        if self.encoding == None:
+            self.encoding = "UTF-8"
+        doc.write(file_ref, encoding=self.encoding,
+                  pretty_print=True, xml_declaration=True)
+        file_ref.close()
+
+    # DO NOT REMOVE - used in saving and loading panel states.
+    def _store_float(self, location, node, variable, storage, optional=True):
+        """
+        Get the content of a xpath location and store
+        the result. Check that the units are compatible
+        with the destination. The value is expected to
+        be a float.
+
+        The xpath location might or might not exist.
+        If it does not exist, nothing is done
+
+        :param location: xpath location to fetch
+        :param node: node to read the data from
+        :param variable: name of the data member to store it in [string]
+        :param storage: data object that has the 'variable' data member
+        :param optional: if True, no exception will be raised
+            if unit conversion can't be done
+
+        :raise ValueError: raised when the units are not recognized
+        """
+        entry = get_content(location, node)
+        try:
+            value = float(entry.text)
+        except:
+            value = None
+
+        if value is not None:
+            # If the entry has units, check to see that they are
+            # compatible with what we currently have in the data object
+            units = entry.get('unit')
+            if units is not None:
+                toks = variable.split('.')
+                local_unit = None
+                exec "local_unit = storage.%s_unit" % toks[0]
+                if local_unit != None and units.lower() != local_unit.lower():
+                    if HAS_CONVERTER == True:
+                        try:
+                            conv = Converter(units)
+                            exec "storage.%s = %g" % \
+                                (variable, conv(value, units=local_unit))
+                        except:
+                            _, exc_value, _ = sys.exc_info()
+                            err_mess = "CanSAS reader: could not convert"
+                            err_mess += " %s unit [%s]; expecting [%s]\n  %s" \
+                                % (variable, units, local_unit, exc_value)
+                            self.errors.add(err_mess)
+                            if optional:
+                                logging.info(err_mess)
+                            else:
+                                raise ValueError, err_mess
+                    else:
+                        err_mess = "CanSAS reader: unrecognized %s unit [%s];"\
+                        % (variable, units)
+                        err_mess += " expecting [%s]" % local_unit
+                        self.errors.add(err_mess)
+                        if optional:
+                            logging.info(err_mess)
+                        else:
+                            raise ValueError, err_mess
+                else:
+                    exec "storage.%s = value" % variable
+            else:
+                exec "storage.%s = value" % variable
+
+    # DO NOT REMOVE - used in saving and loading panel states.
+    def _store_content(self, location, node, variable, storage):
+        """
+        Get the content of a xpath location and store
+        the result. The value is treated as a string.
+
+        The xpath location might or might not exist.
+        If it does not exist, nothing is done
+
+        :param location: xpath location to fetch
+        :param node: node to read the data from
+        :param variable: name of the data member to store it in [string]
+        :param storage: data object that has the 'variable' data member
+
+        :return: return a list of errors
+        """
+        entry = get_content(location, node)
+        if entry is not None and entry.text is not None:
+            exec "storage.%s = entry.text.strip()" % variable
Index: src/sas/sascalc/dataloader/readers/danse_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/danse_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/danse_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,280 @@
+"""
+    DANSE/SANS file reader
+"""
+############################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#If you use DANSE applications to do scientific research that leads to
+#publication, we ask that you acknowledge the use of the software with the
+#following sentence:
+#This work benefited from DANSE software developed under NSF award DMR-0520547.
+#copyright 2008, University of Tennessee
+#############################################################################
+import math
+import os
+import sys
+import numpy
+import logging
+from sas.sascalc.dataloader.data_info import Data2D, Detector
+from sas.sascalc.dataloader.manipulations import reader2D_converter
+
+# Look for unit converter
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+
+
+class Reader:
+    """
+    Example data manipulation
+    """
+    ## File type
+    type_name = "DANSE"
+    ## Wildcards
+    type = ["DANSE files (*.sans)|*.sans"]
+    ## Extension
+    ext  = ['.sans', '.SANS']
+        
+    def read(self, filename=None):
+        """
+        Open and read the data in a file
+        @param file: path of the file
+        """
+        
+        read_it = False
+        for item in self.ext:
+            if filename.lower().find(item) >= 0:
+                read_it = True
+                
+        if read_it:
+            try:
+                datafile = open(filename, 'r')
+            except:
+                raise  RuntimeError,"danse_reader cannot open %s" % (filename)
+        
+            # defaults
+            # wavelength in Angstrom
+            wavelength = 10.0
+            # Distance in meter
+            distance   = 11.0
+            # Pixel number of center in x
+            center_x   = 65
+            # Pixel number of center in y
+            center_y   = 65
+            # Pixel size [mm]
+            pixel      = 5.0
+            # Size in x, in pixels
+            size_x     = 128
+            # Size in y, in pixels
+            size_y     = 128
+            # Format version
+            fversion   = 1.0
+            
+            output = Data2D()
+            output.filename = os.path.basename(filename)
+            detector = Detector()
+            output.detector.append(detector)
+            
+            output.data = numpy.zeros([size_x,size_y])
+            output.err_data = numpy.zeros([size_x, size_y])
+            
+            data_conv_q = None
+            data_conv_i = None
+            
+            if has_converter == True and output.Q_unit != '1/A':
+                data_conv_q = Converter('1/A')
+                # Test it
+                data_conv_q(1.0, output.Q_unit)
+                
+            if has_converter == True and output.I_unit != '1/cm':
+                data_conv_i = Converter('1/cm')
+                # Test it
+                data_conv_i(1.0, output.I_unit)
+        
+            read_on = True
+            while read_on:
+                line = datafile.readline()
+                if line.find("DATA:") >= 0:
+                    read_on = False
+                    break
+                toks = line.split(':')
+                if toks[0] == "FORMATVERSION":
+                    fversion = float(toks[1])
+                if toks[0] == "WAVELENGTH":
+                    wavelength = float(toks[1])
+                elif toks[0] == "DISTANCE":
+                    distance = float(toks[1])
+                elif toks[0] == "CENTER_X":
+                    center_x = float(toks[1])
+                elif toks[0] == "CENTER_Y":
+                    center_y = float(toks[1])
+                elif toks[0] == "PIXELSIZE":
+                    pixel = float(toks[1])
+                elif toks[0] == "SIZE_X":
+                    size_x = int(toks[1])
+                elif toks[0] == "SIZE_Y":
+                    size_y = int(toks[1])
+            
+            # Read the data
+            data = []
+            error = []
+            if fversion == 1.0:
+                data_str = datafile.readline()
+                data = data_str.split(' ')
+            else:
+                read_on = True
+                while read_on:
+                    data_str = datafile.readline()
+                    if len(data_str) == 0:
+                        read_on = False
+                    else:
+                        toks = data_str.split()
+                        try:
+                            val = float(toks[0])
+                            err = float(toks[1])
+                            if data_conv_i is not None:
+                                val = data_conv_i(val, units=output._yunit)
+                                err = data_conv_i(err, units=output._yunit)
+                            data.append(val)
+                            error.append(err)
+                        except:
+                            logging.info("Skipping line:%s,%s" %(data_str,
+                                                                sys.exc_value))
+            
+            # Initialize
+            x_vals = []
+            y_vals = []
+            ymin = None
+            ymax = None
+            xmin = None
+            xmax = None
+            
+            # Qx and Qy vectors
+            theta = pixel / distance / 100.0
+            stepq = 4.0 * math.pi / wavelength * math.sin(theta / 2.0)
+            for i_x in range(size_x):
+                theta = (i_x - center_x + 1) * pixel / distance / 100.0
+                qx = 4.0 * math.pi / wavelength * math.sin(theta / 2.0)
+                
+                if has_converter == True and output.Q_unit != '1/A':
+                    qx = data_conv_q(qx, units=output.Q_unit)
+                
+                x_vals.append(qx)
+                if xmin == None or qx < xmin:
+                    xmin = qx
+                if xmax == None or qx > xmax:
+                    xmax = qx
+            
+            ymin = None
+            ymax = None
+            for i_y in range(size_y):
+                theta = (i_y - center_y + 1) * pixel / distance / 100.0
+                qy = 4.0 * math.pi / wavelength * math.sin(theta/2.0)
+                
+                if has_converter == True and output.Q_unit != '1/A':
+                    qy = data_conv_q(qy, units=output.Q_unit)
+                
+                y_vals.append(qy)
+                if ymin == None or qy < ymin:
+                    ymin = qy
+                if ymax == None or qy > ymax:
+                    ymax = qy
+            
+            # Store the data in the 2D array
+            i_x = 0
+            i_y = -1
+            
+            for i_pt in range(len(data)):
+                try:
+                    value = float(data[i_pt])
+                except:
+                    # For version 1.0, the data were still
+                    # stored as strings at this point.
+                    msg = "Skipping entry (v1.0):%s,%s" % (str(data[i_pt]),
+                                                           sys.exc_value)
+                    logging.info(msg)
+                
+                # Get bin number
+                if math.fmod(i_pt, size_x) == 0:
+                    i_x = 0
+                    i_y += 1
+                else:
+                    i_x += 1
+                    
+                output.data[i_y][i_x] = value
+                if fversion>1.0:
+                    output.err_data[i_y][i_x] = error[i_pt]
+                
+            # Store all data
+            # Store wavelength
+            if has_converter == True and output.source.wavelength_unit != 'A':
+                conv = Converter('A')
+                wavelength = conv(wavelength,
+                                  units=output.source.wavelength_unit)
+            output.source.wavelength = wavelength
+                
+            # Store distance
+            if has_converter == True and detector.distance_unit != 'm':
+                conv = Converter('m')
+                distance = conv(distance, units=detector.distance_unit)
+            detector.distance = distance
+            
+            # Store pixel size
+            if has_converter == True and detector.pixel_size_unit != 'mm':
+                conv = Converter('mm')
+                pixel = conv(pixel, units=detector.pixel_size_unit)
+            detector.pixel_size.x = pixel
+            detector.pixel_size.y = pixel
+
+            # Store beam center in distance units
+            detector.beam_center.x = center_x * pixel
+            detector.beam_center.y = center_y * pixel
+            
+            # Store limits of the image (2D array)
+            xmin = xmin - stepq / 2.0
+            xmax = xmax + stepq / 2.0
+            ymin = ymin - stepq /2.0
+            ymax = ymax + stepq / 2.0
+            
+            if has_converter == True and output.Q_unit != '1/A':
+                xmin = data_conv_q(xmin, units=output.Q_unit)
+                xmax = data_conv_q(xmax, units=output.Q_unit)
+                ymin = data_conv_q(ymin, units=output.Q_unit)
+                ymax = data_conv_q(ymax, units=output.Q_unit)
+            output.xmin = xmin
+            output.xmax = xmax
+            output.ymin = ymin
+            output.ymax = ymax
+            
+            # Store x and y axis bin centers
+            output.x_bins = x_vals
+            output.y_bins = y_vals
+           
+            # Units
+            if data_conv_q is not None:
+                output.xaxis("\\rm{Q_{x}}", output.Q_unit)
+                output.yaxis("\\rm{Q_{y}}", output.Q_unit)
+            else:
+                output.xaxis("\\rm{Q_{x}}", 'A^{-1}')
+                output.yaxis("\\rm{Q_{y}}", 'A^{-1}')
+                
+            if data_conv_i is not None:
+                output.zaxis("\\rm{Intensity}", output.I_unit)
+            else:
+                output.zaxis("\\rm{Intensity}", "cm^{-1}")
+           
+            if not fversion >= 1.0:
+                msg = "Danse_reader can't read this file %s" % filename
+                raise ValueError, msg
+            else:
+                logging.info("Danse_reader Reading %s \n" % filename)
+            
+            # Store loading process information
+            output.meta_data['loader'] = self.type_name
+            output = reader2D_converter(output)
+            return output
+        
+        return None
Index: src/sas/sascalc/dataloader/readers/defaults.json
===================================================================
--- src/sas/sascalc/dataloader/readers/defaults.json	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/defaults.json	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,42 @@
+{
+   "SasLoader":{
+      "FileType":[
+         {
+            "-extension":".xml",
+            "-reader":"cansas_reader"
+         },
+         {
+            "-extension":".ses",
+            "-reader":"sesans_reader"
+         },
+         {
+            "-extension":".txt",
+            "-reader":"ascii_reader"
+         },
+         {
+            "-extension":".asc",
+            "-reader":"IgorReader"
+         },
+         {
+            "-extension":".dat",
+            "-reader":"red2d_reader"
+         },
+         {
+            "-extension":".abs",
+            "-reader":"abs_reader"
+         },
+         {
+            "-extension":".d1d",
+            "-reader":"hfir1d_reader"
+         },
+         {
+            "-extension":".sans",
+            "-reader":"danse_reader"
+         },
+         {
+            "-extension":".nxs",
+            "-reader":"nexus_reader"
+         }
+      ]
+   }
+}
Index: src/sas/sascalc/dataloader/readers/hfir1d_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/hfir1d_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/hfir1d_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,129 @@
+"""
+    HFIR 1D 4-column data reader
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+import numpy
+import os
+from sas.sascalc.dataloader.data_info import Data1D
+
+# Check whether we have a converter available
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+
+class Reader(object):
+    """
+    Class to load HFIR 1D 4-column files
+    """
+    ## File type
+    type_name = "HFIR 1D"
+    ## Wildcards
+    type = ["HFIR 1D files (*.d1d)|*.d1d"]
+    ## List of allowed extensions
+    ext = ['.d1d']
+    
+    def read(self, path):
+        """ 
+        Load data file
+        
+        :param path: file path
+        
+        :return: Data1D object, or None
+        
+        :raise RuntimeError: when the file can't be opened
+        :raise ValueError: when the length of the data vectors are inconsistent
+        """
+        if os.path.isfile(path):
+            basename = os.path.basename(path)
+            root, extension = os.path.splitext(basename)
+            if extension.lower() in self.ext:
+                try:
+                    input_f = open(path,'r')
+                except:
+                    raise  RuntimeError, "hfir1d_reader: cannot open %s" % path
+                buff = input_f.read()
+                lines = buff.split('\n')
+                x = numpy.zeros(0)
+                y = numpy.zeros(0)
+                dx = numpy.zeros(0)
+                dy = numpy.zeros(0)
+                output = Data1D(x, y, dx=dx, dy=dy)
+                self.filename = output.filename = basename
+           
+                data_conv_q = None
+                data_conv_i = None
+                
+                if has_converter == True and output.x_unit != '1/A':
+                    data_conv_q = Converter('1/A')
+                    # Test it
+                    data_conv_q(1.0, output.x_unit)
+                    
+                if has_converter == True and output.y_unit != '1/cm':
+                    data_conv_i = Converter('1/cm')
+                    # Test it
+                    data_conv_i(1.0, output.y_unit)
+                           
+                for line in lines:
+                    toks = line.split()
+                    try:
+                        _x = float(toks[0])
+                        _y = float(toks[1])
+                        _dx = float(toks[3])
+                        _dy = float(toks[2])
+                        
+                        if data_conv_q is not None:
+                            _x = data_conv_q(_x, units=output.x_unit)
+                            _dx = data_conv_q(_dx, units=output.x_unit)
+                            
+                        if data_conv_i is not None:
+                            _y = data_conv_i(_y, units=output.y_unit)
+                            _dy = data_conv_i(_dy, units=output.y_unit)
+                                                    
+                        x = numpy.append(x, _x)
+                        y = numpy.append(y, _y)
+                        dx = numpy.append(dx, _dx)
+                        dy = numpy.append(dy, _dy)
+                    except:
+                        # Couldn't parse this line, skip it 
+                        pass
+                         
+                # Sanity check
+                if not len(y) == len(dy):
+                    msg = "hfir1d_reader: y and dy have different length"
+                    raise RuntimeError, msg
+                if not len(x) == len(dx):
+                    msg = "hfir1d_reader: x and dx have different length"
+                    raise RuntimeError, msg
+
+                # If the data length is zero, consider this as
+                # though we were not able to read the file.
+                if len(x) == 0:
+                    raise RuntimeError, "hfir1d_reader: could not load file"
+               
+                output.x = x
+                output.y = y
+                output.dy = dy
+                output.dx = dx
+                if data_conv_q is not None:
+                    output.xaxis("\\rm{Q}", output.x_unit)
+                else:
+                    output.xaxis("\\rm{Q}", 'A^{-1}')
+                if data_conv_i is not None:
+                    output.yaxis("\\rm{Intensity}", output.y_unit)
+                else:
+                    output.yaxis("\\rm{Intensity}", "cm^{-1}")
+                
+                # Store loading process information
+                output.meta_data['loader'] = self.type_name
+                return output
+        else:
+            raise RuntimeError, "%s is not a file" % path
+        return None
Index: src/sas/sascalc/dataloader/readers/nexus_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/nexus_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/nexus_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,96 @@
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#See the license text in license.txt
+#copyright 2012, University of Tennessee
+######################################################################
+"""
+    Nexus reader for 2D data reduced by Mantid
+"""
+import os
+from sas.sascalc.dataloader.data_info import Data2D
+
+
+class Reader:
+    """
+        Nexus reader for 2D data reduced by Mantid
+    """
+    ## File type
+    type_name = "NXS"
+    ## Wildcards
+    type = ["Nexus files (*.nxs)|*.nxs"]
+    ## Extension
+    ext = ['.nxs']
+        
+    def read(self, filename=None):
+        """
+            Open and read the data in a file
+        
+            :param filename: path of the file
+        """
+        try:
+            import nxs
+        except:
+            msg = "Error reading Nexus file: Nexus package is missing.\n"
+            msg += "  Get it from http://http://www.nexusformat.org/"
+            raise RuntimeError, msg
+        
+        # Instantiate data object
+        output = Data2D()
+        output.filename = os.path.basename(filename)
+
+        fd = nxs.open(filename, 'rw')
+        
+        # Read in the 2D data
+        fd.opengroup('mantid_workspace_1')
+        fd.opengroup('workspace')
+        fd.opendata('values')
+        output.data = fd.getdata().copy()
+        fd.closedata()
+        
+        # Read in the errors
+        fd.opendata('errors')
+        output.err_data = fd.getdata().copy()
+        fd.closedata()
+        
+        # Read in the values on each axis
+        fd.opendata('axis1')
+        output.x_bins = fd.getdata().copy()
+        fd.closedata()
+        
+        fd.opendata('axis2')
+        output.y_bins = fd.getdata().copy()
+        fd.closedata()
+        fd.closegroup()
+                
+        output.xmin = min(output.x_bins)
+        output.xmax = max(output.x_bins)
+        output.ymin = min(output.y_bins)
+        output.ymax = max(output.y_bins)
+        
+        output.xaxis("\\rm{Q_{x}}", 'A^{-1}')
+        output.yaxis("\\rm{Q_{y}}", 'A^{-1}')
+        output.zaxis("\\rm{Intensity}", "cm^{-1}")
+
+        # Meta data
+        fd.opendata('title')
+        output.title = fd.getdata()
+        fd.closedata()
+        
+        fd.opengroup('instrument')
+        fd.opendata('name')
+        output.instrument = fd.getdata()
+        fd.closedata()
+        fd.closegroup()
+
+        fd.opengroup('logs')
+        fd.opengroup('run_number')
+        fd.opendata('value')
+        output.run = fd.getdata()
+
+        fd.close()
+
+        # Store loading process information
+        output.meta_data['loader'] = self.type_name
+        return output
Index: src/sas/sascalc/dataloader/readers/red2d_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/red2d_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/red2d_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,367 @@
+"""
+    TXT/IGOR 2D Q Map file reader
+"""
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+import os
+import numpy
+import math
+from sas.sascalc.dataloader.data_info import Data2D, Detector
+
+# Look for unit converter
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+    
+    
+def check_point(x_point):
+    """
+    check point validity
+    """
+    # set zero for non_floats
+    try:
+        return float(x_point)
+    except:
+        return 0
+      
+        
+class Reader:
+    """ Simple data reader for Igor data files """
+    ## File type
+    type_name = "IGOR/DAT 2D Q_map"
+    ## Wildcards
+    type = ["IGOR/DAT 2D file in Q_map (*.dat)|*.DAT"]
+    ## Extension
+    ext = ['.DAT', '.dat']
+    
+    def write(self, filename, data):
+        """
+        Write to .dat
+        
+        :param filename: file name to write
+        :param data: data2D
+        """
+        import time
+        # Write the file
+        fd = open(filename, 'w')
+        t = time.localtime()
+        time_str = time.strftime("%H:%M on %b %d %y", t)
+        
+        header_str = "Data columns are Qx - Qy - I(Qx,Qy)\n\nASCII data"
+        header_str += " created at %s \n\n" % time_str
+        # simple 2D header
+        fd.write(header_str)
+        # write qx qy I values
+        for i in range(len(data.data)):
+            fd.write("%g  %g  %g\n" % (data.qx_data[i],
+                                        data.qy_data[i],
+                                       data.data[i]))
+        # close
+        fd.close()
+
+    def read(self, filename=None):
+        """ Read file """
+        if not os.path.isfile(filename):
+            raise ValueError, \
+            "Specified file %s is not a regular file" % filename
+
+        # Read file
+        f = open(filename, 'r')
+        buf = f.read()
+        f.close()
+        # Instantiate data object
+        output = Data2D()
+        output.filename = os.path.basename(filename)
+        detector = Detector()
+        if len(output.detector) > 0:
+            print str(output.detector[0])
+        output.detector.append(detector)
+                
+        # Get content
+        dataStarted = False
+        
+        ## Defaults
+        lines = buf.split('\n')
+        x = []
+        y = []
+        
+        wavelength = None
+        distance = None
+        transmission = None
+        
+        pixel_x = None
+        pixel_y = None
+        
+        isInfo = False
+        isCenter = False
+
+        data_conv_q = None
+        data_conv_i = None
+        
+        # Set units: This is the unit assumed for Q and I in the data file.
+        if has_converter == True and output.Q_unit != '1/A':
+            data_conv_q = Converter('1/A')
+            # Test it
+            data_conv_q(1.0, output.Q_unit)
+            
+        if has_converter == True and output.I_unit != '1/cm':
+            data_conv_i = Converter('1/cm')
+            # Test it
+            data_conv_i(1.0, output.I_unit)
+        
+              
+        # Remove the last lines before the for loop if the lines are empty
+        # to calculate the exact number of data points
+        count = 0
+        while (len(lines[len(lines) - (count + 1)].lstrip().rstrip()) < 1):
+            del lines[len(lines) - (count + 1)]
+            count = count + 1
+
+        #Read Header and find the dimensions of 2D data
+        line_num = 0
+        # Old version NIST files: 0
+        ver = 0
+        for line in lines:
+            line_num += 1
+            ## Reading the header applies only to IGOR/NIST 2D q_map data files
+            # Find setup info line
+            if isInfo:
+                isInfo = False
+                line_toks = line.split()
+                # Wavelength in Angstrom
+                try:
+                    wavelength = float(line_toks[1])
+                    # Units
+                    if has_converter == True and \
+                    output.source.wavelength_unit != 'A':
+                        conv = Converter('A')
+                        wavelength = conv(wavelength,
+                                          units=output.source.wavelength_unit)
+                except:
+                    #Not required
+                    pass
+                # Distance in mm
+                try:
+                    distance = float(line_toks[3])
+                    # Units
+                    if has_converter == True and detector.distance_unit != 'm':
+                        conv = Converter('m')
+                        distance = conv(distance, units=detector.distance_unit)
+                except:
+                    #Not required
+                    pass
+                
+                # Distance in meters
+                try:
+                    transmission = float(line_toks[4])
+                except:
+                    #Not required
+                    pass
+                                            
+            if line.count("LAMBDA") > 0:
+                isInfo = True
+                
+            # Find center info line
+            if isCenter:
+                isCenter = False
+                line_toks = line.split()
+                # Center in bin number
+                center_x = float(line_toks[0])
+                center_y = float(line_toks[1])
+
+            if line.count("BCENT") > 0:
+                isCenter = True
+            # Check version
+            if line.count("Data columns") > 0:
+                if line.count("err(I)") > 0:
+                    ver = 1
+            # Find data start
+            if line.count("ASCII data") > 0:
+                dataStarted = True
+                continue
+
+            ## Read and get data.
+            if dataStarted == True:
+                line_toks = line.split()
+                if len(line_toks) == 0:
+                    #empty line
+                    continue
+                # the number of columns must be stayed same 
+                col_num = len(line_toks)
+                break
+        # Make numpy array to remove header lines using index
+        lines_array = numpy.array(lines)
+
+        # index for lines_array
+        lines_index = numpy.arange(len(lines))
+        
+        # get the data lines
+        data_lines = lines_array[lines_index >= (line_num - 1)]
+        # Now we get the total number of rows (i.e., # of data points)
+        row_num = len(data_lines)
+        # make it as list again to control the separators
+        data_list = " ".join(data_lines.tolist())
+        # split all data to one big list w/" "separator
+        data_list = data_list.split()
+ 
+        # Check if the size is consistent with data, otherwise
+        #try the tab(\t) separator
+        # (this may be removed once get the confidence
+        #the former working all cases).
+        if len(data_list) != (len(data_lines)) * col_num:
+            data_list = "\t".join(data_lines.tolist())
+            data_list = data_list.split()
+
+        # Change it(string) into float
+        #data_list = map(float,data_list)
+        data_list1 = map(check_point, data_list)
+
+        # numpy array form
+        data_array = numpy.array(data_list1)
+        # Redimesion based on the row_num and col_num,
+        #otherwise raise an error.
+        try:
+            data_point = data_array.reshape(row_num, col_num).transpose()
+        except:
+            msg = "red2d_reader: Can't read this file: Not a proper file format"
+            raise ValueError, msg
+        ## Get the all data: Let's HARDcoding; Todo find better way
+        # Defaults
+        dqx_data = numpy.zeros(0)
+        dqy_data = numpy.zeros(0)
+        err_data = numpy.ones(row_num)
+        qz_data = numpy.zeros(row_num)
+        mask = numpy.ones(row_num, dtype=bool)
+        # Get from the array
+        qx_data = data_point[0]
+        qy_data = data_point[1]
+        data = data_point[2]
+        if ver == 1:
+            if col_num > (2 + ver):
+                err_data = data_point[(2 + ver)]
+        if col_num > (3 + ver):
+            qz_data = data_point[(3 + ver)]
+        if col_num > (4 + ver):
+            dqx_data = data_point[(4 + ver)]
+        if col_num > (5 + ver):
+            dqy_data = data_point[(5 + ver)]
+        #if col_num > (6 + ver): mask[data_point[(6 + ver)] < 1] = False
+        q_data = numpy.sqrt(qx_data*qx_data+qy_data*qy_data+qz_data*qz_data)
+           
+        # Extra protection(it is needed for some data files): 
+        # If all mask elements are False, put all True
+        if not mask.any():
+            mask[mask == False] = True
+  
+        # Store limits of the image in q space
+        xmin = numpy.min(qx_data)
+        xmax = numpy.max(qx_data)
+        ymin = numpy.min(qy_data)
+        ymax = numpy.max(qy_data)
+
+        # units
+        if has_converter == True and output.Q_unit != '1/A':
+            xmin = data_conv_q(xmin, units=output.Q_unit)
+            xmax = data_conv_q(xmax, units=output.Q_unit)
+            ymin = data_conv_q(ymin, units=output.Q_unit)
+            ymax = data_conv_q(ymax, units=output.Q_unit)
+            
+        ## calculate the range of the qx and qy_data
+        x_size = math.fabs(xmax - xmin)
+        y_size = math.fabs(ymax - ymin)
+        
+        # calculate the number of pixels in the each axes
+        npix_y = math.floor(math.sqrt(len(data)))
+        npix_x = math.floor(len(data) / npix_y)
+        
+        # calculate the size of bins
+        xstep = x_size / (npix_x - 1)
+        ystep = y_size / (npix_y - 1)
+        
+        # store x and y axis bin centers in q space
+        x_bins = numpy.arange(xmin, xmax + xstep, xstep)
+        y_bins = numpy.arange(ymin, ymax + ystep, ystep)
+       
+        # get the limits of q values
+        xmin = xmin - xstep / 2
+        xmax = xmax + xstep / 2
+        ymin = ymin - ystep / 2
+        ymax = ymax + ystep / 2
+        
+        #Store data in outputs
+        #TODO: Check the lengths
+        output.data = data
+        if (err_data == 1).all():
+            output.err_data = numpy.sqrt(numpy.abs(data))
+            output.err_data[output.err_data == 0.0] = 1.0
+        else:
+            output.err_data = err_data
+            
+        output.qx_data = qx_data
+        output.qy_data = qy_data
+        output.q_data = q_data
+        output.mask = mask
+        
+        output.x_bins = x_bins
+        output.y_bins = y_bins
+               
+        output.xmin = xmin
+        output.xmax = xmax
+        output.ymin = ymin
+        output.ymax = ymax
+        
+        output.source.wavelength = wavelength
+        
+        # Store pixel size in mm
+        detector.pixel_size.x = pixel_x
+        detector.pixel_size.y = pixel_y
+        
+        # Store the sample to detector distance
+        detector.distance = distance
+        
+        # optional data: if all of dq data == 0, do not pass to output
+        if len(dqx_data) == len(qx_data) and dqx_data.any() != 0:
+            # if no dqx_data, do not pass dqy_data.
+            #(1 axis dq is not supported yet).
+            if len(dqy_data) == len(qy_data) and dqy_data.any() != 0:
+                # Currently we do not support dq parr, perp.
+                # tranfer the comp. to cartesian coord. for newer version.
+                if ver != 1:
+                    diag = numpy.sqrt(qx_data * qx_data + qy_data * qy_data)
+                    cos_th = qx_data / diag
+                    sin_th = qy_data / diag
+                    output.dqx_data = numpy.sqrt((dqx_data * cos_th) * \
+                                                 (dqx_data * cos_th) \
+                                                 + (dqy_data * sin_th) * \
+                                                  (dqy_data * sin_th))
+                    output.dqy_data = numpy.sqrt((dqx_data * sin_th) * \
+                                                 (dqx_data * sin_th) \
+                                                 + (dqy_data * cos_th) * \
+                                                  (dqy_data * cos_th))
+                else:
+                    output.dqx_data = dqx_data
+                    output.dqy_data = dqy_data
+
+        # Units of axes
+        if data_conv_q is not None:
+            output.xaxis("\\rm{Q_{x}}", output.Q_unit)
+            output.yaxis("\\rm{Q_{y}}", output.Q_unit)
+        else:
+            output.xaxis("\\rm{Q_{x}}", 'A^{-1}')
+            output.yaxis("\\rm{Q_{y}}", 'A^{-1}')
+        if data_conv_i is not None:
+            output.zaxis("\\rm{Intensity}", output.I_unit)
+        else:
+            output.zaxis("\\rm{Intensity}", "cm^{-1}")
+    
+        # Store loading process information
+        output.meta_data['loader'] = self.type_name
+
+        return output
Index: src/sas/sascalc/dataloader/readers/schema/cansas1d_v1_0.xsd
===================================================================
--- src/sas/sascalc/dataloader/readers/schema/cansas1d_v1_0.xsd	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/schema/cansas1d_v1_0.xsd	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,238 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="xsd-documentation.xsl" ?>
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+	xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"  jaxb:version="2.0"
+	targetNamespace="cansas1d/1.0" xmlns:tns="cansas1d/1.0"
+	elementFormDefault="qualified">
+
+<!-- JAXB: http://java.sun.com/developer/technicalArticles/WebServices/jaxb/ -->
+
+	<complexType name="floatUnitType">
+		<simpleContent>
+			<extension base="float">
+				<attribute name="unit" type="string" use="required" />
+			</extension>
+		</simpleContent>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<group name="positionGroup">
+		<sequence>
+			<element name="x" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+			<element name="y" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+			<element name="z" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+		</sequence>
+	</group>
+
+	<complexType name="positionType">
+		<group ref="tns:positionGroup" />
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<group name="orientationGroup">
+		<sequence>
+			<element name="roll" type="tns:floatUnitType" minOccurs="0"  maxOccurs="1" />
+			<element name="pitch" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+			<element name="yaw" type="tns:floatUnitType" minOccurs="0"   maxOccurs="1" />
+		</sequence>
+	</group>
+
+	<complexType name="orientationType">
+		<group ref="tns:orientationGroup" />
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="IdataType">
+		<sequence>
+			<element name="Q" minOccurs="1" maxOccurs="1"    type="tns:floatUnitType" />
+			<element name="I" minOccurs="1" maxOccurs="1"    type="tns:floatUnitType" />
+			<element name="Idev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+			<xsd:choice>
+				<!-- [ [Qdev] | [[dQw] | [dQl]] ] -->
+				<element name="Qdev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+				<xsd:sequence>
+					<element name="dQw" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+					<element name="dQl" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+				</xsd:sequence>
+			</xsd:choice>
+			<element name="Qmean" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+			<element name="Shadowfactor" minOccurs="0" maxOccurs="1" type="float" default="1.0" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+	</complexType>
+
+	<complexType name="SASdataType">
+		<sequence>
+			<element name="Idata" minOccurs="1" maxOccurs="unbounded" type="tns:IdataType" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASsampleType">
+		<sequence>
+			<element name="ID" minOccurs="1" maxOccurs="1" type="string" />
+			<element name="thickness" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="transmission" minOccurs="0" maxOccurs="1" type="float" />
+			<element name="temperature" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="position" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="orientation" minOccurs="0" maxOccurs="1" type="tns:orientationType" />
+			<element name="details" minOccurs="0" maxOccurs="unbounded" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="termType">
+		<simpleContent>
+			<extension base="string">
+				<attribute name="name" type="string" use="optional" default="" />
+				<attribute name="unit" type="string" use="optional"/>
+			</extension>
+		</simpleContent>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASprocessType">
+		<annotation>
+			<appinfo>
+				<!-- JAXB: simplify class name -->
+				<jaxb:property name="SASprocess" />
+			</appinfo>
+		</annotation>
+		<sequence>
+			<element name="name" minOccurs="0" maxOccurs="1"
+				type="string" default="">
+				<annotation>
+					<appinfo>
+						<!-- Remap collision of Name when using JAXB -->
+						<jaxb:property name="SASprocessName" />
+					</appinfo>
+				</annotation>
+			</element>
+			<element name="date" minOccurs="0" maxOccurs="1" type="string" />
+			<element name="description" minOccurs="0" maxOccurs="1" />
+			<element name="term" minOccurs="0" maxOccurs="unbounded" type="tns:termType" />
+			<element name="SASprocessnote" minOccurs="1" maxOccurs="unbounded" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="">
+			<annotation>
+				<appinfo>
+					<!-- Remap collision of Name when using JAXB -->
+					<jaxb:property name="SASprocessNameAttr" />
+				</appinfo>
+			</annotation>
+		</attribute>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASsourceType">
+		<sequence>
+			<element name="radiation" minOccurs="1" maxOccurs="1" type="string" />
+			<element name="beam_size" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="beam_shape" minOccurs="0" maxOccurs="1" type="string" />
+			<element name="wavelength" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="wavelength_min" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="wavelength_max" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="wavelength_spread" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<complexType name="SAScollimationType">
+		<sequence>
+			<element name="length" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="aperture" minOccurs="0"
+				maxOccurs="unbounded">
+				<complexType>
+					<sequence>
+						<element name="size" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+						<element name="distance" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+					</sequence>
+					<attribute name="name" type="string" use="optional" default="" />
+					<attribute name="type" type="string" use="optional" default="" />
+				</complexType>
+			</element>
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<complexType name="SASdetectorType">
+		<sequence>
+			<element name="name" minOccurs="1" maxOccurs="1" type="string" default=""/>
+			<element name="SDD" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="offset" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="orientation" minOccurs="0" maxOccurs="1" type="tns:orientationType" />
+			<element name="beam_center" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="pixel_size" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="slit_length" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+		</sequence>
+	</complexType>
+
+	<complexType name="SASinstrumentType">
+		<sequence>
+			<element name="name" type="string" minOccurs="1" maxOccurs="1" default="" />
+			<element name="SASsource" type="tns:SASsourceType" minOccurs="1" maxOccurs="1" />
+			<element name="SAScollimation" type="tns:SAScollimationType" minOccurs="1" maxOccurs="unbounded" />
+			<element name="SASdetector" type="tns:SASdetectorType" minOccurs="1" maxOccurs="unbounded" />
+		</sequence>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASentryType">
+		<sequence>
+			<element name="Title" minOccurs="1" maxOccurs="1" type="string" />
+			<element name="Run" minOccurs="1" maxOccurs="unbounded">
+				<xsd:complexType>
+					<simpleContent>
+						<extension base="string">
+							<attribute name="name" type="string" use="optional" default="" />
+						</extension>
+					</simpleContent>
+				</xsd:complexType>
+			</element>
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+			<element name="SASdata" minOccurs="1" maxOccurs="unbounded" type="tns:SASdataType" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other">
+				<!-- Is a second *any* element needed here? -->
+				<annotation>
+					<appinfo>
+						<!-- Remap collision of Any when using JAXB --> 
+						<jaxb:property name="SASentryAny2" />
+					</appinfo>
+				</annotation>
+			</xsd:any>
+			<element name="SASsample" type="tns:SASsampleType" />
+			<element name="SASinstrument" type="tns:SASinstrumentType" minOccurs="1" maxOccurs="1" />
+			<element name="SASprocess" type="tns:SASprocessType" minOccurs="0" maxOccurs="unbounded" />
+			<element name="SASnote" minOccurs="1" maxOccurs="unbounded" />
+				<!-- SASnote is a free-form element.
+					Do not define any content for it or it will become restrictive.
+				-->
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASrootType">
+		<sequence>
+			<element name="SASentry" minOccurs="1" maxOccurs="unbounded" type="tns:SASentryType" />
+		</sequence>
+		<attribute name="version" type="string" fixed="1.0" use="required" />
+	</complexType>
+
+	<element name="SASroot" type="tns:SASrootType" />
+
+</schema>
Index: src/sas/sascalc/dataloader/readers/schema/cansas1d_v1_1.xsd
===================================================================
--- src/sas/sascalc/dataloader/readers/schema/cansas1d_v1_1.xsd	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/schema/cansas1d_v1_1.xsd	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,271 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<?xml-stylesheet type="text/xsl" href="xsd-documentation.xsl" ?>
+<schema xmlns="http://www.w3.org/2001/XMLSchema"
+	xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+	xmlns:jaxb="http://java.sun.com/xml/ns/jaxb"  jaxb:version="2.0"
+	targetNamespace="urn:cansas1d:1.1" xmlns:tns="urn:cansas1d:1.1"
+	elementFormDefault="qualified">
+
+<!-- 
+Copyright (c) 2013, UChicago Argonne, LLC
+This file is distributed subject to a Software License Agreement found
+in the file LICENSE that is included with this distribution. 
+ -->
+ 
+<!-- JAXB: http://java.sun.com/developer/technicalArticles/WebServices/jaxb/ -->
+
+	<complexType name="floatUnitType">
+		<simpleContent>
+			<extension base="float">
+				<attribute name="unit" type="string" use="required" />
+			</extension>
+		</simpleContent>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<group name="positionGroup">
+		<sequence>
+			<element name="x" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+			<element name="y" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+			<element name="z" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+		</sequence>
+	</group>
+
+	<complexType name="positionType">
+		<group ref="tns:positionGroup" />
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<group name="orientationGroup">
+		<sequence>
+			<element name="roll" type="tns:floatUnitType" minOccurs="0"  maxOccurs="1" />
+			<element name="pitch" type="tns:floatUnitType" minOccurs="0" maxOccurs="1" />
+			<element name="yaw" type="tns:floatUnitType" minOccurs="0"   maxOccurs="1" />
+		</sequence>
+	</group>
+
+	<complexType name="orientationType">
+		<group ref="tns:orientationGroup" />
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+	
+	<complexType name="IdataType">
+		<sequence>
+			<element name="Q" minOccurs="1" maxOccurs="1"    type="tns:floatUnitType" />
+			<element name="I" minOccurs="1" maxOccurs="1"    type="tns:floatUnitType" />
+			<element name="Idev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+			<xsd:choice>
+				<!-- [ [Qdev] | [[dQw] | [dQl]] ] -->
+				<element name="Qdev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+				<xsd:sequence>
+					<element name="dQw" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+					<element name="dQl" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+				</xsd:sequence>
+			</xsd:choice>
+			<element name="Qmean" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+			<element name="Shadowfactor" minOccurs="0" maxOccurs="1" type="float" default="1.0" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+	</complexType>
+	
+	<complexType name="SASdataType">
+		<sequence minOccurs="1" maxOccurs="1">  <!-- http://svn.cansas.org/trac/ticket/21 -->
+			<sequence>
+				<element name="Idata" minOccurs="1" maxOccurs="unbounded" type="tns:IdataType" />
+			</sequence>
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+		<attribute name="timestamp" type="dateTime" use="optional" />
+	</complexType>
+	
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+	
+	<complexType name="TdataType">
+			<sequence>
+				<element name="Lambda" minOccurs="1" maxOccurs="1"    type="tns:floatUnitType" />
+				<element name="T" minOccurs="1" maxOccurs="1"    type="tns:floatUnitType" />
+				<element name="Tdev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" />
+				<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+	</complexType>
+	
+	<complexType name="SAStransmission_spectrumType">
+		<sequence minOccurs="1" maxOccurs="1">
+			<sequence minOccurs="1" maxOccurs="1">
+				<element name="Tdata" minOccurs="1" maxOccurs="unbounded" type="tns:TdataType" />
+			</sequence>
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+		<attribute name="timestamp" type="dateTime" use="optional" />
+	</complexType>
+	
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASsampleType">
+		<sequence>
+			<element name="ID" minOccurs="1" maxOccurs="1" type="string" />
+			<element name="thickness" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="transmission" minOccurs="0" maxOccurs="1" type="float" />
+			<element name="temperature" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="position" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="orientation" minOccurs="0" maxOccurs="1" type="tns:orientationType" />
+			<element name="details" minOccurs="0" maxOccurs="unbounded" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="termType">
+		<simpleContent>
+			<extension base="string">
+				<attribute name="name" type="string" use="optional" default="" />
+				<attribute name="unit" type="string" use="optional"/>
+			</extension>
+		</simpleContent>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASprocessType">
+		<annotation>
+			<appinfo>
+				<!-- JAXB: simplify class name -->
+				<jaxb:property name="SASprocess" />
+			</appinfo>
+		</annotation>
+		<sequence>
+			<element name="name" minOccurs="0" maxOccurs="1"
+				type="string" default="">
+				<annotation>
+					<appinfo>
+						<!-- Remap collision of Name when using JAXB -->
+						<jaxb:property name="SASprocessName" />
+					</appinfo>
+				</annotation>
+			</element>
+			<element name="date" minOccurs="0" maxOccurs="1" type="string" />
+			<element name="description" minOccurs="0" maxOccurs="1" />
+			<element name="term" minOccurs="0" maxOccurs="unbounded" type="tns:termType" />
+			<element name="SASprocessnote" minOccurs="1" maxOccurs="unbounded" />
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="">
+			<annotation>
+				<appinfo>
+					<!-- Remap collision of Name when using JAXB -->
+					<jaxb:property name="SASprocessNameAttr" />
+				</appinfo>
+			</annotation>
+		</attribute>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASsourceType">
+		<sequence>
+			<element name="radiation" minOccurs="1" maxOccurs="1" type="string" />
+			<element name="beam_size" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="beam_shape" minOccurs="0" maxOccurs="1" type="string" />
+			<element name="wavelength" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="wavelength_min" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="wavelength_max" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="wavelength_spread" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<complexType name="SAScollimationType">
+		<sequence>
+			<element name="length" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="aperture" minOccurs="0"
+				maxOccurs="unbounded">
+				<complexType>
+					<sequence>
+						<element name="size" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+						<element name="distance" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+					</sequence>
+					<attribute name="name" type="string" use="optional" default="" />
+					<attribute name="type" type="string" use="optional" default="" />
+				</complexType>
+			</element>
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<complexType name="SASdetectorType">
+		<sequence>
+			<element name="name" minOccurs="1" maxOccurs="1" type="string" default=""/>
+			<element name="SDD" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+			<element name="offset" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="orientation" minOccurs="0" maxOccurs="1" type="tns:orientationType" />
+			<element name="beam_center" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="pixel_size" minOccurs="0" maxOccurs="1" type="tns:positionType" />
+			<element name="slit_length" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" />
+		</sequence>
+	</complexType>
+
+	<complexType name="SASinstrumentType">
+		<sequence>
+			<element name="name" type="string" minOccurs="1" maxOccurs="1" default="" />
+			<element name="SASsource" type="tns:SASsourceType" minOccurs="1" maxOccurs="1" />
+			<element name="SAScollimation" type="tns:SAScollimationType" minOccurs="1" maxOccurs="unbounded" />
+			<element name="SASdetector" type="tns:SASdetectorType" minOccurs="1" maxOccurs="unbounded" />
+		</sequence>
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASentryType">
+		<sequence>
+			<element name="Title" minOccurs="1" maxOccurs="1" type="string" />
+			<element name="Run" minOccurs="1" maxOccurs="unbounded">
+				<xsd:complexType>
+					<simpleContent>
+						<extension base="string">
+							<attribute name="name" type="string" use="optional" default="" />
+						</extension>
+					</simpleContent>
+				</xsd:complexType>
+			</element>
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other" />
+			<element name="SASdata" minOccurs="1" maxOccurs="unbounded" type="tns:SASdataType" />
+			<element name="SAStransmission_spectrum" minOccurs="0" maxOccurs="unbounded" type="tns:SAStransmission_spectrumType"/>
+			<xsd:any minOccurs="0" maxOccurs="unbounded" processContents="skip" namespace="##other">
+				<!-- Is a second *any* element needed here? -->
+				<annotation>
+					<appinfo>
+						<!-- Remap collision of Any when using JAXB --> 
+						<jaxb:property name="SASentryAny2" />
+					</appinfo>
+				</annotation>
+			</xsd:any>
+			<element name="SASsample" type="tns:SASsampleType" />
+			<element name="SASinstrument" type="tns:SASinstrumentType" minOccurs="1" maxOccurs="1" />
+			<element name="SASprocess" type="tns:SASprocessType" minOccurs="0" maxOccurs="unbounded" />
+			<element name="SASnote" minOccurs="1" maxOccurs="unbounded" />
+				<!-- SASnote is a free-form element.
+					Do not define any content for it or it will become restrictive.
+				-->
+		</sequence>
+		<attribute name="name" type="string" use="optional" default="" />
+	</complexType>
+
+	<!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+
+	<complexType name="SASrootType">
+		<sequence>
+			<element name="SASentry" minOccurs="1" maxOccurs="unbounded" type="tns:SASentryType" />
+		</sequence>
+		<attribute name="version" type="string" fixed="1.1" use="required" />
+	</complexType>
+
+	<element name="SASroot" type="tns:SASrootType" />
+
+</schema>
Index: src/sas/sascalc/dataloader/readers/sesans_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/sesans_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/sesans_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,273 @@
+"""
+    SESANS reader (based on ASCII reader)
+    
+    Reader for .ses or .sesans file format
+    
+    Jurrian Bakker 
+"""
+import numpy
+import os
+from sas.sascalc.dataloader.data_info import SESANSData1D
+
+# Check whether we have a converter available
+has_converter = True
+try:
+    from sas.sascalc.data_util.nxsunit import Converter
+except:
+    has_converter = False
+_ZERO = 1e-16
+
+class Reader:
+    """
+    Class to load sesans files (6 columns).
+    """
+    ## File type
+    type_name = "SESANS"
+    
+    ## Wildcards
+    type = ["SESANS files (*.ses)|*.ses",
+            "SESANS files (*..sesans)|*.sesans"]
+    ## List of allowed extensions
+    ext = ['.ses', '.SES', '.sesans', '.SESANS']
+    
+    ## Flag to bypass extension check
+    allow_all = True
+    
+    def read(self, path):
+        
+#        print "reader triggered"
+        
+        """
+        Load data file
+        
+        :param path: file path
+        
+        :return: SESANSData1D object, or None
+        
+        :raise RuntimeError: when the file can't be opened
+        :raise ValueError: when the length of the data vectors are inconsistent
+        """
+        if os.path.isfile(path):
+            basename = os.path.basename(path)
+            _, extension = os.path.splitext(basename)
+            if self.allow_all or extension.lower() in self.ext:
+                try:
+                    # Read in binary mode since GRASP frequently has no-ascii
+                    # characters that brakes the open operation
+                    input_f = open(path,'rb')
+                except:
+                    raise  RuntimeError, "sesans_reader: cannot open %s" % path
+                buff = input_f.read()
+#                print buff
+                lines = buff.splitlines()
+#                print lines
+                #Jae could not find python universal line spliter:
+                #keep the below for now
+                # some ascii data has \r line separator,
+                # try it when the data is on only one long line
+#                if len(lines) < 2 :
+#                    lines = buff.split('\r')
+                 
+                x  = numpy.zeros(0)
+                y  = numpy.zeros(0)
+                dy = numpy.zeros(0)
+                lam  = numpy.zeros(0)
+                dlam = numpy.zeros(0)
+                dx = numpy.zeros(0)
+                
+               #temp. space to sort data
+                tx  = numpy.zeros(0)
+                ty  = numpy.zeros(0)
+                tdy = numpy.zeros(0)
+                tlam  = numpy.zeros(0)
+                tdlam = numpy.zeros(0)
+                tdx = numpy.zeros(0)
+#                print "all good"
+                output = SESANSData1D(x=x, y=y, lam=lam, dy=dy, dx=dx, dlam=dlam)
+#                print output                
+                self.filename = output.filename = basename
+                
+                
+                data_conv_z = None
+                data_conv_P = None
+#                print "passing"
+                if has_converter == True and output.x_unit != 'A':
+                    data_conv_z = Converter('nm')
+                    # Test it
+                    data_conv_z(1.0, output.x_unit)
+#                    print data_conv_z
+#                    print data_conv_z(1.0, output.x_unit)
+                if has_converter == True and output.y_unit != ' ':
+                    data_conv_P = Converter('a.u.')
+                    # Test it
+                    data_conv_P(1.0, output.y_unit)
+#                    print data_conv_P
+#                    print data_conv_P(1.0, output.y_unit)
+                # The first good line of data will define whether
+                # we have 2-column or 3-column ascii
+#                print output.x_unit
+#                print output.y_unit
+                
+#                print "past output"
+                
+#                has_error_dx = None
+#                has_error_dy = None
+                
+#                #Initialize counters for data lines and header lines.
+#                is_data = False  # Has more than 5 lines
+#                # More than "5" lines of data is considered as actual
+#                # data unless that is the only data
+#                mum_data_lines = 5
+#                # To count # of current data candidate lines
+#                i = -1
+#                # To count total # of previous data candidate lines
+#                i1 = -1
+#                # To count # of header lines
+#                j = -1
+#                # Helps to count # of header lines
+#                j1 = -1
+#                #minimum required number of columns of data; ( <= 4).
+#                lentoks = 2
+                paramnames=[]
+                paramvals=[]
+                zvals=[]
+                dzvals=[]
+                lamvals=[]
+                dlamvals=[]
+                Pvals=[]
+                dPvals=[]
+#                print x
+#                print zvals
+                for line in lines:
+                    # Initial try for CSV (split on ,)
+#                    print line
+                    line=line.strip()
+#                    print line
+                    toks = line.split('\t')
+#                    print toks
+                    if len(toks)==2:
+                        paramnames.append(toks[0])
+#                        print paramnames
+                        paramvals.append(toks[1])
+#                        print paramvals
+                    if len(toks)>5:
+                        zvals.append(toks[0])
+                        dzvals.append(toks[1])
+                        lamvals.append(toks[2])
+                        dlamvals.append(toks[3])
+                        Pvals.append(toks[4])
+                        dPvals.append(toks[5])
+                    else:
+                        continue
+#                print varheaders
+#                print paramnames
+#                print paramvals
+#                print zvals  
+#                print len(zvals)
+                
+                x=[]
+                y=[]
+                lam=[]
+                dx=[]
+                dy=[]
+                dlam=[]
+                varheader=[zvals[0],dzvals[0],lamvals[0],dlamvals[0],Pvals[0],dPvals[0]]
+#                print varheader
+                valrange=range(len(zvals)-1)
+#                print valrange
+                for i in valrange:
+                    x.append(float(zvals[i+1]))
+                    y.append(float(Pvals[i+1]))
+                    lam.append(float(lamvals[i+1]))
+                    dy.append(float(dPvals[i+1]))
+                    dx.append(float(dzvals[i+1]))
+                    dlam.append(float(dlamvals[i+1]))
+                    
+                x,y,lam,dy,dx,dlam = [
+                   numpy.asarray(v, 'double')
+                   for v in (x,y,lam,dy,dx,dlam)
+                ]
+
+#                print x
+#                print y
+#                print dx
+#                print dy
+#                print len(x)
+#                print len(y)
+#                print len(dx)
+#                print len(dy)
+                
+                
+                input_f.close()
+                # Sanity check
+#                if has_error_dy == True and not len(y) == len(dy):
+#                    msg = "sesans_reader: y and dy have different length"
+#                    raise RuntimeError, msg
+#                if has_error_dx == True and not len(x) == len(dx):
+#                    msg = "sesans_reader: y and dy have different length"
+#                    raise RuntimeError, msg
+#                # If the data length is zero, consider this as
+#                # though we were not able to read the file.
+#                if len(x) == 0:
+#                    raise RuntimeError, "sesans_reader: could not load file"
+#                print "alive"
+                #Let's re-order the data to make cal.
+                # curve look better some cases
+#                ind = numpy.lexsort((ty, tx))
+#                for i in ind:
+#                    x[i] = tx[ind[i]]
+#                    y[i] = ty[ind[i]]
+#                    if has_error_dy == True:
+#                        dy[i] = tdy[ind[i]]
+#                    if has_error_dx == True:
+#                        dx[i] = tdx[ind[i]]
+                # Zeros in dx, dy
+#                if has_error_dx:
+#                    dx[dx == 0] = _ZERO
+#                if has_error_dy:
+#                    dy[dy == 0] = _ZERO
+                #Data
+                output.x = x #[x != 0]
+#                print output.x
+                output.y = y #[x != 0]
+#                print output.y
+#                output.dy = dy[x != 0] if has_error_dy == True\
+#                    else numpy.zeros(len(output.y))
+#                output.dx = dx[x != 0] if has_error_dx == True\
+#                    else numpy.zeros(len(output.x))
+                output.dy = dy
+                output.dx = dx
+                output.lam = lam
+                output.dlam = dlam
+
+
+#                print "still alive"                
+#                if data_conv_z is not None:
+#                    output.xaxis("\\rm{z}", output.x_unit)
+#                else:
+#                    output.xaxis("\\rm{z}", 'nm')
+#                if data_conv_P is not None:
+#                    output.yaxis("\\rm{P/P0}", output.y_unit)
+#                else:
+#                    output.yaxis("\\rm{P/P0}", "a.u.")
+                output.xaxis("\\rm{z}", 'nm')   
+                output.yaxis("\\rm{P/P0}", " ")
+                # Store loading process information
+                output.meta_data['loader'] = self.type_name
+                output.sample.thickness = float(paramvals[6])
+                output.sample.name = paramvals[1]
+                output.sample.ID = paramvals[0]
+                output.sample.zacceptance=float(paramvals[7])
+                output.vars=varheader
+
+#                print "sesans_reader end"
+                
+                if len(output.x) < 1:
+                    raise RuntimeError, "%s is empty" % path
+#                print output
+#                print output.lam
+                return output
+            
+        else:
+            raise RuntimeError, "%s is not a file" % path
+        return None
Index: src/sas/sascalc/dataloader/readers/tiff_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/tiff_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/tiff_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,106 @@
+#####################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation. 
+#See the license text in license.txt
+#copyright 2008, University of Tennessee
+######################################################################
+"""
+    Image reader. Untested.
+"""
+#TODO: load and check data and orientation of the image (needs rendering)
+import math
+import logging
+import os
+import numpy
+from sas.sascalc.dataloader.data_info import Data2D
+from sas.sascalc.dataloader.manipulations import reader2D_converter
+    
+class Reader:
+    """
+    Example data manipulation
+    """
+    ## File type
+    type_name = "TIF"
+    ## Wildcards
+    type = ["TIF files (*.tif)|*.tif",
+            "TIFF files (*.tiff)|*.tiff",
+            ]
+    ## Extension
+    ext = ['.tif', '.tiff']
+        
+    def read(self, filename=None):
+        """
+        Open and read the data in a file
+        
+        :param file: path of the file
+        """
+        try:
+            import Image
+            import TiffImagePlugin
+            Image._initialized=2
+        except:
+            msg = "tiff_reader: could not load file. Missing Image module."
+            raise RuntimeError, msg
+        
+        # Instantiate data object
+        output = Data2D()
+        output.filename = os.path.basename(filename)
+            
+        # Read in the image
+        try:
+            im = Image.open(filename)
+        except:
+            raise  RuntimeError, "cannot open %s"%(filename)
+        data = im.getdata()
+
+        # Initiazed the output data object
+        output.data = numpy.zeros([im.size[0], im.size[1]])
+        output.err_data = numpy.zeros([im.size[0], im.size[1]])
+        output.mask = numpy.ones([im.size[0], im.size[1]], dtype=bool)
+        
+        # Initialize
+        x_vals = []
+        y_vals = []
+
+        # x and y vectors
+        for i_x in range(im.size[0]):
+            x_vals.append(i_x)
+            
+        itot = 0
+        for i_y in range(im.size[1]):
+            y_vals.append(i_y)
+
+        for val in data:
+            try:
+                value = float(val)
+            except:
+                logging.error("tiff_reader: had to skip a non-float point")
+                continue
+            
+            # Get bin number
+            if math.fmod(itot, im.size[0]) == 0:
+                i_x = 0
+                i_y += 1
+            else:
+                i_x += 1
+                
+            output.data[im.size[1] - 1 - i_y][i_x] = value
+            
+            itot += 1
+                
+        output.xbins = im.size[0]
+        output.ybins = im.size[1]
+        output.x_bins = x_vals
+        output.y_bins = y_vals
+        output.qx_data = numpy.array(x_vals)
+        output.qy_data = numpy.array(y_vals)
+        output.xmin = 0
+        output.xmax = im.size[0] - 1
+        output.ymin = 0
+        output.ymax = im.size[0] - 1
+        
+        # Store loading process information
+        output.meta_data['loader'] = self.type_name
+        output = reader2D_converter(output)
+        return output
Index: src/sas/sascalc/dataloader/readers/xml_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/xml_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
+++ src/sas/sascalc/dataloader/readers/xml_reader.py	(revision b699768550de9d4b35b087234ec1d2a461922bab)
@@ -0,0 +1,286 @@
+"""
+    Generic XML read and write utility
+
+    Usage: Either extend xml_reader or add as a class variable.
+"""
+############################################################################
+#This software was developed by the University of Tennessee as part of the
+#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
+#project funded by the US National Science Foundation.
+#If you use DANSE applications to do scientific research that leads to
+#publication, we ask that you acknowledge the use of the software with the
+#following sentence:
+#This work benefited from DANSE software developed under NSF award DMR-0520547.
+#copyright 2008,2009 University of Tennessee
+#############################################################################
+
+import logging
+from lxml import etree
+from lxml.builder import E
+
+PARSER = etree.ETCompatXMLParser(remove_comments=True, remove_pis=False)
+
+class XMLreader():
+    """
+    Generic XML read and write class. Mostly helper functions.
+    Makes reading/writing XML a bit easier than calling lxml libraries directly.
+
+    :Dependencies:
+        This class requires lxml 2.3 or higher.
+    """
+
+    xml = None
+    xmldoc = None
+    xmlroot = None
+    schema = None
+    schemadoc = None
+    encoding = None
+    processing_instructions = None
+
+    def __init__(self, xml=None, schema=None):
+        self.xml = xml
+        self.schema = schema
+        self.processing_instructions = {}
+        if xml is not None:
+            self.set_xml_file(xml)
+        else:
+            self.xmldoc = None
+            self.xmlroot = None
+        if schema is not None:
+            self.set_schema(schema)
+        else:
+            self.schemadoc = None
+
+    def reader(self):
+        """
+        Read in an XML file into memory and return an lxml dictionary
+        """
+        if self.validate_xml():
+            self.xmldoc = etree.parse(self.xml, parser=PARSER)
+        else:
+            raise etree.XMLSchemaValidateError(self, self.find_invalid_xml())
+        return self.xmldoc
+
+    def set_xml_file(self, xml):
+        """
+        Set the XML file and parse
+        """
+        try:
+            self.xml = xml
+            self.xmldoc = etree.parse(self.xml, parser=PARSER)
+            self.xmlroot = self.xmldoc.getroot()
+        except etree.XMLSyntaxError as xml_error:
+            logging.info(xml_error)
+        except Exception:
+            self.xml = None
+            self.xmldoc = None
+            self.xmlroot = None
+
+    def set_schema(self, schema):
+        """
+        Set the schema file and parse
+        """
+        try:
+            self.schema = schema
+            self.schemadoc = etree.parse(self.schema, parser=PARSER)
+        except etree.XMLSyntaxError as xml_error:
+            logging.info(xml_error)
+        except Exception:
+            self.schema = None
+            self.schemadoc = None
+
+    def validate_xml(self):
+        """
+        Checks to see if the XML file meets the schema
+        """
+        valid = True
+        if self.schema is not None:
+            self.parse_schema_and_doc()
+            schema_check = etree.XMLSchema(self.schemadoc)
+            valid = schema_check.validate(self.xmldoc)
+        return valid
+
+    def find_invalid_xml(self):
+        """
+        Finds the first offending element that should not be present in XML file
+        """
+        first_error = ""
+        self.parse_schema_and_doc()
+        schema = etree.XMLSchema(self.schemadoc)
+        try:
+            first_error = schema.assertValid(self.xmldoc)
+        except etree.DocumentInvalid as err:
+            first_error = str(err)
+        return first_error
+
+    def parse_schema_and_doc(self):
+        """
+        Creates a dictionary of the parsed schema and xml files.
+        """
+        self.set_xml_file(self.xml)
+        self.set_schema(self.schema)
+
+    def to_string(self, elem, pretty_print=False, encoding=None):
+        """
+        Converts an etree element into a string
+        """
+        return etree.tostring(elem, pretty_print=pretty_print, \
+                              encoding=encoding)
+
+    def break_processing_instructions(self, string, dic):
+        """
+        Method to break a processing instruction string apart and add to a dict
+
+        :param string: A processing instruction as a string
+        :param dic: The dictionary to save the PIs to
+        """
+        pi_string = string.replace("<?", "").replace("?>", "")
+        split = pi_string.split(" ", 1)
+        pi_name = split[0]
+        attr = split[1]
+        new_pi_name = self._create_unique_key(dic, pi_name)
+        dic[new_pi_name] = attr
+        return dic
+
+    def set_processing_instructions(self):
+        """
+        Take out all processing instructions and create a dictionary from them
+        If there is a default encoding, the value is also saved
+        """
+        dic = {}
+        proc_instr = self.xmlroot.getprevious()
+        while proc_instr is not None:
+            pi_string = self.to_string(proc_instr)
+            if "?>\n<?" in pi_string:
+                pi_string = pi_string.split("?>\n<?")
+            if isinstance(pi_string, str):
+                dic = self.break_processing_instructions(pi_string, dic)
+            elif isinstance(pi_string, list):
+                for item in pi_string:
+                    dic = self.break_processing_instructions(item, dic)
+            proc_instr = proc_instr.getprevious()
+        if 'xml' in dic:
+            self.set_encoding(dic['xml'])
+            del dic['xml']
+        self.processing_instructions = dic
+
+    def set_encoding(self, attr_str):
+        """
+        Find the encoding in the xml declaration and save it as a string
+
+        :param attr_str: All attributes as a string
+            e.g. "foo1="bar1" foo2="bar2" foo3="bar3" ... foo_n="bar_n""
+        """
+        attr_str = attr_str.replace(" = ", "=")
+        attr_list = attr_str.split()
+        for item in attr_list:
+            name_value = item.split("\"=")
+            name = name_value[0].lower()
+            value = name_value[1]
+            if name == "encoding":
+                self.encoding = value
+                return
+        self.encoding = None
+
+    def _create_unique_key(self, dictionary, name, numb=0):
+        """
+        Create a unique key value for any dictionary to prevent overwriting
+        Recurses until a unique key value is found.
+        
+        :param dictionary: A dictionary with any number of entries
+        :param name: The index of the item to be added to dictionary
+        :param numb: The number to be appended to the name, starts at 0
+        """
+        if dictionary.get(name) is not None:
+            numb += 1
+            name = name.split("_")[0]
+            name += "_{0}".format(numb)
+            name = self._create_unique_key(dictionary, name, numb)
+        return name
+
+    def create_tree(self, root):
+        """
+        Create an element tree for processing from an etree element
+
+        :param root: etree Element(s) 
+        """
+        return etree.ElementTree(root)
+
+    def create_element_from_string(self, xml_string):
+        """
+        Create an element from an XML string
+
+        :param xml_string: A string of xml
+        """
+        return etree.fromstring(xml_string)
+
+    def create_element(self, name, attrib=None, nsmap=None):
+        """
+        Create an XML element for writing to file
+
+        :param name: The name of the element to be created
+        """
+        if attrib == None:
+            attrib = {}
+        return etree.Element(name, attrib, nsmap)
+
+    def write_text(self, elem, text):
+        """
+        Write text to an etree Element
+
+        :param elem: etree.Element object
+        :param text: text to write to the element
+        """
+        elem.text = text
+        return elem
+
+    def write_attribute(self, elem, attr_name, attr_value):
+        """
+        Write attributes to an Element
+
+        :param elem: etree.Element object
+        :param attr_name: attribute name to write
+        :param attr_value: attribute value to set
+        """
+        attr = elem.attrib
+        attr[attr_name] = attr_value
+
+    def return_processing_instructions(self):
+        """
+        Get all processing instructions saved when loading the document
+
+        :param tree: etree.ElementTree object to write PIs to
+        """
+        pi_list = []
+        if self.processing_instructions is not None:
+            for key in self.processing_instructions:
+                value = self.processing_instructions.get(key)
+                pi_item = etree.ProcessingInstruction(key, value)
+                pi_list.append(pi_item)
+        return pi_list
+
+    def append(self, element, tree):
+        """
+        Append an etree Element to an ElementTree.
+
+        :param element: etree Element to append
+        :param tree: ElementTree object to append to
+        """
+        tree = tree.append(element)
+        return tree
+
+    def ebuilder(self, parent, elementname, text=None, attrib=None):
+        """
+        Use lxml E builder class with arbitrary inputs.
+
+        :param parnet: The parent element to append a child to
+        :param elementname: The name of the child in string form
+        :param text: The element text
+        :param attrib: A dictionary of attribute names to attribute values
+        """
+        text = str(text)
+        if attrib == None:
+            attrib = {}
+        elem = E(elementname, attrib, text)
+        parent = parent.append(elem)
+        return parent
