Index: src/sas/sascalc/dataloader/readers/cansas_reader.py
===================================================================
--- src/sas/sascalc/dataloader/readers/cansas_reader.py	(revision 83c09af62d5011f226defc52e4715b83e6494e0f)
+++ src/sas/sascalc/dataloader/readers/cansas_reader.py	(revision af08e55a12fe753ae7a904c4876a757795017deb)
@@ -21,5 +21,5 @@
 # For saving individual sections of data
 from sas.sascalc.dataloader.data_info import Data1D, Data2D, DataInfo, \
-    plottable_1D
+    plottable_1D, plottable_2D
 from sas.sascalc.dataloader.data_info import Collimation, TransmissionSpectrum, \
     Detector, Process, Aperture
@@ -59,5 +59,5 @@
         The CanSAS reader requires PyXML 0.8.4 or later.
     """
-    ## CanSAS version - defaults to version 1.0
+    # CanSAS version - defaults to version 1.0
     cansas_version = "1.0"
     base_ns = "{cansas1d/1.0}"
@@ -66,22 +66,22 @@
     invalid = True
     frm = ""
-    ## Log messages and errors
+    # Log messages and errors
     logging = None
     errors = set()
-    ## Namespace hierarchy for current xml_file object
+    # Namespace hierarchy for current xml_file object
     names = None
     ns_list = None
-    ## Temporary storage location for loading multiple data sets in a single file
+    # Temporary storage location for loading multiple data sets in a single file
     current_datainfo = None
     current_dataset = None
     current_data1d = None
     data = None
-    ## List of data1D objects to be sent back to SasView
+    # List of data1D objects to be sent back to SasView
     output = None
-    ## Wildcards
+    # Wildcards
     type = ["XML files (*.xml)|*.xml", "SasView Save Files (*.svs)|*.svs"]
-    ## List of allowed extensions
+    # List of allowed extensions
     ext = ['.xml', '.XML', '.svs', '.SVS']
-    ## Flag to bypass extension check
+    # Flag to bypass extension check
     allow_all = True
 
@@ -223,6 +223,13 @@
                 self.parent_class = tagname_original
                 if tagname == 'SASdata':
-                    self._initialize_new_data_set()
-                ## Recursion step to access data within the group
+                    self._initialize_new_data_set(node)
+                    if isinstance(self.current_dataset, plottable_2D):
+                        x_bins = attr.get("x_bins", "")
+                        y_bins = attr.get("y_bins", "")
+                        if x_bins is not "" and y_bins is not "":
+                            self.current_dataset.shape = (x_bins, y_bins)
+                        else:
+                            self.current_dataset.shape = ()
+                # Recursion step to access data within the group
                 self._parse_entry(node, True)
                 if tagname == "SASsample":
@@ -239,5 +246,5 @@
                 data_point, unit = self._get_node_value(node, tagname)
 
-                ## If this is a dataset, store the data appropriately
+                # If this is a dataset, store the data appropriately
                 if tagname == 'Run':
                     self.current_datainfo.run_name[data_point] = name
@@ -248,9 +255,9 @@
                     self.current_datainfo.notes.append(data_point)
 
-                ## I and Q Data
-                elif tagname == 'I':
+                # I and Q - 1D data
+                elif tagname == 'I' and isinstance(self.current_dataset, plottable_1D):
                     self.current_dataset.yaxis("Intensity", unit)
                     self.current_dataset.y = np.append(self.current_dataset.y, data_point)
-                elif tagname == 'Idev':
+                elif tagname == 'Idev' and isinstance(self.current_dataset, plottable_1D):
                     self.current_dataset.dy = np.append(self.current_dataset.dy, data_point)
                 elif tagname == 'Q':
@@ -267,6 +274,26 @@
                 elif tagname == 'Shadowfactor':
                     pass
-
-                ## Sample Information
+                # I and Qx, Qy - 2D data
+                elif tagname == 'I' and isinstance(self.current_dataset, plottable_2D):
+                    self.current_dataset.yaxis("Intensity", unit)
+                    self.current_dataset.data = np.append(self.current_dataset.data, data_point)
+                elif tagname == 'Idev' and isinstance(self.current_dataset, plottable_2D):
+                    self.current_dataset.err_data = np.append(self.current_dataset.err_data, data_point)
+                elif tagname == 'Qx':
+                    self.current_dataset.xaxis("Qx", unit)
+                    self.current_dataset.qx_data = np.append(self.current_dataset.qx_data, data_point)
+                elif tagname == 'Qy':
+                    self.current_dataset.yaxis("Qy", unit)
+                    self.current_dataset.qy_data = np.append(self.current_dataset.qy_data, data_point)
+                elif tagname == 'Qxdev':
+                    self.current_dataset.xaxis("Qxdev", unit)
+                    self.current_dataset.dqx_data = np.append(self.current_dataset.dqx_data, data_point)
+                elif tagname == 'Qydev':
+                    self.current_dataset.yaxis("Qydev", unit)
+                    self.current_dataset.dqy_data = np.append(self.current_dataset.dqy_data, data_point)
+                elif tagname == 'Mask':
+                    self.current_dataset.mask = np.append(self.current_dataset.mask, bool(data_point))
+
+                # Sample Information
                 elif tagname == 'ID' and self.parent_class == 'SASsample':
                     self.current_datainfo.sample.ID = data_point
@@ -302,8 +329,8 @@
                     self.current_datainfo.sample.orientation_unit = unit
 
-                ## Instrumental Information
+                # Instrumental Information
                 elif tagname == 'name' and self.parent_class == 'SASinstrument':
                     self.current_datainfo.instrument = data_point
-                ## Detector Information
+                # Detector Information
                 elif tagname == 'name' and self.parent_class == 'SASdetector':
                     self.detector.name = data_point
@@ -350,5 +377,5 @@
                     self.detector.orientation.z = data_point
                     self.detector.orientation_unit = unit
-                ## Collimation and Aperture
+                # Collimation and Aperture
                 elif tagname == 'length' and self.parent_class == 'SAScollimation':
                     self.collimation.length = data_point
@@ -369,5 +396,5 @@
                     self.collimation.size_unit = unit
 
-                ## Process Information
+                # Process Information
                 elif tagname == 'name' and self.parent_class == 'SASprocess':
                     self.process.name = data_point
@@ -389,5 +416,5 @@
                     self.process.term.append(dic)
 
-                ## Transmission Spectrum
+                # Transmission Spectrum
                 elif tagname == 'T' and self.parent_class == 'Tdata':
                     self.transspectrum.transmission = np.append(self.transspectrum.transmission, data_point)
@@ -400,5 +427,5 @@
                     self.transspectrum.wavelength_unit = unit
 
-                ## Source Information
+                # Source Information
                 elif tagname == 'wavelength' and (self.parent_class == 'SASsource' or self.parent_class == 'SASData'):
                     self.current_datainfo.source.wavelength = data_point
@@ -427,5 +454,5 @@
                     self.current_datainfo.source.beam_shape = data_point
 
-                ## Everything else goes in meta_data
+                # Everything else goes in meta_data
                 else:
                     new_key = self._create_unique_key(self.current_datainfo.meta_data, tagname)
@@ -441,9 +468,4 @@
             self.add_data_set()
             empty = None
-            if self.output[0].dx is not None:
-                self.output[0].dxl = np.empty(0)
-                self.output[0].dxw = np.empty(0)
-            else:
-                self.output[0].dx = np.empty(0)
             return self.output[0], empty
 
@@ -517,16 +539,19 @@
         self.current_datainfo = DataInfo()
 
-    def _initialize_new_data_set(self, parent_list=None):
+    def _initialize_new_data_set(self, node=None):
         """
         A private class method to generate a new 1D data object.
         Outside methods should call add_data_set() to be sure any existing data is stored properly.
 
