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

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

Fixed the issue with error handling in the cansas reader.

  • Property mode set to 100644
File size: 35.8 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 = "CanSAS reader: could not convert the units"
308                            self.errors.append(err_msg)
309                            return
310                    else:
311                        value_unit = local_unit
312                        err_msg = "CanSAS reader: unrecognized %s unit [%s];"\
313                        % (node_value, default_unit)
314                        err_msg += " expecting [%s]" % local_unit
315                        self.errors.append(err_msg)
316                        raise ValueError, err_msg
317                        return
318                else:
319                    value_unit = local_unit
320            except:
321                err_msg = "CanSAS reader: could not convert "
322                err_msg += "Q unit [%s]; " % attr['unit'],
323                exec "err_msg += \"expecting [%s]\n  %s\" % (data1d.{0}, sys.exc_info()[1])".format(unitname)
324                self.errors.append(err_msg)
325                raise ValueError, err_msg
326                return
327        elif 'unit' in attr:
328            value_unit = attr['unit']
329        node_value = "float({0})".format(node_value)
330        return node_value, value_unit
331   
332    def _parse_entry(self, dom, ns, data1d, extras = []):
333        """
334        Parse a SASEntry - new recursive method for parsing the dom of
335            the CanSAS data format. This will allow multiple data files
336            and extra nodes to be read in simultaneously.
337       
338        :param dom: dom object with a namespace base of ns
339        :param ns: A list of element names that lead up to the dom object
340        :param data1d: The data1d object that will be modified
341        :param extras: Any values that should go into meta_data when data1d
342            is not a Data1D object
343        """
344         
345        # A portion of every namespace entry
346        base_ns = "{0}{1}{2}".format("{", \
347                            CANSAS_NS.get(self.cansas_version).get("ns"), "}")
348        unit = ''
349       
350        # Go through each child in the parent element
351        for node in dom:
352            try:
353                # Get the element name and set the current ns level
354                tagname = node.tag.replace(base_ns, "")
355                tagname_original = tagname
356                ns.append(tagname)
357                attr = node.attrib
358                children = node.getchildren()
359                save_data1d = data1d
360               
361                # Look for special cases
362                if tagname == "SASdetector":
363                    data1d = Detector()
364                elif tagname == "SAScollimation":
365                    data1d = Collimation()
366                elif tagname == "SAStransmission_spectrum":
367                    data1d = TransmissionSpectrum()
368                elif tagname == "SASprocess":
369                    data1d = Process()
370                    for child in node:
371                        if child.tag.replace(base_ns, "") == "term":
372                            term_attr = {}
373                            for attr in child.keys():
374                                term_attr[attr] = \
375                                    ' '.join(child.get(attr).split())
376                            if child.text is not None:
377                                term_attr['value'] = \
378                                    ' '.join(child.text.split())
379                            data1d.term.append(term_attr)
380                elif tagname == "aperture":
381                    data1d = Aperture()
382                if tagname == "Idata" and children is not None:
383                    dql = 0
384                    dqw = 0
385                    for child in children:
386                        tag = child.tag.replace(base_ns, "")
387                        if tag == "dQl":
388                            dql = 1
389                        if tag == "dQw":
390                            dqw = 1
391                    if dqw == 1 and dql == 0:
392                        data1d.dxl = numpy.append(data1d.dxl, 0.0)
393                    elif dql == 1 and dqw == 0:
394                        data1d.dxw = numpy.append(data1d.dxw, 0.0)
395                               
396                # Get where to store content
397                cs_values = constants._iterate_namespace(ns)
398                # If the element is a child element, recurse
399                if children is not None:
400                    # Returned value is new Data1D object with all previous and
401                    # new values in it.
402                    data1d, extras = self._parse_entry(node, ns, data1d, extras)
403                   
404                #Get the information from the node
405                node_value = node.text
406                if node_value == "":
407                    node_value = None
408                if node_value is not None:
409                    node_value = ' '.join(node_value.split())
410               
411                # If the value is a float, compile with units.
412                if cs_values.ns_datatype == "float":
413                    # If an empty value is given, store as zero.
414                    if node_value is None or node_value.isspace() \
415                                            or node_value.lower() == "nan":
416                        node_value = "0.0"
417                    node_value, unit = self._unit_conversion(\
418                                cs_values.current_level, attr, data1d, \
419                                tagname, node_value, cs_values.ns_optional)
420                   
421                # If appending to a dictionary (meta_data | run_name)
422                # make sure the key is unique
423                if cs_values.ns_variable == "{0}.meta_data[\"{2}\"] = \"{1}\"":
424                    # If we are within a Process, Detector, Collimation or
425                    # Aperture instance, pull out old data1d
426                    tagname = self._create_unique_key(data1d.meta_data, \
427                                                      tagname, 0)
428                    if isinstance(data1d, Data1D) == False:
429                        store_me = cs_values.ns_variable.format("data1d", \
430                                                            node_value, tagname)
431                        extras.append(store_me)
432                        cs_values.ns_variable = None
433                if cs_values.ns_variable == "{0}.run_name[\"{2}\"] = \"{1}\"":
434                    tagname = self._create_unique_key(data1d.run_name, \
435                                                      tagname, 0)
436               
437                # Check for Data1D object and any extra commands to save
438                if isinstance(data1d, Data1D):
439                    for item in extras:
440                        exec item
441                # Don't bother saving empty information unless it is a float
442                if cs_values.ns_variable is not None and node_value is not None and \
443                            node_value.isspace() == False:
444                    # Format a string and then execute it.
445                    store_me = cs_values.ns_variable.format("data1d", node_value, tagname)
446                    exec store_me
447                # Get attributes and process them
448                if attr is not None:
449                    for key in node.keys():
450                        try:
451                            cansas_attrib = \
452                            cs_values.current_level.get("attributes").get(key)
453                            attrib_variable = cansas_attrib.get("variable")
454                            if key == 'unit' and unit != '':
455                                attrib_value = unit
456                            else:
457                                attrib_value = node.attrib[key]
458                            store_attr = attrib_variable.format("data1d", \
459                                                            attrib_value, key)
460                            exec store_attr
461                        except AttributeError as e:
462                            pass
463           
464            except TypeError:
465                pass
466            except Exception as e:
467                exc_type, exc_obj, exc_tb = sys.exc_info()
468                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
469                print(e, exc_type, fname, exc_tb.tb_lineno, tagname, exc_obj)
470            finally:
471                # Save special cases in original data1d object
472                # then restore the data1d
473                if tagname_original == "SASdetector":
474                    save_data1d.detector.append(data1d)
475                elif tagname_original == "SAScollimation":
476                    save_data1d.collimation.append(data1d)
477                elif tagname == "SAStransmission_spectrum":
478                    save_data1d.trans_spectrum.append(data1d)
479                elif tagname_original == "SASprocess":
480                    save_data1d.process.append(data1d)
481                elif tagname_original == "aperture":
482                    save_data1d.aperture.append(data1d)
483                else:
484                    save_data1d = data1d
485                data1d = save_data1d
486                # Remove tagname from ns to restore original base
487                ns.remove(tagname_original)
488       
489        return data1d, extras
490       
491    def _to_xml_doc(self, datainfo):
492        """
493        Create an XML document to contain the content of a Data1D
494       
495        :param datainfo: Data1D object
496        """
497       
498        if not issubclass(datainfo.__class__, Data1D):
499            raise RuntimeError, "The cansas writer expects a Data1D instance"
500       
501        ns = CANSAS_NS.get(self.cansas_version).get("ns")
502        doc = xml.dom.minidom.Document()
503       
504        main_node = doc.createElement("SASroot")
505        if self.cansas_version == "1.1":
506            pi = doc.createProcessingInstruction('xml-stylesheet', \
507                                    'type="text/xsl" href="cansasxml-html.xsl"')
508            root = doc.firstChild
509            doc.insertBefore(pi, root)
510        main_node.setAttribute("version", self.cansas_version)
511        main_node.setAttribute("xmlns", ns)
512        main_node.setAttribute("xmlns:xsi",
513                               "http://www.w3.org/2001/XMLSchema-instance")
514        if self.cansas_version == "1.0":
515            main_node.setAttribute("xsi:schemaLocation", "cansas1d/1.0 http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd")
516        elif self.cansas_version == "1.1":
517            main_node.setAttribute("xsi:schemaLocation", "urn:cansas1d:1.1 http://www.cansas.org/formats/1.1/cansas1d.xsd")
518       
519        doc.appendChild(main_node)
520       
521        entry_node = doc.createElement("SASentry")
522        main_node.appendChild(entry_node)
523       
524        write_node(doc, entry_node, "Title", datainfo.title)
525        for item in datainfo.run:
526            runname = {}
527            if item in datainfo.run_name and \
528            len(str(datainfo.run_name[item])) > 1:
529                runname = {'name': datainfo.run_name[item]}
530            write_node(doc, entry_node, "Run", item, runname)
531       
532        # Data info
533        node = doc.createElement("SASdata")
534        entry_node.appendChild(node)
535       
536        for i in range(len(datainfo.x)):
537            pt = doc.createElement("Idata")
538            node.appendChild(pt)
539            write_node(doc, pt, "Q", datainfo.x[i], {'unit': datainfo.x_unit})
540            if len(datainfo.y) >= i:
541                write_node(doc, pt, "I", datainfo.y[i],
542                            {'unit': datainfo.y_unit})
543            if datainfo.dy != None and len(datainfo.dy) > i:
544                write_node(doc, pt, "Idev", datainfo.dy[i],
545                            {'unit': datainfo.y_unit})
546            if datainfo.dx != None and len(datainfo.dx) > i:
547                write_node(doc, pt, "Qdev", datainfo.dx[i],
548                            {'unit': datainfo.x_unit})
549            if datainfo.dxw != None and len(datainfo.dxw) > i:
550                write_node(doc, pt, "dQw", datainfo.dxw[i],
551                            {'unit': datainfo.x_unit})
552            if datainfo.dxl != None and len(datainfo.dxl) > i:
553                write_node(doc, pt, "dQl", datainfo.dxl[i],
554                            {'unit': datainfo.x_unit})
555
556        # Transmission Spectrum Info
557        for i in range(len(datainfo.trans_spectrum)):
558            spectrum = datainfo.trans_spectrum[i]
559            node = doc.createElement("SAStransmission_spectrum")
560            entry_node.appendChild(node)
561            for i in range(len(spectrum.wavelength)):
562                pt = doc.createElement("Tdata")
563                node.appendChild(pt)
564                write_node(doc, pt, "Lambda", spectrum.wavelength[i], 
565                           {'unit': spectrum.wavelength_unit})
566                write_node(doc, pt, "T", spectrum.transmission[i], 
567                           {'unit': spectrum.transmission_unit})
568                if spectrum.transmission_deviation != None \
569                and len(spectrum.transmission_deviation) >= i:
570                    write_node(doc, pt, "Tdev", \
571                               spectrum.transmission_deviation[i], \
572                               {'unit': spectrum.transmission_deviation_unit})
573
574        # Sample info
575        sample = doc.createElement("SASsample")
576        if datainfo.sample.name is not None:
577            sample.setAttribute("name", str(datainfo.sample.name))
578        entry_node.appendChild(sample)
579        write_node(doc, sample, "ID", str(datainfo.sample.ID))
580        write_node(doc, sample, "thickness", datainfo.sample.thickness,
581                   {"unit": datainfo.sample.thickness_unit})
582        write_node(doc, sample, "transmission", datainfo.sample.transmission)
583        write_node(doc, sample, "temperature", datainfo.sample.temperature,
584                   {"unit": datainfo.sample.temperature_unit})
585       
586        pos = doc.createElement("position")
587        written = write_node(doc, pos, "x", datainfo.sample.position.x,
588                             {"unit": datainfo.sample.position_unit})
589        written = written | write_node(doc, pos, "y",
590                                       datainfo.sample.position.y,
591                                       {"unit": datainfo.sample.position_unit})
592        written = written | write_node(doc, pos, "z",
593                                       datainfo.sample.position.z,
594                                       {"unit": datainfo.sample.position_unit})
595        if written == True:
596            sample.appendChild(pos)
597       
598        ori = doc.createElement("orientation")
599        written = write_node(doc, ori, "roll",
600                             datainfo.sample.orientation.x,
601                             {"unit": datainfo.sample.orientation_unit})
602        written = written | write_node(doc, ori, "pitch",
603                                       datainfo.sample.orientation.y,
604                                    {"unit": datainfo.sample.orientation_unit})
605        written = written | write_node(doc, ori, "yaw",
606                                       datainfo.sample.orientation.z,
607                                    {"unit": datainfo.sample.orientation_unit})
608        if written == True:
609            sample.appendChild(ori)
610       
611        for item in datainfo.sample.details:
612            write_node(doc, sample, "details", item)
613       
614        # Instrument info
615        instr = doc.createElement("SASinstrument")
616        entry_node.appendChild(instr)
617       
618        write_node(doc, instr, "name", datainfo.instrument)
619       
620        #   Source
621        source = doc.createElement("SASsource")
622        if datainfo.source.name is not None:
623            source.setAttribute("name", str(datainfo.source.name))
624        instr.appendChild(source)
625        write_node(doc, source, "radiation", datainfo.source.radiation)
626       
627        size = doc.createElement("beam_size")
628        if datainfo.source.beam_size_name is not None:
629            size.setAttribute("name", str(datainfo.source.beam_size_name))
630        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
631                             {"unit": datainfo.source.beam_size_unit})
632        written = written | write_node(doc, size, "y",
633                                       datainfo.source.beam_size.y,
634                                       {"unit": datainfo.source.beam_size_unit})
635        written = written | write_node(doc, size, "z",
636                                       datainfo.source.beam_size.z,
637                                       {"unit": datainfo.source.beam_size_unit})
638        if written == True:
639            source.appendChild(size)
640           
641        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
642        write_node(doc, source, "wavelength",
643                   datainfo.source.wavelength,
644                   {"unit": datainfo.source.wavelength_unit})
645        write_node(doc, source, "wavelength_min",
646                   datainfo.source.wavelength_min,
647                   {"unit": datainfo.source.wavelength_min_unit})
648        write_node(doc, source, "wavelength_max",
649                   datainfo.source.wavelength_max,
650                   {"unit": datainfo.source.wavelength_max_unit})
651        write_node(doc, source, "wavelength_spread",
652                   datainfo.source.wavelength_spread,
653                   {"unit": datainfo.source.wavelength_spread_unit})
654       
655        #   Collimation
656        for item in datainfo.collimation:
657            coll = doc.createElement("SAScollimation")
658            if item.name is not None:
659                coll.setAttribute("name", str(item.name))
660            instr.appendChild(coll)
661           
662            write_node(doc, coll, "length", item.length,
663                       {"unit": item.length_unit})
664           
665            for apert in item.aperture:
666                ap = doc.createElement("aperture")
667                if apert.name is not None:
668                    ap.setAttribute("name", str(apert.name))
669                if apert.type is not None:
670                    ap.setAttribute("type", str(apert.type))
671                coll.appendChild(ap)
672               
673                size = doc.createElement("size")
674                if apert.size_name is not None:
675                    size.setAttribute("name", str(apert.size_name))
676                written = write_node(doc, size, "x", apert.size.x,
677                                     {"unit": apert.size_unit})
678                written = written | write_node(doc, size, "y", apert.size.y,
679                                               {"unit": apert.size_unit})
680                written = written | write_node(doc, size, "z", apert.size.z,
681                                               {"unit": apert.size_unit})
682                if written == True:
683                    ap.appendChild(size)
684               
685                write_node(doc, ap, "distance", apert.distance,
686                           {"unit": apert.distance_unit})
687
688        #   Detectors
689        for item in datainfo.detector:
690            det = doc.createElement("SASdetector")
691            written = write_node(doc, det, "name", item.name)
692            written = written | write_node(doc, det, "SDD", item.distance,
693                                           {"unit": item.distance_unit})
694            if written == True:
695                instr.appendChild(det)
696           
697            off = doc.createElement("offset")
698            written = write_node(doc, off, "x", item.offset.x,
699                                 {"unit": item.offset_unit})
700            written = written | write_node(doc, off, "y", item.offset.y,
701                                           {"unit": item.offset_unit})
702            written = written | write_node(doc, off, "z", item.offset.z,
703                                           {"unit": item.offset_unit})
704            if written == True:
705                det.appendChild(off)
706               
707            ori = doc.createElement("orientation")
708            written = write_node(doc, ori, "roll", item.orientation.x,
709                                 {"unit": item.orientation_unit})
710            written = written | write_node(doc, ori, "pitch",
711                                           item.orientation.y,
712                                           {"unit": item.orientation_unit})
713            written = written | write_node(doc, ori, "yaw",
714                                           item.orientation.z,
715                                           {"unit": item.orientation_unit})
716            if written == True:
717                det.appendChild(ori)
718           
719            center = doc.createElement("beam_center")
720            written = write_node(doc, center, "x", item.beam_center.x,
721                                 {"unit": item.beam_center_unit})
722            written = written | write_node(doc, center, "y",
723                                           item.beam_center.y,
724                                           {"unit": item.beam_center_unit})
725            written = written | write_node(doc, center, "z",
726                                           item.beam_center.z,
727                                           {"unit": item.beam_center_unit})
728            if written == True:
729                det.appendChild(center)
730               
731            pix = doc.createElement("pixel_size")
732            written = write_node(doc, pix, "x", item.pixel_size.x,
733                                 {"unit": item.pixel_size_unit})
734            written = written | write_node(doc, pix, "y", item.pixel_size.y,
735                                           {"unit": item.pixel_size_unit})
736            written = written | write_node(doc, pix, "z", item.pixel_size.z,
737                                           {"unit": item.pixel_size_unit})
738            if written == True:
739                det.appendChild(pix)
740            written = written | write_node(doc, det, "slit_length",
741                                           item.slit_length,
742                                           {"unit": item.slit_length_unit})
743           
744        # Processes info
745        for item in datainfo.process:
746            node = doc.createElement("SASprocess")
747            entry_node.appendChild(node)
748
749            write_node(doc, node, "name", item.name)
750            write_node(doc, node, "date", item.date)
751            write_node(doc, node, "description", item.description)
752            for term in item.term:
753                value = term['value']
754                del term['value']
755                write_node(doc, node, "term", value, term)
756            for note in item.notes:
757                write_node(doc, node, "SASprocessnote", note)
758            if len(item.notes) == 0:
759                write_node(doc, node, "SASprocessnote", "")
760               
761        # Note info
762        if len(datainfo.notes) == 0:
763            node = doc.createElement("SASnote")
764            entry_node.appendChild(node)
765            if node.hasChildNodes():
766                for child in node.childNodes:
767                    node.removeChild(child)
768        else:
769            for item in datainfo.notes:
770                node = doc.createElement("SASnote")
771                entry_node.appendChild(node)
772                node.appendChild(doc.createTextNode(item))
773               
774        # Return the document, and the SASentry node associated with
775        # the data we just wrote
776        return doc, entry_node
777           
778    def write(self, filename, datainfo):
779        """
780        Write the content of a Data1D as a CanSAS XML file
781       
782        :param filename: name of the file to write
783        :param datainfo: Data1D object
784        """
785        # Create XML document
786        doc, _ = self._to_xml_doc(datainfo)
787        # Write the file
788        fd = open(filename, 'w')
789        fd.write(doc.toprettyxml())
790        fd.close()
Note: See TracBrowser for help on using the repository browser.