source: sasview/DataLoader/readers/cansas_reader.py @ 4c00964

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 4c00964 was 4c00964, checked in by Mathieu Doucet <doucetm@…>, 16 years ago

Working on cansas writer

  • Property mode set to 100644
File size: 26.1 KB
RevLine 
[8780e9a]1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2008, University of Tennessee
9"""
10
11#TODO: Unit conversion
[4c00964]12#TODO: missing aperture type: go through all entries and check for additional attributes
13#TODO: check that all vectors are written only if they have at least one non-empty value
14#TODO: multiple SASEntrys
[8780e9a]15#TODO: Store error list
16#TODO: convert from pixel to mm for beam center...
17#TODO: Allow for additional meta data for each section
18#TODO: Notes need to be implemented. They can be any XML structure in version 1.0
19#      Process notes have the same problem.
20
21
22import logging
23import numpy
24import os, sys
[d6513cd]25from DataLoader.data_info import Data1D, Collimation, Detector, Process, Aperture
[8780e9a]26from xml import xpath
[4c00964]27import xml.dom.minidom 
28
[8780e9a]29
[b39c817]30has_converter = True
31try:
32    from data_util.nxsunit import Converter
33except:
34    has_converter = False
35
[4c00964]36def write_node(doc, parent, name, value, attr={}):
37    """
38        @param doc: document DOM
39        @param parent: parent node
40        @param name: tag of the element
41        @param value: value of the child text node
42        @param attr: attribute dictionary
43        @return: True if something was appended, otherwise False
44    """
45    if value is not None:
46        node = doc.createElement(name)
47        node.appendChild(doc.createTextNode(str(value)))
48        for item in attr:
49            node.setAttribute(item, attr[item])
50        parent.appendChild(node)
51        return True
52    return False
53
[8780e9a]54def get_node_text(node):
55    """
56        Get the text context of a node
57       
58        @param node: node to read from
59        @return: content, attribute list
60    """
61    content = None
62    attr    = {}
63    for item in node.childNodes:
64        if item.nodeName.find('text')>=0 \
65            and len(item.nodeValue.strip())>0:
66            content = item.nodeValue.strip()
67            break
68       
69    if node.hasAttributes():
70        for i in range(node.attributes.length):
71            attr[node.attributes.item(i).nodeName] \
72                = node.attributes.item(i).nodeValue
73
74    return content, attr
75
76def get_content(location, node):
77    """
78        Get the first instance of the content of a xpath location
79       
80        @param location: xpath location
81        @param node: node to start at
82    """
83    value = None
84    attr  = {}
85    nodes = xpath.Evaluate(location, node)
86    if len(nodes)>0:
87        try:
88            # Skip comments and empty lines
89            for item in nodes[0].childNodes:
90                if item.nodeName.find('text')>=0 \
91                    and len(item.nodeValue.strip())>0:
92                    value = item.nodeValue.strip()
93                    break
94               
[d6513cd]95            if nodes[0].hasAttributes():
96                for i in range(nodes[0].attributes.length):
97                    attr[nodes[0].attributes.item(i).nodeName] \
98                        = nodes[0].attributes.item(i).nodeValue
[8780e9a]99        except:
100            # problem reading the node. Skip it and return that
101            # nothing was found
102            logging.error("cansas_reader.get_content: %s\n  %s" % (location, sys.exc_value))
103       
104    return value, attr
105
106def get_float(location, node):
107    """
108        Get the content of a node as a float
109       
110        @param location: xpath location
111        @param node: node to start at
112    """
113    value = None
114    attr = {}
115    content, attr = get_content(location, node)
116    if content is not None:
117        try:
[b39c817]118            value = float(content)   
[8780e9a]119        except:
120            # Could not pass, skip and return None
121            logging.error("cansas_reader.get_float: could not convert '%s' to float" % content)
122       
123    return value, attr
124
125def _store_float(location, node, variable, storage):
126    """
127        Get the content of a xpath location and store
128        the result. Check that the units are compatible
129        with the destination. The value is expected to
130        be a float.
131       
132        The xpath location might or might not exist.
133        If it does not exist, nothing is done
134       
135        @param location: xpath location to fetch
136        @param node: node to read the data from
137        @param variable: name of the data member to store it in [string]
138        @param storage: data object that has the 'variable' data member
139       
140        @raise ValueError: raised when the units are not recognized
141    """
142    value, attr = get_float(location, node)
143    if value is not None:
144        # If the entry has units, check to see that they are
145        # compatible with what we currently have in the data object
146        if attr.has_key('unit'):
147            toks = variable.split('.')
148            exec "local_unit = storage.%s_unit.lower()" % toks[0]
149            if attr['unit'].lower()!=local_unit:
[b39c817]150                if has_converter==True:
151                    try:
152                        conv = Converter(attr['unit'])
153                        exec "storage.%s = %g" % (variable, conv(value, units=local_unit))
154                    except:
155                        raise ValueError, "CanSAS reader: could not convert %s unit [%s]; expecting [%s]\n  %s" \
156                        % (variable, attr['unit'], local_unit, sys.exc_value)
157                else:
158                    raise ValueError, "CanSAS reader: unrecognized %s unit [%s]; expecting [%s]" \
159                        % (variable, attr['unit'], local_unit)
160            else:
161                exec "storage.%s = value" % variable
162        else:
163            exec "storage.%s = value" % variable
164           
[8780e9a]165
166def _store_content(location, node, variable, storage):
167    """
168        Get the content of a xpath location and store
169        the result. The value is treated as a string.
170       
171        The xpath location might or might not exist.
172        If it does not exist, nothing is done
173       
174        @param location: xpath location to fetch
175        @param node: node to read the data from
176        @param variable: name of the data member to store it in [string]
177        @param storage: data object that has the 'variable' data member
178    """
179    value, attr = get_content(location, node)
180    if value is not None:
181        exec "storage.%s = value" % variable
182
183
184class Reader:
185    """
186        Class to load cansas 1D XML files
187       
188        Dependencies:
189            The CanSas reader requires PyXML 0.8.4 or later.
190    """
191    ## CanSAS version
192    version = '1.0'
193    ## File type
194    type = ["CanSAS 1D files (*.xml)|*.xml"]
195    ## List of allowed extensions
196    ext=['.xml', '.XML'] 
197   
198    def read(self, path):
199        """
200            Load data file
201           
202            @param path: file path
203            @return: Data1D object if a single SASentry was found,
204                        or a list of Data1D objects if multiple entries were found,
205                        or None of nothing was found
206            @raise RuntimeError: when the file can't be opened
207            @raise ValueError: when the length of the data vectors are inconsistent
208        """
209        from xml.dom.minidom import parse
210       
211        output = []
212       
213        if os.path.isfile(path):
214            basename  = os.path.basename(path)
215            root, extension = os.path.splitext(basename)
216            if extension.lower() in self.ext:
217               
218                dom = parse(path)
219               
220                # Check the format version number
221                nodes = xpath.Evaluate('SASroot', dom)
222                if nodes[0].hasAttributes():
223                    for i in range(nodes[0].attributes.length):
224                        if nodes[0].attributes.item(i).nodeName=='version':
225                            if nodes[0].attributes.item(i).nodeValue != self.version:
226                                raise ValueError, "cansas_reader: unrecognized version number %s" % \
227                                    nodes[0].attributes.item(i).nodeValue
228               
229                entry_list = xpath.Evaluate('SASroot/SASentry', dom)
230                for entry in entry_list:
231                    sas_entry = self._parse_entry(entry)
232                    sas_entry.filename = basename
233                    output.append(sas_entry)
234               
235        else:
236            raise RuntimeError, "%s is not a file" % path
237       
238        # Return output consistent with the loader's api
239        if len(output)==0:
240            return None
241        elif len(output)==1:
242            return output[0]
243        else:
244            return output               
245               
246    def _parse_entry(self, dom):
247        """
248            Parse a SASentry
249           
250            @param node: SASentry node
251            @return: Data1D object
252        """
253        x = numpy.zeros(0)
254        y = numpy.zeros(0)
255       
256        data_info = Data1D(x, y)
257       
258        # Look up title
259        _store_content('Title', dom, 'title', data_info)
260        # Look up run number                   
261        _store_content('Run', dom, 'run', data_info)                   
262        # Look up instrument name             
263        value, attr = get_content('SASinstrument', dom)
264        if attr.has_key('name'):
265            data_info.instrument = attr['name']
266
267        note_list = xpath.Evaluate('SASnote', dom)
268        for note in note_list:
269            try:
270                note_value, note_attr = get_node_text(note)
271                if note_value is not None:
272                    data_info.notes.append(note_value)
273            except:
274                logging.error("cansas_reader.read: error processing entry notes\n  %s" % sys.exc_value)
275
276       
277        # Sample info ###################
278        _store_content('SASsample/ID', 
279                     dom, 'ID', data_info.sample)                   
280        _store_float('SASsample/thickness', 
281                     dom, 'thickness', data_info.sample)
282        _store_float('SASsample/transmission', 
283                     dom, 'transmission', data_info.sample)
284        _store_float('SASsample/temperature', 
285                     dom, 'temperature', data_info.sample)
286        nodes = xpath.Evaluate('SASsample/details', dom)
287        for item in nodes:
288            try:
289                detail_value, detail_attr = get_node_text(item)
290                if detail_value is not None:
291                    data_info.sample.details.append(detail_value)
292            except:
293                logging.error("cansas_reader.read: error processing sample details\n  %s" % sys.exc_value)
294       
295        # Position (as a vector)
296        _store_float('SASsample/position/x', 
297                     dom, 'position.x', data_info.sample)         
298        _store_float('SASsample/position/y', 
299                     dom, 'position.y', data_info.sample)         
300        _store_float('SASsample/position/z', 
301                     dom, 'position.z', data_info.sample)         
302       
303        # Orientation (as a vector)
304        _store_float('SASsample/orientation/roll', 
305                     dom, 'orientation.x', data_info.sample)         
306        _store_float('SASsample/orientation/pitch', 
307                     dom, 'orientation.y', data_info.sample)         
308        _store_float('SASsample/orientation/yaw', 
309                     dom, 'orientation.z', data_info.sample)         
310       
311        # Source info ###################
[4c00964]312        value, attr = get_content('SASinstrument/SASsource', dom)
313        if attr.has_key('name'):
314            data_info.source.name = attr['name']
315       
[8780e9a]316        _store_content('SASinstrument/SASsource/radiation', 
317                     dom, 'radiation', data_info.source)                   
318        _store_content('SASinstrument/SASsource/beam_shape', 
319                     dom, 'beam_shape', data_info.source)                   
320        _store_float('SASinstrument/SASsource/wavelength', 
321                     dom, 'wavelength', data_info.source)         
322        _store_float('SASinstrument/SASsource/wavelength_min', 
323                     dom, 'wavelength_min', data_info.source)         
324        _store_float('SASinstrument/SASsource/wavelength_max', 
325                     dom, 'wavelength_max', data_info.source)         
326        _store_float('SASinstrument/SASsource/wavelength_spread', 
327                     dom, 'wavelength_spread', data_info.source)   
328       
329        # Beam size (as a vector)     
330        _store_float('SASinstrument/SASsource/beam_size/x', 
331                     dom, 'beam_size.x', data_info.source)   
332        _store_float('SASinstrument/SASsource/beam_size/y', 
333                     dom, 'beam_size.y', data_info.source)   
334        _store_float('SASinstrument/SASsource/beam_size/z', 
335                     dom, 'beam_size.z', data_info.source)   
336       
337        # Collimation info ###################
338        nodes = xpath.Evaluate('SASinstrument/SAScollimation', dom)
339        for item in nodes:
340            collim = Collimation()
[4c00964]341            value, attr = get_node_text(item)
342            if attr.has_key('name'):
343                collim.name = attr['name']
[8780e9a]344            _store_float('length', item, 'length', collim) 
345           
346            # Look for apertures
347            apert_list = xpath.Evaluate('aperture', item)
348            for apert in apert_list:
[d6513cd]349                aperture =  Aperture()
[4c00964]350               
351                # Get the name and type of the aperture
352                ap_value, ap_attr = get_node_text(item)
353                if ap_attr.has_key('name'):
354                    aperture.name = ap_attr['name']
355                if ap_attr.has_key('type'):
356                    aperture.type = ap_attr['type']
357                   
[8780e9a]358                _store_float('distance', apert, 'distance', aperture)   
359                _store_float('size/x', apert, 'size.x', aperture)   
360                _store_float('size/y', apert, 'size.y', aperture)   
361                _store_float('size/z', apert, 'size.z', aperture)
362               
363                collim.aperture.append(aperture)
364               
365            data_info.collimation.append(collim)
366       
367        # Detector info ######################
368        nodes = xpath.Evaluate('SASinstrument/SASdetector', dom)
369        for item in nodes:
370           
371            detector = Detector()
372           
373            _store_content('name', item, 'name', detector)
374            _store_float('SDD', item, 'distance', detector)   
375           
376            # Detector offset (as a vector)
377            _store_float('offset/x', item, 'offset.x', detector)   
378            _store_float('offset/y', item, 'offset.y', detector)   
379            _store_float('offset/z', item, 'offset.z', detector)   
380           
381            # Detector orientation (as a vector)
382            _store_float('orientation/pitch', item, 'orientation.x', detector)   
383            _store_float('orientation/yaw',   item, 'orientation.y', detector)   
384            _store_float('orientation/roll',  item, 'orientation.z', detector)   
385           
386            # Beam center (as a vector)
387            _store_float('beam_center/x', item, 'beam_center.x', detector)   
388            _store_float('beam_center/y', item, 'beam_center.y', detector)   
389            _store_float('beam_center/z', item, 'beam_center.z', detector)   
390           
391            # Pixel size (as a vector)
392            _store_float('pixel_size/x', item, 'pixel_size.x', detector)   
393            _store_float('pixel_size/y', item, 'pixel_size.y', detector)   
394            _store_float('pixel_size/z', item, 'pixel_size.z', detector)   
395           
396            _store_float('slit_length', item, 'slit_length', detector)
397           
398            data_info.detector.append(detector)   
399
400        # Processes info ######################
401        nodes = xpath.Evaluate('SASprocess', dom)
402        for item in nodes:
403            process = Process()
404            _store_content('name', item, 'name', process)
405            _store_content('date', item, 'date', process)
406            _store_content('description', item, 'description', process)
407           
408            term_list = xpath.Evaluate('term', item)
409            for term in term_list:
410                try:
411                    term_value, term_attr = get_node_text(term)
412                    term_attr['value'] = term_value
413                    if term_value is not None:
414                        process.term.append(term_attr)
415                except:
416                    logging.error("cansas_reader.read: error processing process term\n  %s" % sys.exc_value)
417           
418            note_list = xpath.Evaluate('SASprocessnote', item)
419            for note in note_list:
420                try:
421                    note_value, note_attr = get_node_text(note)
422                    if note_value is not None:
423                        process.notes.append(note_value)
424                except:
425                    logging.error("cansas_reader.read: error processing process notes\n  %s" % sys.exc_value)
426           
427           
428            data_info.process.append(process)
429           
430           
431        # Data info ######################
432        nodes = xpath.Evaluate('SASdata/Idata', dom)
433        x  = numpy.zeros(0)
434        y  = numpy.zeros(0)
435        dx = numpy.zeros(0)
436        dy = numpy.zeros(0)
437       
438        for item in nodes:
439            _x, attr = get_float('Q', item)
440            _dx, attr = get_float('Qdev', item)
441            if _dx == None:
442                _dx = 0.0
443            if attr.has_key('unit') and attr['unit'].lower() != data_info.x_unit.lower():
444                raise ValueError, "CanSAS reader: unrecognized %s unit [%s]; expecting [%s]" \
445                    % (variable, attr['unit'], local_unit)
446               
447            _y, attr = get_float('I', item)
448            _dy, attr = get_float('Idev', item)
449            if _dy == None:
450                _dy = 0.0
451            if attr.has_key('unit') and attr['unit'].lower() != data_info.y_unit.lower():
452                raise ValueError, "CanSAS reader: unrecognized %s unit [%s]; expecting [%s]" \
453                    % (variable, attr['unit'], local_unit)
454               
455            if _x is not None and _y is not None:
456                x  = numpy.append(x, _x)
457                y  = numpy.append(x, _y)
458                dx = numpy.append(x, _dx)
459                dy = numpy.append(x, _dy)
460           
461        data_info.x = x
462        data_info.y = y
463        data_info.dx = dx
464        data_info.dy = dy
[d6513cd]465       
466        data_conv_q = None
467        data_conv_i = None
468       
469        if has_converter == True and data_info.x_unit != '1/A':
470            data_conv_q = Converter('1/A')
471            # Test it
472            data_conv_q(1.0, output.Q_unit)
473           
474        if has_converter == True and data_info.y_unit != '1/cm':
475            data_conv_i = Converter('1/cm')
476            # Test it
477            data_conv_i(1.0, output.I_unit)           
478   
479       
[99d1af6]480        if data_conv_q is not None:
[d6513cd]481            data_info.xaxis("\\rm{Q}", data_info.x_unit)
[99d1af6]482        else:
483            data_info.xaxis("\\rm{Q}", 'A^{-1}')
484        if data_conv_i is not None:
[d6513cd]485            data_info.yaxis("\\{I(Q)}", data_info.y_unit)
[99d1af6]486        else:
487            data_info.yaxis("\\rm{I(Q)}","cm^{-1}")
488       
[8780e9a]489        return data_info
490
[4c00964]491    def write(self, filename, datainfo):
492        """
493            Write the content of a Data1D as a CanSAS XML file
494           
495            @param filename: name of the file to write
496            @param datainfo: Data1D object
497        """
498       
499        if not datainfo.__class__ == Data1D: 
500            raise RuntimeError, "The cansas writer expects a Data1D instance"
501       
502        doc = xml.dom.minidom.Document()
503        main_node = doc.createElement("SASroot")
504        main_node.setAttribute("version", "1.0")
505        doc.appendChild(main_node)
506       
507        entry_node = doc.createElement("SASentry")
508        main_node.appendChild(entry_node)
509       
510        write_node(doc, entry_node, "title", datainfo.title)
511        write_node(doc, entry_node, "run", datainfo.run)
512       
513        # Data info
514        node = doc.createElement("SASdata")
515        entry_node.appendChild(node)
516       
517        # Sample info
518        sample = doc.createElement("SASsample")
519        entry_node.appendChild(sample)
520        write_node(doc, sample, "ID", datainfo.sample.ID)
521        write_node(doc, sample, "thickness", datainfo.sample.thickness, {"unit":datainfo.sample.thickness_unit})
522        write_node(doc, sample, "transmission", datainfo.sample.transmission)
523        write_node(doc, sample, "temperature", datainfo.sample.temperature, {"unit":datainfo.sample.temperature_unit})
524       
525        for item in datainfo.sample.details:
526            write_node(doc, sample, "details", item)
527       
528        pos = doc.createElement("position")
529        written = False
530        written = written or write_node(doc, pos, "x", datainfo.sample.position.x, {"unit":datainfo.sample.position_unit})
531        written = written or write_node(doc, pos, "y", datainfo.sample.position.y, {"unit":datainfo.sample.position_unit})
532        written = written or write_node(doc, pos, "z", datainfo.sample.position.z, {"unit":datainfo.sample.position_unit})
533        if written == True:
534            sample.appendChild(pos)
535       
536        ori = doc.createElement("orientation")
537        written = False
538        written = written or write_node(doc, ori, "roll",  datainfo.sample.orientation.x, {"unit":datainfo.sample.orientation_unit})
539        written = written or write_node(doc, ori, "pitch", datainfo.sample.orientation.y, {"unit":datainfo.sample.orientation_unit})
540        written = written or write_node(doc, ori, "yaw",   datainfo.sample.orientation.z, {"unit":datainfo.sample.orientation_unit})
541        if written == True:
542            sample.appendChild(ori)
543       
544        # Instrument info
545        instr = doc.createElement("SASinstrument")
546        entry_node.appendChild(instr)
547       
548        write_node(doc, instr, "name", datainfo.instrument)
549       
550        #   Source
551        source = doc.createElement("SASsource")
552        source.setAttribute("name", str(datainfo.source.name))
553        instr.appendChild(source)
554       
555        write_node(doc, source, "radiation", datainfo.source.radiation)
556        write_node(doc, source, "beam_shape", datainfo.source.beam_shape)
557        write_node(doc, source, "wavelength", datainfo.source.wavelength, {"unit":datainfo.source.wavelength_unit})
558        write_node(doc, source, "wavelength_min", datainfo.source.wavelength_min, {"unit":datainfo.source.wavelength_min_unit})
559        write_node(doc, source, "wavelength_max", datainfo.source.wavelength_max, {"unit":datainfo.source.wavelength_max_unit})
560        write_node(doc, source, "wavelength_spread", datainfo.source.wavelength_spread, {"unit":datainfo.source.wavelength_spread_unit})
561       
562        #   Collimation
563        for item in datainfo.collimation:
564            coll = doc.createElement("SAScollimation")
565            coll.setAttribute("name", item.name)
566            instr.appendChild(coll)
567           
568            write_node(doc, coll, "length", item.length, {"unit":item.length_unit})
569           
570            for apert in item.aperture:
571                ap = doc.createElement("SAScollimation")
572                ap.setAttribute("name", apert.name)
573                ap.setAttribute("type", apert.type)
574                instr.appendChild(ap)
575               
576                write_node(doc, ap, "distance", apert.distance, {"unit":apert.distance_unit})
577               
578                size = doc.createElement("size")
579                ap.appendChild(size)
580                write_node(doc, size, "x", apert.size.x, {"unit":apert.size_unit})
581                write_node(doc, size, "y", apert.size.y, {"unit":apert.size_unit})
582                write_node(doc, size, "z", apert.size.z, {"unit":apert.size_unit})
583               
584
585        #   Detectors
586        for item in datainfo.detector:
587            det = doc.createElement("SASdetector")
588            instr.appendChild(det)
589           
590            write_node(doc, det, "name", item.name)
591            write_node(doc, det, "SDD", item.distance, {"unit":item.distance_unit})
592            write_node(doc, det, "slit_length", item.slit_length, {"unit":item.slit_length_unit})
593           
594            off = doc.createElement("offset")
595            det.appendChild(off)
596            write_node(doc, off, "x", item.offset.x, {"unit":item.offset_unit})
597            write_node(doc, off, "y", item.offset.y, {"unit":item.offset_unit})
598            write_node(doc, off, "z", item.offset.z, {"unit":item.offset_unit})
599           
600           
601            center = doc.createElement("beam_center")
602            det.appendChild(center)
603            write_node(doc, center, "x", item.beam_center.x, {"unit":item.beam_center_unit})
604            write_node(doc, center, "y", item.beam_center.y, {"unit":item.beam_center_unit})
605            write_node(doc, center, "z", item.beam_center.z, {"unit":item.beam_center_unit})
606           
607            pix = doc.createElement("pixel_size")
608            det.appendChild(pix)
609            write_node(doc, pix, "x", item.pixel_size.x, {"unit":item.pixel_size_unit})
610            write_node(doc, pix, "y", item.pixel_size.y, {"unit":item.pixel_size_unit})
611            write_node(doc, pix, "z", item.pixel_size.z, {"unit":item.pixel_size_unit})
612           
613       
614        # Sample info
615        for item in datainfo.process:
616            node = doc.createElement("SASprocess")
617            entry_node.appendChild(node)
618
619            write_node(doc, entry_node, "run", item.name)
620           
621       
622        # Write the file
623        fd = open(filename, 'w')
624        fd.write(doc.toprettyxml())
625        fd.close()
626       
627       
[8780e9a]628if __name__ == "__main__": 
629    logging.basicConfig(level=logging.ERROR,
630                        format='%(asctime)s %(levelname)s %(message)s',
631                        filename='cansas_reader.log',
632                        filemode='w')
633    reader = Reader()
634    print reader.read("../test/cansas1d.xml")
635   
636   
637                       
Note: See TracBrowser for help on using the repository browser.