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

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

Fixed the file header writing issue for cansas data files outlined in ticket #204.

  • Property mode set to 100644
File size: 35.7 KB
RevLine 
[7d6351e]1"""
[76cd1ae]2    CanSAS data reader - new recursive cansas_version.
[7d6351e]3"""
[0997158f]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
[8780e9a]15import logging
16import numpy
[a7a5886]17import os
18import sys
[ad8034f]19from sans.dataloader.data_info import Data1D
20from sans.dataloader.data_info import Collimation
[76cd1ae]21from sans.dataloader.data_info import TransmissionSpectrum
[ad8034f]22from sans.dataloader.data_info import Detector
23from sans.dataloader.data_info import Process
24from sans.dataloader.data_info import Aperture
[76cd1ae]25import sans.dataloader.readers.xml_reader as xml_reader
[b0d0723]26import xml.dom.minidom
[76cd1ae]27from sans.dataloader.readers.cansas_constants import cansasConstants
[19b628f]28
[da96629]29_ZERO = 1e-16
[5a0dac1f]30HAS_CONVERTER = True
[b39c817]31try:
[ffbe487]32    from sans.data_util.nxsunit import Converter
[b39c817]33except:
[5a0dac1f]34    HAS_CONVERTER = False
[76cd1ae]35
36constants = cansasConstants()   
37CANSAS_FORMAT = constants.format
38CANSAS_NS = constants.ns
[5a0dac1f]39ALLOW_ALL = True
[b0d0723]40
[7d6351e]41
[8780e9a]42def get_content(location, node):
43    """
[0997158f]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
[8780e9a]50    """
[b0d0723]51    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
52   
[7d6351e]53    if len(nodes) > 0:
[b0d0723]54        return nodes[0]
55    else:
56        return None
[8780e9a]57
[7d6351e]58
[8780e9a]59def get_float(location, node):
60    """
[7d6351e]61    Get the content of a node as a float
[0997158f]62   
63    :param location: xpath location
64    :param node: node to start at
[8780e9a]65    """
[b0d0723]66    nodes = node.xpath(location, namespaces={'ns': CANSAS_NS})
67   
[8780e9a]68    value = None
69    attr = {}
[a7a5886]70    if len(nodes) > 0:
[8780e9a]71        try:
[7d6351e]72            value = float(nodes[0].text)
[8780e9a]73        except:
74            # Could not pass, skip and return None
[a7a5886]75            msg = "cansas_reader.get_float: could not "
76            msg += " convert '%s' to float" % nodes[0].text
77            logging.error(msg)
[b0d0723]78        if nodes[0].get('unit') is not None:
79            attr['unit'] = nodes[0].get('unit')
[8780e9a]80    return value, attr
81
[19b628f]82
[e090c624]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               
[19b628f]102
103class Reader():
[8780e9a]104    """
[0997158f]105    Class to load cansas 1D XML files
106   
107    :Dependencies:
[19b628f]108        The CanSAS reader requires PyXML 0.8.4 or later.
[8780e9a]109    """
[19b628f]110    ##CanSAS version - defaults to version 1.0
[76cd1ae]111    cansas_version = "1.0"
[19b628f]112    ##Data reader
113    reader = xml_reader.XMLreader()
114    errors = []
115   
116    type_name = "canSAS"
117   
[28caa03]118    ## Wildcards
[19b628f]119    type = ["XML files (*.xml)|*.xml"]
[8780e9a]120    ## List of allowed extensions
[19b628f]121    ext = ['.xml', '.XML']
122   
123    ## Flag to bypass extension check
124    allow_all = True
[8780e9a]125   
[fe78c7b]126    def __init__(self):
127        ## List of errors
128        self.errors = []
[19b628f]129       
[76cd1ae]130    def is_cansas(self):
[19b628f]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()
[76cd1ae]136            if (CANSAS_NS.get(self.cansas_version).get("ns") == \
137                    self.reader.xmlroot.get(xmlns[1]).rsplit(" ")[0]):
[19b628f]138                return True
139        return False
[fe78c7b]140   
[19b628f]141    def read(self, xml):
[7d6351e]142        """
[19b628f]143        Validate and read in an xml file in the canSAS format.
[0997158f]144       
[19b628f]145        :param xml: A canSAS file path in proper XML format
[8780e9a]146        """
[19b628f]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
[8780e9a]156        output = []
[19b628f]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__
[1ce36f37]167                base_name = base_name.replace("\\","/")
168                base = base_name.split("/sans/")[0]
[19b628f]169               
[76cd1ae]170                # Load in xml file and get the cansas version from the header
[19b628f]171                self.reader.setXMLFile(xml)
172                root = self.reader.xmlroot
173                if root is None:
174                    root = {}
[76cd1ae]175                self.cansas_version = root.get("version", "1.0")
[19b628f]176               
177                # Generic values for the cansas file based on the version
[76cd1ae]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("\\", "/")
[19b628f]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
[0a5c8f5]187                try:
[76cd1ae]188                    if self.is_cansas():
189                        # Get each SASentry from XML file and add it to a list.
[19b628f]190                        entry_list = root.xpath('/ns:SASroot/ns:SASentry',
[76cd1ae]191                                namespaces={'ns': cansas_defaults.get("ns")})
[19b628f]192                        ns.append("SASentry")
[0a5c8f5]193                       
[76cd1ae]194                        # If multiple files, modify the name for each is unique
195                        multiple_files = len(entry_list) - 1
196                        increment = 0
[19b628f]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
[76cd1ae]201                            data1d = Data1D(x,y,dx,dy)
202                            data1d.dxl = dxl
203                            data1d.dxw = dxw
[19b628f]204                           
[76cd1ae]205                            # If more than one SASentry, increment each in order
206                            if multiple_files:
207                                name += "_{0}".format(increment)
208                                increment += 1
[19b628f]209                           
[76cd1ae]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)
[19b628f]216                            del extras[:]
217                           
[76cd1ae]218                            # Final cleanup
219                            # Remove empty nodes, verify array sizes are correct
[19b628f]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))
[0a5c8f5]242                except:
[19b628f]243                    # If the file does not match the schema, raise this error
[76cd1ae]244                    raise RuntimeError, "%s cannot be read \increment" % xml
[19b628f]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):
[76cd1ae]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        """
[19b628f]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   
[76cd1ae]265   
266    def _unit_conversion(self, new_current_level, attr, data1d, \
267                                    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        """
[19b628f]279        value_unit = ''
280        if 'unit' in attr and new_current_level.get('unit') is not None:
[8780e9a]281            try:
[19b628f]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")
[76cd1ae]286                exec "default_unit = data1d.{0}".format(unitname)
[19b628f]287                local_unit = attr['unit']
[76cd1ae]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:
[19b628f]291                    if HAS_CONVERTER == True:
292                        try:
293                            data_conv_q = Converter(attr['unit'])
294                            value_unit = default_unit
[76cd1ae]295                            exec "node_value = data_conv_q(node_value, units=data1d.{0})".format(unitname)
[19b628f]296                        except:
297                            err_msg = "CanSAS reader: could not convert "
298                            err_msg += "Q unit {0}; ".format(local_unit)
[76cd1ae]299                            intermediate = "err_msg += \"expecting [{1}]  {2}\".format(data1d.{0}, sys.exc_info()[1])".format(unitname, "{0}", "{1}")
[19b628f]300                            exec intermediate
301                            self.errors.append(err_msg)
302                            if optional:
303                                logging.info(err_msg)
304                            else:
305                                raise ValueError, err_msg
306                    else:
307                        value_unit = local_unit
308                        err_msg = "CanSAS reader: unrecognized %s unit [%s];"\
309                        % (node_value, default_unit)
310                        err_msg += " expecting [%s]" % local_unit
311                        self.errors.append(err_msg)
312                        if optional:
313                            logging.info(err_msg)
314                        else:
315                            raise ValueError, err_msg
316                else:
317                    value_unit = local_unit
[8780e9a]318            except:
[19b628f]319                err_msg = "CanSAS reader: could not convert "
320                err_msg += "Q unit [%s]; " % attr['unit'],
[76cd1ae]321                exec "err_msg += \"expecting [%s]\n  %s\" % (data1d.{0}, sys.exc_info()[1])".format(unitname)
[19b628f]322                self.errors.append(err_msg)
323                if optional:
324                    logging.info(err_msg)
325                else:
326                    raise ValueError, err_msg
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   
[76cd1ae]332    def _parse_entry(self, dom, ns, data1d, extras = []):
[19b628f]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.
[8780e9a]337       
[19b628f]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
[76cd1ae]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
[19b628f]343        """
344         
345        # A portion of every namespace entry
[76cd1ae]346        base_ns = "{0}{1}{2}".format("{", \
347                            CANSAS_NS.get(self.cansas_version).get("ns"), "}")
[19b628f]348        unit = ''
[b0d0723]349       
[19b628f]350        # Go through each child in the parent element
351        for node in dom:
[8780e9a]352            try:
[19b628f]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
[e090c624]358                children = node.getchildren()
359                save_data1d = data1d
[579ba85]360               
[19b628f]361                # Look for special cases
362                if tagname == "SASdetector":
[76cd1ae]363                    data1d = Detector()
[19b628f]364                elif tagname == "SAScollimation":
[76cd1ae]365                    data1d = Collimation()
366                elif tagname == "SAStransmission_spectrum":
367                    data1d = TransmissionSpectrum()
[19b628f]368                elif tagname == "SASprocess":
[76cd1ae]369                    data1d = Process()
[19b628f]370                    for child in node:
371                        if child.tag.replace(base_ns, "") == "term":
372                            term_attr = {}
373                            for attr in child.keys():
[76cd1ae]374                                term_attr[attr] = \
375                                    ' '.join(child.get(attr).split())
[19b628f]376                            if child.text is not None:
[76cd1ae]377                                term_attr['value'] = \
378                                    ' '.join(child.text.split())
379                            data1d.term.append(term_attr)
[19b628f]380                elif tagname == "aperture":
[76cd1ae]381                    data1d = Aperture()
[e090c624]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                               
[19b628f]396                # Get where to store content
[e090c624]397                cs_values = constants._iterate_namespace(ns)
[19b628f]398                # If the element is a child element, recurse
[e090c624]399                if children is not None:
400                    # Returned value is new Data1D object with all previous and
401                    # new values in it.
[76cd1ae]402                    data1d, extras = self._parse_entry(node, ns, data1d, extras)
[19b628f]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())
[8780e9a]410               
[19b628f]411                # If the value is a float, compile with units.
[e090c624]412                if cs_values.ns_datatype == "float":
[19b628f]413                    # If an empty value is given, store as zero.
[76cd1ae]414                    if node_value is None or node_value.isspace() \
415                                            or node_value.lower() == "nan":
[19b628f]416                        node_value = "0.0"
[e090c624]417                    node_value, unit = self._unit_conversion(\
418                                cs_values.current_level, attr, data1d, \
419                                node_value, cs_values.ns_optional)
[19b628f]420                   
[e090c624]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)
[76cd1ae]428                    if isinstance(data1d, Data1D) == False:
[e090c624]429                        store_me = cs_values.ns_variable.format("data1d", \
430                                                            node_value, tagname)
[19b628f]431                        extras.append(store_me)
[e090c624]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)
[8780e9a]436               
[19b628f]437                # Check for Data1D object and any extra commands to save
[76cd1ae]438                if isinstance(data1d, Data1D):
[19b628f]439                    for item in extras:
440                        exec item
441                # Don't bother saving empty information unless it is a float
[e090c624]442                if cs_values.ns_variable is not None and node_value is not None and \
[76cd1ae]443                            node_value.isspace() == False:
[19b628f]444                    # Format a string and then execute it.
[e090c624]445                    store_me = cs_values.ns_variable.format("data1d", node_value, tagname)
[19b628f]446                    exec store_me
447                # Get attributes and process them
448                if attr is not None:
449                    for key in node.keys():
450                        try:
[e090c624]451                            cansas_attrib = \
452                            cs_values.current_level.get("attributes").get(key)
[19b628f]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]
[76cd1ae]458                            store_attr = attrib_variable.format("data1d", \
459                                                            attrib_value, key)
[19b628f]460                            exec store_attr
461                        except AttributeError as e:
[7d6351e]462                            pass
[19b628f]463                           
464                     
465            except Exception as e:
466                exc_type, exc_obj, exc_tb = sys.exc_info()
467                fname = os.path.split(exc_tb.tb_frame.f_code.co_filename)[1]
468                print(e, exc_type, fname, exc_tb.tb_lineno, tagname, exc_obj)
469            finally:
[76cd1ae]470                # Save special cases in original data1d object
471                # then restore the data1d
[19b628f]472                if tagname_original == "SASdetector":
[76cd1ae]473                    save_data1d.detector.append(data1d)
[19b628f]474                elif tagname_original == "SAScollimation":
[76cd1ae]475                    save_data1d.collimation.append(data1d)
476                elif tagname == "SAStransmission_spectrum":
477                    save_data1d.trans_spectrum.append(data1d)
[19b628f]478                elif tagname_original == "SASprocess":
[76cd1ae]479                    save_data1d.process.append(data1d)
[19b628f]480                elif tagname_original == "aperture":
[76cd1ae]481                    save_data1d.aperture.append(data1d)
[e390933]482                else:
[76cd1ae]483                    save_data1d = data1d
484                data1d = save_data1d
[19b628f]485                # Remove tagname from ns to restore original base
486                ns.remove(tagname_original)
[d6513cd]487       
[76cd1ae]488        return data1d, extras
[d6513cd]489       
[b3de3a45]490    def _to_xml_doc(self, datainfo):
[4c00964]491        """
[0997158f]492        Create an XML document to contain the content of a Data1D
493       
494        :param datainfo: Data1D object
[4c00964]495        """
496       
[7d8094b]497        if not issubclass(datainfo.__class__, Data1D):
[4c00964]498            raise RuntimeError, "The cansas writer expects a Data1D instance"
499       
[76cd1ae]500        ns = CANSAS_NS.get(self.cansas_version).get("ns")
[4c00964]501        doc = xml.dom.minidom.Document()
[c6ca23d]502       
[4c00964]503        main_node = doc.createElement("SASroot")
[c6ca23d]504        if self.cansas_version == "1.1":
505            pi = doc.createProcessingInstruction('xml-stylesheet', \
506                                    'type="text/xsl" href="cansasxml-html.xsl"')
507            root = doc.firstChild
508            doc.insertBefore(pi, root)
[76cd1ae]509        main_node.setAttribute("version", self.cansas_version)
[19b628f]510        main_node.setAttribute("xmlns", ns)
[a7a5886]511        main_node.setAttribute("xmlns:xsi",
512                               "http://www.w3.org/2001/XMLSchema-instance")
[c6ca23d]513        if self.cansas_version == "1.0":
514            main_node.setAttribute("xsi:schemaLocation", "cansas1d/1.0 http://svn.smallangles.net/svn/canSAS/1dwg/trunk/cansas1d.xsd")
515        elif self.cansas_version == "1.1":
516            main_node.setAttribute("xsi:schemaLocation", "urn:cansas1d:1.1 http://www.cansas.org/formats/1.1/cansas1d.xsd")
[fee780b]517       
[4c00964]518        doc.appendChild(main_node)
519       
520        entry_node = doc.createElement("SASentry")
521        main_node.appendChild(entry_node)
522       
[579ba85]523        write_node(doc, entry_node, "Title", datainfo.title)
524        for item in datainfo.run:
525            runname = {}
[7d6351e]526            if item in datainfo.run_name and \
527            len(str(datainfo.run_name[item])) > 1:
528                runname = {'name': datainfo.run_name[item]}
[579ba85]529            write_node(doc, entry_node, "Run", item, runname)
[4c00964]530       
531        # Data info
532        node = doc.createElement("SASdata")
533        entry_node.appendChild(node)
534       
[579ba85]535        for i in range(len(datainfo.x)):
536            pt = doc.createElement("Idata")
537            node.appendChild(pt)
[7d6351e]538            write_node(doc, pt, "Q", datainfo.x[i], {'unit': datainfo.x_unit})
539            if len(datainfo.y) >= i:
[a7a5886]540                write_node(doc, pt, "I", datainfo.y[i],
[7d6351e]541                            {'unit': datainfo.y_unit})
[76cd1ae]542            if datainfo.dy != None and len(datainfo.dy) > i:
[19b628f]543                write_node(doc, pt, "Idev", datainfo.dy[i],
544                            {'unit': datainfo.y_unit})
[76cd1ae]545            if datainfo.dx != None and len(datainfo.dx) > i:
[a7a5886]546                write_node(doc, pt, "Qdev", datainfo.dx[i],
[7d6351e]547                            {'unit': datainfo.x_unit})
[76cd1ae]548            if datainfo.dxw != None and len(datainfo.dxw) > i:
[0d8642c9]549                write_node(doc, pt, "dQw", datainfo.dxw[i],
[7d6351e]550                            {'unit': datainfo.x_unit})
[76cd1ae]551            if datainfo.dxl != None and len(datainfo.dxl) > i:
[19b628f]552                write_node(doc, pt, "dQl", datainfo.dxl[i],
553                            {'unit': datainfo.x_unit})
554
555        # Transmission Spectrum Info
[76cd1ae]556        for i in range(len(datainfo.trans_spectrum)):
557            spectrum = datainfo.trans_spectrum[i]
[19b628f]558            node = doc.createElement("SAStransmission_spectrum")
559            entry_node.appendChild(node)
[76cd1ae]560            for i in range(len(spectrum.wavelength)):
[19b628f]561                pt = doc.createElement("Tdata")
562                node.appendChild(pt)
[76cd1ae]563                write_node(doc, pt, "Lambda", spectrum.wavelength[i], 
564                           {'unit': spectrum.wavelength_unit})
565                write_node(doc, pt, "T", spectrum.transmission[i], 
566                           {'unit': spectrum.transmission_unit})
567                if spectrum.transmission_deviation != None \
568                and len(spectrum.transmission_deviation) >= i:
569                    write_node(doc, pt, "Tdev", \
570                               spectrum.transmission_deviation[i], \
571                               {'unit': spectrum.transmission_deviation_unit})
[579ba85]572
[4c00964]573        # Sample info
574        sample = doc.createElement("SASsample")
[579ba85]575        if datainfo.sample.name is not None:
576            sample.setAttribute("name", str(datainfo.sample.name))
[4c00964]577        entry_node.appendChild(sample)
[579ba85]578        write_node(doc, sample, "ID", str(datainfo.sample.ID))
[a7a5886]579        write_node(doc, sample, "thickness", datainfo.sample.thickness,
[7d6351e]580                   {"unit": datainfo.sample.thickness_unit})
[4c00964]581        write_node(doc, sample, "transmission", datainfo.sample.transmission)
[a7a5886]582        write_node(doc, sample, "temperature", datainfo.sample.temperature,
[7d6351e]583                   {"unit": datainfo.sample.temperature_unit})
[4c00964]584       
585        pos = doc.createElement("position")
[a7a5886]586        written = write_node(doc, pos, "x", datainfo.sample.position.x,
[7d6351e]587                             {"unit": datainfo.sample.position_unit})
[a7a5886]588        written = written | write_node(doc, pos, "y",
589                                       datainfo.sample.position.y,
[7d6351e]590                                       {"unit": datainfo.sample.position_unit})
[a7a5886]591        written = written | write_node(doc, pos, "z",
592                                       datainfo.sample.position.z,
[7d6351e]593                                       {"unit": datainfo.sample.position_unit})
[4c00964]594        if written == True:
595            sample.appendChild(pos)
596       
597        ori = doc.createElement("orientation")
[a7a5886]598        written = write_node(doc, ori, "roll",
599                             datainfo.sample.orientation.x,
[7d6351e]600                             {"unit": datainfo.sample.orientation_unit})
[a7a5886]601        written = written | write_node(doc, ori, "pitch",
602                                       datainfo.sample.orientation.y,
[7d6351e]603                                    {"unit": datainfo.sample.orientation_unit})
[a7a5886]604        written = written | write_node(doc, ori, "yaw",
605                                       datainfo.sample.orientation.z,
[7d6351e]606                                    {"unit": datainfo.sample.orientation_unit})
[4c00964]607        if written == True:
608            sample.appendChild(ori)
609       
[19b628f]610        for item in datainfo.sample.details:
611            write_node(doc, sample, "details", item)
612       
[4c00964]613        # Instrument info
614        instr = doc.createElement("SASinstrument")
615        entry_node.appendChild(instr)
616       
617        write_node(doc, instr, "name", datainfo.instrument)
618       
619        #   Source
620        source = doc.createElement("SASsource")
[579ba85]621        if datainfo.source.name is not None:
622            source.setAttribute("name", str(datainfo.source.name))
[4c00964]623        instr.appendChild(source)
624        write_node(doc, source, "radiation", datainfo.source.radiation)
[19b628f]625       
[579ba85]626        size = doc.createElement("beam_size")
627        if datainfo.source.beam_size_name is not None:
628            size.setAttribute("name", str(datainfo.source.beam_size_name))
[a7a5886]629        written = write_node(doc, size, "x", datainfo.source.beam_size.x,
[7d6351e]630                             {"unit": datainfo.source.beam_size_unit})
[a7a5886]631        written = written | write_node(doc, size, "y",
632                                       datainfo.source.beam_size.y,
[7d6351e]633                                       {"unit": datainfo.source.beam_size_unit})
[a7a5886]634        written = written | write_node(doc, size, "z",
635                                       datainfo.source.beam_size.z,
[7d6351e]636                                       {"unit": datainfo.source.beam_size_unit})
[579ba85]637        if written == True:
638            source.appendChild(size)
639           
[19b628f]640        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
[a7a5886]641        write_node(doc, source, "wavelength",
642                   datainfo.source.wavelength,
[7d6351e]643                   {"unit": datainfo.source.wavelength_unit})
[a7a5886]644        write_node(doc, source, "wavelength_min",
645                   datainfo.source.wavelength_min,
[7d6351e]646                   {"unit": datainfo.source.wavelength_min_unit})
[a7a5886]647        write_node(doc, source, "wavelength_max",
648                   datainfo.source.wavelength_max,
[7d6351e]649                   {"unit": datainfo.source.wavelength_max_unit})
[a7a5886]650        write_node(doc, source, "wavelength_spread",
651                   datainfo.source.wavelength_spread,
[7d6351e]652                   {"unit": datainfo.source.wavelength_spread_unit})
[4c00964]653       
654        #   Collimation
655        for item in datainfo.collimation:
656            coll = doc.createElement("SAScollimation")
[579ba85]657            if item.name is not None:
658                coll.setAttribute("name", str(item.name))
[4c00964]659            instr.appendChild(coll)
660           
[a7a5886]661            write_node(doc, coll, "length", item.length,
[7d6351e]662                       {"unit": item.length_unit})
[4c00964]663           
664            for apert in item.aperture:
[579ba85]665                ap = doc.createElement("aperture")
666                if apert.name is not None:
667                    ap.setAttribute("name", str(apert.name))
668                if apert.type is not None:
669                    ap.setAttribute("type", str(apert.type))
670                coll.appendChild(ap)
[4c00964]671               
672                size = doc.createElement("size")
[579ba85]673                if apert.size_name is not None:
674                    size.setAttribute("name", str(apert.size_name))
[a7a5886]675                written = write_node(doc, size, "x", apert.size.x,
[7d6351e]676                                     {"unit": apert.size_unit})
[a7a5886]677                written = written | write_node(doc, size, "y", apert.size.y,
[7d6351e]678                                               {"unit": apert.size_unit})
[a7a5886]679                written = written | write_node(doc, size, "z", apert.size.z,
[7d6351e]680                                               {"unit": apert.size_unit})
[579ba85]681                if written == True:
682                    ap.appendChild(size)
[19b628f]683               
684                write_node(doc, ap, "distance", apert.distance,
685                           {"unit": apert.distance_unit})
[4c00964]686
687        #   Detectors
688        for item in datainfo.detector:
689            det = doc.createElement("SASdetector")
[579ba85]690            written = write_node(doc, det, "name", item.name)
[a7a5886]691            written = written | write_node(doc, det, "SDD", item.distance,
[7d6351e]692                                           {"unit": item.distance_unit})
[579ba85]693            if written == True:
694                instr.appendChild(det)
[4c00964]695           
696            off = doc.createElement("offset")
[a7a5886]697            written = write_node(doc, off, "x", item.offset.x,
[7d6351e]698                                 {"unit": item.offset_unit})
[a7a5886]699            written = written | write_node(doc, off, "y", item.offset.y,
[7d6351e]700                                           {"unit": item.offset_unit})
[a7a5886]701            written = written | write_node(doc, off, "z", item.offset.z,
[7d6351e]702                                           {"unit": item.offset_unit})
[579ba85]703            if written == True:
704                det.appendChild(off)
[19b628f]705               
706            ori = doc.createElement("orientation")
707            written = write_node(doc, ori, "roll", item.orientation.x,
708                                 {"unit": item.orientation_unit})
709            written = written | write_node(doc, ori, "pitch",
710                                           item.orientation.y,
711                                           {"unit": item.orientation_unit})
712            written = written | write_node(doc, ori, "yaw",
713                                           item.orientation.z,
714                                           {"unit": item.orientation_unit})
715            if written == True:
716                det.appendChild(ori)
[4c00964]717           
718            center = doc.createElement("beam_center")
[a7a5886]719            written = write_node(doc, center, "x", item.beam_center.x,
[7d6351e]720                                 {"unit": item.beam_center_unit})
[a7a5886]721            written = written | write_node(doc, center, "y",
722                                           item.beam_center.y,
[7d6351e]723                                           {"unit": item.beam_center_unit})
[a7a5886]724            written = written | write_node(doc, center, "z",
725                                           item.beam_center.z,
[7d6351e]726                                           {"unit": item.beam_center_unit})
[579ba85]727            if written == True:
728                det.appendChild(center)
729               
[4c00964]730            pix = doc.createElement("pixel_size")
[a7a5886]731            written = write_node(doc, pix, "x", item.pixel_size.x,
[7d6351e]732                                 {"unit": item.pixel_size_unit})
[a7a5886]733            written = written | write_node(doc, pix, "y", item.pixel_size.y,
[7d6351e]734                                           {"unit": item.pixel_size_unit})
[a7a5886]735            written = written | write_node(doc, pix, "z", item.pixel_size.z,
[7d6351e]736                                           {"unit": item.pixel_size_unit})
[579ba85]737            if written == True:
738                det.appendChild(pix)
[19b628f]739            written = written | write_node(doc, det, "slit_length",
740                                           item.slit_length,
741                                           {"unit": item.slit_length_unit})
742           
[579ba85]743        # Processes info
[4c00964]744        for item in datainfo.process:
745            node = doc.createElement("SASprocess")
746            entry_node.appendChild(node)
747
[579ba85]748            write_node(doc, node, "name", item.name)
749            write_node(doc, node, "date", item.date)
750            write_node(doc, node, "description", item.description)
751            for term in item.term:
752                value = term['value']
753                del term['value']
754                write_node(doc, node, "term", value, term)
755            for note in item.notes:
756                write_node(doc, node, "SASprocessnote", note)
[19b628f]757            if len(item.notes) == 0:
758                write_node(doc, node, "SASprocessnote", "")
759               
760        # Note info
761        if len(datainfo.notes) == 0:
762            node = doc.createElement("SASnote")
763            entry_node.appendChild(node)
764            if node.hasChildNodes():
765                for child in node.childNodes:
766                    node.removeChild(child)
767        else:
768            for item in datainfo.notes:
769                node = doc.createElement("SASnote")
770                entry_node.appendChild(node)
771                node.appendChild(doc.createTextNode(item))
772               
[b3de3a45]773        # Return the document, and the SASentry node associated with
774        # the data we just wrote
775        return doc, entry_node
776           
777    def write(self, filename, datainfo):
778        """
[0997158f]779        Write the content of a Data1D as a CanSAS XML file
780       
781        :param filename: name of the file to write
782        :param datainfo: Data1D object
[b3de3a45]783        """
784        # Create XML document
[7d6351e]785        doc, _ = self._to_xml_doc(datainfo)
[4c00964]786        # Write the file
787        fd = open(filename, 'w')
788        fd.write(doc.toprettyxml())
789        fd.close()
Note: See TracBrowser for help on using the repository browser.