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
Line 
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
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
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
25from DataLoader.data_info import Data1D, Collimation, Detector, Process, Aperture
26from xml import xpath
27import xml.dom.minidom 
28
29
30has_converter = True
31try:
32    from data_util.nxsunit import Converter
33except:
34    has_converter = False
35
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
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               
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
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:
118            value = float(content)   
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:
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           
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 ###################
312        value, attr = get_content('SASinstrument/SASsource', dom)
313        if attr.has_key('name'):
314            data_info.source.name = attr['name']
315       
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()
341            value, attr = get_node_text(item)
342            if attr.has_key('name'):
343                collim.name = attr['name']
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:
349                aperture =  Aperture()
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                   
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
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       
480        if data_conv_q is not None:
481            data_info.xaxis("\\rm{Q}", data_info.x_unit)
482        else:
483            data_info.xaxis("\\rm{Q}", 'A^{-1}')
484        if data_conv_i is not None:
485            data_info.yaxis("\\{I(Q)}", data_info.y_unit)
486        else:
487            data_info.yaxis("\\rm{I(Q)}","cm^{-1}")
488       
489        return data_info
490
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       
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.