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

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 6d7b5dd7 was 2e3b055, checked in by Jeff Krzywon <jeffery.krzywon@…>, 11 years ago

Transitioned from minidom to lxml for writing xml for neater output. XMLreader now has helper methods used to read and write xml data. Methods were taken from cansasReader to separate generic XML functions from cansas specific ones.

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