source: sasview/src/sas/sascalc/dataloader/readers/cansas_reader.py @ ad4632c

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.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since ad4632c was ad4632c, checked in by krzywon, 7 years ago

Save and load SESANS flag and zacceptance to/from project and analysis files.

  • Property mode set to 100644
File size: 62.5 KB
Line 
1"""
2    CanSAS data reader - new recursive cansas_version.
3"""
4############################################################################
5#This software was developed by the University of Tennessee as part of the
6#Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
7#project funded by the US National Science Foundation.
8#If you use DANSE applications to do scientific research that leads to
9#publication, we ask that you acknowledge the use of the software with the
10#following sentence:
11#This work benefited from DANSE software developed under NSF award DMR-0520547.
12#copyright 2008,2009 University of Tennessee
13#############################################################################
14
15import logging
16import numpy as np
17import os
18import sys
19import datetime
20import inspect
21# For saving individual sections of data
22from sas.sascalc.dataloader.data_info import Data1D, DataInfo, plottable_1D
23from sas.sascalc.dataloader.data_info import Collimation, TransmissionSpectrum, Detector, Process, Aperture
24from sas.sascalc.dataloader.data_info import combine_data_info_with_plottable as combine_data
25import sas.sascalc.dataloader.readers.xml_reader as xml_reader
26from sas.sascalc.dataloader.readers.xml_reader import XMLreader
27from sas.sascalc.dataloader.readers.cansas_constants import CansasConstants, CurrentLevel
28
29# The following 2 imports *ARE* used. Do not remove either.
30import xml.dom.minidom
31from xml.dom.minidom import parseString
32
33PREPROCESS = "xmlpreprocess"
34ENCODING = "encoding"
35RUN_NAME_DEFAULT = "None"
36INVALID_SCHEMA_PATH_1_1 = "{0}/sas/sascalc/dataloader/readers/schema/cansas1d_invalid_v1_1.xsd"
37INVALID_SCHEMA_PATH_1_0 = "{0}/sas/sascalc/dataloader/readers/schema/cansas1d_invalid_v1_0.xsd"
38INVALID_XML = "\n\nThe loaded xml file, {0} does not fully meet the CanSAS v1.x specification. SasView loaded " + \
39              "as much of the data as possible.\n\n"
40HAS_CONVERTER = True
41try:
42    from sas.sascalc.data_util.nxsunit import Converter
43except ImportError:
44    HAS_CONVERTER = False
45
46CONSTANTS = CansasConstants()
47CANSAS_FORMAT = CONSTANTS.format
48CANSAS_NS = CONSTANTS.names
49ALLOW_ALL = True
50
51class Reader(XMLreader):
52    """
53    Class to load cansas 1D XML files
54
55    :Dependencies:
56        The CanSAS reader requires PyXML 0.8.4 or later.
57    """
58    ## CanSAS version - defaults to version 1.0
59    cansas_version = "1.0"
60    base_ns = "{cansas1d/1.0}"
61    cansas_defaults = None
62    type_name = "canSAS"
63    invalid = True
64    frm = ""
65    ## Log messages and errors
66    logging = None
67    errors = set()
68    ## Namespace hierarchy for current xml_file object
69    names = None
70    ns_list = None
71    ## Temporary storage location for loading multiple data sets in a single file
72    current_datainfo = None
73    current_dataset = None
74    current_data1d = None
75    data = None
76    ## List of data1D objects to be sent back to SasView
77    output = None
78    ## Wildcards
79    type = ["XML files (*.xml)|*.xml", "SasView Save Files (*.svs)|*.svs"]
80    ## List of allowed extensions
81    ext = ['.xml', '.XML', '.svs', '.SVS']
82    ## Flag to bypass extension check
83    allow_all = True
84
85    def reset_state(self):
86        """
87        Resets the class state to a base case when loading a new data file so previous
88        data files do not appear a second time
89        """
90        self.current_datainfo = None
91        self.current_dataset = None
92        self.current_data1d = None
93        self.data = []
94        self.process = Process()
95        self.transspectrum = TransmissionSpectrum()
96        self.aperture = Aperture()
97        self.collimation = Collimation()
98        self.detector = Detector()
99        self.names = []
100        self.cansas_defaults = {}
101        self.output = []
102        self.ns_list = None
103        self.logging = []
104        self.encoding = None
105
106    def read(self, xml_file, schema_path="", invalid=True):
107        """
108        Validate and read in an xml_file file in the canSAS format.
109
110        :param xml_file: A canSAS file path in proper XML format
111        :param schema_path: A file path to an XML schema to validate the xml_file against
112        """
113        # For every file loaded, reset everything to a base state
114        self.reset_state()
115        self.invalid = invalid
116        # Check that the file exists
117        if os.path.isfile(xml_file):
118            basename, extension = os.path.splitext(os.path.basename(xml_file))
119            # If the file type is not allowed, return nothing
120            if extension in self.ext or self.allow_all:
121                # Get the file location of
122                self.load_file_and_schema(xml_file, schema_path)
123                self.add_data_set()
124                # Try to load the file, but raise an error if unable to.
125                # Check the file matches the XML schema
126                try:
127                    self.is_cansas(extension)
128                    self.invalid = False
129                    # Get each SASentry from XML file and add it to a list.
130                    entry_list = self.xmlroot.xpath(
131                            '/ns:SASroot/ns:SASentry',
132                            namespaces={'ns': self.cansas_defaults.get("ns")})
133                    self.names.append("SASentry")
134
135                    # Get all preprocessing events and encoding
136                    self.set_processing_instructions()
137
138                    # Parse each <SASentry> item
139                    for entry in entry_list:
140                        # Create a new DataInfo object for every <SASentry>
141
142                        # Set the file name and then parse the entry.
143                        self.current_datainfo.filename = basename + extension
144                        self.current_datainfo.meta_data["loader"] = "CanSAS XML 1D"
145                        self.current_datainfo.meta_data[PREPROCESS] = \
146                            self.processing_instructions
147
148                        # Parse the XML SASentry
149                        self._parse_entry(entry)
150                        # Combine datasets with datainfo
151                        self.add_data_set()
152                except RuntimeError:
153                    # If the file does not match the schema, raise this error
154                    invalid_xml = self.find_invalid_xml()
155                    invalid_xml = INVALID_XML.format(basename + extension) + invalid_xml
156                    self.errors.add(invalid_xml)
157                    # Try again with an invalid CanSAS schema, that requires only a data set in each
158                    base_name = xml_reader.__file__
159                    base_name = base_name.replace("\\", "/")
160                    base = base_name.split("/sas/")[0]
161                    if self.cansas_version == "1.1":
162                        invalid_schema = INVALID_SCHEMA_PATH_1_1.format(base, self.cansas_defaults.get("schema"))
163                    else:
164                        invalid_schema = INVALID_SCHEMA_PATH_1_0.format(base, self.cansas_defaults.get("schema"))
165                    self.set_schema(invalid_schema)
166                    try:
167                        if self.invalid:
168                            if self.is_cansas():
169                                self.output = self.read(xml_file, invalid_schema, False)
170                            else:
171                                raise RuntimeError
172                        else:
173                            raise RuntimeError
174                    except RuntimeError:
175                        x = np.zeros(1)
176                        y = np.zeros(1)
177                        self.current_data1d = Data1D(x,y)
178                        self.current_data1d.errors = self.errors
179                        return [self.current_data1d]
180        else:
181            self.output.append("Not a valid file path.")
182        # Return a list of parsed entries that dataloader can manage
183        return self.output
184
185    def _parse_entry(self, dom, recurse=False):
186        """
187        Parse a SASEntry - new recursive method for parsing the dom of
188            the CanSAS data format. This will allow multiple data files
189            and extra nodes to be read in simultaneously.
190
191        :param dom: dom object with a namespace base of names
192        """
193
194        if not self._is_call_local() and not recurse:
195            self.reset_state()
196            self.add_data_set()
197            self.names.append("SASentry")
198            self.parent_class = "SASentry"
199        self._check_for_empty_data()
200        self.base_ns = "{0}{1}{2}".format("{", \
201                            CANSAS_NS.get(self.cansas_version).get("ns"), "}")
202
203        # Go through each child in the parent element
204        for node in dom:
205            attr = node.attrib
206            name = attr.get("name", "")
207            type = attr.get("type", "")
208            # Get the element name and set the current names level
209            tagname = node.tag.replace(self.base_ns, "")
210            tagname_original = tagname
211            # Skip this iteration when loading in save state information
212            if tagname == "fitting_plug_in" or tagname == "pr_inversion" or tagname == "invariant":
213                continue
214
215            # Get where to store content
216            self.names.append(tagname_original)
217            self.ns_list = CONSTANTS.iterate_namespace(self.names)
218            # If the element is a child element, recurse
219            if len(node.getchildren()) > 0:
220                self.parent_class = tagname_original
221                if tagname == 'SASdata':
222                    self._initialize_new_data_set()
223                ## Recursion step to access data within the group
224                self._parse_entry(node, True)
225                if tagname == "SASsample":
226                    self.current_datainfo.sample.name = name
227                elif tagname == "beam_size":
228                    self.current_datainfo.source.beam_size_name = name
229                elif tagname == "SAScollimation":
230                    self.collimation.name = name
231                elif tagname == "aperture":
232                    self.aperture.name = name
233                    self.aperture.type = type
234                self.add_intermediate()
235            else:
236                data_point, unit = self._get_node_value(node, tagname)
237
238                ## If this is a dataset, store the data appropriately
239                if tagname == 'Run':
240                    self.current_datainfo.run_name[data_point] = name
241                    self.current_datainfo.run.append(data_point)
242                elif tagname == 'Title':
243                    self.current_datainfo.title = data_point
244                elif tagname == 'SASnote':
245                    self.current_datainfo.notes.append(data_point)
246
247                ## I and Q Data
248                elif tagname == 'I':
249                    unit_list = unit.split(" | ")
250                    if len(unit_list) > 1:
251                        self.current_dataset.yaxis(unit_list[0], unit_list[1])
252                    else:
253                        self.current_dataset.yaxis("Intensity", unit)
254                    self.current_dataset.y = np.append(self.current_dataset.y, data_point)
255                elif tagname == 'Idev':
256                    self.current_dataset.dy = np.append(self.current_dataset.dy, data_point)
257                elif tagname == 'Q':
258                    unit_list = unit.split(" | ")
259                    if len(unit_list) > 1:
260                        self.current_dataset.xaxis(unit_list[0], unit_list[1])
261                    else:
262                        self.current_dataset.xaxis("Q", unit)
263                    self.current_dataset.x = np.append(self.current_dataset.x, data_point)
264                elif tagname == 'Qdev':
265                    self.current_dataset.dx = np.append(self.current_dataset.dx, data_point)
266                elif tagname == 'dQw':
267                    self.current_dataset.dxw = np.append(self.current_dataset.dxw, data_point)
268                elif tagname == 'dQl':
269                    self.current_dataset.dxl = np.append(self.current_dataset.dxl, data_point)
270                elif tagname == 'Qmean':
271                    pass
272                elif tagname == 'Shadowfactor':
273                    pass
274                elif tagname == 'Sesans':
275                    self.current_datainfo.isSesans = bool(data_point)
276                elif tagname == 'zacceptance':
277                    self.current_datainfo.sample.zacceptance = (data_point, unit)
278
279                ## Sample Information
280                elif tagname == 'ID' and self.parent_class == 'SASsample':
281                    self.current_datainfo.sample.ID = data_point
282                elif tagname == 'Title' and self.parent_class == 'SASsample':
283                    self.current_datainfo.sample.name = data_point
284                elif tagname == 'thickness' and self.parent_class == 'SASsample':
285                    self.current_datainfo.sample.thickness = data_point
286                    self.current_datainfo.sample.thickness_unit = unit
287                elif tagname == 'transmission' and self.parent_class == 'SASsample':
288                    self.current_datainfo.sample.transmission = data_point
289                elif tagname == 'temperature' and self.parent_class == 'SASsample':
290                    self.current_datainfo.sample.temperature = data_point
291                    self.current_datainfo.sample.temperature_unit = unit
292                elif tagname == 'details' and self.parent_class == 'SASsample':
293                    self.current_datainfo.sample.details.append(data_point)
294                elif tagname == 'x' and self.parent_class == 'position':
295                    self.current_datainfo.sample.position.x = data_point
296                    self.current_datainfo.sample.position_unit = unit
297                elif tagname == 'y' and self.parent_class == 'position':
298                    self.current_datainfo.sample.position.y = data_point
299                    self.current_datainfo.sample.position_unit = unit
300                elif tagname == 'z' and self.parent_class == 'position':
301                    self.current_datainfo.sample.position.z = data_point
302                    self.current_datainfo.sample.position_unit = unit
303                elif tagname == 'roll' and self.parent_class == 'orientation' and 'SASsample' in self.names:
304                    self.current_datainfo.sample.orientation.x = data_point
305                    self.current_datainfo.sample.orientation_unit = unit
306                elif tagname == 'pitch' and self.parent_class == 'orientation' and 'SASsample' in self.names:
307                    self.current_datainfo.sample.orientation.y = data_point
308                    self.current_datainfo.sample.orientation_unit = unit
309                elif tagname == 'yaw' and self.parent_class == 'orientation' and 'SASsample' in self.names:
310                    self.current_datainfo.sample.orientation.z = data_point
311                    self.current_datainfo.sample.orientation_unit = unit
312
313                ## Instrumental Information
314                elif tagname == 'name' and self.parent_class == 'SASinstrument':
315                    self.current_datainfo.instrument = data_point
316                ## Detector Information
317                elif tagname == 'name' and self.parent_class == 'SASdetector':
318                    self.detector.name = data_point
319                elif tagname == 'SDD' and self.parent_class == 'SASdetector':
320                    self.detector.distance = data_point
321                    self.detector.distance_unit = unit
322                elif tagname == 'slit_length' and self.parent_class == 'SASdetector':
323                    self.detector.slit_length = data_point
324                    self.detector.slit_length_unit = unit
325                elif tagname == 'x' and self.parent_class == 'offset':
326                    self.detector.offset.x = data_point
327                    self.detector.offset_unit = unit
328                elif tagname == 'y' and self.parent_class == 'offset':
329                    self.detector.offset.y = data_point
330                    self.detector.offset_unit = unit
331                elif tagname == 'z' and self.parent_class == 'offset':
332                    self.detector.offset.z = data_point
333                    self.detector.offset_unit = unit
334                elif tagname == 'x' and self.parent_class == 'beam_center':
335                    self.detector.beam_center.x = data_point
336                    self.detector.beam_center_unit = unit
337                elif tagname == 'y' and self.parent_class == 'beam_center':
338                    self.detector.beam_center.y = data_point
339                    self.detector.beam_center_unit = unit
340                elif tagname == 'z' and self.parent_class == 'beam_center':
341                    self.detector.beam_center.z = data_point
342                    self.detector.beam_center_unit = unit
343                elif tagname == 'x' and self.parent_class == 'pixel_size':
344                    self.detector.pixel_size.x = data_point
345                    self.detector.pixel_size_unit = unit
346                elif tagname == 'y' and self.parent_class == 'pixel_size':
347                    self.detector.pixel_size.y = data_point
348                    self.detector.pixel_size_unit = unit
349                elif tagname == 'z' and self.parent_class == 'pixel_size':
350                    self.detector.pixel_size.z = data_point
351                    self.detector.pixel_size_unit = unit
352                elif tagname == 'roll' and self.parent_class == 'orientation' and 'SASdetector' in self.names:
353                    self.detector.orientation.x = data_point
354                    self.detector.orientation_unit = unit
355                elif tagname == 'pitch' and self.parent_class == 'orientation' and 'SASdetector' in self.names:
356                    self.detector.orientation.y = data_point
357                    self.detector.orientation_unit = unit
358                elif tagname == 'yaw' and self.parent_class == 'orientation' and 'SASdetector' in self.names:
359                    self.detector.orientation.z = data_point
360                    self.detector.orientation_unit = unit
361                ## Collimation and Aperture
362                elif tagname == 'length' and self.parent_class == 'SAScollimation':
363                    self.collimation.length = data_point
364                    self.collimation.length_unit = unit
365                elif tagname == 'name' and self.parent_class == 'SAScollimation':
366                    self.collimation.name = data_point
367                elif tagname == 'distance' and self.parent_class == 'aperture':
368                    self.aperture.distance = data_point
369                    self.aperture.distance_unit = unit
370                elif tagname == 'x' and self.parent_class == 'size':
371                    self.aperture.size.x = data_point
372                    self.collimation.size_unit = unit
373                elif tagname == 'y' and self.parent_class == 'size':
374                    self.aperture.size.y = data_point
375                    self.collimation.size_unit = unit
376                elif tagname == 'z' and self.parent_class == 'size':
377                    self.aperture.size.z = data_point
378                    self.collimation.size_unit = unit
379
380                ## Process Information
381                elif tagname == 'name' and self.parent_class == 'SASprocess':
382                    self.process.name = data_point
383                elif tagname == 'description' and self.parent_class == 'SASprocess':
384                    self.process.description = data_point
385                elif tagname == 'date' and self.parent_class == 'SASprocess':
386                    try:
387                        self.process.date = datetime.datetime.fromtimestamp(data_point)
388                    except:
389                        self.process.date = data_point
390                elif tagname == 'SASprocessnote':
391                    self.process.notes.append(data_point)
392                elif tagname == 'term' and self.parent_class == 'SASprocess':
393                    unit = attr.get("unit", "")
394                    dic = {}
395                    dic["name"] = name
396                    dic["value"] = data_point
397                    dic["unit"] = unit
398                    self.process.term.append(dic)
399
400                ## Transmission Spectrum
401                elif tagname == 'T' and self.parent_class == 'Tdata':
402                    self.transspectrum.transmission = np.append(self.transspectrum.transmission, data_point)
403                    self.transspectrum.transmission_unit = unit
404                elif tagname == 'Tdev' and self.parent_class == 'Tdata':
405                    self.transspectrum.transmission_deviation = np.append(self.transspectrum.transmission_deviation, data_point)
406                    self.transspectrum.transmission_deviation_unit = unit
407                elif tagname == 'Lambda' and self.parent_class == 'Tdata':
408                    self.transspectrum.wavelength = np.append(self.transspectrum.wavelength, data_point)
409                    self.transspectrum.wavelength_unit = unit
410
411                ## Source Information
412                elif tagname == 'wavelength' and (self.parent_class == 'SASsource' or self.parent_class == 'SASData'):
413                    self.current_datainfo.source.wavelength = data_point
414                    self.current_datainfo.source.wavelength_unit = unit
415                elif tagname == 'wavelength_min' and self.parent_class == 'SASsource':
416                    self.current_datainfo.source.wavelength_min = data_point
417                    self.current_datainfo.source.wavelength_min_unit = unit
418                elif tagname == 'wavelength_max' and self.parent_class == 'SASsource':
419                    self.current_datainfo.source.wavelength_max = data_point
420                    self.current_datainfo.source.wavelength_max_unit = unit
421                elif tagname == 'wavelength_spread' and self.parent_class == 'SASsource':
422                    self.current_datainfo.source.wavelength_spread = data_point
423                    self.current_datainfo.source.wavelength_spread_unit = unit
424                elif tagname == 'x' and self.parent_class == 'beam_size':
425                    self.current_datainfo.source.beam_size.x = data_point
426                    self.current_datainfo.source.beam_size_unit = unit
427                elif tagname == 'y' and self.parent_class == 'beam_size':
428                    self.current_datainfo.source.beam_size.y = data_point
429                    self.current_datainfo.source.beam_size_unit = unit
430                elif tagname == 'z' and self.parent_class == 'pixel_size':
431                    self.current_datainfo.source.data_point.z = data_point
432                    self.current_datainfo.source.beam_size_unit = unit
433                elif tagname == 'radiation' and self.parent_class == 'SASsource':
434                    self.current_datainfo.source.radiation = data_point
435                elif tagname == 'beam_shape' and self.parent_class == 'SASsource':
436                    self.current_datainfo.source.beam_shape = data_point
437
438                ## Everything else goes in meta_data
439                else:
440                    new_key = self._create_unique_key(self.current_datainfo.meta_data, tagname)
441                    self.current_datainfo.meta_data[new_key] = data_point
442
443            self.names.remove(tagname_original)
444            length = 0
445            if len(self.names) > 1:
446                length = len(self.names) - 1
447            self.parent_class = self.names[length]
448        if not self._is_call_local() and not recurse:
449            self.frm = ""
450            self.add_data_set()
451            empty = None
452            if self.output[0].dx is not None:
453                self.output[0].dxl = np.empty(0)
454                self.output[0].dxw = np.empty(0)
455            else:
456                self.output[0].dx = np.empty(0)
457            return self.output[0], empty
458
459
460    def _is_call_local(self):
461        """
462
463        """
464        if self.frm == "":
465            inter = inspect.stack()
466            self.frm = inter[2]
467        mod_name = self.frm[1].replace("\\", "/").replace(".pyc", "")
468        mod_name = mod_name.replace(".py", "")
469        mod = mod_name.split("sas/")
470        mod_name = mod[1]
471        if mod_name != "sascalc/dataloader/readers/cansas_reader":
472            return False
473        return True
474
475    def is_cansas(self, ext="xml"):
476        """
477        Checks to see if the xml file is a CanSAS file
478
479        :param ext: The file extension of the data file
480        """
481        if self.validate_xml():
482            name = "{http://www.w3.org/2001/XMLSchema-instance}schemaLocation"
483            value = self.xmlroot.get(name)
484            if CANSAS_NS.get(self.cansas_version).get("ns") == \
485                    value.rsplit(" ")[0]:
486                return True
487        if ext == "svs":
488            return True
489        raise RuntimeError
490
491    def load_file_and_schema(self, xml_file, schema_path=""):
492        """
493        Loads the file and associates a schema, if a schema is passed in or if one already exists
494
495        :param xml_file: The xml file path sent to Reader.read
496        :param schema_path: The path to a schema associated with the xml_file, or find one based on the file
497        """
498        base_name = xml_reader.__file__
499        base_name = base_name.replace("\\", "/")
500        base = base_name.split("/sas/")[0]
501
502        # Load in xml file and get the cansas version from the header
503        self.set_xml_file(xml_file)
504        self.cansas_version = self.xmlroot.get("version", "1.0")
505
506        # Generic values for the cansas file based on the version
507        self.cansas_defaults = CANSAS_NS.get(self.cansas_version, "1.0")
508        if schema_path == "":
509            schema_path = "{0}/sas/sascalc/dataloader/readers/schema/{1}".format \
510                (base, self.cansas_defaults.get("schema")).replace("\\", "/")
511
512        # Link a schema to the XML file.
513        self.set_schema(schema_path)
514
515    def add_data_set(self):
516        """
517        Adds the current_dataset to the list of outputs after preforming final processing on the data and then calls a
518        private method to generate a new data set.
519
520        :param key: NeXus group name for current tree level
521        """
522
523        if self.current_datainfo and self.current_dataset:
524            self._final_cleanup()
525        self.data = []
526        self.current_datainfo = DataInfo()
527
528    def _initialize_new_data_set(self, parent_list=None):
529        """
530        A private class method to generate a new 1D data object.
531        Outside methods should call add_data_set() to be sure any existing data is stored properly.
532
533        :param parent_list: List of names of parent elements
534        """
535
536        if parent_list is None:
537            parent_list = []
538        x = np.array(0)
539        y = np.array(0)
540        self.current_dataset = plottable_1D(x, y)
541
542    def add_intermediate(self):
543        """
544        This method stores any intermediate objects within the final data set after fully reading the set.
545
546        :param parent: The NXclass name for the h5py Group object that just finished being processed
547        """
548
549        if self.parent_class == 'SASprocess':
550            self.current_datainfo.process.append(self.process)
551            self.process = Process()
552        elif self.parent_class == 'SASdetector':
553            self.current_datainfo.detector.append(self.detector)
554            self.detector = Detector()
555        elif self.parent_class == 'SAStransmission_spectrum':
556            self.current_datainfo.trans_spectrum.append(self.transspectrum)
557            self.transspectrum = TransmissionSpectrum()
558        elif self.parent_class == 'SAScollimation':
559            self.current_datainfo.collimation.append(self.collimation)
560            self.collimation = Collimation()
561        elif self.parent_class == 'aperture':
562            self.collimation.aperture.append(self.aperture)
563            self.aperture = Aperture()
564        elif self.parent_class == 'SASdata':
565            self._check_for_empty_resolution()
566            self.data.append(self.current_dataset)
567
568    def _final_cleanup(self):
569        """
570        Final cleanup of the Data1D object to be sure it has all the
571        appropriate information needed for perspectives
572        """
573
574        ## Append errors to dataset and reset class errors
575        self.current_datainfo.errors = set()
576        for error in self.errors:
577            self.current_datainfo.errors.add(error)
578        self.errors.clear()
579
580        ## Combine all plottables with datainfo and append each to output
581        ## Type cast data arrays to float64 and find min/max as appropriate
582        for dataset in self.data:
583            if dataset.x is not None:
584                dataset.x = np.delete(dataset.x, [0])
585                dataset.x = dataset.x.astype(np.float64)
586                dataset.xmin = np.min(dataset.x)
587                dataset.xmax = np.max(dataset.x)
588            if dataset.y is not None:
589                dataset.y = np.delete(dataset.y, [0])
590                dataset.y = dataset.y.astype(np.float64)
591                dataset.ymin = np.min(dataset.y)
592                dataset.ymax = np.max(dataset.y)
593            if dataset.dx is not None:
594                dataset.dx = np.delete(dataset.dx, [0])
595                dataset.dx = dataset.dx.astype(np.float64)
596            if dataset.dxl is not None:
597                dataset.dxl = np.delete(dataset.dxl, [0])
598                dataset.dxl = dataset.dxl.astype(np.float64)
599            if dataset.dxw is not None:
600                dataset.dxw = np.delete(dataset.dxw, [0])
601                dataset.dxw = dataset.dxw.astype(np.float64)
602            if dataset.dy is not None:
603                dataset.dy = np.delete(dataset.dy, [0])
604                dataset.dy = dataset.dy.astype(np.float64)
605            np.trim_zeros(dataset.x)
606            np.trim_zeros(dataset.y)
607            np.trim_zeros(dataset.dy)
608            final_dataset = combine_data(dataset, self.current_datainfo)
609            self.output.append(final_dataset)
610
611    def _create_unique_key(self, dictionary, name, numb=0):
612        """
613        Create a unique key value for any dictionary to prevent overwriting
614        Recurse until a unique key value is found.
615
616        :param dictionary: A dictionary with any number of entries
617        :param name: The index of the item to be added to dictionary
618        :param numb: The number to be appended to the name, starts at 0
619        """
620        if dictionary.get(name) is not None:
621            numb += 1
622            name = name.split("_")[0]
623            name += "_{0}".format(numb)
624            name = self._create_unique_key(dictionary, name, numb)
625        return name
626
627    def _get_node_value(self, node, tagname):
628        """
629        Get the value of a node and any applicable units
630
631        :param node: The XML node to get the value of
632        :param tagname: The tagname of the node
633        """
634        #Get the text from the node and convert all whitespace to spaces
635        units = ''
636        node_value = node.text
637        if node_value is not None:
638            node_value = ' '.join(node_value.split())
639        else:
640            node_value = ""
641
642        # If the value is a float, compile with units.
643        if self.ns_list.ns_datatype == "float":
644            # If an empty value is given, set as zero.
645            if node_value is None or node_value.isspace() \
646                                    or node_value.lower() == "nan":
647                node_value = "0.0"
648            #Convert the value to the base units
649            node_value, units = self._unit_conversion(node, tagname, node_value)
650
651        # If the value is a timestamp, convert to a datetime object
652        elif self.ns_list.ns_datatype == "timestamp":
653            if node_value is None or node_value.isspace():
654                pass
655            else:
656                try:
657                    node_value = \
658                        datetime.datetime.fromtimestamp(node_value)
659                except ValueError:
660                    node_value = None
661        return node_value, units
662
663    def _unit_conversion(self, node, tagname, node_value):
664        """
665        A unit converter method used to convert the data included in the file
666        to the default units listed in data_info
667
668        :param node: XML node
669        :param tagname: name of the node
670        :param node_value: The value of the current dom node
671        """
672        attr = node.attrib
673        value_unit = ''
674        err_msg = None
675        default_unit = None
676        if not isinstance(node_value, float):
677            node_value = float(node_value)
678        if 'unit' in attr and attr.get('unit') is not None:
679            try:
680                local_unit = attr['unit']
681                unitname = self.ns_list.current_level.get("unit", "")
682                if "SASdetector" in self.names:
683                    save_in = "detector"
684                elif "aperture" in self.names:
685                    save_in = "aperture"
686                elif "SAScollimation" in self.names:
687                    save_in = "collimation"
688                elif "SAStransmission_spectrum" in self.names:
689                    save_in = "transspectrum"
690                elif "SASdata" in self.names:
691                    x = np.zeros(1)
692                    y = np.zeros(1)
693                    self.current_data1d = Data1D(x, y)
694                    save_in = "current_data1d"
695                elif "SASsource" in self.names:
696                    save_in = "current_datainfo.source"
697                elif "SASsample" in self.names:
698                    save_in = "current_datainfo.sample"
699                elif "SASprocess" in self.names:
700                    save_in = "process"
701                else:
702                    save_in = "current_datainfo"
703                exec "default_unit = self.{0}.{1}".format(save_in, unitname)
704                if local_unit and default_unit and local_unit.lower() != default_unit.lower() \
705                        and local_unit.lower() != "none":
706                    if HAS_CONVERTER == True:
707                        ## Check local units - bad units raise KeyError
708                        data_conv_q = Converter(local_unit)
709                        value_unit = default_unit
710                        node_value = data_conv_q(node_value, units=default_unit)
711                    else:
712                        value_unit = local_unit
713                        err_msg = "Unit converter is not available.\n"
714                else:
715                    value_unit = local_unit
716            except KeyError:
717                err_msg = "CanSAS reader: unexpected "
718                err_msg += "\"{0}\" unit [{1}]; "
719                err_msg = err_msg.format(tagname, local_unit)
720                err_msg += "expecting [{0}]".format(default_unit)
721                value_unit = local_unit
722            except:
723                err_msg = "CanSAS reader: unknown error converting "
724                err_msg += "\"{0}\" unit [{1}]"
725                err_msg = err_msg.format(tagname, local_unit)
726                value_unit = local_unit
727        elif 'unit' in attr:
728            value_unit = attr['unit']
729        if err_msg:
730            self.errors.add(err_msg)
731        return node_value, value_unit
732
733    def _check_for_empty_data(self):
734        """
735        Creates an empty data set if no data is passed to the reader
736
737        :param data1d: presumably a Data1D object
738        """
739        if self.current_dataset == None:
740            x_vals = np.empty(0)
741            y_vals = np.empty(0)
742            dx_vals = np.empty(0)
743            dy_vals = np.empty(0)
744            dxl = np.empty(0)
745            dxw = np.empty(0)
746            self.current_dataset = plottable_1D(x_vals, y_vals, dx_vals, dy_vals)
747            self.current_dataset.dxl = dxl
748            self.current_dataset.dxw = dxw
749
750    def _check_for_empty_resolution(self):
751        """
752        A method to check all resolution data sets are the same size as I and Q
753        """
754        dql_exists = False
755        dqw_exists = False
756        dq_exists = False
757        di_exists = False
758        if self.current_dataset.dxl is not None:
759            dql_exists = True
760        if self.current_dataset.dxw is not None:
761            dqw_exists = True
762        if self.current_dataset.dx is not None:
763            dq_exists = True
764        if self.current_dataset.dy is not None:
765            di_exists = True
766        if dqw_exists and not dql_exists:
767            array_size = self.current_dataset.dxw.size - 1
768            self.current_dataset.dxl = np.append(self.current_dataset.dxl, np.zeros([array_size]))
769        elif dql_exists and not dqw_exists:
770            array_size = self.current_dataset.dxl.size - 1
771            self.current_dataset.dxw = np.append(self.current_dataset.dxw, np.zeros([array_size]))
772        elif not dql_exists and not dqw_exists and not dq_exists:
773            array_size = self.current_dataset.x.size - 1
774            self.current_dataset.dx = np.append(self.current_dataset.dx, np.zeros([array_size]))
775        if not di_exists:
776            array_size = self.current_dataset.y.size - 1
777            self.current_dataset.dy = np.append(self.current_dataset.dy, np.zeros([array_size]))
778
779
780    ####### All methods below are for writing CanSAS XML files #######
781
782
783    def write(self, filename, datainfo):
784        """
785        Write the content of a Data1D as a CanSAS XML file
786
787        :param filename: name of the file to write
788        :param datainfo: Data1D object
789        """
790        # Create XML document
791        doc, _ = self._to_xml_doc(datainfo)
792        # Write the file
793        file_ref = open(filename, 'w')
794        if self.encoding == None:
795            self.encoding = "UTF-8"
796        doc.write(file_ref, encoding=self.encoding,
797                  pretty_print=True, xml_declaration=True)
798        file_ref.close()
799
800    def _to_xml_doc(self, datainfo):
801        """
802        Create an XML document to contain the content of a Data1D
803
804        :param datainfo: Data1D object
805        """
806        if not issubclass(datainfo.__class__, Data1D):
807            raise RuntimeError, "The cansas writer expects a Data1D instance"
808
809        # Get PIs and create root element
810        pi_string = self._get_pi_string()
811        # Define namespaces and create SASroot object
812        main_node = self._create_main_node()
813        # Create ElementTree, append SASroot and apply processing instructions
814        base_string = pi_string + self.to_string(main_node)
815        base_element = self.create_element_from_string(base_string)
816        doc = self.create_tree(base_element)
817        # Create SASentry Element
818        entry_node = self.create_element("SASentry")
819        root = doc.getroot()
820        root.append(entry_node)
821
822        # Add Title to SASentry
823        self.write_node(entry_node, "Title", datainfo.title)
824        # Add Run to SASentry
825        self._write_run_names(datainfo, entry_node)
826        # Add Data info to SASEntry
827        self._write_data(datainfo, entry_node)
828        # Transmission Spectrum Info
829        self._write_trans_spectrum(datainfo, entry_node)
830        # Sample info
831        self._write_sample_info(datainfo, entry_node)
832        # Instrument info
833        instr = self._write_instrument(datainfo, entry_node)
834        #   Source
835        self._write_source(datainfo, instr)
836        #   Collimation
837        self._write_collimation(datainfo, instr)
838        #   Detectors
839        self._write_detectors(datainfo, instr)
840        # Processes info
841        self._write_process_notes(datainfo, entry_node)
842        # Note info
843        self._write_notes(datainfo, entry_node)
844        # Return the document, and the SASentry node associated with
845        #      the data we just wrote
846        # If the calling function was not the cansas reader, return a minidom
847        #      object rather than an lxml object.
848        self.frm = inspect.stack()[1]
849        doc, entry_node = self._check_origin(entry_node, doc)
850        return doc, entry_node
851
852    def write_node(self, parent, name, value, attr=None):
853        """
854        :param doc: document DOM
855        :param parent: parent node
856        :param name: tag of the element
857        :param value: value of the child text node
858        :param attr: attribute dictionary
859
860        :return: True if something was appended, otherwise False
861        """
862        if value is not None:
863            parent = self.ebuilder(parent, name, value, attr)
864            return True
865        return False
866
867    def _get_pi_string(self):
868        """
869        Creates the processing instructions header for writing to file
870        """
871        pis = self.return_processing_instructions()
872        if len(pis) > 0:
873            pi_tree = self.create_tree(pis[0])
874            i = 1
875            for i in range(1, len(pis) - 1):
876                pi_tree = self.append(pis[i], pi_tree)
877            pi_string = self.to_string(pi_tree)
878        else:
879            pi_string = ""
880        return pi_string
881
882    def _create_main_node(self):
883        """
884        Creates the primary xml header used when writing to file
885        """
886        xsi = "http://www.w3.org/2001/XMLSchema-instance"
887        version = self.cansas_version
888        n_s = CANSAS_NS.get(version).get("ns")
889        if version == "1.1":
890            url = "http://www.cansas.org/formats/1.1/"
891        else:
892            url = "http://svn.smallangles.net/svn/canSAS/1dwg/trunk/"
893        schema_location = "{0} {1}cansas1d.xsd".format(n_s, url)
894        attrib = {"{" + xsi + "}schemaLocation" : schema_location,
895                  "version" : version}
896        nsmap = {'xsi' : xsi, None: n_s}
897
898        main_node = self.create_element("{" + n_s + "}SASroot",
899                                        attrib=attrib, nsmap=nsmap)
900        return main_node
901
902    def _write_run_names(self, datainfo, entry_node):
903        """
904        Writes the run names to the XML file
905
906        :param datainfo: The Data1D object the information is coming from
907        :param entry_node: lxml node ElementTree object to be appended to
908        """
909        if datainfo.run == None or datainfo.run == []:
910            datainfo.run.append(RUN_NAME_DEFAULT)
911            datainfo.run_name[RUN_NAME_DEFAULT] = RUN_NAME_DEFAULT
912        for item in datainfo.run:
913            runname = {}
914            if item in datainfo.run_name and \
915            len(str(datainfo.run_name[item])) > 1:
916                runname = {'name': datainfo.run_name[item]}
917            self.write_node(entry_node, "Run", item, runname)
918
919    def _write_data(self, datainfo, entry_node):
920        """
921        Writes the I and Q data to the XML file
922
923        :param datainfo: The Data1D object the information is coming from
924        :param entry_node: lxml node ElementTree object to be appended to
925        """
926        node = self.create_element("SASdata")
927        self.append(node, entry_node)
928
929        for i in range(len(datainfo.x)):
930            point = self.create_element("Idata")
931            node.append(point)
932            self.write_node(point, "Q", datainfo.x[i],
933                            {'unit': datainfo._xaxis + " | " + datainfo._xunit})
934            if len(datainfo.y) >= i:
935                self.write_node(point, "I", datainfo.y[i],
936                                {'unit': datainfo._yaxis + " | " + datainfo._yunit})
937            if datainfo.dy is not None and len(datainfo.dy) > i:
938                self.write_node(point, "Idev", datainfo.dy[i],
939                                {'unit': datainfo._yaxis + " | " + datainfo._yunit})
940            if datainfo.dx is not None and len(datainfo.dx) > i:
941                self.write_node(point, "Qdev", datainfo.dx[i],
942                                {'unit': datainfo._xaxis + " | " + datainfo._xunit})
943            if datainfo.dxw is not None and len(datainfo.dxw) > i:
944                self.write_node(point, "dQw", datainfo.dxw[i],
945                                {'unit': datainfo._xaxis + " | " + datainfo._xunit})
946            if datainfo.dxl is not None and len(datainfo.dxl) > i:
947                self.write_node(point, "dQl", datainfo.dxl[i],
948                                {'unit': datainfo._xaxis + " | " + datainfo._xunit})
949        if datainfo.isSesans:
950            sesans = self.create_element("Sesans")
951            sesans.text = str(datainfo.isSesans)
952            node.append(sesans)
953            self.write_node(node, "zacceptance", datainfo.sample.zacceptance[0],
954                             {'unit': datainfo.sample.zacceptance[1]})
955
956
957    def _write_trans_spectrum(self, datainfo, entry_node):
958        """
959        Writes the transmission spectrum data to the XML file
960
961        :param datainfo: The Data1D object the information is coming from
962        :param entry_node: lxml node ElementTree object to be appended to
963        """
964        for i in range(len(datainfo.trans_spectrum)):
965            spectrum = datainfo.trans_spectrum[i]
966            node = self.create_element("SAStransmission_spectrum",
967                                       {"name" : spectrum.name})
968            self.append(node, entry_node)
969            if isinstance(spectrum.timestamp, datetime.datetime):
970                node.setAttribute("timestamp", spectrum.timestamp)
971            for i in range(len(spectrum.wavelength)):
972                point = self.create_element("Tdata")
973                node.append(point)
974                self.write_node(point, "Lambda", spectrum.wavelength[i],
975                                {'unit': spectrum.wavelength_unit})
976                self.write_node(point, "T", spectrum.transmission[i],
977                                {'unit': spectrum.transmission_unit})
978                if spectrum.transmission_deviation != None \
979                and len(spectrum.transmission_deviation) >= i:
980                    self.write_node(point, "Tdev",
981                                    spectrum.transmission_deviation[i],
982                                    {'unit':
983                                     spectrum.transmission_deviation_unit})
984
985    def _write_sample_info(self, datainfo, entry_node):
986        """
987        Writes the sample information to the XML file
988
989        :param datainfo: The Data1D object the information is coming from
990        :param entry_node: lxml node ElementTree object to be appended to
991        """
992        sample = self.create_element("SASsample")
993        if datainfo.sample.name is not None:
994            self.write_attribute(sample, "name",
995                                 str(datainfo.sample.name))
996        self.append(sample, entry_node)
997        self.write_node(sample, "ID", str(datainfo.sample.ID))
998        self.write_node(sample, "thickness", datainfo.sample.thickness,
999                        {"unit": datainfo.sample.thickness_unit})
1000        self.write_node(sample, "transmission", datainfo.sample.transmission)
1001        self.write_node(sample, "temperature", datainfo.sample.temperature,
1002                        {"unit": datainfo.sample.temperature_unit})
1003
1004        pos = self.create_element("position")
1005        written = self.write_node(pos,
1006                                  "x",
1007                                  datainfo.sample.position.x,
1008                                  {"unit": datainfo.sample.position_unit})
1009        written = written | self.write_node( \
1010            pos, "y", datainfo.sample.position.y,
1011            {"unit": datainfo.sample.position_unit})
1012        written = written | self.write_node( \
1013            pos, "z", datainfo.sample.position.z,
1014            {"unit": datainfo.sample.position_unit})
1015        if written == True:
1016            self.append(pos, sample)
1017
1018        ori = self.create_element("orientation")
1019        written = self.write_node(ori, "roll",
1020                                  datainfo.sample.orientation.x,
1021                                  {"unit": datainfo.sample.orientation_unit})
1022        written = written | self.write_node( \
1023            ori, "pitch", datainfo.sample.orientation.y,
1024            {"unit": datainfo.sample.orientation_unit})
1025        written = written | self.write_node( \
1026            ori, "yaw", datainfo.sample.orientation.z,
1027            {"unit": datainfo.sample.orientation_unit})
1028        if written == True:
1029            self.append(ori, sample)
1030
1031        for item in datainfo.sample.details:
1032            self.write_node(sample, "details", item)
1033
1034    def _write_instrument(self, datainfo, entry_node):
1035        """
1036        Writes the instrumental information to the XML file
1037
1038        :param datainfo: The Data1D object the information is coming from
1039        :param entry_node: lxml node ElementTree object to be appended to
1040        """
1041        instr = self.create_element("SASinstrument")
1042        self.append(instr, entry_node)
1043        self.write_node(instr, "name", datainfo.instrument)
1044        return instr
1045
1046    def _write_source(self, datainfo, instr):
1047        """
1048        Writes the source information to the XML file
1049
1050        :param datainfo: The Data1D object the information is coming from
1051        :param instr: instrument node  to be appended to
1052        """
1053        source = self.create_element("SASsource")
1054        if datainfo.source.name is not None:
1055            self.write_attribute(source, "name",
1056                                 str(datainfo.source.name))
1057        self.append(source, instr)
1058        if datainfo.source.radiation == None or datainfo.source.radiation == '':
1059            datainfo.source.radiation = "neutron"
1060        self.write_node(source, "radiation", datainfo.source.radiation)
1061
1062        size = self.create_element("beam_size")
1063        if datainfo.source.beam_size_name is not None:
1064            self.write_attribute(size, "name",
1065                                 str(datainfo.source.beam_size_name))
1066        written = self.write_node( \
1067            size, "x", datainfo.source.beam_size.x,
1068            {"unit": datainfo.source.beam_size_unit})
1069        written = written | self.write_node( \
1070            size, "y", datainfo.source.beam_size.y,
1071            {"unit": datainfo.source.beam_size_unit})
1072        written = written | self.write_node( \
1073            size, "z", datainfo.source.beam_size.z,
1074            {"unit": datainfo.source.beam_size_unit})
1075        if written == True:
1076            self.append(size, source)
1077
1078        self.write_node(source, "beam_shape", datainfo.source.beam_shape)
1079        self.write_node(source, "wavelength",
1080                        datainfo.source.wavelength,
1081                        {"unit": datainfo.source.wavelength_unit})
1082        self.write_node(source, "wavelength_min",
1083                        datainfo.source.wavelength_min,
1084                        {"unit": datainfo.source.wavelength_min_unit})
1085        self.write_node(source, "wavelength_max",
1086                        datainfo.source.wavelength_max,
1087                        {"unit": datainfo.source.wavelength_max_unit})
1088        self.write_node(source, "wavelength_spread",
1089                        datainfo.source.wavelength_spread,
1090                        {"unit": datainfo.source.wavelength_spread_unit})
1091
1092    def _write_collimation(self, datainfo, instr):
1093        """
1094        Writes the collimation information to the XML file
1095
1096        :param datainfo: The Data1D object the information is coming from
1097        :param instr: lxml node ElementTree object to be appended to
1098        """
1099        if datainfo.collimation == [] or datainfo.collimation == None:
1100            coll = Collimation()
1101            datainfo.collimation.append(coll)
1102        for item in datainfo.collimation:
1103            coll = self.create_element("SAScollimation")
1104            if item.name is not None:
1105                self.write_attribute(coll, "name", str(item.name))
1106            self.append(coll, instr)
1107
1108            self.write_node(coll, "length", item.length,
1109                            {"unit": item.length_unit})
1110
1111            for aperture in item.aperture:
1112                apert = self.create_element("aperture")
1113                if aperture.name is not None:
1114                    self.write_attribute(apert, "name", str(aperture.name))
1115                if aperture.type is not None:
1116                    self.write_attribute(apert, "type", str(aperture.type))
1117                self.append(apert, coll)
1118
1119                size = self.create_element("size")
1120                if aperture.size_name is not None:
1121                    self.write_attribute(size, "name",
1122                                         str(aperture.size_name))
1123                written = self.write_node(size, "x", aperture.size.x,
1124                                          {"unit": aperture.size_unit})
1125                written = written | self.write_node( \
1126                    size, "y", aperture.size.y,
1127                    {"unit": aperture.size_unit})
1128                written = written | self.write_node( \
1129                    size, "z", aperture.size.z,
1130                    {"unit": aperture.size_unit})
1131                if written == True:
1132                    self.append(size, apert)
1133
1134                self.write_node(apert, "distance", aperture.distance,
1135                                {"unit": aperture.distance_unit})
1136
1137    def _write_detectors(self, datainfo, instr):
1138        """
1139        Writes the detector information to the XML file
1140
1141        :param datainfo: The Data1D object the information is coming from
1142        :param inst: lxml instrument node to be appended to
1143        """
1144        if datainfo.detector == None or datainfo.detector == []:
1145            det = Detector()
1146            det.name = ""
1147            datainfo.detector.append(det)
1148
1149        for item in datainfo.detector:
1150            det = self.create_element("SASdetector")
1151            written = self.write_node(det, "name", item.name)
1152            written = written | self.write_node(det, "SDD", item.distance,
1153                                                {"unit": item.distance_unit})
1154            if written == True:
1155                self.append(det, instr)
1156
1157            off = self.create_element("offset")
1158            written = self.write_node(off, "x", item.offset.x,
1159                                      {"unit": item.offset_unit})
1160            written = written | self.write_node(off, "y", item.offset.y,
1161                                                {"unit": item.offset_unit})
1162            written = written | self.write_node(off, "z", item.offset.z,
1163                                                {"unit": item.offset_unit})
1164            if written == True:
1165                self.append(off, det)
1166
1167            ori = self.create_element("orientation")
1168            written = self.write_node(ori, "roll", item.orientation.x,
1169                                      {"unit": item.orientation_unit})
1170            written = written | self.write_node(ori, "pitch",
1171                                                item.orientation.y,
1172                                                {"unit": item.orientation_unit})
1173            written = written | self.write_node(ori, "yaw",
1174                                                item.orientation.z,
1175                                                {"unit": item.orientation_unit})
1176            if written == True:
1177                self.append(ori, det)
1178
1179            center = self.create_element("beam_center")
1180            written = self.write_node(center, "x", item.beam_center.x,
1181                                      {"unit": item.beam_center_unit})
1182            written = written | self.write_node(center, "y",
1183                                                item.beam_center.y,
1184                                                {"unit": item.beam_center_unit})
1185            written = written | self.write_node(center, "z",
1186                                                item.beam_center.z,
1187                                                {"unit": item.beam_center_unit})
1188            if written == True:
1189                self.append(center, det)
1190
1191            pix = self.create_element("pixel_size")
1192            written = self.write_node(pix, "x", item.pixel_size.x,
1193                                      {"unit": item.pixel_size_unit})
1194            written = written | self.write_node(pix, "y", item.pixel_size.y,
1195                                                {"unit": item.pixel_size_unit})
1196            written = written | self.write_node(pix, "z", item.pixel_size.z,
1197                                                {"unit": item.pixel_size_unit})
1198            if written == True:
1199                self.append(pix, det)
1200            self.write_node(det, "slit_length", item.slit_length,
1201                {"unit": item.slit_length_unit})
1202
1203
1204    def _write_process_notes(self, datainfo, entry_node):
1205        """
1206        Writes the process notes to the XML file
1207
1208        :param datainfo: The Data1D object the information is coming from
1209        :param entry_node: lxml node ElementTree object to be appended to
1210
1211        """
1212        for item in datainfo.process:
1213            node = self.create_element("SASprocess")
1214            self.append(node, entry_node)
1215            self.write_node(node, "name", item.name)
1216            self.write_node(node, "date", item.date)
1217            self.write_node(node, "description", item.description)
1218            for term in item.term:
1219                if isinstance(term, list):
1220                    value = term['value']
1221                    del term['value']
1222                elif isinstance(term, dict):
1223                    value = term.get("value")
1224                    del term['value']
1225                else:
1226                    value = term
1227                self.write_node(node, "term", value, term)
1228            for note in item.notes:
1229                self.write_node(node, "SASprocessnote", note)
1230            if len(item.notes) == 0:
1231                self.write_node(node, "SASprocessnote", "")
1232
1233    def _write_notes(self, datainfo, entry_node):
1234        """
1235        Writes the notes to the XML file and creates an empty note if none
1236        exist
1237
1238        :param datainfo: The Data1D object the information is coming from
1239        :param entry_node: lxml node ElementTree object to be appended to
1240
1241        """
1242        if len(datainfo.notes) == 0:
1243            node = self.create_element("SASnote")
1244            self.append(node, entry_node)
1245        else:
1246            for item in datainfo.notes:
1247                node = self.create_element("SASnote")
1248                self.write_text(node, item)
1249                self.append(node, entry_node)
1250
1251    def _check_origin(self, entry_node, doc):
1252        """
1253        Return the document, and the SASentry node associated with
1254        the data we just wrote.
1255        If the calling function was not the cansas reader, return a minidom
1256        object rather than an lxml object.
1257
1258        :param entry_node: lxml node ElementTree object to be appended to
1259        :param doc: entire xml tree
1260        """
1261        if not self.frm:
1262            self.frm = inspect.stack()[1]
1263        mod_name = self.frm[1].replace("\\", "/").replace(".pyc", "")
1264        mod_name = mod_name.replace(".py", "")
1265        mod = mod_name.split("sas/")
1266        mod_name = mod[1]
1267        if mod_name != "sascalc/dataloader/readers/cansas_reader":
1268            string = self.to_string(doc, pretty_print=False)
1269            doc = parseString(string)
1270            node_name = entry_node.tag
1271            node_list = doc.getElementsByTagName(node_name)
1272            entry_node = node_list.item(0)
1273        return doc, entry_node
1274
1275    # DO NOT REMOVE - used in saving and loading panel states.
1276    def _store_float(self, location, node, variable, storage, optional=True):
1277        """
1278        Get the content of a xpath location and store
1279        the result. Check that the units are compatible
1280        with the destination. The value is expected to
1281        be a float.
1282
1283        The xpath location might or might not exist.
1284        If it does not exist, nothing is done
1285
1286        :param location: xpath location to fetch
1287        :param node: node to read the data from
1288        :param variable: name of the data member to store it in [string]
1289        :param storage: data object that has the 'variable' data member
1290        :param optional: if True, no exception will be raised
1291            if unit conversion can't be done
1292
1293        :raise ValueError: raised when the units are not recognized
1294        """
1295        entry = get_content(location, node)
1296        try:
1297            value = float(entry.text)
1298        except:
1299            value = None
1300
1301        if value is not None:
1302            # If the entry has units, check to see that they are
1303            # compatible with what we currently have in the data object
1304            units = entry.get('unit')
1305            if units is not None:
1306                toks = variable.split('.')
1307                local_unit = None
1308                exec "local_unit = storage.%s_unit" % toks[0]
1309                if local_unit != None and units.lower() != local_unit.lower():
1310                    if HAS_CONVERTER == True:
1311                        try:
1312                            conv = Converter(units)
1313                            exec "storage.%s = %g" % \
1314                                (variable, conv(value, units=local_unit))
1315                        except:
1316                            _, exc_value, _ = sys.exc_info()
1317                            err_mess = "CanSAS reader: could not convert"
1318                            err_mess += " %s unit [%s]; expecting [%s]\n  %s" \
1319                                % (variable, units, local_unit, exc_value)
1320                            self.errors.add(err_mess)
1321                            if optional:
1322                                logging.info(err_mess)
1323                            else:
1324                                raise ValueError, err_mess
1325                    else:
1326                        err_mess = "CanSAS reader: unrecognized %s unit [%s];"\
1327                        % (variable, units)
1328                        err_mess += " expecting [%s]" % local_unit
1329                        self.errors.add(err_mess)
1330                        if optional:
1331                            logging.info(err_mess)
1332                        else:
1333                            raise ValueError, err_mess
1334                else:
1335                    exec "storage.%s = value" % variable
1336            else:
1337                exec "storage.%s = value" % variable
1338
1339    # DO NOT REMOVE - used in saving and loading panel states.
1340    def _store_content(self, location, node, variable, storage):
1341        """
1342        Get the content of a xpath location and store
1343        the result. The value is treated as a string.
1344
1345        The xpath location might or might not exist.
1346        If it does not exist, nothing is done
1347
1348        :param location: xpath location to fetch
1349        :param node: node to read the data from
1350        :param variable: name of the data member to store it in [string]
1351        :param storage: data object that has the 'variable' data member
1352
1353        :return: return a list of errors
1354        """
1355        entry = get_content(location, node)
1356        if entry is not None and entry.text is not None:
1357            exec "storage.%s = entry.text.strip()" % variable
1358
1359
1360# DO NOT REMOVE Called by outside packages:
1361#    sas.sasgui.perspectives.invariant.invariant_state
1362#    sas.sasgui.perspectives.fitting.pagestate
1363def get_content(location, node):
1364    """
1365    Get the first instance of the content of a xpath location.
1366
1367    :param location: xpath location
1368    :param node: node to start at
1369
1370    :return: Element, or None
1371    """
1372    nodes = node.xpath(location,
1373                       namespaces={'ns': CANSAS_NS.get("1.0").get("ns")})
1374    if len(nodes) > 0:
1375        return nodes[0]
1376    else:
1377        return None
1378
1379# DO NOT REMOVE Called by outside packages:
1380#    sas.sasgui.perspectives.fitting.pagestate
1381def write_node(doc, parent, name, value, attr=None):
1382    """
1383    :param doc: document DOM
1384    :param parent: parent node
1385    :param name: tag of the element
1386    :param value: value of the child text node
1387    :param attr: attribute dictionary
1388
1389    :return: True if something was appended, otherwise False
1390    """
1391    if attr is None:
1392        attr = {}
1393    if value is not None:
1394        node = doc.createElement(name)
1395        node.appendChild(doc.createTextNode(str(value)))
1396        for item in attr:
1397            node.setAttribute(item, attr[item])
1398        parent.appendChild(node)
1399        return True
1400    return False
Note: See TracBrowser for help on using the repository browser.