Changeset eda8972 in sasview for src/sans/dataloader


Ignore:
Timestamp:
Dec 13, 2013 4:48:36 PM (10 years ago)
Author:
Jeff Krzywon <jeffery.krzywon@…>
Branches:
master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
Children:
a25d242
Parents:
f28fd9cf
Message:

I will finalize this on Monday.

Location:
src/sans/dataloader/readers
Files:
2 added
1 deleted
1 edited

Legend:

Unmodified
Added
Removed
  • src/sans/dataloader/readers/cansas_reader.py

    rffbe487 reda8972  
    11""" 
    2     CanSAS data reader 
     2    CanSAS data reader - new recursive cansasVersion. 
    33""" 
    44############################################################################ 
     
    1313############################################################################# 
    1414 
    15 # Known issue: reader not compatible with multiple SASdata entries 
    16 # within a single SASentry. Will raise a runtime error. 
    17  
    18 #TODO: check that all vectors are written only if they have at  
    19 #    least one non-empty value 
    20 #TODO: Writing only allows one SASentry per file. 
    21 #     Would be best to allow multiple entries. 
    22 #TODO: Store error list 
    23 #TODO: Allow for additional meta data for each section 
    24 #TODO: Notes need to be implemented. They can be any XML  
    25 #    structure in version 1.0 
    26 #      Process notes have the same problem. 
    27 #TODO: Unit conversion is not complete (temperature units are missing) 
    28  
    2915import logging 
    3016import numpy 
     
    3622from sans.dataloader.data_info import Process 
    3723from sans.dataloader.data_info import Aperture 
    38 from lxml import etree 
     24import xml_reader 
    3925import xml.dom.minidom 
     26from cansas_constants import cansasConstants 
     27 
    4028_ZERO = 1e-16 
    4129HAS_CONVERTER = True 
     
    4432except: 
    4533    HAS_CONVERTER = False 
    46  
    47 CANSAS_NS = "cansas1d/1.0" 
     34     
     35CANSAS_FORMAT = cansasConstants.CANSAS_FORMAT 
     36CANSAS_NS = cansasConstants.CANSAS_NS 
    4837ALLOW_ALL = True 
    4938 
     
    6756        return True 
    6857    return False 
    69  
     58                 
    7059 
    7160def get_content(location, node): 
     
    10998    return value, attr 
    11099 
    111              
    112 class Reader: 
     100 
     101 
     102class CANSASError(Exception): 
     103    """Base class all CANSAS reader exceptions are derived""" 
     104    pass 
     105 
     106class NotCANSASFileError(CANSASError): 
     107    def __init__(self): 
     108        self.value = "This is not a proper CanSAS file." 
     109    def __str__(self): 
     110        return repr(self.value) 
     111 
     112class Reader(): 
    113113    """ 
    114114    Class to load cansas 1D XML files 
    115115     
    116116    :Dependencies: 
    117         The CanSas reader requires PyXML 0.8.4 or later. 
    118     """ 
    119     ## CanSAS version 
    120     version = '1.0' 
    121     ## File type 
    122     type_name = "CanSAS 1D" 
    123     ## Wildcards 
    124     type = ["CanSAS 1D files (*.xml)|*.xml", 
    125                         "CanSAS 1D AVE files (*.AVEx)|*.AVEx", 
    126                          "CanSAS 1D AVE files (*.ABSx)|*.ABSx"] 
    127  
    128     ## List of allowed extensions 
    129     ext = ['.xml', '.XML', '.avex', '.AVEx', '.absx', 'ABSx'] 
     117        The CanSAS reader requires PyXML 0.8.4 or later. 
     118    """ 
     119    ##CanSAS version - defaults to version 1.0 
     120    cansasVersion = "1.0" 
     121    ##Data reader 
     122    reader = xml_reader.XMLreader() 
     123    errors = [] 
    130124     
    131125    def __init__(self): 
    132126        ## List of errors 
    133127        self.errors = [] 
    134      
    135     def read(self, path): 
    136         """ 
    137         Load data file 
    138          
    139         :param path: file path 
    140          
    141         :return: Data1D object if a single SASentry was found,  
    142                     or a list of Data1D objects if multiple entries were found, 
    143                     or None of nothing was found 
     128         
     129    def isCansas(self): 
     130        """ 
     131        Checks to see if the xml file is a CanSAS file 
     132        """ 
     133        if self.reader.validateXML(): 
     134            xmlns = self.reader.xmlroot.keys() 
     135            if (CANSAS_NS.get(self.cansasVersion).get("ns") == self.reader.xmlroot.get(xmlns[1]).rsplit(" ")[0]): 
     136                return True 
     137        return False 
     138     
     139    def read(self, xml): 
     140        """ 
     141        Validate and read in an xml file in the canSAS format. 
     142         
     143        :param xml: A canSAS file path in proper XML format 
     144        """ 
     145        # X - Q value; Y - Intensity (Abs) 
     146        x = numpy.empty(0) 
     147        y = numpy.empty(0) 
     148        dx = numpy.empty(0) 
     149        dy = numpy.empty(0) 
     150        dxl = numpy.empty(0) 
     151        dxw = numpy.empty(0) 
     152         
     153        # output - Final list of Data1D objects 
     154        output = [] 
     155        # ns - Namespace hierarchy for current xml object 
     156        ns = [] 
     157         
     158        try: 
     159            # Load in the xml file and get the cansas version from the header 
     160            self.reader.setXMLFile(xml) 
     161            root = self.reader.xmlroot 
     162            self.cansasVersion = root.get("version") 
     163            # Generic values for the cansas file based on the version 
     164            cansas_defaults = CANSAS_NS.get(self.cansasVersion) 
     165         
     166            # Link a schema to the XML file. 
     167            basename = os.path.basename(xml) 
     168            base_name = xml_reader.__file__ 
     169            base = base_name.split("\\sans\\")[0] 
     170            schema_path = "{0}\\sans\\dataloader\\readers\\schema\\{1}".format(base, cansas_defaults.get("schema")).replace("\\", "/") 
     171            self.reader.setSchema(schema_path) 
     172         
     173            # Try to load the file, but raise an error if unable to. 
     174            # Check the file matches the XML schema 
     175            if self.isCansas(): 
     176                # Get each SASentry from the XML file and add it to a list. 
     177                entry_list = root.xpath('/ns:SASroot/ns:SASentry', 
     178                                             namespaces={'ns': cansas_defaults.get("ns")}) 
     179                ns.append("SASentry") 
     180                 
     181                # If there are multiple files, modify the name for each is unique 
     182                multipleFiles = len(entry_list) - 1 
     183                n = 0 
     184                name = basename 
     185                # Parse each SASentry item 
     186                for entry in entry_list: 
    144187                     
    145         :raise RuntimeError: when the file can't be opened 
    146         :raise ValueError: when the length of the data vectors are inconsistent 
    147         """ 
    148         output = [] 
    149         if os.path.isfile(path): 
    150             basename = os.path.basename(path) 
    151             root, extension = os.path.splitext(basename) 
    152             if ALLOW_ALL or extension.lower() in self.ext: 
    153                 try: 
    154                     tree = etree.parse(path, parser=etree.ETCompatXMLParser()) 
    155                     # Check the format version number 
    156                     # Specifying the namespace will take care of the file 
    157                     # format version 
    158                     root = tree.getroot() 
     188                    # Define a new Data1D object with zeroes for x and y 
     189                    data1D = Data1D(x,y,dx,dy) 
     190                    data1D.dxl = dxl 
     191                    data1D.dxw = dxw 
    159192                     
    160                     entry_list = root.xpath('/ns:SASroot/ns:SASentry', 
    161                                              namespaces={'ns': CANSAS_NS}) 
     193                    # If more than one SASentry, number each in order 
     194                    if multipleFiles: 
     195                        name += "_{0}".format(n) 
     196                        n += 1 
    162197                     
    163                     for entry in entry_list: 
    164                         self.errors = [] 
    165                         sas_entry = self._parse_entry(entry) 
    166                         sas_entry.filename = basename 
    167                          
    168                         # Store loading process information 
    169                         sas_entry.errors = self.errors 
    170                         sas_entry.meta_data['loader'] = self.type_name 
    171                         output.append(sas_entry) 
    172                 except: 
    173                     raise RuntimeError, "%s cannot be read \n" % path 
    174         else: 
    175             raise RuntimeError, "%s is not a file" % path 
    176         # Return output consistent with the loader's api 
    177         if len(output) == 0: 
    178             #cannot return none when it cannot read 
    179             #return None 
    180             raise RuntimeError, "%s cannot be read \n" % path 
    181         elif len(output) == 1: 
    182             return output[0] 
    183         else: 
    184             return output 
    185                  
    186     def _parse_entry(self, dom): 
    187         """ 
    188         Parse a SASentry 
    189          
    190         :param node: SASentry node 
    191          
    192         :return: Data1D object 
    193         """ 
    194         x = numpy.zeros(0) 
    195         y = numpy.zeros(0) 
    196          
    197         data_info = Data1D(x, y) 
    198          
    199         # Look up title 
    200         self._store_content('ns:Title', dom, 'title', data_info) 
    201          
    202         # Look up run number 
    203         nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS}) 
    204         for item in nodes: 
    205             if item.text is not None: 
    206                 value = item.text.strip() 
    207                 if len(value) > 0: 
    208                     data_info.run.append(value) 
    209                     if item.get('name') is not None: 
    210                         data_info.run_name[value] = item.get('name') 
    211                             
    212         # Look up instrument name 
    213         self._store_content('ns:SASinstrument/ns:name', dom, 'instrument', 
    214                              data_info) 
    215  
    216         # Notes 
    217         note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS}) 
    218         for note in note_list: 
     198                    # Set the Data1D name and then parse the entry. The entry is appended to a list of entry values 
     199                    data1D.filename = name 
     200                    data1D.meta_data["loader"] = "CanSAS 1D" 
     201                    return_value, extras = self._parse_entry(entry, ns, data1D) 
     202                    del extras[:] 
     203                     
     204                    #Final cleanup - Remove empty nodes, verify array sizes are correct 
     205                    return_value.errors = self.errors 
     206                    del self.errors[:] 
     207                    numpy.trim_zeros(return_value.x) 
     208                    numpy.trim_zeros(return_value.y) 
     209                    numpy.trim_zeros(return_value.dy) 
     210                    size_dx = return_value.dx.size 
     211                    size_dxl = return_value.dxl.size 
     212                    size_dxw = return_value.dxw.size 
     213                    if size_dxl == 0 and size_dxw == 0: 
     214                        return_value.dxl = None 
     215                        return_value.dxw = None 
     216                        numpy.trim_zeros(return_value.dx) 
     217                    elif size_dx == 0: 
     218                        return_value.dx = None 
     219                        size_dx = size_dxl 
     220                        numpy.trim_zeros(return_value.dxl) 
     221                        numpy.trim_zeros(return_value.dxw) 
     222                     
     223                    output.append(return_value) 
     224            else: 
     225                # If the file does not match the schema, raise this error 
     226                raise RuntimeError, "%s cannot be read \n" % xml 
     227        # If an exception occurs while loading the file, give a descriptive output. 
     228        except Exception: 
     229            raise RuntimeError, "%s cannot be read \n" % xml 
     230             
     231        # Return a list of parsed entries that dataloader can manage 
     232        return output 
     233     
     234    def _create_unique_key(self, dictionary, name, i): 
     235        if dictionary.get(name) is not None: 
     236            i += 1 
     237            name = name.split("_")[0] 
     238            name += "_{0}".format(i) 
     239            name = self._create_unique_key(dictionary, name, i) 
     240        return name 
     241     
     242    def _iterate_namespace(self, ns): 
     243        # The current level to look through in cansas_constants. 
     244        current_level = CANSAS_FORMAT.get("SASentry") 
     245        # Defaults for variable and datatype 
     246        ns_variable = "{0}.meta_data[\"{2}\"] = \"{1}\"" 
     247        ns_datatype = "content" 
     248        ns_optional = True 
     249        for name in ns: 
     250            if name != "SASentry": 
     251                current_level = current_level.get("children").get(name, "") 
     252                if current_level == "": 
     253                    current_level = current_level.get("<any>", "") 
     254                cl_variable = current_level.get("variable", "") 
     255                cl_datatype = current_level.get("storeas", "") 
     256                cl_units_optional = current_level.get("units_required", "") 
     257                # Where are how to store the variable for the given namespace 
     258                # The CANSAS_CONSTANTS tree is hierarchical, so is no value, inherit 
     259                ns_variable = cl_variable if cl_variable != "" else ns_variable 
     260                ns_datatype = cl_datatype if cl_datatype != "" else ns_datatype 
     261                ns_optional = cl_units_optional if cl_units_optional != ns_optional else ns_optional 
     262        return current_level, ns_variable, ns_datatype, ns_optional 
     263     
     264    def _unit_conversion(self, new_current_level, attr, data1D, node_value, optional = True): 
     265        value_unit = '' 
     266        if 'unit' in attr and 'unit' in new_current_level: 
    219267            try: 
    220                 if note.text is not None: 
    221                     note_value = note.text.strip() 
    222                     if len(note_value) > 0: 
    223                         data_info.notes.append(note_value) 
    224             except: 
    225                 err_mess = "cansas_reader.read: error processing" 
    226                 err_mess += " entry notes\n  %s" % sys.exc_value 
    227                 self.errors.append(err_mess) 
    228                 logging.error(err_mess) 
    229          
    230         # Sample info ################### 
    231         entry = get_content('ns:SASsample', dom) 
    232         if entry is not None: 
    233             data_info.sample.name = entry.get('name') 
    234              
    235         self._store_content('ns:SASsample/ns:ID', 
    236                      dom, 'ID', data_info.sample) 
    237         self._store_float('ns:SASsample/ns:thickness', 
    238                      dom, 'thickness', data_info.sample) 
    239         self._store_float('ns:SASsample/ns:transmission', 
    240                      dom, 'transmission', data_info.sample) 
    241         self._store_float('ns:SASsample/ns:temperature', 
    242                      dom, 'temperature', data_info.sample) 
    243          
    244         nodes = dom.xpath('ns:SASsample/ns:details', 
    245                           namespaces={'ns': CANSAS_NS}) 
    246         for item in nodes: 
     268                if isinstance(node_value, float) is False: 
     269                    exec("node_value = float({0})".format(node_value)) 
     270                default_unit = None 
     271                unitname = new_current_level.get("unit") 
     272                exec "default_unit = data1D.{0}.lower()".format(unitname) 
     273                local_unit = attr['unit'].lower() 
     274                if local_unit != default_unit: 
     275                    if HAS_CONVERTER == True: 
     276                        try: 
     277                            data_conv_q = Converter(attr['unit']) 
     278                            value_unit = default_unit 
     279                            exec "node_value = data_conv_q(node_value, units=data1D.{0})".format(unitname) 
     280                        except: 
     281                            msg = "CanSAS reader: could not convert " 
     282                            msg += "Q unit [%s]; " % attr['unit'], 
     283                            exec "msg += \"expecting [%s]\n  %s\" % (data1D.{0}, sys.exc_info()[1])".format(unitname) 
     284                            raise ValueError, msg 
     285                    else: 
     286                        value_unit = local_unit 
     287                        err_mess = "CanSAS reader: unrecognized %s unit [%s];"\ 
     288                        % (node_value, default_unit) 
     289                        err_mess += " expecting [%s]" % local_unit 
     290                        self.errors.append(err_mess) 
     291                        if optional: 
     292                            logging.info(err_mess) 
     293                        else: 
     294                            raise ValueError, err_mess 
     295            except Exception as e: 
     296                msg = "CanSAS reader: could not convert " 
     297                msg += "Q unit [%s]; " % attr['unit'], 
     298                exec "msg += \"expecting [%s]\n  %s\" % (data1D.{0}, sys.exc_info()[1])".format(unitname) 
     299                self.errors.append(msg) 
     300                raise ValueError, msg 
     301        elif 'unit' in attr: 
     302            value_unit = attr['unit'] 
     303        node_value = "float({0})".format(node_value) 
     304        return node_value, value_unit 
     305     
     306    def _parse_entry(self, dom, ns, data1D, extras = []): 
     307        """ 
     308        Parse a SASEntry - new recursive method for parsing the dom of 
     309            the CanSAS data format. This will allow multiple data files 
     310            and extra nodes to be read in simultaneously. 
     311         
     312        :param dom: dom object with a namespace base of ns 
     313        :param ns: A list of element names that lead up to the dom object 
     314        :param data1D: The data1D object that will be modified 
     315        """ 
     316          
     317        # A portion of every namespace entry 
     318        base_ns = "{0}{1}{2}".format("{", CANSAS_NS.get(self.cansasVersion).get("ns"), "}") 
     319        unit = '' 
     320         
     321        # Go through each child in the parent element 
     322        for node in dom: 
    247323            try: 
    248                 if item.text is not None: 
    249                     detail_value = item.text.strip() 
    250                     if len(detail_value) > 0: 
    251                         data_info.sample.details.append(detail_value) 
    252             except: 
    253                 err_mess = "cansas_reader.read: error processing " 
    254                 err_mess += " sample details\n  %s" % sys.exc_value 
    255                 self.errors.append(err_mess) 
    256                 logging.error(err_mess) 
    257          
    258         # Position (as a vector) 
    259         self._store_float('ns:SASsample/ns:position/ns:x', 
    260                      dom, 'position.x', data_info.sample) 
    261         self._store_float('ns:SASsample/ns:position/ns:y', 
    262                      dom, 'position.y', data_info.sample) 
    263         self._store_float('ns:SASsample/ns:position/ns:z', 
    264                      dom, 'position.z', data_info.sample) 
    265          
    266         # Orientation (as a vector) 
    267         self._store_float('ns:SASsample/ns:orientation/ns:roll', 
    268                      dom, 'orientation.x', data_info.sample) 
    269         self._store_float('ns:SASsample/ns:orientation/ns:pitch', 
    270                      dom, 'orientation.y', data_info.sample) 
    271         self._store_float('ns:SASsample/ns:orientation/ns:yaw', 
    272                      dom, 'orientation.z', data_info.sample) 
    273         
    274         # Source info ################### 
    275         entry = get_content('ns:SASinstrument/ns:SASsource', dom) 
    276         if entry is not None: 
    277             data_info.source.name = entry.get('name') 
    278          
    279         self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation', 
    280                      dom, 'radiation', data_info.source) 
    281         self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape', 
    282                      dom, 'beam_shape', data_info.source) 
    283         self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength', 
    284                      dom, 'wavelength', data_info.source) 
    285         self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min', 
    286                      dom, 'wavelength_min', data_info.source) 
    287         self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max', 
    288                      dom, 'wavelength_max', data_info.source) 
    289         self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread', 
    290                      dom, 'wavelength_spread', data_info.source) 
    291          
    292         # Beam size (as a vector)    
    293         entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom) 
    294         if entry is not None: 
    295             data_info.source.beam_size_name = entry.get('name') 
    296              
    297         self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x', 
    298                      dom, 'beam_size.x', data_info.source) 
    299         self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y', 
    300                      dom, 'beam_size.y', data_info.source) 
    301         self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z', 
    302                      dom, 'beam_size.z', data_info.source) 
    303          
    304         # Collimation info ################### 
    305         nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation', 
    306                           namespaces={'ns': CANSAS_NS}) 
    307         for item in nodes: 
    308             collim = Collimation() 
    309             if item.get('name') is not None: 
    310                 collim.name = item.get('name') 
    311             self._store_float('ns:length', item, 'length', collim) 
    312              
    313             # Look for apertures 
    314             apert_list = item.xpath('ns:aperture', namespaces={'ns': CANSAS_NS}) 
    315             for apert in apert_list: 
    316                 aperture = Aperture() 
    317                  
    318                 # Get the name and type of the aperture 
    319                 aperture.name = apert.get('name') 
    320                 aperture.type = apert.get('type') 
     324                # Get the element name and set the current ns level 
     325                tagname = node.tag.replace(base_ns, "") 
     326                tagname_original = tagname 
     327                ns.append(tagname) 
     328                attr = node.attrib 
     329                 
     330                # Look for special cases 
     331                save_data1D = data1D 
     332                if tagname == "SASdetector": 
     333                    data1D = Detector() 
     334                elif tagname == "SAScollimation": 
     335                    data1D = Collimation() 
     336                elif tagname == "SASprocess": 
     337                    data1D = Process() 
     338                    for child in node: 
     339                        if child.tag.replace(base_ns, "") == "term": 
     340                            term_attr = {} 
     341                            for attr in child.keys(): 
     342                                term_attr[attr] = child.get(attr).strip() 
     343                            if child.text is not None: 
     344                                term_attr['value'] = child.text.strip() 
     345                                data1D.term.append(term_attr) 
     346                elif tagname == "aperture": 
     347                    data1D = Aperture() 
     348                 
     349                # Get where to store content 
     350                new_current_level, ns_variable, ns_datatype, optional = self._iterate_namespace(ns) 
     351                # If the element is a child element, recurse 
     352                if node.getchildren() is not None: 
     353                    # Returned value is new Data1D object with all previous and new values in it. 
     354                    data1D, extras = self._parse_entry(node, ns, data1D, extras) 
    321355                     
    322                 self._store_float('ns:distance', apert, 'distance', aperture) 
    323                  
    324                 entry = get_content('ns:size', apert) 
    325                 if entry is not None: 
    326                     aperture.size_name = entry.get('name') 
    327                  
    328                 self._store_float('ns:size/ns:x', apert, 'size.x', aperture) 
    329                 self._store_float('ns:size/ns:y', apert, 'size.y', aperture) 
    330                 self._store_float('ns:size/ns:z', apert, 'size.z', aperture) 
    331                  
    332                 collim.aperture.append(aperture) 
    333                  
    334             data_info.collimation.append(collim) 
    335          
    336         # Detector info ###################### 
    337         nodes = dom.xpath('ns:SASinstrument/ns:SASdetector', 
    338                            namespaces={'ns': CANSAS_NS}) 
    339         for item in nodes: 
    340              
    341             detector = Detector() 
    342              
    343             self._store_content('ns:name', item, 'name', detector) 
    344             self._store_float('ns:SDD', item, 'distance', detector) 
    345              
    346             # Detector offset (as a vector) 
    347             self._store_float('ns:offset/ns:x', item, 'offset.x', detector) 
    348             self._store_float('ns:offset/ns:y', item, 'offset.y', detector) 
    349             self._store_float('ns:offset/ns:z', item, 'offset.z', detector) 
    350              
    351             # Detector orientation (as a vector) 
    352             self._store_float('ns:orientation/ns:roll', item, 'orientation.x', 
    353                                detector) 
    354             self._store_float('ns:orientation/ns:pitch', item, 'orientation.y', 
    355                                detector) 
    356             self._store_float('ns:orientation/ns:yaw', item, 'orientation.z', 
    357                                detector) 
    358              
    359             # Beam center (as a vector) 
    360             self._store_float('ns:beam_center/ns:x', item, 'beam_center.x', 
    361                                detector) 
    362             self._store_float('ns:beam_center/ns:y', item, 'beam_center.y', 
    363                               detector) 
    364             self._store_float('ns:beam_center/ns:z', item, 'beam_center.z', 
    365                                detector) 
    366              
    367             # Pixel size (as a vector) 
    368             self._store_float('ns:pixel_size/ns:x', item, 'pixel_size.x', 
    369                                detector) 
    370             self._store_float('ns:pixel_size/ns:y', item, 'pixel_size.y', 
    371                                detector) 
    372             self._store_float('ns:pixel_size/ns:z', item, 'pixel_size.z', 
    373                                detector) 
    374              
    375             self._store_float('ns:slit_length', item, 'slit_length', detector) 
    376              
    377             data_info.detector.append(detector) 
    378  
    379         # Processes info ###################### 
    380         nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS}) 
    381         for item in nodes: 
    382             process = Process() 
    383             self._store_content('ns:name', item, 'name', process) 
    384             self._store_content('ns:date', item, 'date', process) 
    385             self._store_content('ns:description', item, 'description', process) 
    386              
    387             term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS}) 
    388             for term in term_list: 
    389                 try: 
    390                     term_attr = {} 
    391                     for attr in term.keys(): 
    392                         term_attr[attr] = term.get(attr).strip() 
    393                     if term.text is not None: 
    394                         term_attr['value'] = term.text.strip() 
    395                         process.term.append(term_attr) 
    396                 except: 
    397                     err_mess = "cansas_reader.read: error processing " 
    398                     err_mess += " process term\n  %s" % sys.exc_value 
    399                     self.errors.append(err_mess) 
    400                     logging.error(err_mess) 
    401              
    402             note_list = item.xpath('ns:SASprocessnote', 
    403                                    namespaces={'ns': CANSAS_NS}) 
    404             for note in note_list: 
    405                 if note.text is not None: 
    406                     process.notes.append(note.text.strip()) 
    407              
    408             data_info.process.append(process) 
    409              
    410         # Data info ###################### 
    411         nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS}) 
    412         if len(nodes) > 1: 
    413             msg = "CanSAS reader is not compatible with multiple" 
    414             msg += " SASdata entries" 
    415             raise RuntimeError, msg 
    416          
    417         nodes = dom.xpath('ns:SASdata/ns:Idata', namespaces={'ns': CANSAS_NS}) 
    418  
    419         x = numpy.zeros(0) 
    420         y = numpy.zeros(0) 
    421         dx = numpy.zeros(0) 
    422         dy = numpy.zeros(0) 
    423         dxw = numpy.zeros(0) 
    424         dxl = numpy.zeros(0) 
    425          
    426         for item in nodes: 
    427             _x, attr = get_float('ns:Q', item) 
    428             _dx, attr_d = get_float('ns:Qdev', item) 
    429             _dxl, attr_l = get_float('ns:dQl', item) 
    430             _dxw, attr_w = get_float('ns:dQw', item) 
    431             if _dx == None: 
    432                 _dx = 0.0 
    433             if _dxl == None: 
    434                 _dxl = 0.0 
    435             if _dxw == None: 
    436                 _dxw = 0.0 
    437                  
    438             if 'unit' in attr and \ 
    439                 attr['unit'].lower() != data_info.x_unit.lower(): 
    440                 if HAS_CONVERTER == True: 
    441                     try: 
    442                         data_conv_q = Converter(attr['unit']) 
    443                         _x = data_conv_q(_x, units=data_info.x_unit) 
    444                     except: 
    445                         msg = "CanSAS reader: could not convert " 
    446                         msg += "Q unit [%s]; " % attr['unit'], 
    447                         msg += "expecting [%s]\n  %s" % (data_info.x_unit, 
    448                                                          sys.exc_value) 
    449                         raise ValueError, msg 
    450                          
     356                #Get the information from the node 
     357                node_value = node.text 
     358                if node_value is not None: 
     359                    node_value = node_value.strip().replace("\r"," ").replace("\n"," ") 
     360                if node_value == "": 
     361                    node_value = None 
     362                 
     363                # If the value is a float, compile with units. 
     364                if ns_datatype == "float": 
     365                    # If an empty value is given, store as zero. 
     366                    if node_value is None: 
     367                        node_value = "0.0" 
     368                    elif node_value.isspace(): 
     369                        node_value = "0.0" 
     370                    node_value, unit = self._unit_conversion(new_current_level, attr, data1D, node_value, optional) 
     371                     
     372                # If appending to a dictionary (meta_data | run_name), name sure the key is unique 
     373                if ns_variable == "{0}.meta_data[\"{2}\"] = \"{1}\"": 
     374                    # If we are within a Process, Detector, Collimation or Aperture instance, pull out old data1D 
     375                    tagname = self._create_unique_key(data1D.meta_data, tagname, 0) 
     376                    if isinstance(data1D, Data1D) == False: 
     377                        store_me = ns_variable.format("data1D", node_value, tagname) 
     378                        extras.append(store_me) 
     379                        ns_variable = None 
     380                if ns_variable == "{0}.run_name[\"{2}\"] = \"{1}\"": 
     381                    tagname = self._create_unique_key(data1D.run_name, tagname, 0) 
     382                 
     383                # Check for Data1D object and any extra commands to save 
     384                if isinstance(data1D, Data1D): 
     385                    for item in extras: 
     386                        exec item 
     387                # Don't bother saving empty information unless it is a float 
     388                if ns_variable is not None and node_value is not None and node_value.isspace() == False: 
     389                    # Format a string and then execute it. 
     390                    store_me = ns_variable.format("data1D", node_value, tagname) 
     391                    exec store_me 
     392                # Get attributes and process them 
     393                if attr is not None: 
     394                    for key in node.keys(): 
     395                        try: 
     396                            cansas_attrib = new_current_level.get("attributes").get(key) 
     397                            attrib_variable = cansas_attrib.get("variable") 
     398                            if key == 'unit' and unit != '': 
     399                                attrib_value = unit 
     400                            else: 
     401                                attrib_value = node.attrib[key] 
     402                            store_attr = attrib_variable.format("data1D", attrib_value, key) 
     403                            exec store_attr 
     404                        except AttributeError as e: 
     405                            pass 
     406                             
     407                      
     408            except Exception as e: 
     409                exc_type, exc_obj, exc_tb = sys.exc_info() 
     410                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1] 
     411                print(e, exc_type, fname, exc_tb.tb_lineno, tagname, exc_obj) 
     412            finally: 
     413                # Save special cases in original data1D object and then restore the data1D 
     414                if tagname_original == "SASdetector": 
     415                    save_data1D.detector.append(data1D) 
     416                elif tagname_original == "SAScollimation": 
     417                    save_data1D.collimation.append(data1D) 
     418                elif tagname_original == "SASprocess": 
     419                    save_data1D.process.append(data1D) 
     420                elif tagname_original == "aperture": 
     421                    save_data1D.aperture.append(data1D) 
    451422                else: 
    452                     msg = "CanSAS reader: unrecognized Q unit [%s]; "\ 
    453                     % attr['unit'] 
    454                     msg += "expecting [%s]" % data_info.x_unit 
    455                     raise ValueError, msg 
    456                          
    457             # Error in Q 
    458             if 'unit' in attr_d and \ 
    459                 attr_d['unit'].lower() != data_info.x_unit.lower(): 
    460                 if HAS_CONVERTER == True: 
    461                     try: 
    462                         data_conv_q = Converter(attr_d['unit']) 
    463                         _dx = data_conv_q(_dx, units=data_info.x_unit) 
    464                     except: 
    465                         msg = "CanSAS reader: could not convert dQ unit [%s]; "\ 
    466                         % attr['unit'] 
    467                         msg += " expecting " 
    468                         msg += "[%s]\n  %s" % (data_info.x_unit, sys.exc_value) 
    469                         raise ValueError, msg 
    470                          
    471                 else: 
    472                     msg = "CanSAS reader: unrecognized dQ unit [%s]; "\ 
    473                     % attr['unit'] 
    474                     msg += "expecting [%s]" % data_info.x_unit 
    475                     raise ValueError, msg 
    476                          
    477             # Slit length 
    478             if 'unit' in attr_l and \ 
    479                 attr_l['unit'].lower() != data_info.x_unit.lower(): 
    480                 if HAS_CONVERTER == True: 
    481                     try: 
    482                         data_conv_q = Converter(attr_l['unit']) 
    483                         _dxl = data_conv_q(_dxl, units=data_info.x_unit) 
    484                     except: 
    485                         msg = "CanSAS reader: could not convert dQl unit [%s];"\ 
    486                         % attr['unit'] 
    487                         msg += " expecting [%s]\n  %s" % (data_info.x_unit, 
    488                                                           sys.exc_value) 
    489                         raise ValueError, msg  
    490                 else: 
    491                     msg = "CanSAS reader: unrecognized dQl unit [%s];"\ 
    492                     % attr['unit'] 
    493                     msg += " expecting [%s]" % data_info.x_unit 
    494                     raise ValueError, msg 
    495                          
    496             # Slit width 
    497             if 'unit' in attr_w and \ 
    498             attr_w['unit'].lower() != data_info.x_unit.lower(): 
    499                 if HAS_CONVERTER == True: 
    500                     try: 
    501                         data_conv_q = Converter(attr_w['unit']) 
    502                         _dxw = data_conv_q(_dxw, units=data_info.x_unit) 
    503                     except: 
    504                         msg = "CanSAS reader: could not convert dQw unit [%s];"\ 
    505                         % attr['unit'] 
    506                         msg += " expecting [%s]\n  %s" % (data_info.x_unit, 
    507                                                           sys.exc_value) 
    508                         raise ValueError, msg 
    509                          
    510                 else: 
    511                     msg = "CanSAS reader: unrecognized dQw unit [%s];"\ 
    512                     % attr['unit'] 
    513                     msg += " expecting [%s]" % data_info.x_unit 
    514                     raise ValueError, msg 
    515             _y, attr = get_float('ns:I', item) 
    516             _dy, attr_d = get_float('ns:Idev', item) 
    517             if _dy == None: 
    518                 _dy = 0.0 
    519             if 'unit' in attr and \ 
    520             attr['unit'].lower() != data_info.y_unit.lower(): 
    521                 if HAS_CONVERTER == True: 
    522                     try: 
    523                         data_conv_i = Converter(attr['unit']) 
    524                         _y = data_conv_i(_y, units=data_info.y_unit) 
    525                     except: 
    526                         if attr['unit'].lower() == 'count': 
    527                             pass 
    528                         else: 
    529                             msg = "CanSAS reader: could not" 
    530                             msg += " convert I(q) unit [%s];" % str(attr['unit']) 
    531                             msg += " expecting [%s]\n" % str(data_info.y_unit) 
    532                             msg += "  %s" % str(sys.exc_value) 
    533                             raise ValueError, msg 
    534                 else: 
    535                     msg = "CanSAS reader: unrecognized I(q) unit [%s];"\ 
    536                     % attr['unit'] 
    537                     msg += " expecting [%s]" % data_info.y_unit 
    538                     raise ValueError, msg  
    539                          
    540             if 'unit' in attr_d and \ 
    541             attr_d['unit'].lower() != data_info.y_unit.lower(): 
    542                 if HAS_CONVERTER == True: 
    543                     try: 
    544                         data_conv_i = Converter(attr_d['unit']) 
    545                         _dy = data_conv_i(_dy, units=data_info.y_unit) 
    546                     except: 
    547                         if attr_d['unit'].lower() == 'count': 
    548                             pass 
    549                         else: 
    550                             msg = "CanSAS reader: could not convert dI(q) unit " 
    551                             msg += "[%s]; expecting [%s]\n  %s" % (attr_d['unit'], 
    552                                                  data_info.y_unit, sys.exc_value) 
    553                             raise ValueError, msg 
    554                 else: 
    555                     msg = "CanSAS reader: unrecognized dI(q) unit [%s]; "\ 
    556                     % attr_d['unit'] 
    557                     msg += "expecting [%s]" % data_info.y_unit 
    558                     raise ValueError, msg 
    559                  
    560             if _x is not None and _y is not None: 
    561                 x = numpy.append(x, _x) 
    562                 y = numpy.append(y, _y) 
    563                 dx = numpy.append(dx, _dx) 
    564                 dy = numpy.append(dy, _dy) 
    565                 dxl = numpy.append(dxl, _dxl) 
    566                 dxw = numpy.append(dxw, _dxw) 
    567         # Zeros in dx, dy 
    568         if not numpy.all(dx == 0): 
    569             dx[dx == 0] = _ZERO 
    570         if not numpy.all(dy == 0): 
    571             dy[dy == 0] = _ZERO 
    572         
    573         data_info.x = x[x != 0] 
    574         data_info.y = y[x != 0] 
    575         data_info.dx = dx[x != 0] 
    576          
    577         data_info.dy = dy[x != 0] 
    578         data_info.dxl = dxl[x != 0] 
    579         data_info.dxw = dxw[x != 0] 
    580          
    581         data_conv_q = None 
    582         data_conv_i = None 
    583          
    584         if HAS_CONVERTER == True and data_info.x_unit != '1/A': 
    585             data_conv_q = Converter('1/A') 
    586             # Test it 
    587             data_conv_q(1.0, data_info.x_unit) 
    588              
    589         if HAS_CONVERTER == True and data_info.y_unit != '1/cm': 
    590             data_conv_i = Converter('1/cm') 
    591             # Test it 
    592             data_conv_i(1.0, data_info.y_unit) 
    593                  
    594         if data_conv_q is not None: 
    595             data_info.xaxis("\\rm{Q}", data_info.x_unit) 
    596         else: 
    597             data_info.xaxis("\\rm{Q}", 'A^{-1}') 
    598         if data_conv_i is not None: 
    599             data_info.yaxis("\\rm{Intensity}", data_info.y_unit) 
    600         else: 
    601             data_info.yaxis("\\rm{Intensity}", "cm^{-1}") 
    602  
    603         return data_info 
    604  
     423                    save_data1D = data1D 
     424                data1D = save_data1D 
     425                # Remove tagname from ns to restore original base 
     426                ns.remove(tagname_original) 
     427         
     428        return data1D, extras 
     429         
    605430    def _to_xml_doc(self, datainfo): 
    606431        """ 
     
    615440        doc = xml.dom.minidom.Document() 
    616441        main_node = doc.createElement("SASroot") 
    617         main_node.setAttribute("version", self.version) 
    618         main_node.setAttribute("xmlns", "cansas1d/%s" % self.version) 
     442        main_node.setAttribute("cansasVersion", self.cansasVersion) 
     443        main_node.setAttribute("xmlns", "cansas1d/%s" % self.cansasVersion) 
    619444        main_node.setAttribute("xmlns:xsi", 
    620445                               "http://www.w3.org/2001/XMLSchema-instance") 
    621446        main_node.setAttribute("xsi:schemaLocation", 
    622                                "cansas1d/%s http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd" % self.version) 
     447                               "cansas1d/%s http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd" % self.cansasVersion) 
    623448         
    624449        doc.appendChild(main_node) 
     
    861686        fd.write(doc.toprettyxml()) 
    862687        fd.close() 
    863          
    864     def _store_float(self, location, node, variable, storage, optional=True): 
    865         """ 
    866         Get the content of a xpath location and store 
    867         the result. Check that the units are compatible 
    868         with the destination. The value is expected to 
    869         be a float. 
    870          
    871         The xpath location might or might not exist. 
    872         If it does not exist, nothing is done 
    873          
    874         :param location: xpath location to fetch 
    875         :param node: node to read the data from 
    876         :param variable: name of the data member to store it in [string] 
    877         :param storage: data object that has the 'variable' data member 
    878         :param optional: if True, no exception will be raised 
    879             if unit conversion can't be done 
    880  
    881         :raise ValueError: raised when the units are not recognized 
    882         """ 
    883         entry = get_content(location, node) 
    884         try: 
    885             value = float(entry.text) 
    886         except: 
    887             value = None 
    888              
    889         if value is not None: 
    890             # If the entry has units, check to see that they are 
    891             # compatible with what we currently have in the data object 
    892             units = entry.get('unit') 
    893             if units is not None: 
    894                 toks = variable.split('.') 
    895                 local_unit = None 
    896                 exec "local_unit = storage.%s_unit" % toks[0] 
    897                 if local_unit != None and units.lower() != local_unit.lower(): 
    898                     if HAS_CONVERTER == True: 
    899                         try: 
    900                             conv = Converter(units) 
    901                             exec "storage.%s = %g" % (variable, 
    902                                             conv(value, units=local_unit)) 
    903                         except: 
    904                             err_mess = "CanSAS reader: could not convert" 
    905                             err_mess += " %s unit [%s]; expecting [%s]\n  %s" \ 
    906                                 % (variable, units, local_unit, sys.exc_value) 
    907                             self.errors.append(err_mess) 
    908                             if optional: 
    909                                 logging.info(err_mess) 
    910                             else: 
    911                                 raise ValueError, err_mess 
    912                     else: 
    913                         err_mess = "CanSAS reader: unrecognized %s unit [%s];"\ 
    914                         % (variable, units) 
    915                         err_mess += " expecting [%s]" % local_unit 
    916                         self.errors.append(err_mess) 
    917                         if optional: 
    918                             logging.info(err_mess) 
    919                         else: 
    920                             raise ValueError, err_mess 
    921                 else: 
    922                     exec "storage.%s = value" % variable 
    923             else: 
    924                 exec "storage.%s = value" % variable 
    925                  
    926     def _store_content(self, location, node, variable, storage): 
    927         """ 
    928         Get the content of a xpath location and store 
    929         the result. The value is treated as a string. 
    930          
    931         The xpath location might or might not exist. 
    932         If it does not exist, nothing is done 
    933          
    934         :param location: xpath location to fetch 
    935         :param node: node to read the data from 
    936         :param variable: name of the data member to store it in [string] 
    937         :param storage: data object that has the 'variable' data member 
    938          
    939         :return: return a list of errors 
    940         """ 
    941         entry = get_content(location, node) 
    942         if entry is not None and entry.text is not None: 
    943             exec "storage.%s = entry.text.strip()" % variable 
Note: See TracChangeset for help on using the changeset viewer.