-        :param parent_list: List of names of parent elements
-        """
-
-        if parent_list is None:
-            parent_list = []
+        :param node: XML node to determine if 1D or 2D data
+        """
         x = np.array(0)
         y = np.array(0)
+        for child in node:
+            if child.tag.replace(self.base_ns, "") == "Idata":
+                for i_child in child:
+                    if i_child.tag.replace(self.base_ns, "") == "Qx":
+                        self.current_dataset = plottable_2D()
+                        return
         self.current_dataset = plottable_1D(x, y)
 
@@ -563,5 +588,5 @@
         """
 
-        ## Append errors to dataset and reset class errors
+        # Append errors to dataset and reset class errors
         self.current_datainfo.errors = set()
         for error in self.errors:
@@ -569,32 +594,69 @@
         self.errors.clear()
 
-        ## Combine all plottables with datainfo and append each to output
-        ## Type cast data arrays to float64 and find min/max as appropriate
+        # Combine all plottables with datainfo and append each to output
+        # Type cast data arrays to float64 and find min/max as appropriate
         for dataset in self.data:
-            if dataset.x is not None:
-                dataset.x = np.delete(dataset.x, [0])
-                dataset.x = dataset.x.astype(np.float64)
-                dataset.xmin = np.min(dataset.x)
-                dataset.xmax = np.max(dataset.x)
-            if dataset.y is not None:
-                dataset.y = np.delete(dataset.y, [0])
-                dataset.y = dataset.y.astype(np.float64)
-                dataset.ymin = np.min(dataset.y)
-                dataset.ymax = np.max(dataset.y)
-            if dataset.dx is not None:
-                dataset.dx = np.delete(dataset.dx, [0])
-                dataset.dx = dataset.dx.astype(np.float64)
-            if dataset.dxl is not None:
-                dataset.dxl = np.delete(dataset.dxl, [0])
-                dataset.dxl = dataset.dxl.astype(np.float64)
-            if dataset.dxw is not None:
-                dataset.dxw = np.delete(dataset.dxw, [0])
-                dataset.dxw = dataset.dxw.astype(np.float64)
-            if dataset.dy is not None:
-                dataset.dy = np.delete(dataset.dy, [0])
-                dataset.dy = dataset.dy.astype(np.float64)
-            np.trim_zeros(dataset.x)
-            np.trim_zeros(dataset.y)
-            np.trim_zeros(dataset.dy)
+            if isinstance(dataset, plottable_1D):
+                if dataset.x is not None:
+                    dataset.x = np.delete(dataset.x, [0])
+                    dataset.x = dataset.x.astype(np.float64)
+                    dataset.xmin = np.min(dataset.x)
+                    dataset.xmax = np.max(dataset.x)
+                if dataset.y is not None:
+                    dataset.y = np.delete(dataset.y, [0])
+                    dataset.y = dataset.y.astype(np.float64)
+                    dataset.ymin = np.min(dataset.y)
+                    dataset.ymax = np.max(dataset.y)
+                if dataset.dx is not None:
+                    dataset.dx = np.delete(dataset.dx, [0])
+                    dataset.dx = dataset.dx.astype(np.float64)
+                if dataset.dxl is not None:
+                    dataset.dxl = np.delete(dataset.dxl, [0])
+                    dataset.dxl = dataset.dxl.astype(np.float64)
+                if dataset.dxw is not None:
+                    dataset.dxw = np.delete(dataset.dxw, [0])
+                    dataset.dxw = dataset.dxw.astype(np.float64)
+                if dataset.dy is not None:
+                    dataset.dy = np.delete(dataset.dy, [0])
+                    dataset.dy = dataset.dy.astype(np.float64)
+                np.trim_zeros(dataset.x)
+                np.trim_zeros(dataset.y)
+                np.trim_zeros(dataset.dy)
+            elif isinstance(dataset, plottable_2D):
+                dataset.data = np.delete(dataset.data, [0])
+                dataset.data = dataset.data.astype(np.float64)
+                dataset.qx_data = np.delete(dataset.qx_data, [0])
+                dataset.qx_data = dataset.qx_data.astype(np.float64)
+                dataset.xmin = np.min(dataset.qx_data)
+                dataset.xmax = np.max(dataset.qx_data)
+                dataset.qy_data = np.delete(dataset.qy_data, [0])
+                dataset.qy_data = dataset.qy_data.astype(np.float64)
+                dataset.ymin = np.min(dataset.qy_data)
+                dataset.ymax = np.max(dataset.qy_data)
+                dataset.q_data = np.sqrt(dataset.qx_data * dataset.qx_data
+                                         + dataset.qy_data * dataset.qy_data)
+                if dataset.err_data is not None:
+                    dataset.err_data = np.delete(dataset.err_data, [0])
+                    dataset.err_data = dataset.err_data.astype(np.float64)
+                if dataset.dqx_data is not None:
+                    dataset.dqx_data = np.delete(dataset.dqx_data, [0])
+                    dataset.dqx_data = dataset.dqx_data.astype(np.float64)
+                if dataset.dqy_data is not None:
+                    dataset.dqy_data = np.delete(dataset.dqy_data, [0])
+                    dataset.dqy_data = dataset.dqy_data.astype(np.float64)
+                if dataset.mask is not None:
+                    dataset.mask = np.delete(dataset.mask, [0])
+                    dataset.mask = dataset.mask.astype(dtype=bool)
+
+                if len(dataset.shape) == 2:
+                    n_rows, n_cols = dataset.shape
+                    dataset.y_bins = dataset.qy_data[0::int(n_cols)]
+                    dataset.x_bins = dataset.qx_data[:int(n_cols)]
+                    dataset.data = dataset.data.flatten()
+                else:
+                    dataset.y_bins = []
+                    dataset.x_bins = []
+                    dataset.data = dataset.data.flatten()
+
             final_dataset = combine_data(dataset, self.current_datainfo)
             self.output.append(final_dataset)
@@ -696,5 +758,5 @@
                         and local_unit.lower() != "none":
                     if HAS_CONVERTER == True:
-                        ## Check local units - bad units raise KeyError
+                        # Check local units - bad units raise KeyError
                         data_conv_q = Converter(local_unit)
                         value_unit = default_unit
@@ -743,32 +805,65 @@
         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
-        if self.current_dataset.dxl is not None:
-            dql_exists = True
-        if self.current_dataset.dxw is not None:
-            dqw_exists = True
-        if self.current_dataset.dx is not None:
-            dq_exists = True
-        if self.current_dataset.dy is not None:
-            di_exists = True
-        if dqw_exists and not dql_exists:
-            array_size = self.current_dataset.dxw.size - 1
-            self.current_dataset.dxl = np.append(self.current_dataset.dxl, np.zeros([array_size]))
-        elif dql_exists and not dqw_exists:
-            array_size = self.current_dataset.dxl.size - 1
-            self.current_dataset.dxw = np.append(self.current_dataset.dxw, np.zeros([array_size]))
-        elif not dql_exists and not dqw_exists and not dq_exists:
-            array_size = self.current_dataset.x.size - 1
-            self.current_dataset.dx = np.append(self.current_dataset.dx, np.zeros([array_size]))
-        if not di_exists:
-            array_size = self.current_dataset.y.size - 1
-            self.current_dataset.dy = np.append(self.current_dataset.dy, np.zeros([array_size]))
-
+        if isinstance(self.current_dataset, plottable_1D):
+            dql_exists = False
+            dqw_exists = False
+            dq_exists = False
+            di_exists = False
+            if self.current_dataset.dxl is not None:
+                dql_exists = True
+            if self.current_dataset.dxw is not None:
+                dqw_exists = True
+            if self.current_dataset.dx is not None:
+                dq_exists = True
+            if self.current_dataset.dy is not None:
+                di_exists = True
+            if dqw_exists and not dql_exists:
+                array_size = self.current_dataset.dxw.size - 1
+                self.current_dataset.dxl = np.append(self.current_dataset.dxl,
+                                                     np.zeros([array_size]))
+            elif dql_exists and not dqw_exists:
+                array_size = self.current_dataset.dxl.size - 1
+                self.current_dataset.dxw = np.append(self.current_dataset.dxw,
+                                                     np.zeros([array_size]))
+            elif not dql_exists and not dqw_exists and not dq_exists:
+                array_size = self.current_dataset.x.size - 1
+                self.current_dataset.dx = np.append(self.current_dataset.dx,
+                                                    np.zeros([array_size]))
+            if not di_exists:
+                array_size = self.current_dataset.y.size - 1
+                self.current_dataset.dy = np.append(self.current_dataset.dy,
+                                                    np.zeros([array_size]))
+        elif isinstance(self.current_dataset, plottable_2D):
+            dqx_exists = False
+            dqy_exists = False
+            di_exists = False
+            mask_exists = False
+            if self.current_dataset.dqx_data is not None:
+                dqx_exists = True
+            if self.current_dataset.dqy_data is not None:
+                dqy_exists = True
+            if self.current_dataset.err_data is not None:
+                di_exists = True
+            if self.current_dataset.mask is not None:
+                mask_exists = True
+            if not dqy_exists:
+                array_size = self.current_dataset.qy_data.size - 1
+                self.current_dataset.dqy_data = np.append(
+                    self.current_dataset.dqy_data, np.zeros([array_size]))
+            if not dqx_exists:
+                array_size = self.current_dataset.qx_data.size - 1
+                self.current_dataset.dqx_data = np.append(
+                    self.current_dataset.dqx_data, np.zeros([array_size]))
+            if not di_exists:
+                array_size = self.current_dataset.data.size - 1
+                self.current_dataset.err_data = np.append(
+                    self.current_dataset.err_data, np.zeros([array_size]))
+            if not mask_exists:
+                array_size = self.current_dataset.data.size - 1
+                self.current_dataset.mask = np.append(
+                    self.current_dataset.mask,
+                    np.ones([array_size] ,dtype=bool))
 
     ####### All methods below are for writing CanSAS XML files #######
-
 
     def write(self, filename, datainfo):
@@ -795,4 +890,5 @@
         :param datainfo: Data1D object
         """
+        is_2d = False
         if issubclass(datainfo.__class__, Data2D):
             is_2d = True
@@ -950,4 +1046,8 @@
         """
         node = self.create_element("SASdata")
+        attr = {}
+        if datainfo.data.shape:
+            attr["x_bins"] = len(datainfo.x_bins)
+            attr["y_bins"] = len(datainfo.y_bins)
         self.append(node, entry_node)
 
@@ -971,5 +1071,5 @@
                                 {'unit': datainfo._xunit})
             if datainfo.mask is not None and len(datainfo.mask) > i:
-                self.write_node(point, "Mask", datainfo.err_data[i])
+                self.write_node(point, "Mask", datainfo.mask[i])
 
     def _write_trans_spectrum(self, datainfo, entry_node):
