source: sasview/DataLoader/readers/cansas_reader.py @ f304194

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 f304194 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
Line 
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
8copyright 2008, 2009, University of Tennessee
9"""
10# Known issue: reader not compatible with multiple SASdata entries
11# within a single SASentry. Will raise a runtime error.
12
13#TODO: check that all vectors are written only if they have at least one non-empty value
14#TODO: Writing only allows one SASentry per file. Would be best to allow multiple entries.
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.
19#TODO: Unit conversion is not complete (temperature units are missing)
20
21
22import logging
23import numpy
24import os, sys
25from DataLoader.data_info import Data1D, Collimation, Detector, Process, Aperture
26from lxml import etree
27import xml.dom.minidom
28
29has_converter = True
30try:
31    from data_util.nxsunit import Converter
32except:
33    has_converter = False
34
35CANSAS_NS = "cansas1d/1.0"
36
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
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
61        @return: Element, or None
62    """
63    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
64   
65    if len(nodes)>0:
66        return nodes[0]
67    else:
68        return None
69
70def get_float(location, node):
71    """
72        Get the content of a node as a float
73       
74        @param location: xpath location
75        @param node: node to start at
76    """
77    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
78   
79    value = None
80    attr = {}
81   
82    if len(nodes)>0:
83        try:
84            value = float(nodes[0].text)   
85        except:
86            # Could not pass, skip and return None
87            logging.error("cansas_reader.get_float: could not convert '%s' to float" % nodes[0].text)
88       
89        if nodes[0].get('unit') is not None:
90            attr['unit'] = nodes[0].get('unit')
91           
92    return value, attr
93
94           
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
106    type_name = "CanSAS 1D"
107    ## Wildcards
108    type = ["CanSAS 1D files (*.xml)|*.xml"]
109    ## List of allowed extensions
110    ext=['.xml', '.XML'] 
111   
112    def __init__(self):
113        ## List of errors
114        self.errors = []
115   
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               
134                tree = etree.parse(path, parser=etree.ETCompatXMLParser())
135                # Check the format version number
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})
140               
141                for entry in entry_list:
142                    self.errors = []
143                    sas_entry = self._parse_entry(entry)
144                    sas_entry.filename = basename
145                   
146                    # Store loading process information
147                    sas_entry.errors = self.errors
148                    sas_entry.meta_data['loader'] = self.type_name
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       
174        # Look up title     
175        self._store_content('ns:Title', dom, 'title', data_info)
176       
177        # Look up run number   
178        nodes = dom.xpath('ns:Run', namespaces={'ns': CANSAS_NS})
179        for item in nodes:   
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')
186                           
187        # Look up instrument name             
188        self._store_content('ns:SASinstrument/ns:name', dom, 'instrument', data_info)
189
190        # Notes
191        note_list = dom.xpath('ns:SASnote', namespaces={'ns': CANSAS_NS})
192        for note in note_list:
193            try:
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)
198            except:
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)
202       
203        # Sample info ###################
204        entry = get_content('ns:SASsample', dom)
205        if entry is not None:
206            data_info.sample.name = entry.get('name')
207           
208        self._store_content('ns:SASsample/ns:ID', 
209                     dom, 'ID', data_info.sample)                   
210        self._store_float('ns:SASsample/ns:thickness', 
211                     dom, 'thickness', data_info.sample)
212        self._store_float('ns:SASsample/ns:transmission', 
213                     dom, 'transmission', data_info.sample)
214        self._store_float('ns:SASsample/ns:temperature', 
215                     dom, 'temperature', data_info.sample)
216       
217        nodes = dom.xpath('ns:SASsample/ns:details', namespaces={'ns': CANSAS_NS})
218        for item in nodes:
219            try:
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)
224            except:
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)
228       
229        # Position (as a vector)
230        self._store_float('ns:SASsample/ns:position/ns:x', 
231                     dom, 'position.x', data_info.sample)         
232        self._store_float('ns:SASsample/ns:position/ns:y', 
233                     dom, 'position.y', data_info.sample)         
234        self._store_float('ns:SASsample/ns:position/ns:z', 
235                     dom, 'position.z', data_info.sample)         
236       
237        # Orientation (as a vector)
238        self._store_float('ns:SASsample/ns:orientation/ns:roll', 
239                     dom, 'orientation.x', data_info.sample)         
240        self._store_float('ns:SASsample/ns:orientation/ns:pitch', 
241                     dom, 'orientation.y', data_info.sample)         
242        self._store_float('ns:SASsample/ns:orientation/ns:yaw', 
243                     dom, 'orientation.z', data_info.sample)         
244       
245        # Source info ###################
246        entry = get_content('ns:SASinstrument/ns:SASsource', dom)
247        if entry is not None:
248            data_info.source.name = entry.get('name')
249       
250        self._store_content('ns:SASinstrument/ns:SASsource/ns:radiation', 
251                     dom, 'radiation', data_info.source)                   
252        self._store_content('ns:SASinstrument/ns:SASsource/ns:beam_shape', 
253                     dom, 'beam_shape', data_info.source)                   
254        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength', 
255                     dom, 'wavelength', data_info.source)         
256        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_min', 
257                     dom, 'wavelength_min', data_info.source)         
258        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_max', 
259                     dom, 'wavelength_max', data_info.source)         
260        self._store_float('ns:SASinstrument/ns:SASsource/ns:wavelength_spread', 
261                     dom, 'wavelength_spread', data_info.source)   
262       
263        # Beam size (as a vector)   
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')
267           
268        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:x', 
269                     dom, 'beam_size.x', data_info.source)   
270        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:y', 
271                     dom, 'beam_size.y', data_info.source)   
272        self._store_float('ns:SASinstrument/ns:SASsource/ns:beam_size/ns:z', 
273                     dom, 'beam_size.z', data_info.source)   
274       
275        # Collimation info ###################
276        nodes = dom.xpath('ns:SASinstrument/ns:SAScollimation', namespaces={'ns': CANSAS_NS})
277        for item in nodes:
278            collim = Collimation()
279            if item.get('name') is not None:
280                collim.name = item.get('name')
281            self._store_float('ns:length', item, 'length', collim) 
282           
283            # Look for apertures
284            apert_list = item.xpath('ns:aperture', namespaces={'ns': CANSAS_NS})
285            for apert in apert_list:
286                aperture =  Aperture()
287               
288                # Get the name and type of the aperture
289                aperture.name = apert.get('name')
290                aperture.type = apert.get('type')
291                   
292                self._store_float('ns:distance', apert, 'distance', aperture)   
293               
294                entry = get_content('ns:size', apert)
295                if entry is not None:
296                    aperture.size_name = entry.get('name')
297               
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)
301               
302                collim.aperture.append(aperture)
303               
304            data_info.collimation.append(collim)
305       
306        # Detector info ######################
307        nodes = dom.xpath('ns:SASinstrument/ns:SASdetector', namespaces={'ns': CANSAS_NS})
308        for item in nodes:
309           
310            detector = Detector()
311           
312            self._store_content('ns:name', item, 'name', detector)
313            self._store_float('ns:SDD', item, 'distance', detector)   
314           
315            # Detector offset (as a vector)
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)   
319           
320            # Detector orientation (as a vector)
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)   
324           
325            # Beam center (as a vector)
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)   
329           
330            # Pixel size (as a vector)
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)   
334           
335            self._store_float('ns:slit_length', item, 'slit_length', detector)
336           
337            data_info.detector.append(detector)   
338
339        # Processes info ######################
340        nodes = dom.xpath('ns:SASprocess', namespaces={'ns': CANSAS_NS})
341        for item in nodes:
342            process = Process()
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)
346           
347            term_list = item.xpath('ns:term', namespaces={'ns': CANSAS_NS})
348            for term in term_list:
349                try:
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()
355                        process.term.append(term_attr)
356                except:
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)
360           
361            note_list = item.xpath('ns:SASprocessnote', namespaces={'ns': CANSAS_NS})
362            for note in note_list:
363                if note.text is not None:
364                    process.notes.append(note.text.strip())
365           
366            data_info.process.append(process)
367           
368           
369        # Data info ######################
370        nodes = dom.xpath('ns:SASdata', namespaces={'ns': CANSAS_NS})
371        if len(nodes)>1:
372            raise RuntimeError, "CanSAS reader is not compatible with multiple SASdata entries"
373       
374        nodes = dom.xpath('ns:SASdata/ns:Idata', namespaces={'ns': CANSAS_NS})
375
376        x  = numpy.zeros(0)
377        y  = numpy.zeros(0)
378        dx = numpy.zeros(0)
379        dy = numpy.zeros(0)
380        dxw = numpy.zeros(0)
381        dxl = numpy.zeros(0)
382       
383        for item in nodes:
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)
388            if _dx == None:
389                _dx = 0.0
390            if _dxl == None:
391                _dxl = 0.0
392            if _dxw == None:
393                _dxw = 0.0
394               
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)
406            # Error in Q
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)
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)
442                   
443            _y, attr = get_float('ns:I', item)
444            _dy, attr_d = get_float('ns:Idev', item)
445            if _dy == None:
446                _dy = 0.0
447            if attr.has_key('unit') and attr['unit'].lower() != data_info.y_unit.lower():
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)
469               
470            if _x is not None and _y is not None:
471                x  = numpy.append(x, _x)
472                y  = numpy.append(y, _y)
473                dx = numpy.append(dx, _dx)
474                dy = numpy.append(dy, _dy)
475                dxl = numpy.append(dxl, _dxl)
476                dxw = numpy.append(dxw, _dxw)
477               
478           
479        data_info.x = x
480        data_info.y = y
481        data_info.dx = dx
482        data_info.dy = dy
483        data_info.dxl = dxl
484        data_info.dxw = dxw
485       
486        data_conv_q = None
487        data_conv_i = None
488       
489        if has_converter == True and data_info.x_unit != '1/A':
490            data_conv_q = Converter('1/A')
491            # Test it
492            data_conv_q(1.0, output.Q_unit)
493           
494        if has_converter == True and data_info.y_unit != '1/cm':
495            data_conv_i = Converter('1/cm')
496            # Test it
497            data_conv_i(1.0, output.I_unit)                   
498               
499        if data_conv_q is not None:
500            data_info.xaxis("\\rm{Q}", data_info.x_unit)
501        else:
502            data_info.xaxis("\\rm{Q}", 'A^{-1}')
503        if data_conv_i is not None:
504            data_info.yaxis("\\rm{Intensity}", data_info.y_unit)
505        else:
506            data_info.yaxis("\\rm{Intensity}","cm^{-1}")
507       
508        return data_info
509
510    def _to_xml_doc(self, datainfo):
511        """
512            Create an XML document to contain the content of a Data1D
513           
514            @param datainfo: Data1D object
515        """
516       
517        if not issubclass(datainfo.__class__, Data1D):
518            raise RuntimeError, "The cansas writer expects a Data1D instance"
519       
520        doc = xml.dom.minidom.Document()
521        main_node = doc.createElement("SASroot")
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       
527        doc.appendChild(main_node)
528       
529        entry_node = doc.createElement("SASentry")
530        main_node.appendChild(entry_node)
531       
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)
539       
540        # Data info
541        node = doc.createElement("SASdata")
542        entry_node.appendChild(node)
543       
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})
550            if datainfo.dx !=None and len(datainfo.dx)>=i:
551                write_node(doc, pt, "Qdev", datainfo.dx[i], {'unit':datainfo.x_unit})
552            if datainfo.dy !=None and len(datainfo.dy)>=i:
553                write_node(doc, pt, "Idev", datainfo.dy[i], {'unit':datainfo.y_unit})
554
555       
556        # Sample info
557        sample = doc.createElement("SASsample")
558        if datainfo.sample.name is not None:
559            sample.setAttribute("name", str(datainfo.sample.name))
560        entry_node.appendChild(sample)
561        write_node(doc, sample, "ID", str(datainfo.sample.ID))
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")
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})
573        if written == True:
574            sample.appendChild(pos)
575       
576        ori = doc.createElement("orientation")
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})
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")
591        if datainfo.source.name is not None:
592            source.setAttribute("name", str(datainfo.source.name))
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)
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           
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")
614            if item.name is not None:
615                coll.setAttribute("name", str(item.name))
616            instr.appendChild(coll)
617           
618            write_node(doc, coll, "length", item.length, {"unit":item.length_unit})
619           
620            for apert in item.aperture:
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)
627               
628                write_node(doc, ap, "distance", apert.distance, {"unit":apert.distance_unit})
629               
630                size = doc.createElement("size")
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)
638
639        #   Detectors
640        for item in datainfo.detector:
641            det = doc.createElement("SASdetector")
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)
647           
648            off = doc.createElement("offset")
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)
654           
655            center = doc.createElement("beam_center")
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               
662            pix = doc.createElement("pixel_size")
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               
676       
677        # Processes info
678        for item in datainfo.process:
679            node = doc.createElement("SASprocess")
680            entry_node.appendChild(node)
681
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)
691       
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)
705        # Write the file
706        fd = open(filename, 'w')
707        fd.write(doc.toprettyxml())
708        fd.close()
709       
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           
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")
794    #print reader.read("../test/latex_smeared.xml")
795   
796   
797                       
Note: See TracBrowser for help on using the repository browser.