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
