import time
import sys
import os
import logging
import sas.sascalc.dataloader
from lxml import etree
from sas.sascalc.dataloader.readers.cansas_reader import Reader as CansasReader
from sas.sascalc.dataloader.readers.cansas_reader import get_content
from sas.sasgui.guiframe.utils import format_number
from sas.sasgui.guiframe.gui_style import GUIFRAME_ID
from sas.sasgui.guiframe.dataFitting import Data1D
from sas.sascalc.dataloader.data_info import Data1D as LoaderData1D
from sas.sascalc.dataloader.loader import Loader
CORNODE_NAME = 'corfunc'
CANSAS_NS = 'cansas1d/1.0'
# The default state
DEFAULT_STATE = {
'qmin_tcl': None,
'qmax1_tcl': None,
'qmax2_tcl': None,
'background_tcl': None
}
# List of output parameters, used by __str__
output_list = [
['max', "Long Period (A): "],
['Lc', "Average Hard Block Thickness (A): "],
['dtr', "Average Interface Thickness (A): "],
['d0', "Average Core Thickness: "],
['A', "PolyDispersity: "],
['fill', "Filling Fraction: "]
]
[docs]class CorfuncState(object):
"""
Stores information about the state of CorfuncPanel
"""
def __init__(self):
# Inputs
self.file = None
self.data = None
self.qmin = None
self.qmax = [0, 0]
self.background = None
self.outputs = {}
self.is_extrapolated = False
self.transform_type = 'fourier'
self.is_transformed = False
self.saved_state = DEFAULT_STATE
self.timestamp = time.time()
# Raw Data
self.q = None
self.iq = None
# TODO: Add extrapolated data and transformed data (when implemented)
def __str__(self):
"""
Pretty print the state
:return: A string representing the state
"""
state = "File: {}\n".format(self.file)
state += "Timestamp: {}\n".format(self.timestamp)
state += "Qmin: {}\n".format(str(self.qmin))
state += "Qmax: {}\n".format(str(self.qmax))
state += "Background: {}\n".format(str(self.background))
if self.outputs != {} and self.outputs is not None:
state += "\nOutputs:\n"
for key, value in self.outputs.iteritems():
name = output_list[key][1]
state += "{}: {}\n".format(name, str(value))
return state
[docs] def set_saved_state(self, name, value):
"""
Set a value in the current state.
:param name: The name of the parameter to set
:param value: The value to set the parameter to
"""
self.saved_state[name] = value
if name == 'qmin_tcl':
self.qmin = value
elif name == 'qmax1_tcl':
self.qmax[0] = value
elif name == 'qmax2_tcl':
self.qmax[1] = value
elif name == 'background_tcl':
self.background = value
[docs] def toXML(self, filename='corfunc_state.crf', doc=None, entry_node=None):
"""
Writes the state of the CorfuncPanel panel to file, as XML.
Compatible with standalone writing, or appending to an
already existing XML document. In that case, the XML document
is required. An optional entry node in the XML document
may also be given.
:param file: file to write to
:param doc: XML document object [optional]
:param entry_node: XML node within the XML document at which
we will append the data [optional]
:return: None if no doc is provided, modified XML document if doc!=None
"""
from xml.dom.minidom import getDOMImplementation
top_element = None
new_doc = None
if doc is None:
# Create a new XML document
impl = getDOMImplementation()
doc_type = impl.createDocumentType(CORNODE_NAME, "1.0", "1.0")
new_doc = impl.createDocument(None, CORNODE_NAME, doc_type)
top_element = new_doc.documentElement
else:
# Create a new element in the document provided
top_element = doc.createElement(CORNODE_NAME)
if entry_node is None:
doc.documentElement.appendChild(top_element)
else:
entry_node.appendChild(top_element)
new_doc = doc
# Version
attr = new_doc.createAttribute("version")
attr.nodeValue = '1.0'
top_element.setAttributeNode(attr)
# Filename
element = new_doc.createElement("filename")
if self.file is not None and self.file != '':
element.appendChild(new_doc.createTextNode(str(self.file)))
else:
element.appendChild(new_doc.createTextNode(str(filename)))
top_element.appendChild(element)
# Timestamp
element = new_doc.createElement("timestamp")
# Pretty printed format
element.appendChild(new_doc.createTextNode(time.ctime(self.timestamp)))
attr = new_doc.createAttribute("epoch")
# Epoch value (used in self.fromXML)
attr.nodeValue = str(self.timestamp)
element.setAttributeNode(attr)
top_element.appendChild(element)
# Current state
state = new_doc.createElement("state")
top_element.appendChild(state)
for name, value in self.saved_state.iteritems():
element = new_doc.createElement(name)
element.appendChild(new_doc.createTextNode(str(value)))
state.appendChild(element)
# Whether or not the extrapolate & transform buttons have been clicked
element = new_doc.createElement("is_extrapolated")
top_element.appendChild(element)
element.appendChild(new_doc.createTextNode(str(int(self.is_extrapolated))))
element = new_doc.createElement("is_transformed")
top_element.appendChild(element)
element.appendChild(new_doc.createTextNode(str(int(self.is_transformed))))
if self.is_transformed:
element = new_doc.createElement("transform_type")
top_element.appendChild(element)
element.appendChild(new_doc.createTextNode(self.transform_type))
# Output parameters
if self.outputs != {} and self.outputs is not None:
output = new_doc.createElement("output")
top_element.appendChild(output)
for key, value in self.outputs.iteritems():
element = new_doc.createElement(key)
element.appendChild(new_doc.createTextNode(str(value)))
output.appendChild(element)
# Save the file or return the original document with the state
# data appended
if doc is None:
fd = open(filename, 'w')
fd.write(new_doc.toprettyxml())
fd.close()
return None
else:
return new_doc
[docs] def fromXML(self, node):
"""
Load corfunc states from a file
:param node: node of an XML document to read from (optional)
"""
if node.get('version') and node.get('version') == '1.0':
# Parse filename
entry = get_content('ns:filename', node)
if entry is not None:
self.file = entry.text.strip()
# Parse timestamp
entry = get_content('ns:timestamp', node)
if entry is not None and entry.get('epoch'):
try:
self.timestamp = (entry.get('epoch'))
except:
msg = ("CorfuncState.fromXML: Could not read timestamp",
"\n{}").format(sys.exc_value)
logging.error(msg)
# Parse current state
entry = get_content('ns:state', node)
if entry is not None:
for item in DEFAULT_STATE.iterkeys():
input_field = get_content("ns:{}".format(item), entry)
if input_field is not None:
try:
value = float(input_field.text.strip())
except:
value = None
self.set_saved_state(name=item, value=value)
# Parse is_extrapolated and is_transformed
entry = get_content('ns:is_extrapolated', node)
if entry is not None:
self.is_extrapolated = bool(int(entry.text.strip()))
entry = get_content('ns:is_transformed', node)
if entry is not None:
self.is_transformed = bool(int(entry.text.strip()))
entry = get_content('ns:transform_type', node)
self.transform_type = entry.text.strip()
# Parse outputs
entry = get_content('ns:output', node)
if entry is not None:
for item in output_list:
parameter = get_content("ns:{}".format(item[0]), entry)
if parameter is not None:
self.outputs[item[0]] = float(parameter.text.strip())
[docs]class Reader(CansasReader):
"""
Reads a CanSAS file containing the state of a CorfuncPanel
"""
type_name = "Corfunc"
type = ["Corfunc file (*.crf)|*.crf",
"SASView file (*.svs)|*.svs"]
ext = ['.crf', '.CRF', '.svs', '.SVS']
def __init__(self, callback):
self.callback = callback
self.state = None
[docs] def read(self, path):
"""
Load data and corfunc information frmo a CanSAS file.
:param path: The file path to read from
:return: Data1D object, a list of Data1D objects, or None
:raise IOError: When the file can't be found
:raise IOError: When the file is an invalid file type
:raise ValueError: When the length of the data vectors are inconsistent
"""
output = []
if os.path.isfile(path):
# Load file
basename = os.path.basename(path)
root, ext = os.path.splitext(basename)
if not ext.lower() in self.ext:
raise IOError, "{} is not a supported file type".format(ext)
tree = etree.parse(path, parser=etree.ETCompatXMLParser())
root = tree.getroot()
entry_list = root.xpath('/ns:SASroot/ns:SASentry',
namespaces={'ns': CANSAS_NS})
for entry in entry_list:
sas_entry, _ = self._parse_entry(entry)
corstate = self._parse_state(entry)
if corstate != None:
sas_entry.meta_data['corstate'] = corstate
sas_entry.filename = corstate.file
output.append(sas_entry)
else:
# File not found
msg = "{} is not a valid file path or doesn't exist".format(path)
raise IOError, msg
if len(output) == 0:
return None
elif len(output) == 1:
self.callback(output[0].meta_data['corstate'], datainfo=output[0])
return output[0]
else:
return output
[docs] def write(self, filename, datainfo=None, state=None):
"""
Write the content of a Data1D as a CanSAS file.
: param filename: Name of the file to write
: param datainfo: Data1D object
: param state: CorfuncState object
"""
# Prepare datainfo
if datainfo is None:
datainfo = Data1D(x=[], y=[])
elif not (isinstance(datainfo, Data1D) or isinstance(datainfo, LoaderData1D)):
msg = ("The CanSAS writer expects a Data1D instance. {} was "
"provided").format(datainfo.__class__.__name__)
raise RuntimeError, msg
if datainfo.title is None or datainfo.title == '':
datainfo.title = datainfo.name
if datainfo.run_name == None or datainfo.run_name == '':
datainfo.run = [str(datainfo.name)]
datainfo.run_name[0] = datainfo.name
# Create the XMl doc
doc, sasentry = self._to_xml_doc(datainfo)
if state is not None:
doc = state.toXML(doc=doc, entry_node=sasentry)
# Write the XML doc to a file
fd = open(filename, 'w')
fd.write(doc.toprettyxml())
fd.close()
[docs] def get_state(self):
return self.state
def _parse_state(self, entry):
"""
Read state data from an XML node
:param entry: The XML node to read from
:return: CorfuncState object
"""
state = None
try:
nodes = entry.xpath('ns:{}'.format(CORNODE_NAME),
namespaces={'ns': CANSAS_NS})
if nodes != []:
state = CorfuncState()
state.fromXML(nodes[0])
except:
msg = "XML document does not contain CorfuncState information\n{}"
msg.format(sys.exc_value)
logging.info(msg)
return state