- Timestamp:
- May 15, 2014 7:08:38 PM (11 years ago)
- Branches:
- master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, costrafo411, magnetic_scatt, release-4.1.1, release-4.1.2, release-4.2.2, release_4.0.1, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
- Children:
- 4a0dc427
- Parents:
- 4e9f227 (diff), bb1b892 (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the (diff) links above to see all the changes relative to each parent. - Location:
- src/sans
- Files:
-
- 3 added
- 22 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sans/dataloader/readers/cansas_constants.py
r9e2bc6c rac5b69d 3 3 the cansas_reader.py file to read in any version of the cansas format. 4 4 """ 5 class cansasConstants: 6 7 ns = '' 5 class CansasConstants: 6 """ 7 The base class to define where all of the data is to be saved by 8 cansas_reader.py. 9 """ 10 11 names = '' 8 12 format = '' 9 13 10 14 11 15 def __init__(self): 12 self.n s = self.CANSAS_NS16 self.names = self.CANSAS_NS 13 17 self.format = self.CANSAS_FORMAT 14 18 15 19 16 def _iterate_namespace(self, namespace):20 def iterate_namespace(self, namespace): 17 21 """ 18 22 Method to iterate through a cansas constants tree based on a list of … … 23 27 """ 24 28 # The current level to look through in cansas_constants. 25 return_me = currentLevel()29 return_me = CurrentLevel() 26 30 return_me.current_level = self.CANSAS_FORMAT.get("SASentry") 27 31 # Defaults for variable and datatype … … 30 34 return_me.ns_optional = True 31 35 for name in namespace: 32 if name != "SASentry": 33 return_me.current_level = \ 34 return_me.current_level.get("children").get(name, "") 35 if return_me.current_level == "": 36 try: 37 if name != "SASentry": 36 38 return_me.current_level = \ 37 return_me.current_level.get("<any>", "") 38 cl_variable = return_me.current_level.get("variable", "") 39 cl_datatype = return_me.current_level.get("storeas", "") 40 cl_units_optional = \ 41 return_me.current_level.get("units_required", "") 42 # Where are how to store the variable for the given namespace 43 # CANSAS_CONSTANTS tree is hierarchical, so is no value, inherit 44 return_me.ns_variable = cl_variable if cl_variable != "" else \ 45 return_me.ns_variable 46 return_me.ns_datatype = cl_datatype if cl_datatype != "" else \ 47 return_me.ns_datatype 48 return_me.ns_optional = cl_units_optional if \ 49 cl_units_optional != return_me.ns_optional \ 50 else return_me.ns_optional 39 return_me.current_level.get("children").get(name, "") 40 if return_me.current_level == "": 41 return_me.current_level = \ 42 return_me.current_level.get("<any>", "") 43 cl_variable = return_me.current_level.get("variable", "") 44 cl_datatype = return_me.current_level.get("storeas", "") 45 cl_units_optional = \ 46 return_me.current_level.get("units_required", "") 47 # Where are how to store the variable for the given namespace 48 # CANSAS_CONSTANTS tree is hierarchical, so is no value, inherit 49 return_me.ns_variable = cl_variable if cl_variable != "" else \ 50 return_me.ns_variable 51 return_me.ns_datatype = cl_datatype if cl_datatype != "" else \ 52 return_me.ns_datatype 53 return_me.ns_optional = cl_units_optional if \ 54 cl_units_optional != return_me.ns_optional \ 55 else return_me.ns_optional 56 except AttributeError: 57 return_me.ns_variable = "{0}.meta_data[\"{2}\"] = \"{1}\"" 58 return_me.ns_datatype = "content" 59 return_me.ns_optional = True 51 60 return return_me 52 61 … … 454 463 } 455 464 SASINSTR_SRC = { 456 "attributes" : {"name" : {"variable" : "{0}.source.name = \"{1}\""}}, 465 "attributes" : {"name" : {"variable" : \ 466 "{0}.source.name = \"{1}\""}}, 457 467 "variable" : None, 458 468 "children" : { 459 "radiation" : {"variable" : "{0}.source.radiation = \"{1}\""}, 469 "radiation" : {"variable" : \ 470 "{0}.source.radiation = \"{1}\""}, 460 471 "beam_size" : SASINSTR_SRC_BEAMSIZE, 461 472 "beam_shape" : {"variable" : \ … … 647 658 "unit" : "pixel_size_unit", 648 659 "attributes" : { 649 "unit" : "{0}.pixel_size_unit = \"{1}\"", 660 "unit" : \ 661 "{0}.pixel_size_unit = \"{1}\"", 650 662 "storeas" : "content" 651 663 } … … 655 667 "unit" : "pixel_size_unit", 656 668 "attributes" : { 657 "unit" : "{0}.pixel_size_unit = \"{1}\"", 669 "unit" : \ 670 "{0}.pixel_size_unit = \"{1}\"", 658 671 "storeas" : "content" 659 672 } … … 663 676 "unit" : "pixel_size_unit", 664 677 "attributes" : { 665 "unit" : "{0}.pixel_size_unit = \"{1}\"", 678 "unit" : \ 679 "{0}.pixel_size_unit = \"{1}\"", 666 680 "storeas" : "content" 667 681 } … … 740 754 } 741 755 742 class currentLevel: 756 class CurrentLevel: 757 """ 758 A helper class to hold information on where you are in the constants tree 759 """ 743 760 744 761 current_level = '' … … 748 765 749 766 def __init__(self): 750 self.current_level = ''767 self.current_level = {} 751 768 self.ns_variable = '' 752 769 self.ns_datatype = "content" -
src/sans/dataloader/readers/cansas_reader.py
r2e3b055 rbb1b892 18 18 import sys 19 19 import datetime 20 import inspect 21 # For saving individual sections of data 20 22 from sans.dataloader.data_info import Data1D 21 23 from sans.dataloader.data_info import Collimation … … 24 26 from sans.dataloader.data_info import Process 25 27 from sans.dataloader.data_info import Aperture 28 # Both imports used. Do not remove either. 29 from xml.dom.minidom import parseString 26 30 import sans.dataloader.readers.xml_reader as xml_reader 27 from sans.dataloader.readers.cansas_constants import cansasConstants 31 from sans.dataloader.readers.xml_reader import XMLreader 32 from sans.dataloader.readers.cansas_constants import CansasConstants 28 33 29 34 _ZERO = 1e-16 … … 36 41 HAS_CONVERTER = False 37 42 38 constants = cansasConstants()39 CANSAS_FORMAT = constants.format40 CANSAS_NS = constants.ns43 CONSTANTS = CansasConstants() 44 CANSAS_FORMAT = CONSTANTS.format 45 CANSAS_NS = CONSTANTS.names 41 46 ALLOW_ALL = True 42 47 43 48 49 # minidom used in functions called by outside classes 50 import xml.dom.minidom 51 # DO NOT REMOVE 52 # Called by outside packages: 53 # sans.perspectives.invariant.invariant_state 54 # sans.perspectives.fitting.pagestate 44 55 def get_content(location, node): 45 56 """ … … 51 62 :return: Element, or None 52 63 """ 53 nodes = node.xpath(location, namespaces={'ns': CANSAS_NS}) 64 nodes = node.xpath(location, 65 namespaces={'ns': CANSAS_NS.get("1.0").get("ns")}) 54 66 55 67 if len(nodes) > 0: … … 59 71 60 72 61 def get_float(location, node): 62 """ 63 Get the content of a node as a float 64 65 :param location: xpath location 66 :param node: node to start at 67 """ 68 nodes = node.xpath(location, namespaces={'ns': CANSAS_NS}) 69 70 value = None 71 attr = {} 72 if len(nodes) > 0: 73 try: 74 value = float(nodes[0].text) 75 except: 76 # Could not pass, skip and return None 77 msg = "cansas_reader.get_float: could not " 78 msg += " convert '%s' to float" % nodes[0].text 79 logging.error(msg) 80 if nodes[0].get('unit') is not None: 81 attr['unit'] = nodes[0].get('unit') 82 return value, attr 83 84 85 # This is called by sans.perspectives.fitting.pagestate.py 86 # Do not remove 73 # DO NOT REMOVE 74 # Called by outside packages: 75 # sans.perspectives.fitting.pagestate 87 76 def write_node(doc, parent, name, value, attr={}): 88 77 """ … … 105 94 106 95 107 class Reader( ):96 class Reader(XMLreader): 108 97 """ 109 98 Class to load cansas 1D XML files … … 114 103 ##CanSAS version - defaults to version 1.0 115 104 cansas_version = "1.0" 116 ##Data reader 117 # TODO: make the reader extend the XMLreader class? 118 reader = xml_reader.XMLreader() 105 106 logging = [] 119 107 errors = [] 120 108 121 109 type_name = "canSAS" 122 123 110 ## Wildcards 124 type = ["XML files (*.xml)|*.xml" ]111 type = ["XML files (*.xml)|*.xml", "SasView Save Files (*.svs)|*.svs"] 125 112 ## List of allowed extensions 126 ext = ['.xml', '.XML' ]113 ext = ['.xml', '.XML', '.svs', '.SVS'] 127 114 128 115 ## Flag to bypass extension check … … 133 120 self.errors = [] 134 121 135 def is_cansas(self ):122 def is_cansas(self, ext="xml"): 136 123 """ 137 124 Checks to see if the xml file is a CanSAS file 138 125 """ 139 if self. reader.validateXML():126 if self.validate_xml(): 140 127 name = "{http://www.w3.org/2001/XMLSchema-instance}schemaLocation" 141 value = self. reader.xmlroot.get(name)142 if (CANSAS_NS.get(self.cansas_version).get("ns") == \143 value.rsplit(" ")[0] ):128 value = self.xmlroot.get(name) 129 if CANSAS_NS.get(self.cansas_version).get("ns") == \ 130 value.rsplit(" ")[0]: 144 131 return True 132 if ext == "svs": 133 return True 145 134 return False 146 135 … … 152 141 """ 153 142 # X - Q value; Y - Intensity (Abs) 154 x = numpy.empty(0)155 y = numpy.empty(0)156 dx = numpy.empty(0)157 dy = numpy.empty(0)143 x_vals = numpy.empty(0) 144 y_vals = numpy.empty(0) 145 dx_vals = numpy.empty(0) 146 dy_vals = numpy.empty(0) 158 147 dxl = numpy.empty(0) 159 148 dxw = numpy.empty(0) … … 162 151 output = [] 163 152 # ns - Namespace hierarchy for current xml object 164 ns = []153 ns_list = [] 165 154 166 155 # Check that the file exists … … 168 157 basename = os.path.basename(xml) 169 158 _, extension = os.path.splitext(basename) 170 # If the fi eltype is not allowed, return nothing159 # If the file type is not allowed, return nothing 171 160 if extension in self.ext or self.allow_all: 161 # Get the file location of 172 162 base_name = xml_reader.__file__ 173 163 base_name = base_name.replace("\\","/") … … 175 165 176 166 # Load in xml file and get the cansas version from the header 177 self. reader.setXMLFile(xml)178 root = self. reader.xmlroot167 self.set_xml_file(xml) 168 root = self.xmlroot 179 169 if root is None: 180 170 root = {} … … 187 177 188 178 # Link a schema to the XML file. 189 self. reader.setSchema(schema_path)179 self.set_schema(schema_path) 190 180 191 181 # Try to load the file, but raise an error if unable to. 192 182 # Check the file matches the XML schema 193 183 try: 194 if self.is_cansas( ):184 if self.is_cansas(extension): 195 185 # Get each SASentry from XML file and add it to a list. 196 186 entry_list = root.xpath('/ns:SASroot/ns:SASentry', 197 187 namespaces={'ns': cansas_defaults.get("ns")}) 198 ns .append("SASentry")188 ns_list.append("SASentry") 199 189 200 190 # If multiple files, modify the name for each is unique … … 204 194 # Parse each SASentry item 205 195 for entry in entry_list: 206 # Define a new Data1D object with zeroes for x and y 207 data1d = Data1D(x,y,dx,dy) 196 # Define a new Data1D object with zeroes for 197 # x_vals and y_vals 198 data1d = Data1D(x_vals, y_vals, dx_vals, dy_vals) 208 199 data1d.dxl = dxl 209 200 data1d.dxw = dxw … … 220 211 221 212 # Get all preprocessing events and encoding 222 self. reader.setProcessingInstructions()213 self.set_processing_instructions() 223 214 data1d.meta_data[PREPROCESS] = \ 224 self. reader.processingInstructions215 self.processing_instructions 225 216 226 217 # Parse the XML file 227 218 return_value, extras = \ 228 self._parse_entry(entry, ns , data1d)219 self._parse_entry(entry, ns_list, data1d) 229 220 del extras[:] 230 221 … … 251 242 output.append(return_value) 252 243 else: 253 value = self. reader.findInvalidXML()244 value = self.find_invalid_xml() 254 245 output.append("Invalid XML at: {0}".format(value)) 255 246 except: 256 247 # If the file does not match the schema, raise this error 257 raise RuntimeError, "%s cannot be read \increment" % xml248 raise RuntimeError, "%s cannot be read" % xml 258 249 return output 259 250 # Return a list of parsed entries that dataloader can manage 260 251 return None 261 252 262 def _create_unique_key(self, dictionary, name, i= 0):253 def _create_unique_key(self, dictionary, name, numb = 0): 263 254 """ 264 255 Create a unique key value for any dictionary to prevent overwriting … … 267 258 :param dictionary: A dictionary with any number of entries 268 259 :param name: The index of the item to be added to dictionary 269 :param i: The number to be appended to the name, starts at 0260 :param numb: The number to be appended to the name, starts at 0 270 261 """ 271 262 if dictionary.get(name) is not None: 272 i+= 1263 numb += 1 273 264 name = name.split("_")[0] 274 name += "_{0}".format( i)275 name = self._create_unique_key(dictionary, name, i)265 name += "_{0}".format(numb) 266 name = self._create_unique_key(dictionary, name, numb) 276 267 return name 277 268 … … 284 275 285 276 :param new_current_level: cansas_constants level as returned by 286 _iterate_namespace277 iterate_namespace 287 278 :param attr: The attributes of the node 288 279 :param data1d: Where the values will be saved … … 318 309 return 319 310 except: 320 err_msg = "CanSAS reader: could not convert the units" 311 err_msg = \ 312 "CanSAS reader: could not convert the units" 321 313 self.errors.append(err_msg) 322 314 return … … 343 335 return node_value, value_unit 344 336 345 def _parse_entry(self, dom, n s, data1d, extras =[]):337 def _parse_entry(self, dom, names=["SASentry"], data1d=None, extras=[]): 346 338 """ 347 339 Parse a SASEntry - new recursive method for parsing the dom of … … 349 341 and extra nodes to be read in simultaneously. 350 342 351 :param dom: dom object with a namespace base of n s352 :param n s: A list of element names that lead up to the dom object343 :param dom: dom object with a namespace base of names 344 :param names: A list of element names that lead up to the dom object 353 345 :param data1d: The data1d object that will be modified 354 346 :param extras: Any values that should go into meta_data when data1d 355 347 is not a Data1D object 356 348 """ 357 349 358 350 # A portion of every namespace entry 351 if data1d == None: 352 x_vals = numpy.empty(0) 353 y_vals = numpy.empty(0) 354 dx_vals = numpy.empty(0) 355 dy_vals = numpy.empty(0) 356 dxl = numpy.empty(0) 357 dxw = numpy.empty(0) 358 data1d = Data1D(x_vals, y_vals, dx_vals, dy_vals) 359 data1d.dxl = dxl 360 data1d.dxw = dxw 361 359 362 base_ns = "{0}{1}{2}".format("{", \ 360 363 CANSAS_NS.get(self.cansas_version).get("ns"), "}") … … 366 369 for node in dom: 367 370 try: 368 # Get the element name and set the current n s level371 # Get the element name and set the current names level 369 372 tagname = node.tag.replace(base_ns, "") 370 373 tagname_original = tagname 371 ns.append(tagname) 374 if tagname == "fitting_plug_in" or tagname == "pr_inversion" or\ 375 tagname == "invariant": 376 continue 377 names.append(tagname) 372 378 attr = node.attrib 373 379 children = node.getchildren() 380 if len(children) == 0: 381 children = None 374 382 save_data1d = data1d 375 383 … … 410 418 411 419 # Get where to store content 412 cs_values = constants._iterate_namespace(ns)420 cs_values = CONSTANTS.iterate_namespace(names) 413 421 # If the element is a child element, recurse 414 422 if children is not None: 415 423 # Returned value is new Data1D object with all previous and 416 424 # new values in it. 417 data1d, extras = self._parse_entry(node, ns, data1d, extras) 425 data1d, extras = self._parse_entry(node, 426 names, data1d, extras) 418 427 419 428 #Get the information from the node … … 488 497 pass 489 498 490 except TypeError :499 except TypeError as e: 491 500 pass 492 501 except Exception as e: … … 509 518 else: 510 519 save_data1d = data1d 511 data1d = save_data1d 512 # Remove tagname from ns to restore original base 513 ns.remove(tagname_original) 514 520 if tagname_original == "fitting_plug_in" or \ 521 tagname_original == "invariant" or \ 522 tagname_original == "pr_inversion": 523 pass 524 else: 525 data1d = save_data1d 526 # Remove tagname from names to restore original base 527 names.remove(tagname_original) 515 528 return data1d, extras 516 529 530 517 531 def _to_xml_doc(self, datainfo): 518 532 """ … … 521 535 :param datainfo: Data1D object 522 536 """ 523 524 537 if not issubclass(datainfo.__class__, Data1D): 525 538 raise RuntimeError, "The cansas writer expects a Data1D instance" 526 539 527 540 # Get PIs and create root element 528 pis = self.reader.return_processing_instructions() 529 doc = self.reader.create_tree(pis[0]) 530 i = 1 531 for i in range(1,len(pis) - 1): 532 doc = self.reader.append(pis[i], doc) 541 pis = self.return_processing_instructions() 542 if len(pis) > 0: 543 pi_tree = self.create_tree(pis[0]) 544 i = 1 545 for i in range(1,len(pis) - 1): 546 pi_tree = self.append(pis[i], pi_tree) 547 pi_string = self.to_string(pi_tree) 548 else: 549 pi_string = "" 533 550 534 551 # Define namespaces and create SASroot object … … 540 557 else: 541 558 url = "http://svn.smallangles.net/svn/canSAS/1dwg/trunk/" 542 schema Location = "{0} {1}cansas1d.xsd".format(ns, url)543 attrib = {"{" + xsi + "}schemaLocation" : schema Location,559 schema_location = "{0} {1}cansas1d.xsd".format(ns, url) 560 attrib = {"{" + xsi + "}schemaLocation" : schema_location, 544 561 "version" : version} 545 562 nsmap = {'xsi' : xsi, None: ns} 546 563 547 main_node = self. reader.create_element("{" + ns + "}SASroot", \564 main_node = self.create_element("{" + ns + "}SASroot", \ 548 565 attrib = attrib, \ 549 566 nsmap = nsmap) 550 567 551 568 # Create ElementTree, append SASroot and apply processing instructions 552 base_string = self.reader.toString(doc) + \ 553 self.reader.toString(main_node) 554 base_element = self.reader.create_element_from_string(base_string) 555 doc = self.reader.create_tree(base_element) 569 base_string = pi_string + self.to_string(main_node) 570 base_element = self.create_element_from_string(base_string) 571 doc = self.create_tree(base_element) 556 572 557 573 # Create SASentry Element 558 entry_node = self. reader.create_element("SASentry")574 entry_node = self.create_element("SASentry") 559 575 root = doc.getroot() 560 576 root.append(entry_node) … … 576 592 577 593 # Data info 578 node = self. reader.create_element("SASdata")579 self. reader.append(node, entry_node)594 node = self.create_element("SASdata") 595 self.append(node, entry_node) 580 596 581 597 for i in range(len(datainfo.x)): 582 pt = self. reader.create_element("Idata")598 pt = self.create_element("Idata") 583 599 node.append(pt) 584 600 self.write_node(pt, "Q", datainfo.x[i], {'unit': datainfo.x_unit}) … … 598 614 self.write_node(pt, "dQl", datainfo.dxl[i], 599 615 {'unit': datainfo.x_unit}) 600 616 601 617 # Transmission Spectrum Info 602 618 for i in range(len(datainfo.trans_spectrum)): 603 619 spectrum = datainfo.trans_spectrum[i] 604 node = self. reader.create_element("SAStransmission_spectrum",620 node = self.create_element("SAStransmission_spectrum", 605 621 {"name" : spectrum.name}) 606 self. reader.append(node, entry_node)622 self.append(node, entry_node) 607 623 if isinstance(spectrum.timestamp, datetime.datetime): 608 624 node.setAttribute("timestamp", spectrum.timestamp) 609 625 for i in range(len(spectrum.wavelength)): 610 pt = self. reader.create_element("Tdata")626 pt = self.create_element("Tdata") 611 627 node.append(pt) 612 628 self.write_node(pt, "Lambda", spectrum.wavelength[i], … … 621 637 622 638 # Sample info 623 sample = self. reader.create_element("SASsample")639 sample = self.create_element("SASsample") 624 640 if datainfo.sample.name is not None: 625 self. reader.write_attribute(sample,641 self.write_attribute(sample, 626 642 "name", 627 643 str(datainfo.sample.name)) 628 self. reader.append(sample, entry_node)644 self.append(sample, entry_node) 629 645 self.write_node(sample, "ID", str(datainfo.sample.ID)) 630 646 self.write_node(sample, "thickness", datainfo.sample.thickness, … … 634 650 {"unit": datainfo.sample.temperature_unit}) 635 651 636 pos = self. reader.create_element("position")652 pos = self.create_element("position") 637 653 written = self.write_node(pos, 638 654 "x", … … 648 664 {"unit": datainfo.sample.position_unit}) 649 665 if written == True: 650 self. reader.append(pos, sample)651 652 ori = self. reader.create_element("orientation")666 self.append(pos, sample) 667 668 ori = self.create_element("orientation") 653 669 written = self.write_node(ori, "roll", 654 670 datainfo.sample.orientation.x, … … 661 677 {"unit": datainfo.sample.orientation_unit}) 662 678 if written == True: 663 self. reader.append(ori, sample)679 self.append(ori, sample) 664 680 665 681 for item in datainfo.sample.details: … … 667 683 668 684 # Instrument info 669 instr = self. reader.create_element("SASinstrument")670 self. reader.append(instr, entry_node)685 instr = self.create_element("SASinstrument") 686 self.append(instr, entry_node) 671 687 672 688 self.write_node(instr, "name", datainfo.instrument) 673 689 674 690 # Source 675 source = self. reader.create_element("SASsource")691 source = self.create_element("SASsource") 676 692 if datainfo.source.name is not None: 677 self. reader.write_attribute(source,693 self.write_attribute(source, 678 694 "name", 679 695 str(datainfo.source.name)) 680 self. reader.append(source, instr)696 self.append(source, instr) 681 697 if datainfo.source.radiation == None or datainfo.source.radiation == '': 682 698 datainfo.source.radiation = "neutron" 683 699 self.write_node(source, "radiation", datainfo.source.radiation) 684 700 685 size = self. reader.create_element("beam_size")701 size = self.create_element("beam_size") 686 702 if datainfo.source.beam_size_name is not None: 687 self. reader.write_attribute(size,703 self.write_attribute(size, 688 704 "name", 689 705 str(datainfo.source.beam_size_name)) … … 697 713 {"unit": datainfo.source.beam_size_unit}) 698 714 if written == True: 699 self. reader.append(size, source)715 self.append(size, source) 700 716 701 717 self.write_node(source, "beam_shape", datainfo.source.beam_shape) … … 718 734 datainfo.collimation.append(coll) 719 735 for item in datainfo.collimation: 720 coll = self. reader.create_element("SAScollimation")736 coll = self.create_element("SAScollimation") 721 737 if item.name is not None: 722 self. reader.write_attribute(coll, "name", str(item.name))723 self. reader.append(coll, instr)738 self.write_attribute(coll, "name", str(item.name)) 739 self.append(coll, instr) 724 740 725 741 self.write_node(coll, "length", item.length, … … 727 743 728 744 for apert in item.aperture: 729 ap = self. reader.create_element("aperture")745 ap = self.create_element("aperture") 730 746 if apert.name is not None: 731 self. reader.write_attribute(ap, "name", str(apert.name))747 self.write_attribute(ap, "name", str(apert.name)) 732 748 if apert.type is not None: 733 self. reader.write_attribute(ap, "type", str(apert.type))734 self. reader.append(ap, coll)735 736 size = self. reader.create_element("size")749 self.write_attribute(ap, "type", str(apert.type)) 750 self.append(ap, coll) 751 752 size = self.create_element("size") 737 753 if apert.size_name is not None: 738 self. reader.write_attribute(size,754 self.write_attribute(size, 739 755 "name", 740 756 str(apert.size_name)) … … 746 762 {"unit": apert.size_unit}) 747 763 if written == True: 748 self. reader.append(size, ap)764 self.append(size, ap) 749 765 750 766 self.write_node(ap, "distance", apert.distance, … … 758 774 759 775 for item in datainfo.detector: 760 det = self. reader.create_element("SASdetector")776 det = self.create_element("SASdetector") 761 777 written = self.write_node(det, "name", item.name) 762 778 written = written | self.write_node(det, "SDD", item.distance, 763 779 {"unit": item.distance_unit}) 764 780 if written == True: 765 self. reader.append(det, instr)781 self.append(det, instr) 766 782 767 off = self. reader.create_element("offset")783 off = self.create_element("offset") 768 784 written = self.write_node(off, "x", item.offset.x, 769 785 {"unit": item.offset_unit}) … … 773 789 {"unit": item.offset_unit}) 774 790 if written == True: 775 self. reader.append(off, det)776 777 ori = self. reader.create_element("orientation")791 self.append(off, det) 792 793 ori = self.create_element("orientation") 778 794 written = self.write_node(ori, "roll", item.orientation.x, 779 795 {"unit": item.orientation_unit}) … … 785 801 {"unit": item.orientation_unit}) 786 802 if written == True: 787 self. reader.append(ori, det)803 self.append(ori, det) 788 804 789 center = self. reader.create_element("beam_center")805 center = self.create_element("beam_center") 790 806 written = self.write_node(center, "x", item.beam_center.x, 791 807 {"unit": item.beam_center_unit}) … … 797 813 {"unit": item.beam_center_unit}) 798 814 if written == True: 799 self. reader.append(center, det)800 801 pix = self. reader.create_element("pixel_size")815 self.append(center, det) 816 817 pix = self.create_element("pixel_size") 802 818 written = self.write_node(pix, "x", item.pixel_size.x, 803 819 {"unit": item.pixel_size_unit}) … … 810 826 {"unit": item.slit_length_unit}) 811 827 if written == True: 812 self. reader.append(pix, det)828 self.append(pix, det) 813 829 814 830 # Processes info 815 831 for item in datainfo.process: 816 node = self. reader.create_element("SASprocess")817 self. reader.append(node, entry_node)832 node = self.create_element("SASprocess") 833 self.append(node, entry_node) 818 834 819 835 self.write_node(node, "name", item.name) … … 831 847 # Note info 832 848 if len(datainfo.notes) == 0: 833 node = self. reader.create_element("SASnote")834 self. reader.append(node, entry_node)849 node = self.create_element("SASnote") 850 self.append(node, entry_node) 835 851 else: 836 852 for item in datainfo.notes: 837 node = self. reader.create_element("SASnote")838 self. reader.write_text(node, item)839 self. reader.append(node, entry_node)853 node = self.create_element("SASnote") 854 self.write_text(node, item) 855 self.append(node, entry_node) 840 856 841 857 842 858 # Return the document, and the SASentry node associated with 843 # the data we just wrote 859 # the data we just wrote 860 # If the calling function was not the cansas reader, return a minidom 861 # object rather than an lxml object. 862 863 frm = inspect.stack()[1] 864 mod_name = frm[1].replace("\\", "/").replace(".pyc", "") 865 mod_name = mod_name.replace(".py", "") 866 mod = mod_name.split("/readers/") 867 mod_name = mod[1] 868 if mod_name != "cansas_reader": 869 string = self.to_string(doc, pp=False) 870 doc = parseString(string) 871 node_name = entry_node.tag 872 node_list = doc.getElementsByTagName(node_name) 873 entry_node = node_list.item(0) 874 844 875 return doc, entry_node 845 876 846 877 847 def write_node(self, parent, name, value, attr= {}):878 def write_node(self, parent, name, value, attr=None): 848 879 """ 849 880 :param doc: document DOM … … 856 887 """ 857 888 if value is not None: 858 parent = self. reader.ebuilder(parent, name, value, attr)889 parent = self.ebuilder(parent, name, value, attr) 859 890 return True 860 891 return False … … 872 903 # Write the file 873 904 fd = open(filename, 'w') 874 if self. reader.encoding == None:875 self. reader.encoding = "UTF-8"876 doc.write(fd, encoding=self. reader.encoding,905 if self.encoding == None: 906 self.encoding = "UTF-8" 907 doc.write(fd, encoding=self.encoding, 877 908 pretty_print=True, xml_declaration=True) 878 909 fd.close() 879 910 911 912 # DO NOT REMOVE - used in saving and loading panel states. 913 def _store_float(self, location, node, variable, storage, optional=True): 914 """ 915 Get the content of a xpath location and store 916 the result. Check that the units are compatible 917 with the destination. The value is expected to 918 be a float. 919 920 The xpath location might or might not exist. 921 If it does not exist, nothing is done 922 923 :param location: xpath location to fetch 924 :param node: node to read the data from 925 :param variable: name of the data member to store it in [string] 926 :param storage: data object that has the 'variable' data member 927 :param optional: if True, no exception will be raised 928 if unit conversion can't be done 929 930 :raise ValueError: raised when the units are not recognized 931 """ 932 entry = get_content(location, node) 933 try: 934 value = float(entry.text) 935 except: 936 value = None 937 938 if value is not None: 939 # If the entry has units, check to see that they are 940 # compatible with what we currently have in the data object 941 units = entry.get('unit') 942 if units is not None: 943 toks = variable.split('.') 944 local_unit = None 945 exec "local_unit = storage.%s_unit" % toks[0] 946 if local_unit != None and units.lower() != local_unit.lower(): 947 if HAS_CONVERTER == True: 948 try: 949 conv = Converter(units) 950 exec "storage.%s = %g" % (variable, 951 conv(value, units=local_unit)) 952 except: 953 exc_type, exc_value, exc_traceback = sys.exc_info() 954 err_mess = "CanSAS reader: could not convert" 955 err_mess += " %s unit [%s]; expecting [%s]\n %s" \ 956 % (variable, units, local_unit, exc_value) 957 self.errors.append(err_mess) 958 if optional: 959 logging.info(err_mess) 960 else: 961 raise ValueError, err_mess 962 else: 963 err_mess = "CanSAS reader: unrecognized %s unit [%s];"\ 964 % (variable, units) 965 err_mess += " expecting [%s]" % local_unit 966 self.errors.append(err_mess) 967 if optional: 968 logging.info(err_mess) 969 else: 970 raise ValueError, err_mess 971 else: 972 exec "storage.%s = value" % variable 973 else: 974 exec "storage.%s = value" % variable 975 976 977 # DO NOT REMOVE - used in saving and loading panel states. 978 def _store_content(self, location, node, variable, storage): 979 """ 980 Get the content of a xpath location and store 981 the result. The value is treated as a string. 982 983 The xpath location might or might not exist. 984 If it does not exist, nothing is done 985 986 :param location: xpath location to fetch 987 :param node: node to read the data from 988 :param variable: name of the data member to store it in [string] 989 :param storage: data object that has the 'variable' data member 990 991 :return: return a list of errors 992 """ 993 entry = get_content(location, node) 994 if entry is not None and entry.text is not None: 995 exec "storage.%s = entry.text.strip()" % variable -
src/sans/dataloader/readers/xml_reader.py
r2e3b055 rac5b69d 17 17 from lxml import etree 18 18 from lxml.builder import E 19 parser = etree.ETCompatXMLParser(remove_comments=True, remove_pis=False) 19 20 PARSER = etree.ETCompatXMLParser(remove_comments=True, remove_pis=False) 20 21 21 22 class XMLreader(): 23 """ 24 Generic XML read and write class. Mostly helper functions. 25 Makes reading/writing XML a bit easier than calling lxml libraries directly. 26 27 :Dependencies: 28 This class requires lxml 2.3 or higher. 29 """ 22 30 23 31 xml = None … … 27 35 schemadoc = None 28 36 encoding = None 29 processing Instructions = None37 processing_instructions = None 30 38 31 39 def __init__(self, xml = None, schema = None, root = None): 32 40 self.xml = xml 33 41 self.schema = schema 34 self.processing Instructions = {}42 self.processing_instructions = {} 35 43 if xml is not None: 36 self.set XMLFile(xml, root)44 self.set_xml_file(xml, root) 37 45 else: 38 46 self.xmldoc = None 39 47 self.xmlroot = None 40 48 if schema is not None: 41 self.set Schema(schema)49 self.set_schema(schema) 42 50 else: 43 51 self.schemadoc = None … … 47 55 Read in an XML file into memory and return an lxml dictionary 48 56 """ 49 if self.validate XML():50 self.xmldoc = etree.parse(self.xml, parser = parser)57 if self.validate_xml(): 58 self.xmldoc = etree.parse(self.xml, parser = PARSER) 51 59 else: 52 raise etree. ValidationError(self, self.findInvalidXML())60 raise etree.XMLSchemaValidateError(self, self.find_invalid_xml()) 53 61 return self.xmldoc 54 62 55 def set XMLFile(self, xml, root = None):63 def set_xml_file(self, xml, root = None): 56 64 """ 57 65 Set the XML file and parse … … 59 67 try: 60 68 self.xml = xml 61 self.xmldoc = etree.parse(self.xml, parser = parser)69 self.xmldoc = etree.parse(self.xml, parser = PARSER) 62 70 self.xmlroot = self.xmldoc.getroot() 63 71 except Exception: … … 66 74 self.xmlroot = None 67 75 68 def set Schema(self, schema):76 def set_schema(self, schema): 69 77 """ 70 78 Set the schema file and parse … … 72 80 try: 73 81 self.schema = schema 74 self.schemadoc = etree.parse(self.schema, parser = parser)82 self.schemadoc = etree.parse(self.schema, parser = PARSER) 75 83 except Exception: 76 84 self.schema = None 77 85 self.schemadoc = None 78 86 79 def validate XML(self):87 def validate_xml(self): 80 88 """ 81 89 Checks to see if the XML file meets the schema … … 83 91 valid = True 84 92 if self.schema is not None: 85 self.parse SchemaAndDoc()86 schema Check = etree.XMLSchema(self.schemadoc)87 valid = schema Check.validate(self.xmldoc)93 self.parse_schema_and_doc() 94 schema_check = etree.XMLSchema(self.schemadoc) 95 valid = schema_check.validate(self.xmldoc) 88 96 return valid 89 97 90 def find InvalidXML(self):98 def find_invalid_xml(self): 91 99 """ 92 100 Finds the first offending element that should not be present in XML file 93 101 """ 94 first Error = ""95 self.parse SchemaAndDoc()102 first_error = "" 103 self.parse_schema_and_doc() 96 104 schema = etree.XMLSchema(self.schemadoc) 97 105 try: 98 first Error = schema.assertValid(self.xmldoc)106 first_error = schema.assertValid(self.xmldoc) 99 107 except etree.DocumentInvalid as e: 100 first Error = str(e)101 return first Error102 103 def parse SchemaAndDoc(self):108 first_error = str(e) 109 return first_error 110 111 def parse_schema_and_doc(self): 104 112 """ 105 113 Creates a dictionary of the parsed schema and xml files. 106 114 """ 107 self.set XMLFile(self.xml)108 self.set Schema(self.schema)109 110 def to String(self, elem, pp=False, encoding=None):115 self.set_xml_file(self.xml) 116 self.set_schema(self.schema) 117 118 def to_string(self, elem, pp=False, encoding=None): 111 119 """ 112 120 Converts an etree element into a string … … 129 137 return dic 130 138 131 def set ProcessingInstructions(self):139 def set_processing_instructions(self): 132 140 """ 133 141 Take out all processing instructions and create a dictionary from them … … 135 143 """ 136 144 dic = {} 137 p i= self.xmlroot.getprevious()138 while p iis not None:139 pi_string = self.to String(pi)145 proc_instr = self.xmlroot.getprevious() 146 while proc_instr is not None: 147 pi_string = self.to_string(proc_instr) 140 148 if "?>\n<?" in pi_string: 141 149 pi_string = pi_string.split("?>\n<?") … … 145 153 for item in pi_string: 146 154 dic = self.break_processing_instructions(item, dic) 147 p i = pi.getprevious()155 proc_instr = proc_instr.getprevious() 148 156 if 'xml' in dic: 149 self.set Encoding(dic['xml'])157 self.set_encoding(dic['xml']) 150 158 del dic['xml'] 151 self.processing Instructions = dic152 153 def set Encoding(self, attr_str):159 self.processing_instructions = dic 160 161 def set_encoding(self, attr_str): 154 162 """ 155 163 Find the encoding in the xml declaration and save it as a string … … 169 177 self.encoding = None 170 178 171 def _create_unique_key(self, dictionary, name, i= 0):179 def _create_unique_key(self, dictionary, name, numb = 0): 172 180 """ 173 181 Create a unique key value for any dictionary to prevent overwriting … … 176 184 :param dictionary: A dictionary with any number of entries 177 185 :param name: The index of the item to be added to dictionary 178 :param i: The number to be appended to the name, starts at 0186 :param numb: The number to be appended to the name, starts at 0 179 187 """ 180 188 if dictionary.get(name) is not None: 181 i+= 1189 numb += 1 182 190 name = name.split("_")[0] 183 name += "_{0}".format( i)184 name = self._create_unique_key(dictionary, name, i)191 name += "_{0}".format(numb) 192 name = self._create_unique_key(dictionary, name, numb) 185 193 return name 186 194 … … 193 201 return etree.ElementTree(root) 194 202 195 def create_element_from_string(self, s):203 def create_element_from_string(self, xml_string): 196 204 """ 197 205 Create an element from an XML string 198 206 199 :param s: A string of xml200 """ 201 return etree.fromstring( s)202 203 def create_element(self, name, attrib= {}, nsmap=None):207 :param xml_string: A string of xml 208 """ 209 return etree.fromstring(xml_string) 210 211 def create_element(self, name, attrib=None, nsmap=None): 204 212 """ 205 213 Create an XML element for writing to file … … 207 215 :param name: The name of the element to be created 208 216 """ 217 if attrib == None: 218 attrib = {} 209 219 return etree.Element(name, attrib, nsmap) 210 220 … … 237 247 """ 238 248 pi_list = [] 239 for key in self.processingInstructions: 240 value = self.processingInstructions.get(key) 241 pi = etree.ProcessingInstruction(key, value) 242 pi_list.append(pi) 249 if self.processing_instructions is not None: 250 for key in self.processing_instructions: 251 value = self.processing_instructions.get(key) 252 pi_item = etree.ProcessingInstruction(key, value) 253 pi_list.append(pi_item) 243 254 return pi_list 244 255 … … 253 264 return tree 254 265 255 def ebuilder(self, parent, elementname, text=None, attrib={}): 266 def ebuilder(self, parent, elementname, text=None, attrib=None): 267 """ 268 Use lxml E builder class with arbitrary inputs. 269 270 :param parnet: The parent element to append a child to 271 :param elementname: The name of the child in string form 272 :param text: The element text 273 :param attrib: A dictionary of attribute names to attribute values 274 """ 256 275 text = str(text) 276 if attrib == None: 277 attrib = {} 257 278 elem = E(elementname, attrib, text) 258 279 parent = parent.append(elem) -
src/sans/guiframe/gui_manager.py
r98816c43 ra3b635b 776 776 # Append item from plugin under menu file if necessary 777 777 self._populate_file_menu() 778 779 778 780 if not wx.VERSION_STRING >= '3.0.0.0': 779 781 self.SetMenuBar(self._menubar) … … 1349 1351 if config._do_aboutbox: 1350 1352 self._help_menu.AppendSeparator() 1351 id = wx.NewId() 1352 self._help_menu.Append(id, '&About', 'Software information') 1353 wx.EVT_MENU(self, id, self._onAbout) 1353 self._help_menu.Append(wx.ID_ABOUT, '&About', 'Software information') 1354 wx.EVT_MENU(self, wx.ID_ABOUT, self._onAbout) 1354 1355 1355 1356 # Checking for updates … … 1491 1492 self._file_menu.AppendSeparator() 1492 1493 1493 def _add_menu_file(self):1494 """1495 add menu file1496 """1497 1498 # File menu1499 self._file_menu = wx.Menu()1500 1494 style1 = self.__gui_style & GUIFRAME.MULTIPLE_APPLICATIONS 1501 1495 if OPEN_SAVE_MENU: … … 1526 1520 'Save state of the current active analysis panel') 1527 1521 wx.EVT_MENU(self, id, self._on_save_application) 1522 if not sys.platform =='darwin': 1528 1523 self._file_menu.AppendSeparator() 1529 1530 id = wx.NewId() 1531 self._file_menu.Append(id, '&Quit', 'Exit') 1532 wx.EVT_MENU(self, id, self.Close) 1524 id = wx.NewId() 1525 self._file_menu.Append(id, '&Quit', 'Exit') 1526 wx.EVT_MENU(self, id, self.Close) 1527 1528 def _add_menu_file(self): 1529 """ 1530 add menu file 1531 """ 1532 # File menu 1533 self._file_menu = wx.Menu() 1533 1534 # Add sub menus 1534 1535 self._menubar.Append(self._file_menu, '&File') … … 1947 1948 if temp is not None: 1948 1949 doc = temp 1949 1950 1950 1951 # Write the XML document 1951 1952 extens = APPLICATION_STATE_EXTENSION … … 1961 1962 except: 1962 1963 msg = "Error occurred while saving: " 1963 msg += "To save, at lea t one application panel "1964 msg += "To save, at least one application panel " 1964 1965 msg += "should have a data set.." 1965 1966 wx.PostEvent(self, StatusEvent(status=msg)) -
src/sans/perspectives/fitting/pagestate.py
reddb6ec rac5b69d 20 20 21 21 import xml.dom.minidom 22 from xml.dom.minidom import parseString 22 23 from lxml import etree 23 24 … … 672 673 # We are appending to an existing document 673 674 newdoc = doc 674 top_element = newdoc.createElement(FITTING_NODE_NAME) 675 try: 676 top_element = newdoc.createElement(FITTING_NODE_NAME) 677 except: 678 string = etree.tostring(doc, pretty_print=True) 679 newdoc = parseString(string) 680 top_element = newdoc.createElement(FITTING_NODE_NAME) 675 681 if entry_node is None: 676 682 newdoc.documentElement.appendChild(top_element) 677 683 else: 678 entry_node.appendChild(top_element) 679 684 try: 685 entry_node.appendChild(top_element) 686 except: 687 node_name = entry_node.tag 688 node_list = newdoc.getElementsByTagName(node_name) 689 entry_node = node_list.item(0) 690 entry_node.appendChild(top_element) 691 680 692 attr = newdoc.createAttribute("version") 681 693 attr.nodeValue = '1.0' … … 750 762 for item in list_of_state_parameters: 751 763 element = newdoc.createElement(item[0]) 752 com = "self._toXML_helper( list=self.%s,"764 com = "self._toXML_helper(thelist=self.%s," 753 765 com += " element=element, newdoc=newdoc)" 754 766 exec com % item[1] … … 762 774 return None 763 775 else: 764 return newdoc .toprettyxml()776 return newdoc 765 777 766 778 def _fromXML_helper(self, node, list): … … 1252 1264 state = PageState() 1253 1265 state.fromXML(node=nodes[0]) 1266 1254 1267 except: 1255 1268 logging.info("XML document does not contain fitting information.\n %s" % sys.exc_value) … … 1257 1270 return state 1258 1271 1259 def _parse_ entry(self, dom):1272 def _parse_save_state_entry(self, dom): 1260 1273 """ 1261 1274 Parse a SASentry … … 1268 1281 node = dom.xpath('ns:data_class', namespaces={'ns': CANSAS_NS}) 1269 1282 if not node or node[0].text.lstrip().rstrip() != "Data2D": 1270 return CansasReader._parse_entry(self, dom) 1283 return_value, _ = self._parse_entry(dom) 1284 numpy.trim_zeros(return_value.x) 1285 numpy.trim_zeros(return_value.y) 1286 numpy.trim_zeros(return_value.dy) 1287 size_dx = return_value.dx.size 1288 size_dxl = return_value.dxl.size 1289 size_dxw = return_value.dxw.size 1290 if size_dxl == 0 and size_dxw == 0: 1291 return_value.dxl = None 1292 return_value.dxw = None 1293 numpy.trim_zeros(return_value.dx) 1294 elif size_dx == 0: 1295 return_value.dx = None 1296 size_dx = size_dxl 1297 numpy.trim_zeros(return_value.dxl) 1298 numpy.trim_zeros(return_value.dxw) 1299 1300 return return_value, _ 1271 1301 1272 1302 #Parse 2D … … 1539 1569 for entry in entry_list: 1540 1570 try: 1541 sas_entry = self._parse_entry(entry)1571 sas_entry, _ = self._parse_save_state_entry(entry) 1542 1572 except: 1543 1573 raise … … 1608 1638 # Sanity check 1609 1639 if self.cansas == True: 1610 1611 1640 # Add fitting information to the XML document 1612 1641 doc = self.write_toXML(datainfo, fitstate) 1613 1642 # Write the XML document 1614 fd = open(filename, 'w')1615 fd.write(doc.toprettyxml())1616 fd.close()1617 1643 else: 1618 fitstate.toXML(file=filename) 1644 doc = fitstate.toXML(file=filename) 1645 1646 # Save the document no matter the type 1647 fd = open(filename, 'w') 1648 fd.write(doc.toprettyxml()) 1649 fd.close() 1619 1650 1620 1651 def write_toXML(self, datainfo=None, state=None): … … 1638 1669 state.data.run = [str(state.data.name)] 1639 1670 state.data.run_name[0] = state.data.name 1640 1671 1641 1672 if issubclass(state.data.__class__, 1642 1673 sans.dataloader.data_info.Data1D): … … 1648 1679 1649 1680 if state is not None: 1650 state.toXML(doc=doc, file=data.filename, entry_node=sasentry)1651 1681 doc = state.toXML(doc=doc, file=data.filename, entry_node=sasentry) 1682 1652 1683 return doc 1653 1684 -
src/sans/perspectives/invariant/invariant_panel.py
r5777106 r92a2ecd 218 218 if new_doc is not None: 219 219 if doc is not None and hasattr(doc, "firstChild"): 220 child = new_doc.firstChild.firstChild 221 doc.firstChild.appendChild(child) 220 child = new_doc.getElementsByTagName("SASentry") 221 for item in child: 222 doc.firstChild.appendChild(item) 222 223 else: 223 224 doc = new_doc -
src/sans/perspectives/invariant/invariant_state.py
r51f14603 r92a2ecd 254 254 : param entry_node: XML node within the XML document at which we will append the data [optional] 255 255 """ 256 # TODO: Get this to work 256 257 from xml.dom.minidom import getDOMImplementation 257 258 import time … … 273 274 else: 274 275 entry_node.appendChild(top_element) 275 276 276 277 attr = newdoc.createAttribute("version") 277 278 attr.nodeValue = '1.0' … … 352 353 return None 353 354 else: 354 return newdoc .toprettyxml()355 return newdoc 355 356 356 357 def fromXML(self, file=None, node=None): … … 742 743 for entry in entry_list: 743 744 744 sas_entry = self._parse_entry(entry)745 sas_entry, _ = self._parse_entry(entry) 745 746 invstate = self._parse_state(entry) 746 747 … … 809 810 # Add the invariant information to the XML document 810 811 if state is not None: 811 state.toXML(datainfo.name,doc=doc, entry_node=sasentry)812 doc = state.toXML(datainfo.name,doc=doc, entry_node=sasentry) 812 813 return doc 813 814 -
src/sans/perspectives/pr/inversion_panel.py
r5777106 r92a2ecd 255 255 if new_doc is not None: 256 256 if doc is not None and hasattr(doc, "firstChild"): 257 child = new_doc.firstChild.firstChild 258 doc.firstChild.appendChild(child) 257 child = new_doc.getElementsByTagName("SASentry") 258 for item in child: 259 doc.firstChild.appendChild(item) 259 260 else: 260 261 doc = new_doc -
src/sans/perspectives/pr/inversion_state.py
r5777106 r92a2ecd 148 148 149 149 """ 150 #TODO: Get this to work 150 151 from xml.dom.minidom import getDOMImplementation 151 152 … … 221 222 return None 222 223 else: 223 return newdoc .toprettyxml()224 return newdoc 224 225 225 226 def fromXML(self, file=None, node=None): … … 470 471 471 472 for entry in entry_list: 472 sas_entry = self._parse_entry(entry)473 sas_entry, _ = self._parse_entry(entry) 473 474 prstate = self._parse_prstate(entry) 474 475 #prstate could be None when .svs file is loaded … … 504 505 # Sanity check 505 506 if self.cansas == True: 506 doc = self.write_toXML(datainfo, prstate)507 doc = self.write_toXML(datainfo, prstate) 507 508 # Write the XML document 508 509 fd = open(filename, 'w') … … 527 528 # Create basic XML document 528 529 doc, sasentry = self._to_xml_doc(datainfo) 529 530 530 531 # Add the invariant information to the XML document 531 532 if state is not None: 532 state.toXML(doc=doc, entry_node=sasentry)533 doc = state.toXML(doc=doc, entry_node=sasentry) 533 534 534 535 return doc -
src/sans/fit/AbstractFitEngine.py
r6c00702 r8d074d9 3 3 #import logging 4 4 import sys 5 import math 5 6 import numpy 6 import math 7 import park 7 8 8 from sans.dataloader.data_info import Data1D 9 9 from sans.dataloader.data_info import Data2D 10 _SMALLVALUE = 1.0e-10 11 12 class SansParameter(park.Parameter): 13 """ 14 SANS model parameters for use in the PARK fitting service. 15 The parameter attribute value is redirected to the underlying 16 parameter value in the SANS model. 17 """ 18 def __init__(self, name, model, data): 19 """ 20 :param name: the name of the model parameter 21 :param model: the sans model to wrap as a park model 22 """ 23 park.Parameter.__init__(self, name) 24 self._model, self._name = model, name 25 self.data = data 26 self.model = model 27 #set the value for the parameter of the given name 28 self.set(model.getParam(name)) 29 30 def _getvalue(self): 31 """ 32 override the _getvalue of park parameter 33 34 :return value the parameter associates with self.name 35 36 """ 37 return self._model.getParam(self.name) 38 39 def _setvalue(self, value): 40 """ 41 override the _setvalue pf park parameter 42 43 :param value: the value to set on a given parameter 44 45 """ 46 self._model.setParam(self.name, value) 47 48 value = property(_getvalue, _setvalue) 49 50 def _getrange(self): 51 """ 52 Override _getrange of park parameter 53 return the range of parameter 54 """ 55 #if not self.name in self._model.getDispParamList(): 56 lo, hi = self._model.details[self.name][1:3] 57 if lo is None: lo = -numpy.inf 58 if hi is None: hi = numpy.inf 59 if lo > hi: 60 raise ValueError, "wrong fit range for parameters" 61 62 return lo, hi 63 64 def get_name(self): 65 """ 66 """ 67 return self._getname() 68 69 def _setrange(self, r): 70 """ 71 override _setrange of park parameter 72 73 :param r: the value of the range to set 74 75 """ 76 self._model.details[self.name][1:3] = r 77 range = property(_getrange, _setrange) 78 79 80 class Model(park.Model): 81 """ 82 PARK wrapper for SANS models. 10 _SMALLVALUE = 1.0e-10 11 12 # Note: duplicated from park 13 class FitHandler(object): 14 """ 15 Abstract interface for fit thread handler. 16 17 The methods in this class are called by the optimizer as the fit 18 progresses. 19 20 Note that it is up to the optimizer to call the fit handler correctly, 21 reporting all status changes and maintaining the 'done' flag. 22 """ 23 done = False 24 """True when the fit job is complete""" 25 result = None 26 """The current best result of the fit""" 27 28 def improvement(self): 29 """ 30 Called when a result is observed which is better than previous 31 results from the fit. 32 33 result is a FitResult object, with parameters, #calls and fitness. 34 """ 35 def error(self, msg): 36 """ 37 Model had an error; print traceback 38 """ 39 def progress(self, current, expected): 40 """ 41 Called each cycle of the fit, reporting the current and the 42 expected amount of work. The meaning of these values is 43 optimizer dependent, but they can be converted into a percent 44 complete using (100*current)//expected. 45 46 Progress is updated each iteration of the fit, whatever that 47 means for the particular optimization algorithm. It is called 48 after any calls to improvement for the iteration so that the 49 update handler can control I/O bandwidth by suppressing 50 intermediate improvements until the fit is complete. 51 """ 52 def finalize(self): 53 """ 54 Fit is complete; best results are reported 55 """ 56 def abort(self): 57 """ 58 Fit was aborted. 59 """ 60 61 # TODO: not sure how these are used, but they are needed for running the fit 62 def update_fit(self, last=False): pass 63 def set_result(self, result=None): self.result = result 64 65 class Model: 66 """ 67 Fit wrapper for SANS models. 83 68 """ 84 69 def __init__(self, sans_model, sans_data=None, **kw): 85 70 """ 86 71 :param sans_model: the sans model to wrap using park interface 87 88 """ 89 park.Model.__init__(self, **kw) 72 73 """ 90 74 self.model = sans_model 91 75 self.name = sans_model.name 92 76 self.data = sans_data 93 #list of parameters names 94 self.sansp = sans_model.getParamList() 95 #list of park parameter 96 self.parkp = [SansParameter(p, sans_model, sans_data) for p in self.sansp] 97 #list of parameter set 98 self.parameterset = park.ParameterSet(sans_model.name, pars=self.parkp) 99 self.pars = [] 100 77 101 78 def get_params(self, fitparams): 102 79 """ 103 80 return a list of value of paramter to fit 104 81 105 82 :param fitparams: list of paramaters name to fit 106 107 """ 108 list_params = [] 109 self.pars = [] 110 self.pars = fitparams 111 for item in fitparams: 112 for element in self.parkp: 113 if element.name == str(item): 114 list_params.append(element.value) 115 return list_params 116 83 84 """ 85 return [self.model.getParam(k) for k in fitparams] 86 117 87 def set_params(self, paramlist, params): 118 88 """ 119 89 Set value for parameters to fit 120 90 121 91 :param params: list of value for parameters to fit 122 123 """ 124 try: 125 for i in range(len(self.parkp)): 126 for j in range(len(paramlist)): 127 if self.parkp[i].name == paramlist[j]: 128 self.parkp[i].value = params[j] 129 self.model.setParam(self.parkp[i].name, params[j]) 130 except: 131 raise 132 92 93 """ 94 for k,v in zip(paramlist, params): 95 self.model.setParam(k,v) 96 97 def set(self, **kw): 98 self.set_params(*zip(*kw.items())) 99 133 100 def eval(self, x): 134 101 """ 135 102 Override eval method of park model. 136 103 137 104 :param x: the x value used to compute a function 138 105 """ … … 141 108 except: 142 109 raise 143 110 144 111 def eval_derivs(self, x, pars=[]): 145 112 """ … … 154 121 instead of calling eval. 155 122 """ 156 return [] 157 158 123 raise NotImplementedError('no derivatives available') 124 125 def __call__(self, x): 126 return self.eval(x) 127 159 128 class FitData1D(Data1D): 160 129 """ … … 185 154 """ 186 155 Data1D.__init__(self, x=x, y=y, dx=dx, dy=dy) 156 self.num_points = len(x) 187 157 self.sans_data = data 188 158 self.smearer = smearer … … 251 221 """ 252 222 return self.qmin, self.qmax 253 223 224 def size(self): 225 """ 226 Number of measurement points in data set after masking, etc. 227 """ 228 return len(self.x) 229 254 230 def residuals(self, fn): 255 231 """ … … 293 269 def __init__(self, sans_data2d, data=None, err_data=None): 294 270 Data2D.__init__(self, data=data, err_data=err_data) 295 """ 296 Data can be initital with a data (sans plottable) 297 or with vectors. 298 """ 271 # Data can be initialized with a sans plottable or with vectors. 299 272 self.res_err_image = [] 273 self.num_points = 0 # will be set by set_data 300 274 self.idx = [] 301 275 self.qmin = None … … 339 313 self.idx = (self.idx) & (self.mask) 340 314 self.idx = (self.idx) & (numpy.isfinite(self.data)) 315 self.num_points = numpy.sum(self.idx) 341 316 342 317 def set_smearer(self, smearer): … … 372 347 """ 373 348 return self.qmin, self.qmax 374 349 350 def size(self): 351 """ 352 Number of measurement points in data set after masking, etc. 353 """ 354 return numpy.sum(self.idx) 355 375 356 def residuals(self, fn): 376 357 """ … … 409 390 410 391 411 class SansAssembly: 412 """ 413 Sans Assembly class a class wrapper to be call in optimizer.leastsq method 414 """ 415 def __init__(self, paramlist, model=None, data=None, fitresult=None, 416 handler=None, curr_thread=None, msg_q=None): 417 """ 418 :param Model: the model wrapper fro sans -model 419 :param Data: the data wrapper for sans data 420 421 """ 422 self.model = model 423 self.data = data 424 self.paramlist = paramlist 425 self.msg_q = msg_q 426 self.curr_thread = curr_thread 427 self.handler = handler 428 self.fitresult = fitresult 429 self.res = [] 430 self.true_res = [] 431 self.func_name = "Functor" 432 self.theory = None 433 434 def chisq(self): 435 """ 436 Calculates chi^2 437 438 :param params: list of parameter values 439 440 :return: chi^2 441 442 """ 443 total = 0 444 for item in self.true_res: 445 total += item * item 446 if len(self.true_res) == 0: 447 return None 448 return total / len(self.true_res) 449 450 def __call__(self, params): 451 """ 452 Compute residuals 453 :param params: value of parameters to fit 454 """ 455 #import thread 456 self.model.set_params(self.paramlist, params) 457 #print "params", params 458 self.true_res, theory = self.data.residuals(self.model.eval) 459 self.theory = copy.deepcopy(theory) 460 # check parameters range 461 if self.check_param_range(): 462 # if the param value is outside of the bound 463 # just silent return res = inf 464 return self.res 465 self.res = self.true_res 466 467 if self.fitresult is not None: 468 self.fitresult.set_model(model=self.model) 469 self.fitresult.residuals = self.true_res 470 self.fitresult.iterations += 1 471 self.fitresult.theory = theory 472 473 #fitness = self.chisq(params=params) 474 fitness = self.chisq() 475 self.fitresult.pvec = params 476 self.fitresult.set_fitness(fitness=fitness) 477 if self.msg_q is not None: 478 self.msg_q.put(self.fitresult) 479 480 if self.handler is not None: 481 self.handler.set_result(result=self.fitresult) 482 self.handler.update_fit() 483 484 if self.curr_thread != None: 485 try: 486 self.curr_thread.isquit() 487 except: 488 #msg = "Fitting: Terminated... Note: Forcing to stop " 489 #msg += "fitting may cause a 'Functor error message' " 490 #msg += "being recorded in the log file....." 491 #self.handler.stop(msg) 492 raise 493 494 return self.res 495 496 def check_param_range(self): 497 """ 498 Check the lower and upper bound of the parameter value 499 and set res to the inf if the value is outside of the 500 range 501 :limitation: the initial values must be within range. 502 """ 503 504 #time.sleep(0.01) 505 is_outofbound = False 506 # loop through the fit parameters 507 for p in self.model.parameterset: 508 param_name = p.get_name() 509 if param_name in self.paramlist: 510 511 # if the range was defined, check the range 512 if numpy.isfinite(p.range[0]): 513 if p.value == 0: 514 # This value works on Scipy 515 # Do not change numbers below 516 value = _SMALLVALUE 517 else: 518 value = p.value 519 # For leastsq, it needs a bit step back from the boundary 520 val = p.range[0] - value * _SMALLVALUE 521 if p.value < val: 522 self.res *= 1e+6 523 524 is_outofbound = True 525 break 526 if numpy.isfinite(p.range[1]): 527 # This value works on Scipy 528 # Do not change numbers below 529 if p.value == 0: 530 value = _SMALLVALUE 531 else: 532 value = p.value 533 # For leastsq, it needs a bit step back from the boundary 534 val = p.range[1] + value * _SMALLVALUE 535 if p.value > val: 536 self.res *= 1e+6 537 is_outofbound = True 538 break 539 540 return is_outofbound 541 542 392 543 393 class FitEngine: 544 394 def __init__(self): … … 571 421 572 422 """ 573 if model == None: 574 raise ValueError, "AbstractFitEngine: Need to set model to fit" 575 576 new_model = model 423 if not pars: 424 raise ValueError("no fitting parameters") 425 426 if model is None: 427 raise ValueError("no model to fit") 428 577 429 if not issubclass(model.__class__, Model): 578 new_model = Model(model, data) 579 580 if len(constraints) > 0: 581 for constraint in constraints: 582 name, value = constraint 583 try: 584 new_model.parameterset[str(name)].set(str(value)) 585 except: 586 msg = "Fit Engine: Error occurs when setting the constraint" 587 msg += " %s for parameter %s " % (value, name) 588 raise ValueError, msg 589 590 if len(pars) > 0: 591 temp = [] 592 for item in pars: 593 if item in new_model.model.getParamList(): 594 temp.append(item) 595 self.param_list.append(item) 596 else: 597 598 msg = "wrong parameter %s used " % str(item) 599 msg += "to set model %s. Choose " % str(new_model.model.name) 600 msg += "parameter name within %s" % \ 601 str(new_model.model.getParamList()) 602 raise ValueError, msg 603 604 #A fitArrange is already created but contains data_list only at id 605 if self.fit_arrange_dict.has_key(id): 606 self.fit_arrange_dict[id].set_model(new_model) 607 self.fit_arrange_dict[id].pars = pars 608 else: 609 #no fitArrange object has been create with this id 610 fitproblem = FitArrange() 611 fitproblem.set_model(new_model) 612 fitproblem.pars = pars 613 self.fit_arrange_dict[id] = fitproblem 614 vals = [] 615 for name in pars: 616 vals.append(new_model.model.getParam(name)) 617 self.fit_arrange_dict[id].vals = vals 618 else: 619 raise ValueError, "park_integration:missing parameters" 620 430 model = Model(model, data) 431 432 sasmodel = model.model 433 available_parameters = sasmodel.getParamList() 434 for p in pars: 435 if p not in available_parameters: 436 raise ValueError("parameter %s not available in model %s; use one of [%s] instead" 437 %(p, sasmodel.name, ", ".join(available_parameters))) 438 439 if id not in self.fit_arrange_dict: 440 self.fit_arrange_dict[id] = FitArrange() 441 442 self.fit_arrange_dict[id].set_model(model) 443 self.fit_arrange_dict[id].pars = pars 444 self.fit_arrange_dict[id].vals = [sasmodel.getParam(name) for name in pars] 445 self.fit_arrange_dict[id].constraints = constraints 446 447 self.param_list.extend(pars) 448 621 449 def set_data(self, data, id, smearer=None, qmin=None, qmax=None): 622 450 """ … … 700 528 self.vals = [] 701 529 self.selected = 0 702 530 703 531 def set_model(self, model): 704 532 """ … … 752 580 """ 753 581 return self.selected 754 755 756 IS_MAC = True757 if sys.platform.count("win32") > 0:758 IS_MAC = False759 760 582 761 583 class FResult(object): … … 765 587 def __init__(self, model=None, param_list=None, data=None): 766 588 self.calls = None 767 self.pars = []768 589 self.fitness = None 769 590 self.chisqr = None … … 776 597 self.residuals = [] 777 598 self.index = [] 778 self.parameters = None779 self.is_mac = IS_MAC780 599 self.model = model 781 600 self.data = data … … 803 622 if self.pvec == None and self.model is None and self.param_list is None: 804 623 return "No results" 805 n = len(self.model.parameterset) 806 807 result_param = zip(xrange(n), self.model.parameterset) 808 msg1 = ["[Iteration #: %s ]" % self.iterations] 809 msg3 = ["=== goodness of fit: %s ===" % (str(self.fitness))] 810 if not self.is_mac: 811 msg2 = ["P%-3d %s......|.....%s" % \ 812 (p[0], p[1], p[1].value)\ 813 for p in result_param if p[1].name in self.param_list] 814 msg = msg1 + msg3 + msg2 815 else: 816 msg = msg1 + msg3 817 msg = "\n".join(msg) 818 return msg 624 625 sasmodel = self.model.model 626 pars = enumerate(sasmodel.getParamList()) 627 msg1 = "[Iteration #: %s ]" % self.iterations 628 msg3 = "=== goodness of fit: %s ===" % (str(self.fitness)) 629 msg2 = ["P%-3d %s......|.....%s" % (i, v, sasmodel.getParam(v)) 630 for i,v in pars if v in self.param_list] 631 msg = [msg1, msg3] + msg2 632 return "\n".join(msg) 819 633 820 634 def print_summary(self): 821 635 """ 822 636 """ 823 print s elf637 print str(self) -
src/sans/fit/Fitting.py
r5777106 re3efa6b3 8 8 from sans.fit.ScipyFitting import ScipyFit 9 9 from sans.fit.ParkFitting import ParkFit 10 from sans.fit.BumpsFitting import BumpsFit 10 11 12 ENGINES={ 13 'scipy': ScipyFit, 14 'park': ParkFit, 15 'bumps': BumpsFit, 16 } 11 17 12 18 class Fit(object): … … 26 32 27 33 """ 28 def __init__(self, engine='scipy' ):34 def __init__(self, engine='scipy', *args, **kw): 29 35 """ 30 36 """ … … 32 38 self._engine = None 33 39 self.fitter_id = None 34 self.set_engine(engine )40 self.set_engine(engine, *args, **kw) 35 41 36 42 def __setattr__(self, name, value): … … 49 55 self.__dict__[name] = value 50 56 51 def set_engine(self, word ):57 def set_engine(self, word, *args, **kw): 52 58 """ 53 59 Select the type of Fit … … 59 65 60 66 """ 61 if word == "scipy": 62 self._engine = ScipyFit() 63 elif word == "park": 64 self._engine = ParkFit() 65 else: 66 raise ValueError, "enter the keyword scipy or park" 67 try: 68 self._engine = ENGINES[word](*args, **kw) 69 except KeyError, exc: 70 raise KeyError("fit engine should be one of scipy, park or bumps") 67 71 68 72 def fit(self, msg_q=None, q=None, handler=None, -
src/sans/fit/Loader.py
r5777106 r6fe5100 8 8 This class is loading values from given file or value giving by the user 9 9 """ 10 11 10 def __init__(self, x=None, y=None, dx=None, dy=None): 11 raise NotImplementedError("a code search shows that this code is not active, and you are not seeing this message") 12 12 # variable to store loaded values 13 13 self.x = x -
src/sans/fit/ParkFitting.py
r9d6d5ba r8d074d9 24 24 from sans.fit.AbstractFitEngine import FitEngine 25 25 from sans.fit.AbstractFitEngine import FResult 26 26 27 class SansParameter(park.Parameter): 28 """ 29 SANS model parameters for use in the PARK fitting service. 30 The parameter attribute value is redirected to the underlying 31 parameter value in the SANS model. 32 """ 33 def __init__(self, name, model, data): 34 """ 35 :param name: the name of the model parameter 36 :param model: the sans model to wrap as a park model 37 """ 38 park.Parameter.__init__(self, name) 39 #self._model, self._name = model, name 40 self.data = data 41 self.model = model 42 #set the value for the parameter of the given name 43 self.set(model.getParam(name)) 44 45 # TODO: model is missing parameter ranges for dispersion parameters 46 if name not in model.details: 47 #print "setting details for",name 48 model.details[name] = ["", None, None] 49 50 def _getvalue(self): 51 """ 52 override the _getvalue of park parameter 53 54 :return value the parameter associates with self.name 55 56 """ 57 return self.model.getParam(self.name) 58 59 def _setvalue(self, value): 60 """ 61 override the _setvalue pf park parameter 62 63 :param value: the value to set on a given parameter 64 65 """ 66 self.model.setParam(self.name, value) 67 68 value = property(_getvalue, _setvalue) 69 70 def _getrange(self): 71 """ 72 Override _getrange of park parameter 73 return the range of parameter 74 """ 75 #if not self.name in self._model.getDispParamList(): 76 lo, hi = self.model.details[self.name][1:3] 77 if lo is None: lo = -numpy.inf 78 if hi is None: hi = numpy.inf 79 if lo > hi: 80 raise ValueError, "wrong fit range for parameters" 81 82 return lo, hi 83 84 def get_name(self): 85 """ 86 """ 87 return self._getname() 88 89 def _setrange(self, r): 90 """ 91 override _setrange of park parameter 92 93 :param r: the value of the range to set 94 95 """ 96 self.model.details[self.name][1:3] = r 97 range = property(_getrange, _setrange) 98 99 100 class ParkModel(park.Model): 101 """ 102 PARK wrapper for SANS models. 103 """ 104 def __init__(self, sans_model, sans_data=None, **kw): 105 """ 106 :param sans_model: the sans model to wrap using park interface 107 108 """ 109 park.Model.__init__(self, **kw) 110 self.model = sans_model 111 self.name = sans_model.name 112 self.data = sans_data 113 #list of parameters names 114 self.sansp = sans_model.getParamList() 115 #list of park parameter 116 self.parkp = [SansParameter(p, sans_model, sans_data) for p in self.sansp] 117 #list of parameter set 118 self.parameterset = park.ParameterSet(sans_model.name, pars=self.parkp) 119 self.pars = [] 120 121 def get_params(self, fitparams): 122 """ 123 return a list of value of paramter to fit 124 125 :param fitparams: list of paramaters name to fit 126 127 """ 128 list_params = [] 129 self.pars = fitparams 130 for item in fitparams: 131 for element in self.parkp: 132 if element.name == str(item): 133 list_params.append(element.value) 134 return list_params 135 136 def set_params(self, paramlist, params): 137 """ 138 Set value for parameters to fit 139 140 :param params: list of value for parameters to fit 141 142 """ 143 try: 144 for i in range(len(self.parkp)): 145 for j in range(len(paramlist)): 146 if self.parkp[i].name == paramlist[j]: 147 self.parkp[i].value = params[j] 148 self.model.setParam(self.parkp[i].name, params[j]) 149 except: 150 raise 151 152 def eval(self, x): 153 """ 154 Override eval method of park model. 155 156 :param x: the x value used to compute a function 157 """ 158 try: 159 return self.model.evalDistribution(x) 160 except: 161 raise 162 163 def eval_derivs(self, x, pars=[]): 164 """ 165 Evaluate the model and derivatives wrt pars at x. 166 167 pars is a list of the names of the parameters for which derivatives 168 are desired. 169 170 This method needs to be specialized in the model to evaluate the 171 model function. Alternatively, the model can implement is own 172 version of residuals which calculates the residuals directly 173 instead of calling eval. 174 """ 175 return [] 176 177 27 178 class SansFitResult(fitresult.FitResult): 28 179 def __init__(self, *args, **kwrds): … … 244 395 return fitpars 245 396 246 def all_results(self, result):397 def extend_results_with_calculated_parameters(self, result): 247 398 """ 248 399 Extend result from the fit with the calculated parameters. … … 292 443 # dividing residuals by N in order to be consistent with Scipy 293 444 m.chisq = numpy.sum(m.residuals**2/N) 294 resid.append(m.weight*m.residuals /math.sqrt(N))445 resid.append(m.weight*m.residuals) 295 446 self.residuals = numpy.hstack(resid) 296 447 N = len(self.residuals) 297 448 self.degrees_of_freedom = N-k if N>k else 1 298 449 self.chisq = numpy.sum(self.residuals**2) 299 return self.chisq 450 return self.chisq/self.degrees_of_freedom 300 451 301 452 class ParkFit(FitEngine): … … 354 505 if fproblem.get_to_fit() == 1: 355 506 fitproblems.append(fproblem) 356 if len(fitproblems) == 0: 507 if len(fitproblems) == 0: 357 508 raise RuntimeError, "No Assembly scheduled for Park fitting." 358 return359 509 for item in fitproblems: 360 parkmodel = item.get_model() 510 model = item.get_model() 511 parkmodel = ParkModel(model.model, model.data) 512 parkmodel.pars = item.pars 361 513 if reset_flag: 362 514 # reset the initial value; useful for batch … … 364 516 ind = item.pars.index(name) 365 517 parkmodel.model.setParam(name, item.vals[ind]) 518 519 # set the constraints into the model 520 for p,v in item.constraints: 521 parkmodel.parameterset[str(p)].set(str(v)) 366 522 367 523 for p in parkmodel.parameterset: 368 524 ## does not allow status change for constraint parameters 369 525 if p.status != 'computed': 370 if p.get_name() in item.pars:526 if p.get_name() in item.pars: 371 527 ## make parameters selected for 372 528 #fit will be between boundaries … … 383 539 def fit(self, msg_q=None, 384 540 q=None, handler=None, curr_thread=None, 385 541 ftol=1.49012e-8, reset_flag=False): 386 542 """ 387 543 Performs fit with park.fit module.It can perform fit with one model … … 407 563 localfit = SansFitSimplex() 408 564 localfit.ftol = ftol 409 565 localfit.xtol = 1e-6 566 410 567 # See `park.fitresult.FitHandler` for details. 411 568 fitter = SansFitMC(localfit=localfit, start_points=1) … … 416 573 try: 417 574 result = fit.fit(self.problem, fitter=fitter, handler=handler) 418 self.problem. all_results(result)575 self.problem.extend_results_with_calculated_parameters(result) 419 576 420 577 except LinAlgError: 421 578 raise ValueError, "SVD did not converge" 579 580 if result is None: 581 raise RuntimeError("park did not return a fit result") 422 582 423 583 for m in self.problem.parts: … … 427 587 small_result.theory = theory 428 588 small_result.residuals = residuals 429 small_result.pvec = [] 430 small_result.cov = [] 431 small_result.stderr = [] 432 small_result.param_list = [] 433 small_result.residuals = m.residuals 434 if result is not None: 435 for p in result.parameters: 436 if p.data.name == small_result.data.name and \ 437 p.model.name == small_result.model.name: 438 small_result.index = m.data.idx 439 small_result.fitness = result.fitness 440 small_result.pvec.append(p.value) 441 small_result.stderr.append(p.stderr) 442 name_split = p.name.split('.') 443 name = name_split[1].strip() 444 if len(name_split) > 2: 445 name += '.' + name_split[2].strip() 446 small_result.param_list.append(name) 589 small_result.index = m.data.idx 590 small_result.fitness = result.fitness 591 592 # Extract the parameters that are part of this model; make sure 593 # they match the fitted parameters for this model, and place them 594 # in the same order as they occur in the model. 595 pars = {} 596 for p in result.parameters: 597 #if p.data.name == small_result.data.name and 598 if p.model.name == small_result.model.name: 599 model_name, par_name = p.name.split('.', 1) 600 pars[par_name] = (p.value, p.stderr) 601 #assert len(pars.keys()) == len(m.model.pars) 602 v,dv = zip(*[pars[p] for p in m.model.pars]) 603 small_result.pvec = v 604 small_result.stderr = dv 605 small_result.param_list = m.model.pars 606 607 # normalize chisq by degrees of freedom 608 dof = len(small_result.residuals)-len(small_result.pvec) 609 small_result.fitness = numpy.sum(residuals**2)/dof 610 447 611 result_list.append(small_result) 448 612 if q != None: -
src/sans/fit/ScipyFitting.py
r5777106 r8d074d9 1 2 3 1 """ 4 2 ScipyFitting module contains FitArrange , ScipyFit, … … 6 4 simple fit with scipy optimizer. 7 5 """ 6 import sys 7 import copy 8 8 9 9 import numpy 10 import sys11 12 10 13 11 from sans.fit.AbstractFitEngine import FitEngine 14 from sans.fit.AbstractFitEngine import SansAssembly 15 from sans.fit.AbstractFitEngine import FitAbort 16 from sans.fit.AbstractFitEngine import Model 17 from sans.fit.AbstractFitEngine import FResult 12 from sans.fit.AbstractFitEngine import FResult 13 14 class SansAssembly: 15 """ 16 Sans Assembly class a class wrapper to be call in optimizer.leastsq method 17 """ 18 def __init__(self, paramlist, model=None, data=None, fitresult=None, 19 handler=None, curr_thread=None, msg_q=None): 20 """ 21 :param Model: the model wrapper fro sans -model 22 :param Data: the data wrapper for sans data 23 24 """ 25 self.model = model 26 self.data = data 27 self.paramlist = paramlist 28 self.msg_q = msg_q 29 self.curr_thread = curr_thread 30 self.handler = handler 31 self.fitresult = fitresult 32 self.res = [] 33 self.true_res = [] 34 self.func_name = "Functor" 35 self.theory = None 36 37 def chisq(self): 38 """ 39 Calculates chi^2 40 41 :param params: list of parameter values 42 43 :return: chi^2 44 45 """ 46 total = 0 47 for item in self.true_res: 48 total += item * item 49 if len(self.true_res) == 0: 50 return None 51 return total / (len(self.true_res) - len(self.paramlist)) 52 53 def __call__(self, params): 54 """ 55 Compute residuals 56 :param params: value of parameters to fit 57 """ 58 #import thread 59 self.model.set_params(self.paramlist, params) 60 #print "params", params 61 self.true_res, theory = self.data.residuals(self.model.eval) 62 self.theory = copy.deepcopy(theory) 63 # check parameters range 64 if self.check_param_range(): 65 # if the param value is outside of the bound 66 # just silent return res = inf 67 return self.res 68 self.res = self.true_res 69 70 if self.fitresult is not None: 71 self.fitresult.set_model(model=self.model) 72 self.fitresult.residuals = self.true_res 73 self.fitresult.iterations += 1 74 self.fitresult.theory = theory 75 76 #fitness = self.chisq(params=params) 77 fitness = self.chisq() 78 self.fitresult.pvec = params 79 self.fitresult.set_fitness(fitness=fitness) 80 if self.msg_q is not None: 81 self.msg_q.put(self.fitresult) 82 83 if self.handler is not None: 84 self.handler.set_result(result=self.fitresult) 85 self.handler.update_fit() 86 87 if self.curr_thread != None: 88 try: 89 self.curr_thread.isquit() 90 except: 91 #msg = "Fitting: Terminated... Note: Forcing to stop " 92 #msg += "fitting may cause a 'Functor error message' " 93 #msg += "being recorded in the log file....." 94 #self.handler.stop(msg) 95 raise 96 97 return self.res 98 99 def check_param_range(self): 100 """ 101 Check the lower and upper bound of the parameter value 102 and set res to the inf if the value is outside of the 103 range 104 :limitation: the initial values must be within range. 105 """ 106 107 #time.sleep(0.01) 108 is_outofbound = False 109 # loop through the fit parameters 110 model = self.model.model 111 for p in self.paramlist: 112 value = model.getParam(p) 113 low,high = model.details[p][1:3] 114 if low is not None and numpy.isfinite(low): 115 if p.value == 0: 116 # This value works on Scipy 117 # Do not change numbers below 118 value = _SMALLVALUE 119 # For leastsq, it needs a bit step back from the boundary 120 val = low - value * _SMALLVALUE 121 if value < val: 122 self.res *= 1e+6 123 is_outofbound = True 124 break 125 if high is not None and numpy.isfinite(high): 126 # This value works on Scipy 127 # Do not change numbers below 128 if value == 0: 129 value = _SMALLVALUE 130 # For leastsq, it needs a bit step back from the boundary 131 val = high + value * _SMALLVALUE 132 if value > val: 133 self.res *= 1e+6 134 is_outofbound = True 135 break 136 137 return is_outofbound 18 138 19 139 class ScipyFit(FitEngine): … … 50 170 """ 51 171 FitEngine.__init__(self) 52 self.fit_arrange_dict = {}53 self.param_list = []54 172 self.curr_thread = None 55 173 #def fit(self, *args, **kw): … … 68 186 msg = "Scipy can't fit more than a single fit problem at a time." 69 187 raise RuntimeError, msg 70 return 71 elif len(fitproblem) == 0 : 188 elif len(fitproblem) == 0 : 72 189 raise RuntimeError, "No Assembly scheduled for Scipy fitting." 73 return74 190 model = fitproblem[0].get_model() 75 191 if reset_flag: … … 87 203 88 204 # Check the initial value if it is within range 89 self._check_param_range(model)205 _check_param_range(model.model, self.param_list) 90 206 91 result = FResult(model=model, data=data, param_list=self.param_list) 92 result.pars = fitproblem[0].pars 207 result = FResult(model=model.model, data=data, param_list=self.param_list) 93 208 result.fitter_id = self.fitter_id 94 209 if handler is not None: 95 210 handler.set_result(result=result) 211 functor = SansAssembly(paramlist=self.param_list, 212 model=model, 213 data=data, 214 handler=handler, 215 fitresult=result, 216 curr_thread=curr_thread, 217 msg_q=msg_q) 96 218 try: 97 219 # This import must be here; otherwise it will be confused when more … … 99 221 from scipy import optimize 100 222 101 functor = SansAssembly(paramlist=self.param_list,102 model=model,103 data=data,104 handler=handler,105 fitresult=result,106 curr_thread=curr_thread,107 msg_q=msg_q)108 223 out, cov_x, _, mesg, success = optimize.leastsq(functor, 109 224 model.get_params(self.param_list), 110 111 225 ftol=ftol, 226 full_output=1) 112 227 except: 113 228 if hasattr(sys, 'last_type') and sys.last_type == KeyboardInterrupt: … … 142 257 143 258 144 def _check_param_range(self, model): 145 """ 146 Check parameter range and set the initial value inside 147 if it is out of range. 148 149 : model: park model object 150 """ 151 is_outofbound = False 152 # loop through parameterset 153 for p in model.parameterset: 154 param_name = p.get_name() 155 # proceed only if the parameter name is in the list of fitting 156 if param_name in self.param_list: 157 # if the range was defined, check the range 158 if numpy.isfinite(p.range[0]): 159 if p.value <= p.range[0]: 160 # 10 % backing up from the border if not zero 161 # for Scipy engine to work properly. 162 shift = self._get_zero_shift(p.range[0]) 163 new_value = p.range[0] + shift 164 p.value = new_value 165 is_outofbound = True 166 if numpy.isfinite(p.range[1]): 167 if p.value >= p.range[1]: 168 shift = self._get_zero_shift(p.range[1]) 169 # 10 % backing up from the border if not zero 170 # for Scipy engine to work properly. 171 new_value = p.range[1] - shift 172 # Check one more time if the new value goes below 173 # the low bound, If so, re-evaluate the value 174 # with the mean of the range. 175 if numpy.isfinite(p.range[0]): 176 if new_value < p.range[0]: 177 new_value = (p.range[0] + p.range[1]) / 2.0 178 # Todo: 179 # Need to think about when both min and max are same. 180 p.value = new_value 181 is_outofbound = True 182 183 return is_outofbound 184 185 def _get_zero_shift(self, range): 186 """ 187 Get 10% shift of the param value = 0 based on the range value 188 189 : param range: min or max value of the bounds 190 """ 191 if range == 0: 192 shift = 0.1 193 else: 194 shift = 0.1 * range 195 196 return shift 197 259 def _check_param_range(model, param_list): 260 """ 261 Check parameter range and set the initial value inside 262 if it is out of range. 263 264 : model: park model object 265 """ 266 # loop through parameterset 267 for p in param_list: 268 value = model.getParam(p) 269 low,high = model.details.setdefault(p,["",None,None])[1:3] 270 # if the range was defined, check the range 271 if low is not None and value <= low: 272 value = low + _get_zero_shift(low) 273 if high is not None and value > high: 274 value = high - _get_zero_shift(high) 275 # Check one more time if the new value goes below 276 # the low bound, If so, re-evaluate the value 277 # with the mean of the range. 278 if low is not None and value < low: 279 value = 0.5 * (low+high) 280 model.setParam(p, value) 281 282 def _get_zero_shift(limit): 283 """ 284 Get 10% shift of the param value = 0 based on the range value 285 286 : param range: min or max value of the bounds 287 """ 288 return 0.1 * (limit if limit != 0.0 else 1.0) 289 198 290 199 291 #def profile(fn, *args, **kw): -
src/sans/fit/__init__.py
r5777106 r6fe5100 1 from .AbstractFitEngine import FitHandler -
src/sans/perspectives/fitting/basepage.py
r116e1a7 r5bf0331 835 835 infor = "warning" 836 836 else: 837 msg = "Error was occured"838 msg += " :No valid parameter values to paste from the clipboard..."837 msg = "Error occured: " 838 msg += "No valid parameter values to paste from the clipboard..." 839 839 infor = "error" 840 840 wx.PostEvent(self._manager.parent, … … 2183 2183 else: 2184 2184 tcrtl.SetBackgroundColour("pink") 2185 msg = "Model Error: wrong value entered: %s" % sys.exc_value2185 msg = "Model Error: wrong value entered: %s" % sys.exc_value 2186 2186 wx.PostEvent(self.parent, StatusEvent(status=msg)) 2187 2187 return 2188 2188 except: 2189 2189 tcrtl.SetBackgroundColour("pink") 2190 msg = "Model Error: wrong value entered: %s" % sys.exc_value2190 msg = "Model Error: wrong value entered: %s" % sys.exc_value 2191 2191 wx.PostEvent(self.parent, StatusEvent(status=msg)) 2192 2192 return … … 2199 2199 #is_modified = True 2200 2200 else: 2201 msg = "Cannot Plot :No npts in that Qrange!!! "2201 msg = "Cannot plot: No points in Q range!!! " 2202 2202 wx.PostEvent(self.parent, StatusEvent(status=msg)) 2203 2203 else: 2204 2204 tcrtl.SetBackgroundColour("pink") 2205 msg = "Model Error: wrong value entered!!!"2205 msg = "Model Error: wrong value entered!!!" 2206 2206 wx.PostEvent(self.parent, StatusEvent(status=msg)) 2207 2207 self.save_current_state() … … 2240 2240 else: 2241 2241 tcrtl.SetBackgroundColour("pink") 2242 msg = "Model Error: wrong value entered: %s" % sys.exc_value2242 msg = "Model Error: wrong value entered: %s" % sys.exc_value 2243 2243 wx.PostEvent(self._manager.parent, StatusEvent(status=msg)) 2244 2244 return 2245 2245 except: 2246 2246 tcrtl.SetBackgroundColour("pink") 2247 msg = "Model Error: wrong value entered: %s" % sys.exc_value2247 msg = "Model Error: wrong value entered: %s" % sys.exc_value 2248 2248 wx.PostEvent(self._manager.parent, StatusEvent(status=msg)) 2249 2249 return … … 2256 2256 is_modified = True 2257 2257 else: 2258 msg = "Cannot Plot :No npts in that Qrange!!! "2258 msg = "Cannot Plot: No points in Q range!!! " 2259 2259 wx.PostEvent(self._manager.parent, StatusEvent(status=msg)) 2260 2260 else: 2261 2261 tcrtl.SetBackgroundColour("pink") 2262 msg = "Model Error: wrong value entered!!!"2262 msg = "Model Error: wrong value entered!!!" 2263 2263 wx.PostEvent(self._manager.parent, StatusEvent(status=msg)) 2264 2264 self.save_current_state() … … 2431 2431 self.qmax.SetBackgroundColour("pink") 2432 2432 self.qmax.Refresh() 2433 msg = " Npts of Data Error :"2434 msg += " No or too little npts of%s." % data.name2433 msg = "Data Error: " 2434 msg += "Too few points in %s." % data.name 2435 2435 wx.PostEvent(self._manager.parent, StatusEvent(status=msg)) 2436 2436 self.fitrange = False … … 2466 2466 self.qmax.SetBackgroundColour("pink") 2467 2467 self.qmax.Refresh() 2468 msg = " Npts of Data Error :"2469 msg += " No or too little npts of%s." % data.name2468 msg = "Data Error: " 2469 msg += "Too few points in %s." % data.name 2470 2470 wx.PostEvent(self._manager.parent, StatusEvent(status=msg)) 2471 2471 self.fitrange = False … … 2518 2518 2519 2519 except: 2520 msg = "Wrong Fit parameter range entered"2520 msg = "Wrong fit parameter range entered" 2521 2521 wx.PostEvent(self._manager.parent, 2522 2522 StatusEvent(status=msg)) -
src/sans/perspectives/fitting/console.py
r5777106 r644ca73 5 5 import time 6 6 import wx 7 import park 8 from park.fitresult import FitHandler 7 from sans.fit import FitHandler 9 8 10 9 class ConsoleUpdate(FitHandler): … … 88 87 Print result object 89 88 """ 90 msg = " \n %s \n" % s elf.result.__str__()89 msg = " \n %s \n" % str(self.result) 91 90 wx.PostEvent(self.parent, StatusEvent(status=msg)) 92 91 … … 137 136 self.fit_duration += self.elapsed_time 138 137 str_time = time.strftime("%a, %d %b %Y %H:%M:%S ", time.localtime(t1)) 139 UPDATE_INTERVAL = 0.5138 UPDATE_INTERVAL = 5.0 140 139 u_flag = False 141 140 if self.fit_duration >= UPDATE_INTERVAL: -
src/sans/perspectives/fitting/fit_thread.py
ra855fec re3efa6b3 18 18 19 19 def __init__(self, 20 21 22 23 24 batch_inputs=None,25 20 fn, 21 page_id, 22 handler, 23 batch_outputs, 24 batch_inputs=None, 25 pars=None, 26 26 completefn = None, 27 27 updatefn = None, … … 30 30 ftol = None, 31 31 reset_flag = False): 32 CalcThread.__init__(self,completefn, 32 CalcThread.__init__(self, 33 completefn, 33 34 updatefn, 34 35 yieldtime, … … 80 81 list_map_get_attr.append(map_getattr) 81 82 #from multiprocessing import Pool 82 inputs = zip(list_map_get_attr, self.fitter, list_fit_function,83 83 inputs = zip(list_map_get_attr, self.fitter, list_fit_function, 84 list_q, list_q, list_handler,list_curr_thread,list_ftol, 84 85 list_reset_flag) 85 86 result = map(map_apply, inputs) … … 87 88 self.complete(result=result, 88 89 batch_inputs=self.batch_inputs, 89 90 batch_outputs=self.batch_outputs, 90 91 page_id=self.page_id, 91 92 pars = self.pars, -
src/sans/perspectives/fitting/fitpage.py
rd44648e r4e9f227 661 661 wx.EXPAND | wx.ADJUST_MINSIZE, 0) 662 662 663 if self.engine_type == "park":663 if self.engine_type in ("park","bumps"): 664 664 self.text_disp_max.Show(True) 665 665 self.text_disp_min.Show(True) … … 738 738 wx.EXPAND | wx.ADJUST_MINSIZE, 0) 739 739 740 if self.engine_type == "park":740 if self.engine_type in ("park","bumps"): 741 741 ctl3.Show(True) 742 742 ctl4.Show(True) … … 1005 1005 return 1006 1006 1007 if len(self._manager.fit_thread_list) > 0 and\1008 self._manager._fit_engine != "park" and\1009 self._manager.sim_page != None and \1010 self._manager.sim_page.uid == self.uid:1007 if (len(self._manager.fit_thread_list) > 0 1008 and self._manager._fit_engine not in ("park","bumps") 1009 and self._manager.sim_page != None 1010 and self._manager.sim_page.uid == self.uid): 1011 1011 msg = "The FitEnging will be set to 'ParkMC'\n" 1012 1012 msg += " to fit with more than one data set..." … … 2108 2108 if chisqr != None and numpy.isfinite(chisqr): 2109 2109 #format chi2 2110 if self.engine_type == "park":2110 if self.engine_type in ("park","bumps"): 2111 2111 npt_fit = float(self.get_npts2fit()) 2112 2112 chi2 = format_number(chisqr, True) -
src/sans/perspectives/fitting/fitproblem.py
r5777106 r5bf0331 454 454 return self.itervalues() 455 455 456 def 456 def set_result(self, result, fid): 457 457 """ 458 458 """ -
src/sans/perspectives/fitting/fitting.py
r767514a r4e9f227 36 36 from .fitproblem import FitProblemDictionary 37 37 from .fitpanel import FitPanel 38 from .resultpanel import ResultPanel, PlotResultEvent 39 38 40 from .fit_thread import FitThread 39 41 from .pagestate import Reader … … 111 113 self.scipy_id = wx.NewId() 112 114 self.park_id = wx.NewId() 115 self.bumps_id = wx.NewId() 113 116 self.menu1 = None 114 117 self.new_model_frame = None … … 198 201 wx.EVT_MENU(owner, self.park_id, self._onset_engine_park) 199 202 203 bumps_help = "Bumps: fitting and uncertainty analysis. More in Help window...." 204 self.menu1.AppendCheckItem(self.bumps_id, "Bumps fit", 205 bumps_help) 206 wx.EVT_MENU(owner, self.bumps_id, self._onset_engine_bumps) 207 200 208 self.menu1.FindItemById(self.scipy_id).Check(True) 201 209 self.menu1.FindItemById(self.park_id).Check(False) 210 self.menu1.FindItemById(self.bumps_id).Check(False) 202 211 self.menu1.AppendSeparator() 203 212 self.id_tol = wx.NewId() … … 207 216 ftol_help) 208 217 wx.EVT_MENU(owner, self.id_tol, self.show_ftol_dialog) 218 219 self.id_bumps_options = wx.NewId() 220 bopts_help = "Bumps fitting options" 221 self.menu1.Append(self.id_bumps_options, 'Bumps &Options', bopts_help) 222 wx.EVT_MENU(owner, self.id_bumps_options, self.on_bumps_options) 223 self.bumps_options_menu = self.menu1.FindItemById(self.id_bumps_options) 224 self.bumps_options_menu.Enable(True) 225 226 self.id_result_panel = wx.NewId() 227 self.menu1.Append(self.id_result_panel, "Fit Results", "Show fit results panel") 228 wx.EVT_MENU(owner, self.id_result_panel, lambda ev: self.result_frame.Show()) 209 229 self.menu1.AppendSeparator() 210 230 … … 511 531 self.perspective = [] 512 532 self.perspective.append(self.fit_panel.window_name) 533 534 self.result_frame = MDIFrame(self.parent, None, ResultPanel.window_caption, (220, 200)) 535 self.result_panel = ResultPanel(parent=self.result_frame, manager=self) 536 self.perspective.append(self.result_panel.window_name) 513 537 514 538 #index number to create random model name … … 525 549 #Send the fitting panel to guiframe 526 550 self.mypanels.append(self.fit_panel) 551 self.mypanels.append(self.result_panel) 527 552 return self.mypanels 528 553 … … 818 843 StatusEvent(status=msg, info='warning')) 819 844 dialog.Destroy() 845 846 def on_bumps_options(self, event=None): 847 from bumps.gui.fit_dialog import OpenFitOptions 848 OpenFitOptions() 820 849 821 850 def stop_fit(self, uid): … … 959 988 self._gui_engine = self._return_engine_type() 960 989 self.fitproblem_count = fitproblem_count 961 if self._fit_engine == "park":990 if self._fit_engine in ("park","bumps"): 962 991 engineType = "Simultaneous Fit" 963 992 else: … … 970 999 #simulatanous fit only one engine need to be created 971 1000 ## if simultaneous fit change automatically the engine to park 972 self._on_change_engine(engine='park') 1001 if self._fit_engine not in ("park","bumps"): 1002 self._on_change_engine(engine='park') 973 1003 sim_fitter = Fit(self._fit_engine) 974 1004 sim_fitter.fitter_id = self.sim_page.uid … … 978 1008 979 1009 self.fitproblem_count = fitproblem_count 980 if self._fit_engine == "park":1010 if self._fit_engine in ("park","bumps"): 981 1011 engineType = "Simultaneous Fit" 982 1012 else: … … 1571 1601 wx.PostEvent(self.parent, StatusEvent(status=msg, info="info", 1572 1602 type="stop")) 1603 wx.PostEvent(self.result_panel, PlotResultEvent(result=result)) 1573 1604 # reset fit_engine if changed by simul_fit 1574 1605 if self._fit_engine != self._gui_engine: … … 1682 1713 self._on_change_engine('scipy') 1683 1714 1715 def _onset_engine_bumps(self, event): 1716 """ 1717 set engine to bumps 1718 """ 1719 self._on_change_engine('bumps') 1720 1684 1721 def _on_slicer_event(self, event): 1685 1722 """ … … 1733 1770 self.menu1.FindItemById(self.park_id).Check(True) 1734 1771 self.menu1.FindItemById(self.scipy_id).Check(False) 1772 self.menu1.FindItemById(self.bumps_id).Check(False) 1773 elif engine == "scipy": 1774 self.menu1.FindItemById(self.park_id).Check(False) 1775 self.menu1.FindItemById(self.scipy_id).Check(True) 1776 self.menu1.FindItemById(self.bumps_id).Check(False) 1735 1777 else: 1736 1778 self.menu1.FindItemById(self.park_id).Check(False) 1737 self.menu1.FindItemById(self.scipy_id).Check(True) 1779 self.menu1.FindItemById(self.scipy_id).Check(False) 1780 self.menu1.FindItemById(self.bumps_id).Check(True) 1738 1781 ## post a message to status bar 1739 1782 msg = "Engine set to: %s" % self._fit_engine -
src/sans/perspectives/fitting/simfitpage.py
r5777106 r4e9f227 152 152 ## making sure all parameters content a constraint 153 153 ## validity of the constraint expression is own by fit engine 154 if self.parent._manager._fit_engine != "park"and flag:154 if self.parent._manager._fit_engine not in ("park","bumps") and flag: 155 155 msg = "The FitEnging will be set to 'Park' fit engine\n" 156 156 msg += " for the simultaneous fit..." … … 378 378 box_description = wx.StaticBox(self, -1,"Easy Setup ") 379 379 boxsizer = wx.StaticBoxSizer(box_description, wx.HORIZONTAL) 380 sizer_constraint = wx.BoxSizer(wx.HORIZONTAL |wx.LEFT|wx.RIGHT|wx.EXPAND)380 sizer_constraint = wx.BoxSizer(wx.HORIZONTAL) 381 381 self.model_cbox_left = wx.ComboBox(self, -1, style=wx.CB_READONLY) 382 382 self.model_cbox_left.Clear()
Note: See TracChangeset
for help on using the changeset viewer.