Changeset 18b7ca96 in sasview for src/sas/sascalc
- Timestamp:
- Jan 13, 2017 10:52:21 AM (8 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, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
- Children:
- eb2dc13
- Parents:
- 1905128 (diff), 12361fd (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/sas/sascalc/dataloader/readers
- Files:
-
- 3 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/sascalc/dataloader/readers/cansas_reader.py
r0639476 r1905128 20 20 import inspect 21 21 # For saving individual sections of data 22 from sas.sascalc.dataloader.data_info import Data1D, DataInfo, plottable_1D 23 from sas.sascalc.dataloader.data_info import Collimation, TransmissionSpectrum, Detector, Process, Aperture 24 from sas.sascalc.dataloader.data_info import combine_data_info_with_plottable as combine_data 22 from sas.sascalc.dataloader.data_info import Data1D, Data2D, DataInfo, \ 23 plottable_1D, plottable_2D 24 from sas.sascalc.dataloader.data_info import Collimation, TransmissionSpectrum, \ 25 Detector, Process, Aperture 26 from sas.sascalc.dataloader.data_info import \ 27 combine_data_info_with_plottable as combine_data 25 28 import sas.sascalc.dataloader.readers.xml_reader as xml_reader 26 29 from sas.sascalc.dataloader.readers.xml_reader import XMLreader … … 56 59 The CanSAS reader requires PyXML 0.8.4 or later. 57 60 """ 58 # #CanSAS version - defaults to version 1.061 # CanSAS version - defaults to version 1.0 59 62 cansas_version = "1.0" 60 63 base_ns = "{cansas1d/1.0}" … … 63 66 invalid = True 64 67 frm = "" 65 # #Log messages and errors68 # Log messages and errors 66 69 logging = None 67 70 errors = set() 68 # #Namespace hierarchy for current xml_file object71 # Namespace hierarchy for current xml_file object 69 72 names = None 70 73 ns_list = None 71 # #Temporary storage location for loading multiple data sets in a single file74 # Temporary storage location for loading multiple data sets in a single file 72 75 current_datainfo = None 73 76 current_dataset = None 74 77 current_data1d = None 75 78 data = None 76 # #List of data1D objects to be sent back to SasView79 # List of data1D objects to be sent back to SasView 77 80 output = None 78 # #Wildcards81 # Wildcards 79 82 type = ["XML files (*.xml)|*.xml", "SasView Save Files (*.svs)|*.svs"] 80 # #List of allowed extensions83 # List of allowed extensions 81 84 ext = ['.xml', '.XML', '.svs', '.SVS'] 82 # #Flag to bypass extension check85 # Flag to bypass extension check 83 86 allow_all = True 84 87 … … 220 223 self.parent_class = tagname_original 221 224 if tagname == 'SASdata': 222 self._initialize_new_data_set() 223 ## Recursion step to access data within the group 225 self._initialize_new_data_set(node) 226 if isinstance(self.current_dataset, plottable_2D): 227 x_bins = attr.get("x_bins", "") 228 y_bins = attr.get("y_bins", "") 229 if x_bins is not "" and y_bins is not "": 230 self.current_dataset.shape = (x_bins, y_bins) 231 else: 232 self.current_dataset.shape = () 233 # Recursion step to access data within the group 224 234 self._parse_entry(node, True) 225 235 if tagname == "SASsample": … … 234 244 self.add_intermediate() 235 245 else: 236 data_point, unit = self._get_node_value(node, tagname) 237 238 ## If this is a dataset, store the data appropriately 246 if isinstance(self.current_dataset, plottable_2D): 247 data_point = node.text 248 unit = attr.get('unit', '') 249 else: 250 data_point, unit = self._get_node_value(node, tagname) 251 252 # If this is a dataset, store the data appropriately 239 253 if tagname == 'Run': 240 254 self.current_datainfo.run_name[data_point] = name … … 245 259 self.current_datainfo.notes.append(data_point) 246 260 247 # # I and Q Data248 elif tagname == 'I' :261 # I and Q - 1D data 262 elif tagname == 'I' and isinstance(self.current_dataset, plottable_1D): 249 263 self.current_dataset.yaxis("Intensity", unit) 250 264 self.current_dataset.y = np.append(self.current_dataset.y, data_point) 251 elif tagname == 'Idev' :265 elif tagname == 'Idev' and isinstance(self.current_dataset, plottable_1D): 252 266 self.current_dataset.dy = np.append(self.current_dataset.dy, data_point) 253 267 elif tagname == 'Q': … … 265 279 pass 266 280 267 ## Sample Information 281 # I and Qx, Qy - 2D data 282 elif tagname == 'I' and isinstance(self.current_dataset, plottable_2D): 283 self.current_dataset.yaxis("Intensity", unit) 284 self.current_dataset.data = np.fromstring(data_point, dtype=float, sep=",") 285 elif tagname == 'Idev' and isinstance(self.current_dataset, plottable_2D): 286 self.current_dataset.err_data = np.fromstring(data_point, dtype=float, sep=",") 287 elif tagname == 'Qx': 288 self.current_dataset.xaxis("Qx", unit) 289 self.current_dataset.qx_data = np.fromstring(data_point, dtype=float, sep=",") 290 elif tagname == 'Qy': 291 self.current_dataset.yaxis("Qy", unit) 292 self.current_dataset.qy_data = np.fromstring(data_point, dtype=float, sep=",") 293 elif tagname == 'Qxdev': 294 self.current_dataset.xaxis("Qxdev", unit) 295 self.current_dataset.dqx_data = np.fromstring(data_point, dtype=float, sep=",") 296 elif tagname == 'Qydev': 297 self.current_dataset.yaxis("Qydev", unit) 298 self.current_dataset.dqy_data = np.fromstring(data_point, dtype=float, sep=",") 299 elif tagname == 'Mask': 300 inter = data_point.split(",") 301 self.current_dataset.mask = np.asarray(inter, dtype=bool) 302 303 # Sample Information 268 304 elif tagname == 'ID' and self.parent_class == 'SASsample': 269 305 self.current_datainfo.sample.ID = data_point … … 299 335 self.current_datainfo.sample.orientation_unit = unit 300 336 301 # #Instrumental Information337 # Instrumental Information 302 338 elif tagname == 'name' and self.parent_class == 'SASinstrument': 303 339 self.current_datainfo.instrument = data_point 304 # #Detector Information340 # Detector Information 305 341 elif tagname == 'name' and self.parent_class == 'SASdetector': 306 342 self.detector.name = data_point … … 347 383 self.detector.orientation.z = data_point 348 384 self.detector.orientation_unit = unit 349 # #Collimation and Aperture385 # Collimation and Aperture 350 386 elif tagname == 'length' and self.parent_class == 'SAScollimation': 351 387 self.collimation.length = data_point … … 366 402 self.collimation.size_unit = unit 367 403 368 # #Process Information404 # Process Information 369 405 elif tagname == 'name' and self.parent_class == 'SASprocess': 370 406 self.process.name = data_point … … 386 422 self.process.term.append(dic) 387 423 388 # #Transmission Spectrum424 # Transmission Spectrum 389 425 elif tagname == 'T' and self.parent_class == 'Tdata': 390 426 self.transspectrum.transmission = np.append(self.transspectrum.transmission, data_point) … … 397 433 self.transspectrum.wavelength_unit = unit 398 434 399 # #Source Information435 # Source Information 400 436 elif tagname == 'wavelength' and (self.parent_class == 'SASsource' or self.parent_class == 'SASData'): 401 437 self.current_datainfo.source.wavelength = data_point … … 424 460 self.current_datainfo.source.beam_shape = data_point 425 461 426 # #Everything else goes in meta_data462 # Everything else goes in meta_data 427 463 else: 428 464 new_key = self._create_unique_key(self.current_datainfo.meta_data, tagname) … … 438 474 self.add_data_set() 439 475 empty = None 440 if self.output[0].dx is not None:441 self.output[0].dxl = np.empty(0)442 self.output[0].dxw = np.empty(0)443 else:444 self.output[0].dx = np.empty(0)445 476 return self.output[0], empty 446 477 … … 514 545 self.current_datainfo = DataInfo() 515 546 516 def _initialize_new_data_set(self, parent_list=None):547 def _initialize_new_data_set(self, node=None): 517 548 """ 518 549 A private class method to generate a new 1D data object. 519 550 Outside methods should call add_data_set() to be sure any existing data is stored properly. 520 551 521 :param parent_list: List of names of parent elements 522 """ 523 524 if parent_list is None: 525 parent_list = [] 552 :param node: XML node to determine if 1D or 2D data 553 """ 526 554 x = np.array(0) 527 555 y = np.array(0) 556 for child in node: 557 if child.tag.replace(self.base_ns, "") == "Idata": 558 for i_child in child: 559 if i_child.tag.replace(self.base_ns, "") == "Qx": 560 self.current_dataset = plottable_2D() 561 return 528 562 self.current_dataset = plottable_1D(x, y) 529 563 … … 560 594 """ 561 595 562 # #Append errors to dataset and reset class errors596 # Append errors to dataset and reset class errors 563 597 self.current_datainfo.errors = set() 564 598 for error in self.errors: … … 566 600 self.errors.clear() 567 601 568 # #Combine all plottables with datainfo and append each to output569 # #Type cast data arrays to float64 and find min/max as appropriate602 # Combine all plottables with datainfo and append each to output 603 # Type cast data arrays to float64 and find min/max as appropriate 570 604 for dataset in self.data: 571 if dataset.x is not None: 572 dataset.x = np.delete(dataset.x, [0]) 573 dataset.x = dataset.x.astype(np.float64) 574 dataset.xmin = np.min(dataset.x) 575 dataset.xmax = np.max(dataset.x) 576 if dataset.y is not None: 577 dataset.y = np.delete(dataset.y, [0]) 578 dataset.y = dataset.y.astype(np.float64) 579 dataset.ymin = np.min(dataset.y) 580 dataset.ymax = np.max(dataset.y) 581 if dataset.dx is not None: 582 dataset.dx = np.delete(dataset.dx, [0]) 583 dataset.dx = dataset.dx.astype(np.float64) 584 if dataset.dxl is not None: 585 dataset.dxl = np.delete(dataset.dxl, [0]) 586 dataset.dxl = dataset.dxl.astype(np.float64) 587 if dataset.dxw is not None: 588 dataset.dxw = np.delete(dataset.dxw, [0]) 589 dataset.dxw = dataset.dxw.astype(np.float64) 590 if dataset.dy is not None: 591 dataset.dy = np.delete(dataset.dy, [0]) 592 dataset.dy = dataset.dy.astype(np.float64) 593 np.trim_zeros(dataset.x) 594 np.trim_zeros(dataset.y) 595 np.trim_zeros(dataset.dy) 605 if isinstance(dataset, plottable_1D): 606 if dataset.x is not None: 607 dataset.x = np.delete(dataset.x, [0]) 608 dataset.x = dataset.x.astype(np.float64) 609 dataset.xmin = np.min(dataset.x) 610 dataset.xmax = np.max(dataset.x) 611 if dataset.y is not None: 612 dataset.y = np.delete(dataset.y, [0]) 613 dataset.y = dataset.y.astype(np.float64) 614 dataset.ymin = np.min(dataset.y) 615 dataset.ymax = np.max(dataset.y) 616 if dataset.dx is not None: 617 dataset.dx = np.delete(dataset.dx, [0]) 618 dataset.dx = dataset.dx.astype(np.float64) 619 if dataset.dxl is not None: 620 dataset.dxl = np.delete(dataset.dxl, [0]) 621 dataset.dxl = dataset.dxl.astype(np.float64) 622 if dataset.dxw is not None: 623 dataset.dxw = np.delete(dataset.dxw, [0]) 624 dataset.dxw = dataset.dxw.astype(np.float64) 625 if dataset.dy is not None: 626 dataset.dy = np.delete(dataset.dy, [0]) 627 dataset.dy = dataset.dy.astype(np.float64) 628 np.trim_zeros(dataset.x) 629 np.trim_zeros(dataset.y) 630 np.trim_zeros(dataset.dy) 631 elif isinstance(dataset, plottable_2D): 632 dataset.data = dataset.data.astype(np.float64) 633 dataset.qx_data = dataset.qx_data.astype(np.float64) 634 dataset.xmin = np.min(dataset.qx_data) 635 dataset.xmax = np.max(dataset.qx_data) 636 dataset.qy_data = dataset.qy_data.astype(np.float64) 637 dataset.ymin = np.min(dataset.qy_data) 638 dataset.ymax = np.max(dataset.qy_data) 639 dataset.q_data = np.sqrt(dataset.qx_data * dataset.qx_data 640 + dataset.qy_data * dataset.qy_data) 641 if dataset.err_data is not None: 642 dataset.err_data = dataset.err_data.astype(np.float64) 643 if dataset.dqx_data is not None: 644 dataset.dqx_data = dataset.dqx_data.astype(np.float64) 645 if dataset.dqy_data is not None: 646 dataset.dqy_data = dataset.dqy_data.astype(np.float64) 647 if dataset.mask is not None: 648 dataset.mask = dataset.mask.astype(dtype=bool) 649 650 if len(dataset.shape) == 2: 651 n_rows, n_cols = dataset.shape 652 dataset.y_bins = dataset.qy_data[0::int(n_cols)] 653 dataset.x_bins = dataset.qx_data[:int(n_cols)] 654 dataset.data = dataset.data.flatten() 655 else: 656 dataset.y_bins = [] 657 dataset.x_bins = [] 658 dataset.data = dataset.data.flatten() 659 596 660 final_dataset = combine_data(dataset, self.current_datainfo) 597 661 self.output.append(final_dataset) … … 693 757 and local_unit.lower() != "none": 694 758 if HAS_CONVERTER == True: 695 # #Check local units - bad units raise KeyError759 # Check local units - bad units raise KeyError 696 760 data_conv_q = Converter(local_unit) 697 761 value_unit = default_unit … … 740 804 A method to check all resolution data sets are the same size as I and Q 741 805 """ 742 dql_exists = False 743 dqw_exists = False 744 dq_exists = False 745 di_exists = False 746 if self.current_dataset.dxl is not None: 747 dql_exists = True 748 if self.current_dataset.dxw is not None: 749 dqw_exists = True 750 if self.current_dataset.dx is not None: 751 dq_exists = True 752 if self.current_dataset.dy is not None: 753 di_exists = True 754 if dqw_exists and not dql_exists: 755 array_size = self.current_dataset.dxw.size - 1 756 self.current_dataset.dxl = np.append(self.current_dataset.dxl, np.zeros([array_size])) 757 elif dql_exists and not dqw_exists: 758 array_size = self.current_dataset.dxl.size - 1 759 self.current_dataset.dxw = np.append(self.current_dataset.dxw, np.zeros([array_size])) 760 elif not dql_exists and not dqw_exists and not dq_exists: 761 array_size = self.current_dataset.x.size - 1 762 self.current_dataset.dx = np.append(self.current_dataset.dx, np.zeros([array_size])) 763 if not di_exists: 764 array_size = self.current_dataset.y.size - 1 765 self.current_dataset.dy = np.append(self.current_dataset.dy, np.zeros([array_size])) 766 806 if isinstance(self.current_dataset, plottable_1D): 807 dql_exists = False 808 dqw_exists = False 809 dq_exists = False 810 di_exists = False 811 if self.current_dataset.dxl is not None: 812 dql_exists = True 813 if self.current_dataset.dxw is not None: 814 dqw_exists = True 815 if self.current_dataset.dx is not None: 816 dq_exists = True 817 if self.current_dataset.dy is not None: 818 di_exists = True 819 if dqw_exists and not dql_exists: 820 array_size = self.current_dataset.dxw.size - 1 821 self.current_dataset.dxl = np.append(self.current_dataset.dxl, 822 np.zeros([array_size])) 823 elif dql_exists and not dqw_exists: 824 array_size = self.current_dataset.dxl.size - 1 825 self.current_dataset.dxw = np.append(self.current_dataset.dxw, 826 np.zeros([array_size])) 827 elif not dql_exists and not dqw_exists and not dq_exists: 828 array_size = self.current_dataset.x.size - 1 829 self.current_dataset.dx = np.append(self.current_dataset.dx, 830 np.zeros([array_size])) 831 if not di_exists: 832 array_size = self.current_dataset.y.size - 1 833 self.current_dataset.dy = np.append(self.current_dataset.dy, 834 np.zeros([array_size])) 835 elif isinstance(self.current_dataset, plottable_2D): 836 dqx_exists = False 837 dqy_exists = False 838 di_exists = False 839 mask_exists = False 840 if self.current_dataset.dqx_data is not None: 841 dqx_exists = True 842 if self.current_dataset.dqy_data is not None: 843 dqy_exists = True 844 if self.current_dataset.err_data is not None: 845 di_exists = True 846 if self.current_dataset.mask is not None: 847 mask_exists = True 848 if not dqy_exists: 849 array_size = self.current_dataset.qy_data.size - 1 850 self.current_dataset.dqy_data = np.append( 851 self.current_dataset.dqy_data, np.zeros([array_size])) 852 if not dqx_exists: 853 array_size = self.current_dataset.qx_data.size - 1 854 self.current_dataset.dqx_data = np.append( 855 self.current_dataset.dqx_data, np.zeros([array_size])) 856 if not di_exists: 857 array_size = self.current_dataset.data.size - 1 858 self.current_dataset.err_data = np.append( 859 self.current_dataset.err_data, np.zeros([array_size])) 860 if not mask_exists: 861 array_size = self.current_dataset.data.size - 1 862 self.current_dataset.mask = np.append( 863 self.current_dataset.mask, 864 np.ones([array_size] ,dtype=bool)) 767 865 768 866 ####### All methods below are for writing CanSAS XML files ####### 769 770 867 771 868 def write(self, filename, datainfo): … … 792 889 :param datainfo: Data1D object 793 890 """ 794 if not issubclass(datainfo.__class__, Data1D): 795 raise RuntimeError, "The cansas writer expects a Data1D instance" 891 is_2d = False 892 if issubclass(datainfo.__class__, Data2D): 893 is_2d = True 796 894 797 895 # Get PIs and create root element … … 813 911 self._write_run_names(datainfo, entry_node) 814 912 # Add Data info to SASEntry 815 self._write_data(datainfo, entry_node) 913 if is_2d: 914 self._write_data_2d(datainfo, entry_node) 915 else: 916 self._write_data(datainfo, entry_node) 816 917 # Transmission Spectrum Info 817 918 self._write_trans_spectrum(datainfo, entry_node) … … 907 1008 def _write_data(self, datainfo, entry_node): 908 1009 """ 909 Writes theI and Q data to the XML file1010 Writes 1D I and Q data to the XML file 910 1011 911 1012 :param datainfo: The Data1D object the information is coming from … … 935 1036 self.write_node(point, "dQl", datainfo.dxl[i], 936 1037 {'unit': datainfo.x_unit}) 1038 1039 def _write_data_2d(self, datainfo, entry_node): 1040 """ 1041 Writes 2D data to the XML file 1042 1043 :param datainfo: The Data2D object the information is coming from 1044 :param entry_node: lxml node ElementTree object to be appended to 1045 """ 1046 attr = {} 1047 if datainfo.data.shape: 1048 attr["x_bins"] = str(len(datainfo.x_bins)) 1049 attr["y_bins"] = str(len(datainfo.y_bins)) 1050 node = self.create_element("SASdata", attr) 1051 self.append(node, entry_node) 1052 1053 point = self.create_element("Idata") 1054 node.append(point) 1055 qx = ','.join([str(datainfo.qx_data[i]) for i in xrange(len(datainfo.qx_data))]) 1056 qy = ','.join([str(datainfo.qy_data[i]) for i in xrange(len(datainfo.qy_data))]) 1057 intensity = ','.join([str(datainfo.data[i]) for i in xrange(len(datainfo.data))]) 1058 err = ','.join([str(datainfo.err_data[i]) for i in xrange(len(datainfo.err_data))]) 1059 dqy = ','.join([str(datainfo.dqy_data[i]) for i in xrange(len(datainfo.dqy_data))]) 1060 dqx = ','.join([str(datainfo.dqx_data[i]) for i in xrange(len(datainfo.dqx_data))]) 1061 mask = ','.join([str(datainfo.mask[i]) for i in xrange(len(datainfo.mask))]) 1062 1063 self.write_node(point, "Qx", qx, 1064 {'unit': datainfo._xunit}) 1065 self.write_node(point, "Qy", qy, 1066 {'unit': datainfo._yunit}) 1067 self.write_node(point, "I", intensity, 1068 {'unit': datainfo._zunit}) 1069 if datainfo.err_data is not None: 1070 self.write_node(point, "Idev", err, 1071 {'unit': datainfo._zunit}) 1072 if datainfo.dqy_data is not None: 1073 self.write_node(point, "Qydev", dqy, 1074 {'unit': datainfo._yunit}) 1075 if datainfo.dqx_data is not None: 1076 self.write_node(point, "Qxdev", dqx, 1077 {'unit': datainfo._xunit}) 1078 if datainfo.mask is not None: 1079 self.write_node(point, "Mask", mask) 937 1080 938 1081 def _write_trans_spectrum(self, datainfo, entry_node): -
src/sas/sascalc/dataloader/readers/schema/cansas1d_invalid_v1_0.xsd
r250fec92 raf08e55 24 24 25 25 <complexType name="IdataType"> 26 <xsd:choice> 26 27 <sequence> 27 28 <element name="Q" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> … … 40 41 <xsd:any minOccurs="0" maxOccurs="unbounded" processContents="lax" namespace="##other" /> 41 42 </sequence> 43 <sequence> 44 <element name="Qx" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> 45 <element name="Qy" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> 46 <element name="I" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> 47 <element name="Idev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" /> 48 <element name="Qydev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" /> 49 <element name="Qxdev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" /> 50 <element name="Mask" minOccurs="0" maxOccurs="1" type="string" default="0" /> 51 </sequence> 52 </xsd:choice> 42 53 </complexType> 43 54 … … 51 62 <attribute name="name" type="string" use="optional" default="" /> 52 63 <attribute name="timestamp" type="dateTime" use="optional" /> 64 <attribute name="x_bins" type="string" use="optional" /> 65 <attribute name="y_bins" type="string" use="optional" /> 53 66 </complexType> 54 67 -
src/sas/sascalc/dataloader/readers/schema/cansas1d_invalid_v1_1.xsd
r250fec92 raf08e55 24 24 25 25 <complexType name="IdataType"> 26 <xsd:choice> 26 27 <sequence> 27 28 <element name="Q" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> … … 40 41 <xsd:any minOccurs="0" maxOccurs="unbounded" processContents="lax" namespace="##other" /> 41 42 </sequence> 43 <sequence> 44 <element name="Qx" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> 45 <element name="Qy" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> 46 <element name="I" minOccurs="1" maxOccurs="1" type="tns:floatUnitType" /> 47 <element name="Idev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" /> 48 <element name="Qydev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" /> 49 <element name="Qxdev" minOccurs="0" maxOccurs="1" type="tns:floatUnitType" default="0" /> 50 <element name="Mask" minOccurs="0" maxOccurs="1" type="string" default="0" /> 51 </sequence> 52 </xsd:choice> 42 53 </complexType> 43 54 … … 51 62 <attribute name="name" type="string" use="optional" default="" /> 52 63 <attribute name="timestamp" type="dateTime" use="optional" /> 64 <attribute name="x_bins" type="string" use="optional" /> 65 <attribute name="y_bins" type="string" use="optional" /> 53 66 </complexType> 54 67
Note: See TracChangeset
for help on using the changeset viewer.