source: sasview/src/sans/dataloader/readers/cansas_reader.py @ 92c8fec

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 92c8fec was 92c8fec, checked in by Jeff Krzywon <jeffery.krzywon@…>, 10 years ago

Better error handling when loading cansas data with improper units.

  • Property mode set to 100644
File size: 35.9 KB
Line 
1"""
2    CanSAS data reader - new recursive cansas_version.
3"""
4############################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7#project funded by the US National Science Foundation.
8#If you use DANSE applications to do scientific research that leads to
9#publication, we ask that you acknowledge the use of the software with the
10#following sentence:
11#This work benefited from DANSE software developed under NSF award DMR-0520547.
12#copyright 2008,2009 University of Tennessee
13#############################################################################
14
15import logging
16import numpy
17import os
18import sys
19from sans.dataloader.data_info import Data1D
20from sans.dataloader.data_info import Collimation
21from sans.dataloader.data_info import TransmissionSpectrum
22from sans.dataloader.data_info import Detector
23from sans.dataloader.data_info import Process
24from sans.dataloader.data_info import Aperture
25import sans.dataloader.readers.xml_reader as xml_reader
26import xml.dom.minidom
27from sans.dataloader.readers.cansas_constants import cansasConstants
28
29_ZERO = 1e-16
30HAS_CONVERTER = True
31try:
32    from sans.data_util.nxsunit import Converter
33except:
34    HAS_CONVERTER = False
35
36constants = cansasConstants()   
37CANSAS_FORMAT = constants.format
38CANSAS_NS = constants.ns
39ALLOW_ALL = True
40
41
42def get_content(location, node):
43    """
44    Get the first instance of the content of a xpath location.
45   
46    :param location: xpath location
47    :param node: node to start at
48   
49    :return: Element, or None
50    """
51    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
52   
53    if len(nodes) > 0:
54        return nodes[0]
55    else:
56        return None
57
58
59def get_float(location, node):
60    """
61    Get the content of a node as a float
62   
63    :param location: xpath location
64    :param node: node to start at
65    """
66    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
67   
68    value = None
69    attr = {}
70    if len(nodes) > 0:
71        try:
72            value = float(nodes[0].text)
73        except:
74            # Could not pass, skip and return None
75            msg = "cansas_reader.get_float: could not "
76            msg += " convert '%s' to float" % nodes[0].text
77            logging.error(msg)
78        if nodes[0].get('unit') is not None:
79            attr['unit'] = nodes[0].get('unit')
80    return value, attr
81
82
83def write_node(doc, parent, name, value, attr={}):
84    """
85    :param doc: document DOM
86    :param parent: parent node
87    :param name: tag of the element
88    :param value: value of the child text node
89    :param attr: attribute dictionary
90   
91    :return: True if something was appended, otherwise False
92    """
93    if value is not None:
94        node = doc.createElement(name)
95        node.appendChild(doc.createTextNode(str(value)))
96        for item in attr:
97            node.setAttribute(item, attr[item])
98        parent.appendChild(node)
99        return True
100    return False
101               
102
103class Reader():
104    """
105    Class to load cansas 1D XML files
106   
107    :Dependencies:
108        The CanSAS reader requires PyXML 0.8.4 or later.
109    """
110    ##CanSAS version - defaults to version 1.0
111    cansas_version = "1.0"
112    ##Data reader
113    reader = xml_reader.XMLreader()
114    errors = []
115   
116    type_name = "canSAS"
117   
118    ## Wildcards
119    type = ["XML files (*.xml)|*.xml"]
120    ## List of allowed extensions
121    ext = ['.xml', '.XML']
122   
123    ## Flag to bypass extension check
124    allow_all = True
125   
126    def __init__(self):
127        ## List of errors
128        self.errors = []
129       
130    def is_cansas(self):
131        """
132        Checks to see if the xml file is a CanSAS file
133        """
134        if self.reader.validateXML():
135            xmlns = self.reader.xmlroot.keys()
136            if (CANSAS_NS.get(self.cansas_version).get("ns") == \
137                    self.reader.xmlroot.get(xmlns[1]).rsplit(" ")[0]):
138                return True
139        return False
140   
141    def read(self, xml):
142        """
143        Validate and read in an xml file in the canSAS format.
144       
145        :param xml: A canSAS file path in proper XML format
146        """
147        # X - Q value; Y - Intensity (Abs)
148        x = numpy.empty(0)
149        y = numpy.empty(0)
150        dx = numpy.empty(0)
151        dy = numpy.empty(0)
152        dxl = numpy.empty(0)
153        dxw = numpy.empty(0)
154       
155        # output - Final list of Data1D objects
156        output = []
157        # ns - Namespace hierarchy for current xml object
158        ns = []
159       
160        # Check that the file exists
161        if os.path.isfile(xml):
162            basename = os.path.basename(xml)
163            _, extension = os.path.splitext(basename)
164            # If the fiel type is not allowed, return nothing
165            if extension in self.ext or self.allow_all:
166                base_name = xml_reader.__file__
167                base_name = base_name.replace("\\","/")
168                base = base_name.split("/sans/")[0]
169               
170                # Load in xml file and get the cansas version from the header
171                self.reader.setXMLFile(xml)
172                root = self.reader.xmlroot
173                if root is None:
174                    root = {}
175                self.cansas_version = root.get("version", "1.0")
176               
177                # Generic values for the cansas file based on the version
178                cansas_defaults = CANSAS_NS.get(self.cansas_version, "1.0")
179                schema_path = "{0}/sans/dataloader/readers/schema/{1}".format\
180                        (base, cansas_defaults.get("schema")).replace("\\", "/")
181               
182                # Link a schema to the XML file.
183                self.reader.setSchema(schema_path)
184           
185                # Try to load the file, but raise an error if unable to.
186                # Check the file matches the XML schema
187                try:
188                    if self.is_cansas():
189                        # Get each SASentry from XML file and add it to a list.
190                        entry_list = root.xpath('/ns:SASroot/ns:SASentry',
191                                namespaces={'ns': cansas_defaults.get("ns")})
192                        ns.append("SASentry")
193                       
194                        # If multiple files, modify the name for each is unique
195                        multiple_files = len(entry_list) - 1
196                        increment = 0
197                        name = basename
198                        # Parse each SASentry item
199                        for entry in entry_list:
200                            # Define a new Data1D object with zeroes for x and y
201                            data1d = Data1D(x,y,dx,dy)
202                            data1d.dxl = dxl
203                            data1d.dxw = dxw
204                           
205                            # If more than one SASentry, increment each in order
206                            if multiple_files:
207                                name += "_{0}".format(increment)
208                                increment += 1
209                           
210                            # Set the Data1D name and then parse the entry.
211                            # The entry is appended to a list of entry values
212                            data1d.filename = name
213                            data1d.meta_data["loader"] = "CanSAS 1D"
214                            return_value, extras = \
215                                self._parse_entry(entry, ns, data1d)
216                            del extras[:]
217                           
218                            # Final cleanup
219                            # Remove empty nodes, verify array sizes are correct
220                            for error in self.errors:
221                                return_value.errors.append(error)
222                            del self.errors[:]
223                            numpy.trim_zeros(return_value.x)
224                            numpy.trim_zeros(return_value.y)
225                            numpy.trim_zeros(return_value.dy)
226                            size_dx = return_value.dx.size
227                            size_dxl = return_value.dxl.size
228                            size_dxw = return_value.dxw.size
229                            if size_dxl == 0 and size_dxw == 0:
230                                return_value.dxl = None
231                                return_value.dxw = None
232                                numpy.trim_zeros(return_value.dx)
233                            elif size_dx == 0:
234                                return_value.dx = None
235                                size_dx = size_dxl
236                                numpy.trim_zeros(return_value.dxl)
237                                numpy.trim_zeros(return_value.dxw)
238                            output.append(return_value)
239                    else:
240                        value = self.reader.findInvalidXML()
241                        output.append("Invalid XML at: {0}".format(value))
242                except:
243                    # If the file does not match the schema, raise this error
244                    raise RuntimeError, "%s cannot be read \increment" % xml
245                return output
246        # Return a list of parsed entries that dataloader can manage
247        return None
248   
249    def _create_unique_key(self, dictionary, name, i):
250        """
251        Create a unique key value for any dictionary to prevent overwriting
252       
253       
254        :param dictionary: A dictionary with any number of entries
255        :param name: The index of the item to be added to dictionary
256        :param i: The number to be appended to the name
257        """
258        if dictionary.get(name) is not None:
259            i += 1
260            name = name.split("_")[0]
261            name += "_{0}".format(i)
262            name = self._create_unique_key(dictionary, name, i)
263        return name
264   
265   
266    def _unit_conversion(self, new_current_level, attr, data1d, \
267                                    tagname, node_value, optional = True):
268        """
269        A unit converter method used to convert the data included in the file
270        to the default units listed in data_info
271       
272        :param new_current_level: cansas_constants level as returned by
273            _iterate_namespace
274        :param attr: The attributes of the node
275        :param data1d: Where the values will be saved
276        :param node_value: The value of the current dom node
277        :param optional: Boolean that says if the units are required
278        """
279        value_unit = ''
280        if 'unit' in attr and new_current_level.get('unit') is not None:
281            try:
282                if isinstance(node_value, float) is False:
283                    exec("node_value = float({0})".format(node_value))
284                default_unit = None
285                unitname = new_current_level.get("unit")
286                exec "default_unit = data1d.{0}".format(unitname)
287                local_unit = attr['unit']
288                if local_unit.lower() != default_unit.lower() and \
289                    local_unit is not None and local_unit.lower() != "none" and\
290                     default_unit is not None:
291                    if HAS_CONVERTER == True:
292                        try:
293                            ## Check local units - bad units should raise KeyError
294                            Converter(local_unit)
295                            data_conv_q = Converter(attr['unit'])
296                            value_unit = default_unit
297                            exec "node_value = data_conv_q(node_value, units=data1d.{0})".format(unitname)
298                        except KeyError as e:
299                            err_msg = "CanSAS reader: could not convert "
300                            err_msg += "{0} unit {1}; ".format(tagname, local_unit)
301                            intermediate = "err_msg += \"expecting [{1}]  {2}\".format(data1d.{0}, sys.exc_info()[1])".format(unitname, "{0}", "{1}")
302                            exec intermediate
303                            self.errors.append(err_msg)
304                            raise ValueError(err_msg)
305                            return
306                        except:
307                            err_msg = "error occured"
308                            return
309                    else:
310                        value_unit = local_unit
311                        err_msg = "CanSAS reader: unrecognized %s unit [%s];"\
312                        % (node_value, default_unit)
313                        err_msg += " expecting [%s]" % local_unit
314                        self.errors.append(err_msg)
315                        raise ValueError, err_msg
316                        return
317                else:
318                    value_unit = local_unit
319            except:
320                err_msg = "CanSAS reader: could not convert "
321                err_msg += "Q unit [%s]; " % attr['unit'],
322                exec "err_msg += \"expecting [%s]\n  %s\" % (data1d.{0}, sys.exc_info()[1])".format(unitname)
323                self.errors.append(err_msg)
324                raise ValueError, err_msg
325                return
326        elif 'unit' in attr:
327            value_unit = attr['unit']
328        node_value = "float({0})".format(node_value)
329        return node_value, value_unit
330   
331    def _parse_entry(self, dom, ns, data1d, extras = []):
332        """
333        Parse a SASEntry - new recursive method for parsing the dom of
334            the CanSAS data format. This will allow multiple data files
335            and extra nodes to be read in simultaneously.
336       
337        :param dom: dom object with a namespace base of ns
338        :param ns: A list of element names that lead up to the dom object
339        :param data1d: The data1d object that will be modified
340        :param extras: Any values that should go into meta_data when data1d
341            is not a Data1D object
342        """
343         
344        # A portion of every namespace entry
345        base_ns = "{0}{1}{2}".format("{", \
346                            CANSAS_NS.get(self.cansas_version).get("ns"), "}")
347        unit = ''
348       
349        # Go through each child in the parent element
350        for node in dom:
351            try:
352                # Get the element name and set the current ns level
353                tagname = node.tag.replace(base_ns, "")
354                tagname_original = tagname
355                ns.append(tagname)
356                attr = node.attrib
357                children = node.getchildren()
358                save_data1d = data1d
359               
360                # Look for special cases
361                if tagname == "SASdetector":
362                    data1d = Detector()
363                elif tagname == "SAScollimation":
364                    data1d = Collimation()
365                elif tagname == "SAStransmission_spectrum":
366                    data1d = TransmissionSpectrum()
367                elif tagname == "SASprocess":
368                    data1d = Process()
369                    for child in node:
370                        if child.tag.replace(base_ns, "") == "term":
371                            term_attr = {}
372                            for attr in child.keys():
373                                term_attr[attr] = \
374                                    ' '.join(child.get(attr).split())
375                            if child.text is not None:
376                                term_attr['value'] = \
377                                    ' '.join(child.text.split())
378                            data1d.term.append(term_attr)
379                elif tagname == "aperture":
380                    data1d = Aperture()
381                if tagname == "Idata" and children is not None:
382                    dql = 0
383                    dqw = 0
384                    for child in children:
385                        tag = child.tag.replace(base_ns, "")
386                        if tag == "dQl":
387                            dql = 1
388                        if tag == "dQw":
389                            dqw = 1
390                    if dqw == 1 and dql == 0:
391                        data1d.dxl = numpy.append(data1d.dxl, 0.0)
392                    elif dql == 1 and dqw == 0:
393                        data1d.dxw = numpy.append(data1d.dxw, 0.0)
394                               
395                # Get where to store content
396                cs_values = constants._iterate_namespace(ns)
397                # If the element is a child element, recurse
398                if children is not None:
399                    # Returned value is new Data1D object with all previous and
400                    # new values in it.
401                    data1d, extras = self._parse_entry(node, ns, data1d, extras)
402                   
403                #Get the information from the node
404                node_value = node.text
405                if node_value == "":
406                    node_value = None
407                if node_value is not None:
408                    node_value = ' '.join(node_value.split())
409               
410                # If the value is a float, compile with units.
411                if cs_values.ns_datatype == "float":
412                    # If an empty value is given, store as zero.
413                    if node_value is None or node_value.isspace() \
414                                            or node_value.lower() == "nan":
415                        node_value = "0.0"
416                    try:
417                        node_value, unit = self._unit_conversion(\
418                                cs_values.current_level, attr, data1d, \
419                                tagname, node_value, cs_values.ns_optional)
420                    except TypeError:
421                        print "TypeError with units of {0} and node of {1}".format(attr['unit'], tagname)
422                   
423                # If appending to a dictionary (meta_data | run_name)
424                # make sure the key is unique
425                if cs_values.ns_variable == "{0}.meta_data[\"{2}\"] = \"{1}\"":
426                    # If we are within a Process, Detector, Collimation or
427                    # Aperture instance, pull out old data1d
428                    tagname = self._create_unique_key(data1d.meta_data, \
429                                                      tagname, 0)
430                    if isinstance(data1d, Data1D) == False:
431                        store_me = cs_values.ns_variable.format("data1d", \
432                                                            node_value, tagname)
433                        extras.append(store_me)
434                        cs_values.ns_variable = None
435                if cs_values.ns_variable == "{0}.run_name[\"{2}\"] = \"{1}\"":
436                    tagname = self._create_unique_key(data1d.run_name, \
437                                                      tagname, 0)
438               
439                # Check for Data1D object and any extra commands to save
440                if isinstance(data1d, Data1D):
441                    for item in extras:
442                        exec item
443                # Don't bother saving empty information unless it is a float
444                if cs_values.ns_variable is not None and node_value is not None and \
445                            node_value.isspace() == False:
446                    # Format a string and then execute it.
447                    store_me = cs_values.ns_variable.format("data1d", node_value, tagname)
448                    exec store_me
449                # Get attributes and process them
450                if attr is not None:
451                    for key in node.keys():
452                        try:
453                            cansas_attrib = \
454                            cs_values.current_level.get("attributes").get(key)
455                            attrib_variable = cansas_attrib.get("variable")
456                            if key == 'unit' and unit != '':
457                                attrib_value = unit
458                            else:
459                                attrib_value = node.attrib[key]
460                            store_attr = attrib_variable.format("data1d", \
461                                                            attrib_value, key)
462                            exec store_attr
463                        except AttributeError as e:
464                            pass
465                           
466                     
467            except Exception as e:
468                exc_type, exc_obj, exc_tb = sys.exc_info()
469                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
470                print(e, exc_type, fname, exc_tb.tb_lineno, tagname, exc_obj)
471            finally:
472                # Save special cases in original data1d object
473                # then restore the data1d
474                if tagname_original == "SASdetector":
475                    save_data1d.detector.append(data1d)
476                elif tagname_original == "SAScollimation":
477                    save_data1d.collimation.append(data1d)
478                elif tagname == "SAStransmission_spectrum":
479                    save_data1d.trans_spectrum.append(data1d)
480                elif tagname_original == "SASprocess":
481                    save_data1d.process.append(data1d)
482                elif tagname_original == "aperture":
483                    save_data1d.aperture.append(data1d)
484                else:
485                    save_data1d = data1d
486                data1d = save_data1d
487                # Remove tagname from ns to restore original base
488                ns.remove(tagname_original)
489       
490        return data1d, extras
491       
492    def _to_xml_doc(self, datainfo):
493        """
494        Create an XML document to contain the content of a Data1D
495       
496        :param datainfo: Data1D object
497        """
498       
499        if not issubclass(datainfo.__class__, Data1D):
500            raise RuntimeError, "The cansas writer expects a Data1D instance"
501       
502        ns = CANSAS_NS.get(self.cansas_version).get("ns")
503        doc = xml.dom.minidom.Document()
504       
505        main_node = doc.createElement("SASroot")
506        if self.cansas_version == "1.1":
507            pi = doc.createProcessingInstruction('xml-stylesheet', \
508                                    'type="text/xsl" href="cansasxml-html.xsl"')
509            root = doc.firstChild
510            doc.insertBefore(pi, root)
511        main_node.setAttribute("version", self.cansas_version)
512        main_node.setAttribute("xmlns", ns)
513        main_node.setAttribute("xmlns:xsi",
514                               "http://www.w3.org/2001/XMLSchema-instance")
515        if self.cansas_version == "1.0":
516            main_node.setAttribute("xsi:schemaLocation", "cansas1d/1.0 http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd")
517        elif self.cansas_version == "1.1":
518            main_node.setAttribute("xsi:schemaLocation", "urn:cansas1d:1.1 http://www.cansas.org/formats/1.1/cansas1d.xsd")
519       
520        doc.appendChild(main_node)
521       
522        entry_node = doc.createElement("SASentry")
523        main_node.appendChild(entry_node)
524       
525        write_node(doc, entry_node, "Title", datainfo.title)
526        for item in datainfo.run:
527            runname = {}
528            if item in datainfo.run_name and \
529            len(str(datainfo.run_name[item])) > 1:
530                runname = {'name': datainfo.run_name[item]}
531            write_node(doc, entry_node, "Run", item, runname)
532       
533        # Data info
534        node = doc.createElement("SASdata")
535        entry_node.appendChild(node)
536       
537        for i in range(len(datainfo.x)):
538            pt = doc.createElement("Idata")
539            node.appendChild(pt)
540            write_node(doc, pt, "Q", datainfo.x[i], {'unit': datainfo.x_unit})
541            if len(datainfo.y) >= i:
542                write_node(doc, pt, "I", datainfo.y[i],
543                            {'unit': datainfo.y_unit})
544            if datainfo.dy != None and len(datainfo.dy) > i:
545                write_node(doc, pt, "Idev", datainfo.dy[i],
546                            {'unit': datainfo.y_unit})
547            if datainfo.dx != None and len(datainfo.dx) > i:
548                write_node(doc, pt, "Qdev", datainfo.dx[i],
549                            {'unit': datainfo.x_unit})
550            if datainfo.dxw != None and len(datainfo.dxw) > i:
551                write_node(doc, pt, "dQw", datainfo.dxw[i],
552                            {'unit': datainfo.x_unit})
553            if datainfo.dxl != None and len(datainfo.dxl) > i:
554                write_node(doc, pt, "dQl", datainfo.dxl[i],
555                            {'unit': datainfo.x_unit})
556
557        # Transmission Spectrum Info
558        for i in range(len(datainfo.trans_spectrum)):
559            spectrum = datainfo.trans_spectrum[i]
560            node = doc.createElement("SAStransmission_spectrum")
561            entry_node.appendChild(node)
562            for i in range(len(spectrum.wavelength)):
563                pt = doc.createElement("Tdata")
564                node.appendChild(pt)
565                write_node(doc, pt, "Lambda", spectrum.wavelength[i], 
566                           {'unit': spectrum.wavelength_unit})
567                write_node(doc, pt, "T", spectrum.transmission[i], 
568                           {'unit': spectrum.transmission_unit})
569                if spectrum.transmission_deviation != None \
570                and len(spectrum.transmission_deviation) >= i:
571                    write_node(doc, pt, "Tdev", \
572                               spectrum.transmission_deviation[i], \
573                               {'unit': spectrum.transmission_deviation_unit})
574
575        # Sample info
576        sample = doc.createElement("SASsample")
577        if datainfo.sample.name is not None:
578            sample.setAttribute("name", str(datainfo.sample.name))
579        entry_node.appendChild(sample)
580        write_node(doc, sample, "ID", str(datainfo.sample.ID))
581        write_node(doc, sample, "thickness", datainfo.sample.thickness,
582                   {"unit": datainfo.sample.thickness_unit})
583        write_node(doc, sample, "transmission", datainfo.sample.transmission)
584        write_node(doc, sample, "temperature", datainfo.sample.temperature,
585                   {"unit": datainfo.sample.temperature_unit})
586       
587        pos = doc.createElement("position")
588        written = write_node(doc, pos, "x", datainfo.sample.position.x,
589                             {"unit": datainfo.sample.position_unit})
590        written = written | write_node(doc, pos, "y",
591                                       datainfo.sample.position.y,
592                                       {"unit": datainfo.sample.position_unit})
593        written = written | write_node(doc, pos, "z",
594                                       datainfo.sample.position.z,
595                                       {"unit": datainfo.sample.position_unit})
596        if written == True:
597            sample.appendChild(pos)
598       
599        ori = doc.createElement("orientation")
600        written = write_node(doc, ori, "roll",
601                             datainfo.sample.orientation.x,
602                             {"unit": datainfo.sample.orientation_unit})
603        written = written | write_node(doc, ori, "pitch",
604                                       datainfo.sample.orientation.y,
605                                    {"unit": datainfo.sample.orientation_unit})
606        written = written | write_node(doc, ori, "yaw",
607                                       datainfo.sample.orientation.z,
608                                    {"unit": datainfo.sample.orientation_unit})
609        if written == True:
610            sample.appendChild(ori)
611       
612        for item in datainfo.sample.details:
613            write_node(doc, sample, "details", item)
614       
615        # Instrument info
616        instr = doc.createElement("SASinstrument")
617        entry_node.appendChild(instr)
618       
619        write_node(doc, instr, "name", datainfo.instrument)
620       
621        #   Source
622        source = doc.createElement("SASsource")
623        if datainfo.source.name is not None:
624            source.setAttribute("name", str(datainfo.source.name))
625        instr.appendChild(source)
626        write_node(doc, source, "radiation", datainfo.source.radiation)
627       
628        size = doc.createElement("beam_size")
629        if datainfo.source.beam_size_name is not None:
630            size.setAttribute("name", str(datainfo.source.beam_size_name))
631        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
632                             {"unit": datainfo.source.beam_size_unit})
633        written = written | write_node(doc, size, "y",
634                                       datainfo.source.beam_size.y,
635                                       {"unit": datainfo.source.beam_size_unit})
636        written = written | write_node(doc, size, "z",
637                                       datainfo.source.beam_size.z,
638                                       {"unit": datainfo.source.beam_size_unit})
639        if written == True:
640            source.appendChild(size)
641           
642        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
643        write_node(doc, source, "wavelength",
644                   datainfo.source.wavelength,
645                   {"unit": datainfo.source.wavelength_unit})
646        write_node(doc, source, "wavelength_min",
647                   datainfo.source.wavelength_min,
648                   {"unit": datainfo.source.wavelength_min_unit})
649        write_node(doc, source, "wavelength_max",
650                   datainfo.source.wavelength_max,
651                   {"unit": datainfo.source.wavelength_max_unit})
652        write_node(doc, source, "wavelength_spread",
653                   datainfo.source.wavelength_spread,
654                   {"unit": datainfo.source.wavelength_spread_unit})
655       
656        #   Collimation
657        for item in datainfo.collimation:
658            coll = doc.createElement("SAScollimation")
659            if item.name is not None:
660                coll.setAttribute("name", str(item.name))
661            instr.appendChild(coll)
662           
663            write_node(doc, coll, "length", item.length,
664                       {"unit": item.length_unit})
665           
666            for apert in item.aperture:
667                ap = doc.createElement("aperture")
668                if apert.name is not None:
669                    ap.setAttribute("name", str(apert.name))
670                if apert.type is not None:
671                    ap.setAttribute("type", str(apert.type))
672                coll.appendChild(ap)
673               
674                size = doc.createElement("size")
675                if apert.size_name is not None:
676                    size.setAttribute("name", str(apert.size_name))
677                written = write_node(doc, size, "x", apert.size.x,
678                                     {"unit": apert.size_unit})
679                written = written | write_node(doc, size, "y", apert.size.y,
680                                               {"unit": apert.size_unit})
681                written = written | write_node(doc, size, "z", apert.size.z,
682                                               {"unit": apert.size_unit})
683                if written == True:
684                    ap.appendChild(size)
685               
686                write_node(doc, ap, "distance", apert.distance,
687                           {"unit": apert.distance_unit})
688
689        #   Detectors
690        for item in datainfo.detector:
691            det = doc.createElement("SASdetector")
692            written = write_node(doc, det, "name", item.name)
693            written = written | write_node(doc, det, "SDD", item.distance,
694                                           {"unit": item.distance_unit})
695            if written == True:
696                instr.appendChild(det)
697           
698            off = doc.createElement("offset")
699            written = write_node(doc, off, "x", item.offset.x,
700                                 {"unit": item.offset_unit})
701            written = written | write_node(doc, off, "y", item.offset.y,
702                                           {"unit": item.offset_unit})
703            written = written | write_node(doc, off, "z", item.offset.z,
704                                           {"unit": item.offset_unit})
705            if written == True:
706                det.appendChild(off)
707               
708            ori = doc.createElement("orientation")
709            written = write_node(doc, ori, "roll", item.orientation.x,
710                                 {"unit": item.orientation_unit})
711            written = written | write_node(doc, ori, "pitch",
712                                           item.orientation.y,
713                                           {"unit": item.orientation_unit})
714            written = written | write_node(doc, ori, "yaw",
715                                           item.orientation.z,
716                                           {"unit": item.orientation_unit})
717            if written == True:
718                det.appendChild(ori)
719           
720            center = doc.createElement("beam_center")
721            written = write_node(doc, center, "x", item.beam_center.x,
722                                 {"unit": item.beam_center_unit})
723            written = written | write_node(doc, center, "y",
724                                           item.beam_center.y,
725                                           {"unit": item.beam_center_unit})
726            written = written | write_node(doc, center, "z",
727                                           item.beam_center.z,
728                                           {"unit": item.beam_center_unit})
729            if written == True:
730                det.appendChild(center)
731               
732            pix = doc.createElement("pixel_size")
733            written = write_node(doc, pix, "x", item.pixel_size.x,
734                                 {"unit": item.pixel_size_unit})
735            written = written | write_node(doc, pix, "y", item.pixel_size.y,
736                                           {"unit": item.pixel_size_unit})
737            written = written | write_node(doc, pix, "z", item.pixel_size.z,
738                                           {"unit": item.pixel_size_unit})
739            if written == True:
740                det.appendChild(pix)
741            written = written | write_node(doc, det, "slit_length",
742                                           item.slit_length,
743                                           {"unit": item.slit_length_unit})
744           
745        # Processes info
746        for item in datainfo.process:
747            node = doc.createElement("SASprocess")
748            entry_node.appendChild(node)
749
750            write_node(doc, node, "name", item.name)
751            write_node(doc, node, "date", item.date)
752            write_node(doc, node, "description", item.description)
753            for term in item.term:
754                value = term['value']
755                del term['value']
756                write_node(doc, node, "term", value, term)
757            for note in item.notes:
758                write_node(doc, node, "SASprocessnote", note)
759            if len(item.notes) == 0:
760                write_node(doc, node, "SASprocessnote", "")
761               
762        # Note info
763        if len(datainfo.notes) == 0:
764            node = doc.createElement("SASnote")
765            entry_node.appendChild(node)
766            if node.hasChildNodes():
767                for child in node.childNodes:
768                    node.removeChild(child)
769        else:
770            for item in datainfo.notes:
771                node = doc.createElement("SASnote")
772                entry_node.appendChild(node)
773                node.appendChild(doc.createTextNode(item))
774               
775        # Return the document, and the SASentry node associated with
776        # the data we just wrote
777        return doc, entry_node
778           
779    def write(self, filename, datainfo):
780        """
781        Write the content of a Data1D as a CanSAS XML file
782       
783        :param filename: name of the file to write
784        :param datainfo: Data1D object
785        """
786        # Create XML document
787        doc, _ = self._to_xml_doc(datainfo)
788        # Write the file
789        fd = open(filename, 'w')
790        fd.write(doc.toprettyxml())
791        fd.close()
Note: See TracBrowser for help on using the repository browser.