source: sasview/DataLoader/readers/cansas_reader.py @ 32e8c78

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 32e8c78 was fe78c7b, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

DataLoader?: exception no longer raised when units are wrong for an entry; the entry is not loaded and an error is logged instead. Loader info is now stored.

  • Property mode set to 100644
File size: 35.9 KB
RevLine 
[8780e9a]1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
[b0d0723]8copyright 2008, 2009, University of Tennessee
[8780e9a]9"""
[579ba85]10# Known issue: reader not compatible with multiple SASdata entries
11# within a single SASentry. Will raise a runtime error.
[8780e9a]12
[4c00964]13#TODO: check that all vectors are written only if they have at least one non-empty value
[579ba85]14#TODO: Writing only allows one SASentry per file. Would be best to allow multiple entries.
[8780e9a]15#TODO: Store error list
16#TODO: Allow for additional meta data for each section
17#TODO: Notes need to be implemented. They can be any XML structure in version 1.0
18#      Process notes have the same problem.
[e390933]19#TODO: Unit conversion is not complete (temperature units are missing)
[8780e9a]20
21
22import logging
23import numpy
24import os, sys
[d6513cd]25from DataLoader.data_info import Data1D, Collimation, Detector, Process, Aperture
[b0d0723]26from lxml import etree
27import xml.dom.minidom
[8780e9a]28
[b39c817]29has_converter = True
30try:
31    from data_util.nxsunit import Converter
32except:
33    has_converter = False
34
[b0d0723]35CANSAS_NS = "cansas1d/1.0"
36
[4c00964]37def write_node(doc, parent, name, value, attr={}):
38    """
39        @param doc: document DOM
40        @param parent: parent node
41        @param name: tag of the element
42        @param value: value of the child text node
43        @param attr: attribute dictionary
44        @return: True if something was appended, otherwise False
45    """
46    if value is not None:
47        node = doc.createElement(name)
48        node.appendChild(doc.createTextNode(str(value)))
49        for item in attr:
50            node.setAttribute(item, attr[item])
51        parent.appendChild(node)
52        return True
53    return False
54
[8780e9a]55def get_content(location, node):
56    """
57        Get the first instance of the content of a xpath location
58       
59        @param location: xpath location
60        @param node: node to start at
[b0d0723]61        @return: Element, or None
[8780e9a]62    """
[b0d0723]63    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
64   
[8780e9a]65    if len(nodes)>0:
[b0d0723]66        return nodes[0]
67    else:
68        return None
[8780e9a]69
70def get_float(location, node):
71    """
[b0d0723]72        Get the content of a node as a float
[8780e9a]73       
74        @param location: xpath location
75        @param node: node to start at
76    """
[b0d0723]77    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
78   
[8780e9a]79    value = None
80    attr = {}
[b0d0723]81   
82    if len(nodes)>0:
[8780e9a]83        try:
[b0d0723]84            value = float(nodes[0].text)   
[8780e9a]85        except:
86            # Could not pass, skip and return None
[b0d0723]87            logging.error("cansas_reader.get_float: could not convert '%s' to float" % nodes[0].text)
[8780e9a]88       
[b0d0723]89        if nodes[0].get('unit') is not None:
90            attr['unit'] = nodes[0].get('unit')
91           
[8780e9a]92    return value, attr
93
[b39c817]94           
[8780e9a]95
96class Reader:
97    """
98        Class to load cansas 1D XML files
99       
100        Dependencies:
101            The CanSas reader requires PyXML 0.8.4 or later.
102    """
103    ## CanSAS version
104    version = '1.0'
105    ## File type
[28caa03]106    type_name = "CanSAS 1D"
107    ## Wildcards
[8780e9a]108    type = ["CanSAS 1D files (*.xml)|*.xml"]
109    ## List of allowed extensions
110    ext=['.xml', '.XML'] 
111   
[fe78c7b]112    def __init__(self):
113        ## List of errors
114        self.errors = []
115   
[8780e9a]116    def read(self, path):
117        """
118            Load data file
119           
120            @param path: file path
121            @return: Data1D object if a single SASentry was found,
122                        or a list of Data1D objects if multiple entries were found,
123                        or None of nothing was found
124            @raise RuntimeError: when the file can't be opened
125            @raise ValueError: when the length of the data vectors are inconsistent
126        """
127        output = []
128       
129        if os.path.isfile(path):
130            basename  = os.path.basename(path)
131            root, extension = os.path.splitext(basename)
132            if extension.lower() in self.ext:
133               
[b0d0723]134                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
[8780e9a]135                # Check the format version number
[b0d0723]136                # Specifying the namespace will take care of the file format version
137                root = tree.getroot()
138               
139                entry_list = root.xpath('/ns:SASroot/ns:SASentry', namespaces={'ns': CANSAS_NS})
[8780e9a]140               
141                for entry in entry_list:
[fe78c7b]142                    self.errors = []
[8780e9a]143                    sas_entry = self._parse_entry(entry)
144                    sas_entry.filename = basename
[fe78c7b]145                   
146                    # Store loading process information
147                    sas_entry.errors = self.errors
148                    sas_entry.meta_data['loader'] = self.type_name
[8780e9a]149                    output.append(sas_entry)
150               
151        else:
152            raise RuntimeError, "%s is not a file" % path
153       
154        # Return output consistent with the loader's api
155        if len(output)==0:
156            return None
157        elif len(output)==1:
158            return output[0]
159        else:
160            return output               
161               
162    def _parse_entry(self, dom):
163        """
164            Parse a SASentry
165           
166            @param node: SASentry node
167            @return: Data1D object
168        """
169        x = numpy.zeros(0)
170        y = numpy.zeros(0)
171       
172        data_info = Data1D(x, y)
173       
[b0d0723]174        # Look up title     
[fe78c7b]175        self._store_content('ns:Title', dom, 'title', data_info)
[b0d0723]176       
[579ba85]177        # Look up run number   
[b0d0723]178        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
[579ba85]179        for item in nodes:   
[b0d0723]180            if item.text is not None:
181                value = item.text.strip()
182                if len(value) > 0:
183                    data_info.run.append(value)
184                    if item.get('name') is not None:
185                        data_info.run_name[value] = item.get('name')
[579ba85]186                           
[8780e9a]187        # Look up instrument name             
[fe78c7b]188        self._store_content('ns:SASinstrument/ns:name', dom, 'instrument', data_info)
[8780e9a]189
[b0d0723]190        # Notes
191        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
[8780e9a]192        for note in note_list:
193            try:
[b0d0723]194                if note.text is not None:
195                    note_value = note.text.strip()
196                    if len(note_value) > 0:
197                        data_info.notes.append(note_value)
[8780e9a]198            except:
[fe78c7b]199                err_mess = "cansas_reader.read: error processing entry notes\n  %s" % sys.exc_value
200                self.errors.append(err_mess)
201                logging.error(err_mess)
[8780e9a]202       
203        # Sample info ###################
[b0d0723]204        entry = get_content('ns:SASsample', dom)
205        if entry is not None:
206            data_info.sample.name = entry.get('name')
[579ba85]207           
[fe78c7b]208        self._store_content('ns:SASsample/ns:ID', 
[8780e9a]209                     dom, 'ID', data_info.sample)                   
[fe78c7b]210        self._store_float('ns:SASsample/ns:thickness', 
[8780e9a]211                     dom, 'thickness', data_info.sample)
[fe78c7b]212        self._store_float('ns:SASsample/ns:transmission', 
[8780e9a]213                     dom, 'transmission', data_info.sample)
[fe78c7b]214        self._store_float('ns:SASsample/ns:temperature', 
[8780e9a]215                     dom, 'temperature', data_info.sample)
[b0d0723]216       
217        nodes = dom.xpath('ns:SASsample/ns:details', namespaces={'ns': CANSAS_NS})
[8780e9a]218        for item in nodes:
219            try:
[b0d0723]220                if item.text is not None:
221                    detail_value = item.text.strip()
222                    if len(detail_value) > 0:
223                        data_info.sample.details.append(detail_value)
[8780e9a]224            except:
[fe78c7b]225                err_mess = "cansas_reader.read: error processing sample details\n  %s" % sys.exc_value
226                self.errors.append(err_mess)
227                logging.error(err_mess)
[8780e9a]228       
229        # Position (as a vector)
[fe78c7b]230        self._store_float('ns:SASsample/ns:position/ns:x', 
[8780e9a]231                     dom, 'position.x', data_info.sample)         
[fe78c7b]232        self._store_float('ns:SASsample/ns:position/ns:y', 
[8780e9a]233                     dom, 'position.y', data_info.sample)         
[fe78c7b]234        self._store_float('ns:SASsample/ns:position/ns:z', 
[8780e9a]235                     dom, 'position.z', data_info.sample)         
236       
237        # Orientation (as a vector)
[fe78c7b]238        self._store_float('ns:SASsample/ns:orientation/ns:roll', 
[8780e9a]239                     dom, 'orientation.x', data_info.sample)         
[fe78c7b]240        self._store_float('ns:SASsample/ns:orientation/ns:pitch', 
[8780e9a]241                     dom, 'orientation.y', data_info.sample)         
[fe78c7b]242        self._store_float('ns:SASsample/ns:orientation/ns:yaw', 
[8780e9a]243                     dom, 'orientation.z', data_info.sample)         
244       
245        # Source info ###################
[b0d0723]246        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
247        if entry is not None:
248            data_info.source.name = entry.get('name')
[4c00964]249       
[fe78c7b]250        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation', 
[8780e9a]251                     dom, 'radiation', data_info.source)                   
[fe78c7b]252        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape', 
[8780e9a]253                     dom, 'beam_shape', data_info.source)                   
[fe78c7b]254        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength', 
[8780e9a]255                     dom, 'wavelength', data_info.source)         
[fe78c7b]256        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min', 
[8780e9a]257                     dom, 'wavelength_min', data_info.source)         
[fe78c7b]258        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max', 
[8780e9a]259                     dom, 'wavelength_max', data_info.source)         
[fe78c7b]260        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread', 
[8780e9a]261                     dom, 'wavelength_spread', data_info.source)   
262       
[579ba85]263        # Beam size (as a vector)   
[b0d0723]264        entry = get_content('ns:SASinstrument/ns:SASsource/ns:beam_size', dom)
265        if entry is not None:
266            data_info.source.beam_size_name = entry.get('name')
[579ba85]267           
[fe78c7b]268        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x', 
[8780e9a]269                     dom, 'beam_size.x', data_info.source)   
[fe78c7b]270        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y', 
[8780e9a]271                     dom, 'beam_size.y', data_info.source)   
[fe78c7b]272        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z', 
[8780e9a]273                     dom, 'beam_size.z', data_info.source)   
274       
275        # Collimation info ###################
[b0d0723]276        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation', namespaces={'ns': CANSAS_NS})
[8780e9a]277        for item in nodes:
278            collim = Collimation()
[b0d0723]279            if item.get('name') is not None:
280                collim.name = item.get('name')
[fe78c7b]281            self._store_float('ns:length', item, 'length', collim) 
[8780e9a]282           
283            # Look for apertures
[b0d0723]284            apert_list = item.xpath('ns:aperture', namespaces={'ns': CANSAS_NS})
[8780e9a]285            for apert in apert_list:
[d6513cd]286                aperture =  Aperture()
[4c00964]287               
288                # Get the name and type of the aperture
[b0d0723]289                aperture.name = apert.get('name')
290                aperture.type = apert.get('type')
[4c00964]291                   
[fe78c7b]292                self._store_float('ns:distance', apert, 'distance', aperture)   
[579ba85]293               
[b0d0723]294                entry = get_content('ns:size', apert)
295                if entry is not None:
296                    aperture.size_name = entry.get('name')
[579ba85]297               
[fe78c7b]298                self._store_float('ns:size/ns:x', apert, 'size.x', aperture)   
299                self._store_float('ns:size/ns:y', apert, 'size.y', aperture)   
300                self._store_float('ns:size/ns:z', apert, 'size.z', aperture)
[8780e9a]301               
302                collim.aperture.append(aperture)
303               
304            data_info.collimation.append(collim)
305       
306        # Detector info ######################
[b0d0723]307        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector', namespaces={'ns': CANSAS_NS})
[8780e9a]308        for item in nodes:
309           
310            detector = Detector()
311           
[fe78c7b]312            self._store_content('ns:name', item, 'name', detector)
313            self._store_float('ns:SDD', item, 'distance', detector)   
[8780e9a]314           
315            # Detector offset (as a vector)
[fe78c7b]316            self._store_float('ns:offset/ns:x', item, 'offset.x', detector)   
317            self._store_float('ns:offset/ns:y', item, 'offset.y', detector)   
318            self._store_float('ns:offset/ns:z', item, 'offset.z', detector)   
[8780e9a]319           
320            # Detector orientation (as a vector)
[fe78c7b]321            self._store_float('ns:orientation/ns:roll',  item, 'orientation.x', detector)   
322            self._store_float('ns:orientation/ns:pitch', item, 'orientation.y', detector)   
323            self._store_float('ns:orientation/ns:yaw',   item, 'orientation.z', detector)   
[8780e9a]324           
325            # Beam center (as a vector)
[fe78c7b]326            self._store_float('ns:beam_center/ns:x', item, 'beam_center.x', detector)   
327            self._store_float('ns:beam_center/ns:y', item, 'beam_center.y', detector)   
328            self._store_float('ns:beam_center/ns:z', item, 'beam_center.z', detector)   
[8780e9a]329           
330            # Pixel size (as a vector)
[fe78c7b]331            self._store_float('ns:pixel_size/ns:x', item, 'pixel_size.x', detector)   
332            self._store_float('ns:pixel_size/ns:y', item, 'pixel_size.y', detector)   
333            self._store_float('ns:pixel_size/ns:z', item, 'pixel_size.z', detector)   
[8780e9a]334           
[fe78c7b]335            self._store_float('ns:slit_length', item, 'slit_length', detector)
[8780e9a]336           
337            data_info.detector.append(detector)   
338
339        # Processes info ######################
[b0d0723]340        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
[8780e9a]341        for item in nodes:
342            process = Process()
[fe78c7b]343            self._store_content('ns:name', item, 'name', process)
344            self._store_content('ns:date', item, 'date', process)
345            self._store_content('ns:description', item, 'description', process)
[8780e9a]346           
[b0d0723]347            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
[8780e9a]348            for term in term_list:
349                try:
[b0d0723]350                    term_attr = {}
351                    for attr in term.keys():
352                        term_attr[attr] = term.get(attr).strip()
353                    if term.text is not None:
354                        term_attr['value'] = term.text.strip()
[8780e9a]355                        process.term.append(term_attr)
356                except:
[fe78c7b]357                    err_mess = "cansas_reader.read: error processing process term\n  %s" % sys.exc_value
358                    self.errors.append(err_mess)
359                    logging.error(err_mess)
[8780e9a]360           
[b0d0723]361            note_list = item.xpath('ns:SASprocessnote', namespaces={'ns': CANSAS_NS})
[8780e9a]362            for note in note_list:
[b0d0723]363                if note.text is not None:
364                    process.notes.append(note.text.strip())
[8780e9a]365           
366            data_info.process.append(process)
367           
368           
369        # Data info ######################
[b0d0723]370        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
[579ba85]371        if len(nodes)>1:
372            raise RuntimeError, "CanSAS reader is not compatible with multiple SASdata entries"
373       
[b0d0723]374        nodes = dom.xpath('ns:SASdata/ns:Idata', namespaces={'ns': CANSAS_NS})
375
[8780e9a]376        x  = numpy.zeros(0)
377        y  = numpy.zeros(0)
378        dx = numpy.zeros(0)
379        dy = numpy.zeros(0)
[d00f8ff]380        dxw = numpy.zeros(0)
381        dxl = numpy.zeros(0)
[8780e9a]382       
383        for item in nodes:
[b0d0723]384            _x, attr = get_float('ns:Q', item)
385            _dx, attr_d = get_float('ns:Qdev', item)
386            _dxl, attr_l = get_float('ns:dQl', item)
387            _dxw, attr_w = get_float('ns:dQw', item)
[8780e9a]388            if _dx == None:
389                _dx = 0.0
[d00f8ff]390            if _dxl == None:
391                _dxl = 0.0
392            if _dxw == None:
393                _dxw = 0.0
[8780e9a]394               
[e390933]395            if attr.has_key('unit') and attr['unit'].lower() != data_info.x_unit.lower():
396                if has_converter==True:
397                    try:
398                        data_conv_q = Converter(attr['unit'])
399                        _x = data_conv_q(_x, units=data_info.x_unit)
400                    except:
401                        raise ValueError, "CanSAS reader: could not convert Q unit [%s]; expecting [%s]\n  %s" \
402                        % (attr['unit'], data_info.x_unit, sys.exc_value)
403                else:
404                    raise ValueError, "CanSAS reader: unrecognized Q unit [%s]; expecting [%s]" \
405                        % (attr['unit'], data_info.x_unit)
[d00f8ff]406            # Error in Q
[e390933]407            if attr_d.has_key('unit') and attr_d['unit'].lower() != data_info.x_unit.lower():
408                if has_converter==True:
409                    try:
410                        data_conv_q = Converter(attr_d['unit'])
411                        _dx = data_conv_q(_dx, units=data_info.x_unit)
412                    except:
413                        raise ValueError, "CanSAS reader: could not convert dQ unit [%s]; expecting [%s]\n  %s" \
414                        % (attr['unit'], data_info.x_unit, sys.exc_value)
415                else:
416                    raise ValueError, "CanSAS reader: unrecognized dQ unit [%s]; expecting [%s]" \
417                        % (attr['unit'], data_info.x_unit)
[d00f8ff]418            # Slit length
419            if attr_l.has_key('unit') and attr_l['unit'].lower() != data_info.x_unit.lower():
420                if has_converter==True:
421                    try:
422                        data_conv_q = Converter(attr_l['unit'])
423                        _dxl = data_conv_q(_dxl, units=data_info.x_unit)
424                    except:
425                        raise ValueError, "CanSAS reader: could not convert dQl unit [%s]; expecting [%s]\n  %s" \
426                        % (attr['unit'], data_info.x_unit, sys.exc_value)
427                else:
428                    raise ValueError, "CanSAS reader: unrecognized dQl unit [%s]; expecting [%s]" \
429                        % (attr['unit'], data_info.x_unit)
430            # Slit width
431            if attr_w.has_key('unit') and attr_w['unit'].lower() != data_info.x_unit.lower():
432                if has_converter==True:
433                    try:
434                        data_conv_q = Converter(attr_w['unit'])
435                        _dxw = data_conv_q(_dxw, units=data_info.x_unit)
436                    except:
437                        raise ValueError, "CanSAS reader: could not convert dQw unit [%s]; expecting [%s]\n  %s" \
438                        % (attr['unit'], data_info.x_unit, sys.exc_value)
439                else:
440                    raise ValueError, "CanSAS reader: unrecognized dQw unit [%s]; expecting [%s]" \
441                        % (attr['unit'], data_info.x_unit)
[e390933]442                   
[b0d0723]443            _y, attr = get_float('ns:I', item)
444            _dy, attr_d = get_float('ns:Idev', item)
[8780e9a]445            if _dy == None:
446                _dy = 0.0
447            if attr.has_key('unit') and attr['unit'].lower() != data_info.y_unit.lower():
[e390933]448                if has_converter==True:
449                    try:
450                        data_conv_i = Converter(attr['unit'])
451                        _y = data_conv_i(_y, units=data_info.y_unit)
452                    except:
453                        raise ValueError, "CanSAS reader: could not convert I(q) unit [%s]; expecting [%s]\n  %s" \
454                        % (attr['unit'], data_info.y_unit, sys.exc_value)
455                else:
456                    raise ValueError, "CanSAS reader: unrecognized I(q) unit [%s]; expecting [%s]" \
457                        % (attr['unit'], data_info.y_unit)
458            if attr_d.has_key('unit') and attr_d['unit'].lower() != data_info.y_unit.lower():
459                if has_converter==True:
460                    try:
461                        data_conv_i = Converter(attr_d['unit'])
462                        _dy = data_conv_i(_dy, units=data_info.y_unit)
463                    except:
464                        raise ValueError, "CanSAS reader: could not convert dI(q) unit [%s]; expecting [%s]\n  %s" \
465                        % (attr_d['unit'], data_info.y_unit, sys.exc_value)
466                else:
467                    raise ValueError, "CanSAS reader: unrecognized dI(q) unit [%s]; expecting [%s]" \
468                        % (attr_d['unit'], data_info.y_unit)
[8780e9a]469               
470            if _x is not None and _y is not None:
471                x  = numpy.append(x, _x)
[579ba85]472                y  = numpy.append(y, _y)
473                dx = numpy.append(dx, _dx)
474                dy = numpy.append(dy, _dy)
[d00f8ff]475                dxl = numpy.append(dxl, _dxl)
476                dxw = numpy.append(dxw, _dxw)
477               
[8780e9a]478           
479        data_info.x = x
480        data_info.y = y
481        data_info.dx = dx
482        data_info.dy = dy
[d00f8ff]483        data_info.dxl = dxl
484        data_info.dxw = dxw
[d6513cd]485       
486        data_conv_q = None
487        data_conv_i = None
488       
[ca10d8e]489        if has_converter == True and data_info.x_unit != '1/A':
490            data_conv_q = Converter('1/A')
[d6513cd]491            # Test it
492            data_conv_q(1.0, output.Q_unit)
493           
[ca10d8e]494        if has_converter == True and data_info.y_unit != '1/cm':
495            data_conv_i = Converter('1/cm')
[d6513cd]496            # Test it
[e390933]497            data_conv_i(1.0, output.I_unit)                   
498               
[99d1af6]499        if data_conv_q is not None:
[d6513cd]500            data_info.xaxis("\\rm{Q}", data_info.x_unit)
[99d1af6]501        else:
502            data_info.xaxis("\\rm{Q}", 'A^{-1}')
503        if data_conv_i is not None:
[0e2aa40]504            data_info.yaxis("\\rm{Intensity}", data_info.y_unit)
[99d1af6]505        else:
[0e2aa40]506            data_info.yaxis("\\rm{Intensity}","cm^{-1}")
[99d1af6]507       
[8780e9a]508        return data_info
509
[b3de3a45]510    def _to_xml_doc(self, datainfo):
[4c00964]511        """
[b3de3a45]512            Create an XML document to contain the content of a Data1D
[4c00964]513           
514            @param datainfo: Data1D object
515        """
516       
[7d8094b]517        if not issubclass(datainfo.__class__, Data1D):
[4c00964]518            raise RuntimeError, "The cansas writer expects a Data1D instance"
519       
520        doc = xml.dom.minidom.Document()
521        main_node = doc.createElement("SASroot")
[fee780b]522        main_node.setAttribute("version", self.version)
523        main_node.setAttribute("xmlns", "cansas1d/%s" % self.version)
524        main_node.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
525        main_node.setAttribute("xsi:schemaLocation", "cansas1d/%s http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd" % self.version)
526       
[4c00964]527        doc.appendChild(main_node)
528       
529        entry_node = doc.createElement("SASentry")
530        main_node.appendChild(entry_node)
531       
[579ba85]532        write_node(doc, entry_node, "Title", datainfo.title)
533       
534        for item in datainfo.run:
535            runname = {}
536            if datainfo.run_name.has_key(item) and len(str(datainfo.run_name[item]))>1:
537                runname = {'name': datainfo.run_name[item] }
538            write_node(doc, entry_node, "Run", item, runname)
[4c00964]539       
540        # Data info
541        node = doc.createElement("SASdata")
542        entry_node.appendChild(node)
543       
[579ba85]544        for i in range(len(datainfo.x)):
545            pt = doc.createElement("Idata")
546            node.appendChild(pt)
547            write_node(doc, pt, "Q", datainfo.x[i], {'unit':datainfo.x_unit})
548            if len(datainfo.y)>=i:
549                write_node(doc, pt, "I", datainfo.y[i], {'unit':datainfo.y_unit})
[5b396b3]550            if datainfo.dx !=None and len(datainfo.dx)>=i:
[579ba85]551                write_node(doc, pt, "Qdev", datainfo.dx[i], {'unit':datainfo.x_unit})
[f31701c]552            if datainfo.dy !=None and len(datainfo.dy)>=i:
[579ba85]553                write_node(doc, pt, "Idev", datainfo.dy[i], {'unit':datainfo.y_unit})
554
555       
[4c00964]556        # Sample info
557        sample = doc.createElement("SASsample")
[579ba85]558        if datainfo.sample.name is not None:
559            sample.setAttribute("name", str(datainfo.sample.name))
[4c00964]560        entry_node.appendChild(sample)
[579ba85]561        write_node(doc, sample, "ID", str(datainfo.sample.ID))
[4c00964]562        write_node(doc, sample, "thickness", datainfo.sample.thickness, {"unit":datainfo.sample.thickness_unit})
563        write_node(doc, sample, "transmission", datainfo.sample.transmission)
564        write_node(doc, sample, "temperature", datainfo.sample.temperature, {"unit":datainfo.sample.temperature_unit})
565       
566        for item in datainfo.sample.details:
567            write_node(doc, sample, "details", item)
568       
569        pos = doc.createElement("position")
[579ba85]570        written = write_node(doc, pos, "x", datainfo.sample.position.x, {"unit":datainfo.sample.position_unit})
571        written = written | write_node(doc, pos, "y", datainfo.sample.position.y, {"unit":datainfo.sample.position_unit})
572        written = written | write_node(doc, pos, "z", datainfo.sample.position.z, {"unit":datainfo.sample.position_unit})
[4c00964]573        if written == True:
574            sample.appendChild(pos)
575       
576        ori = doc.createElement("orientation")
[579ba85]577        written = write_node(doc, ori, "roll",  datainfo.sample.orientation.x, {"unit":datainfo.sample.orientation_unit})
578        written = written | write_node(doc, ori, "pitch", datainfo.sample.orientation.y, {"unit":datainfo.sample.orientation_unit})
579        written = written | write_node(doc, ori, "yaw",   datainfo.sample.orientation.z, {"unit":datainfo.sample.orientation_unit})
[4c00964]580        if written == True:
581            sample.appendChild(ori)
582       
583        # Instrument info
584        instr = doc.createElement("SASinstrument")
585        entry_node.appendChild(instr)
586       
587        write_node(doc, instr, "name", datainfo.instrument)
588       
589        #   Source
590        source = doc.createElement("SASsource")
[579ba85]591        if datainfo.source.name is not None:
592            source.setAttribute("name", str(datainfo.source.name))
[4c00964]593        instr.appendChild(source)
594       
595        write_node(doc, source, "radiation", datainfo.source.radiation)
596        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
[579ba85]597        size = doc.createElement("beam_size")
598        if datainfo.source.beam_size_name is not None:
599            size.setAttribute("name", str(datainfo.source.beam_size_name))
600        written = write_node(doc, size, "x", datainfo.source.beam_size.x, {"unit":datainfo.source.beam_size_unit})
601        written = written | write_node(doc, size, "y", datainfo.source.beam_size.y, {"unit":datainfo.source.beam_size_unit})
602        written = written | write_node(doc, size, "z", datainfo.source.beam_size.z, {"unit":datainfo.source.beam_size_unit})
603        if written == True:
604            source.appendChild(size)
605           
[4c00964]606        write_node(doc, source, "wavelength", datainfo.source.wavelength, {"unit":datainfo.source.wavelength_unit})
607        write_node(doc, source, "wavelength_min", datainfo.source.wavelength_min, {"unit":datainfo.source.wavelength_min_unit})
608        write_node(doc, source, "wavelength_max", datainfo.source.wavelength_max, {"unit":datainfo.source.wavelength_max_unit})
609        write_node(doc, source, "wavelength_spread", datainfo.source.wavelength_spread, {"unit":datainfo.source.wavelength_spread_unit})
610       
611        #   Collimation
612        for item in datainfo.collimation:
613            coll = doc.createElement("SAScollimation")
[579ba85]614            if item.name is not None:
615                coll.setAttribute("name", str(item.name))
[4c00964]616            instr.appendChild(coll)
617           
618            write_node(doc, coll, "length", item.length, {"unit":item.length_unit})
619           
620            for apert in item.aperture:
[579ba85]621                ap = doc.createElement("aperture")
622                if apert.name is not None:
623                    ap.setAttribute("name", str(apert.name))
624                if apert.type is not None:
625                    ap.setAttribute("type", str(apert.type))
626                coll.appendChild(ap)
[4c00964]627               
628                write_node(doc, ap, "distance", apert.distance, {"unit":apert.distance_unit})
629               
630                size = doc.createElement("size")
[579ba85]631                if apert.size_name is not None:
632                    size.setAttribute("name", str(apert.size_name))
633                written = write_node(doc, size, "x", apert.size.x, {"unit":apert.size_unit})
634                written = written | write_node(doc, size, "y", apert.size.y, {"unit":apert.size_unit})
635                written = written | write_node(doc, size, "z", apert.size.z, {"unit":apert.size_unit})
636                if written == True:
637                    ap.appendChild(size)
[4c00964]638
639        #   Detectors
640        for item in datainfo.detector:
641            det = doc.createElement("SASdetector")
[579ba85]642            written = write_node(doc, det, "name", item.name)
643            written = written | write_node(doc, det, "SDD", item.distance, {"unit":item.distance_unit})
644            written = written | write_node(doc, det, "slit_length", item.slit_length, {"unit":item.slit_length_unit})
645            if written == True:
646                instr.appendChild(det)
[4c00964]647           
648            off = doc.createElement("offset")
[579ba85]649            written = write_node(doc, off, "x", item.offset.x, {"unit":item.offset_unit})
650            written = written | write_node(doc, off, "y", item.offset.y, {"unit":item.offset_unit})
651            written = written | write_node(doc, off, "z", item.offset.z, {"unit":item.offset_unit})
652            if written == True:
653                det.appendChild(off)
[4c00964]654           
655            center = doc.createElement("beam_center")
[579ba85]656            written = write_node(doc, center, "x", item.beam_center.x, {"unit":item.beam_center_unit})
657            written = written | write_node(doc, center, "y", item.beam_center.y, {"unit":item.beam_center_unit})
658            written = written | write_node(doc, center, "z", item.beam_center.z, {"unit":item.beam_center_unit})
659            if written == True:
660                det.appendChild(center)
661               
[4c00964]662            pix = doc.createElement("pixel_size")
[579ba85]663            written = write_node(doc, pix, "x", item.pixel_size.x, {"unit":item.pixel_size_unit})
664            written = written | write_node(doc, pix, "y", item.pixel_size.y, {"unit":item.pixel_size_unit})
665            written = written | write_node(doc, pix, "z", item.pixel_size.z, {"unit":item.pixel_size_unit})
666            if written == True:
667                det.appendChild(pix)
668               
669            ori = doc.createElement("orientation")
670            written = write_node(doc, ori, "roll",  item.orientation.x, {"unit":item.orientation_unit})
671            written = written | write_node(doc, ori, "pitch", item.orientation.y, {"unit":item.orientation_unit})
672            written = written | write_node(doc, ori, "yaw",   item.orientation.z, {"unit":item.orientation_unit})
673            if written == True:
674                det.appendChild(ori)
675               
[4c00964]676       
[579ba85]677        # Processes info
[4c00964]678        for item in datainfo.process:
679            node = doc.createElement("SASprocess")
680            entry_node.appendChild(node)
681
[579ba85]682            write_node(doc, node, "name", item.name)
683            write_node(doc, node, "date", item.date)
684            write_node(doc, node, "description", item.description)
685            for term in item.term:
686                value = term['value']
687                del term['value']
688                write_node(doc, node, "term", value, term)
689            for note in item.notes:
690                write_node(doc, node, "SASprocessnote", note)
[4c00964]691       
[b3de3a45]692        # Return the document, and the SASentry node associated with
693        # the data we just wrote
694        return doc, entry_node
695           
696    def write(self, filename, datainfo):
697        """
698            Write the content of a Data1D as a CanSAS XML file
699           
700            @param filename: name of the file to write
701            @param datainfo: Data1D object
702        """
703        # Create XML document
704        doc, sasentry = self._to_xml_doc(datainfo)
[4c00964]705        # Write the file
706        fd = open(filename, 'w')
707        fd.write(doc.toprettyxml())
708        fd.close()
709       
[fe78c7b]710    def _store_float(self, location, node, variable, storage, optional=True):
711        """
712            Get the content of a xpath location and store
713            the result. Check that the units are compatible
714            with the destination. The value is expected to
715            be a float.
716           
717            The xpath location might or might not exist.
718            If it does not exist, nothing is done
719           
720            @param location: xpath location to fetch
721            @param node: node to read the data from
722            @param variable: name of the data member to store it in [string]
723            @param storage: data object that has the 'variable' data member
724            @param optional: if True, no exception will be raised if unit conversion can't be done
725   
726            @raise ValueError: raised when the units are not recognized
727        """
728        entry = get_content(location, node)
729        try:
730            value = float(entry.text)
731        except:
732            value = None
733           
734        if value is not None:
735            # If the entry has units, check to see that they are
736            # compatible with what we currently have in the data object
737            units = entry.get('unit')
738            if units is not None:
739                toks = variable.split('.')
740                exec "local_unit = storage.%s_unit" % toks[0]
741                if units.lower()!=local_unit.lower():
742                    if has_converter==True:
743                        try:
744                            conv = Converter(units)
745                            exec "storage.%s = %g" % (variable, conv(value, units=local_unit))
746                        except:
747                            err_mess = "CanSAS reader: could not convert %s unit [%s]; expecting [%s]\n  %s" \
748                                % (variable, units, local_unit, sys.exc_value)
749                            self.errors.append(err_mess)
750                            if optional:
751                                logging.info(err_mess)
752                            else:
753                                raise ValueError, err_mess
754                    else:
755                        err_mess = "CanSAS reader: unrecognized %s unit [%s]; expecting [%s]" \
756                            % (variable, units, local_unit)
757                        self.errors.append(err_mess)
758                        if optional:
759                            logging.info(err_mess)
760                        else:
761                            raise ValueError, err_mess
762                else:
763                    exec "storage.%s = value" % variable
764            else:
765                exec "storage.%s = value" % variable
766               
767    def _store_content(self, location, node, variable, storage):
768        """
769            Get the content of a xpath location and store
770            the result. The value is treated as a string.
771           
772            The xpath location might or might not exist.
773            If it does not exist, nothing is done
774           
775            @param location: xpath location to fetch
776            @param node: node to read the data from
777            @param variable: name of the data member to store it in [string]
778            @param storage: data object that has the 'variable' data member
779            @return: return a list of errors
780        """
781        entry = get_content(location, node)
782        if entry is not None and entry.text is not None:
783            exec "storage.%s = entry.text.strip()" % variable
784
785           
786           
[8780e9a]787if __name__ == "__main__": 
788    logging.basicConfig(level=logging.ERROR,
789                        format='%(asctime)s %(levelname)s %(message)s',
790                        filename='cansas_reader.log',
791                        filemode='w')
792    reader = Reader()
793    print reader.read("../test/cansas1d.xml")
[b0d0723]794    #print reader.read("../test/latex_smeared.xml")
[8780e9a]795   
796   
797                       
Note: See TracBrowser for help on using the repository browser.