# coding: utf-8 """ Module iodata ============= This module contains all classes useful to manipulate, store and display data results. The :py:class:`Data` and :py:class:`DataSet` are the two enduser classes important to manipulate the data. Here is an example of how to store values in a Data object: .. code-block:: python from msspec.iodata import Data import numpy as np # Let's create first some dumy data X = np.arange(0, 20) Y = X**2 # Create a Data object. You need to give a title as an argument data = Data('all my data') # and append a new DataSet with its title dset = data.add_dset('Dataset 0') # To feed the DataSet with columns, use the add_columns method # and provide as many keywords as you like. Each key being the # column name and each value being an array holding the column # data. dset.add_columns(x=X, y=Y, z=X+2, w=Y**3) # you can provide parameters with their values with keywords as well dset.add_parameter(name='truc', group='main', value='3.14', unit='eV') # To plot these data, you need to add a 'view' with its title view = dset.add_view('my view') # You then need to select which columns you which to plot and # and under wich conditions (with the 'which' keyword) view.select('x', 'y', where="z<10", legend=r"z = 0") view.select('x', 'y', where="z>10", legend=r"z = 1") # To pop up the graphical window data.view() """ import os import numpy as np import h5py from lxml import etree import msspec from msspec.misc import LOGGER import ase.io from io import StringIO import wx import wx.grid from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg from matplotlib.figure import Figure from terminaltables import AsciiTable from distutils.version import StrictVersion, LooseVersion import sys #sys.path.append('../../MsSpecGui/msspecgui/msspec/gui') from .msspecgui.msspec.gui.clusterviewer import ClusterViewer def cols2matrix(x, y, z, nx=88*1+1, ny=360*1+1): # mix the values of existing theta and new theta and return the # unique values newx = np.linspace(np.min(x), np.max(x), nx) newy = np.linspace(np.min(y), np.max(y), ny) ux = np.unique(np.append(x, newx)) uy = np.unique(np.append(y, newy)) # create an empty matrix to hold the results zz = np.empty((len(ux), len(uy))) zz[:] = np.nan for p in zip(x, y, z): i = np.argwhere(ux == p[0]) j = np.argwhere(uy == p[1]) zz[i, j] = p[2] for i in range(len(ux)): #ok, = np.where(-np.isnan(zz[i,:])) ok, = np.where(~np.isnan(zz[i, :])) if len(ok) > 0: xp = uy[ok] fp = zz[i, ok] zz[i,:] = np.interp(uy, xp, fp) for i in range(len(uy)): #ok, = np.where(-np.isnan(zz[:,i])) ok, = np.where(~np.isnan(zz[:, i])) if len(ok) > 0: xp = ux[ok] fp = zz[ok, i] zz[:,i] = np.interp(ux, xp, fp) return ux, uy, zz class _DataPoint(dict): def __init__(self, *args, **kwargs): dict.__init__(self, *args, **kwargs) def __getattr__(self, name): if name in list(self.keys()): return self[name] else: raise AttributeError("'{}' object has no attribute '{}'".format( self.__class__.__name__, name)) class DataSet(object): """ This class can create an object to hold column-oriented data. :param title: The text used to entitled the dataset :type title: str :param notes: Some comments to add to the data :type notes: str """ def __init__(self, title, notes=""): self.title = title self.notes = notes self._views = [] self._parameters = [] self.attributes = {} self._col_names = [] self._col_arrays = [] self._defaults = {'bool': False, 'str': '', 'int': 0, 'float': 0., 'complex': complex(0)} self._formats = {bool: '{:s}', str: '{:s}', int: '{:<20d}', float: '{:<20.10e}', complex: 's'} def _empty_array(self, val): if isinstance(val, str): t = 'S256' else: t = np.dtype(type(val)) if isinstance(val, bool): default = self._defaults['bool'] elif isinstance(val, str): default = self._defaults['str'] elif isinstance(val, int): default = self._defaults['int'] elif isinstance(val, float): default = self._defaults['float'] elif isinstance(val, complex): default = self._defaults['complex'] else: raise TypeError('Not a supported type') return np.array([default]*len(self), dtype=t) def add_row(self, **kwargs): """Add a row of data into the dataset. :param kwargs: Each keyword is a column name. The number of keywords (columns) must be coherent with the number of existing columns. If no column are defined yet, they will be created. """ for k, v in list(kwargs.items()): if k not in self._col_names: self._col_names.append(k) self._col_arrays.append(self._empty_array(v)) for k, v in list(kwargs.items()): i = self._col_names.index(k) arr = self._col_arrays[i] arr = np.append(arr, v) self._col_arrays[i] = arr def add_columns(self, **kwargs): """ Add columns to the dataset. You can provide as many columns as you want to this function. This function can be called several times on the same dataset but each time with different column names. Column names are given as keywords. :Example: >>> from iodata import DataSet >>> dset = DataSet('My Dataset', notes="Just an example") >>> xdata = range(10) >>> ydata = [i**2 for i in xdata] >>> dset.add_columns(x=xdata, y=ydata) >>> print dset >>> +-------+ >>> | x y | >>> +-------+ >>> | 0 0 | >>> | 1 1 | >>> | 2 4 | >>> | 3 9 | >>> | 4 16 | >>> | 5 25 | >>> | 6 36 | >>> | 7 49 | >>> | 8 64 | >>> | 9 81 | >>> +-------+ """ for k, vv in list(kwargs.items()): assert k not in self._col_names, ("'{}' column already exists" "".format(k)) #if len(self) > 0: # assert len(vv) == len(self), ( # 'Too many values in the column (max = {})'.format( # len(self))) for k, vv in list(kwargs.items()): arr = np.array(vv) self._col_names.append(k) self._col_arrays.append(arr) def delete_rows(self, itemspec): """ Delete the rows specified with itemspec. """ for i in range(len(self._col_names)): self._col_arrays[i] = np.delete(self._col_arrays[i], itemspec) def delete_columns(self, *tags): """ Removes all columns name passed as arguments :param tags: column names. :type tags: str """ for tag in tags: i = self._col_names.index(tag) self._col_names.pop(i) self._col_arrays.pop(i) def columns(self): """ Get all the column names. :return: List of column names. :rtype: List of str """ return self._col_names def add_view(self, name, **plotopts): """ Creates a new view named *name* with specied plot options. :param name: name of the view. :type name: str :param plotopts: list of keywords for configuring the plots. :return: a view. :rtype: :py:class:`iodata._DataSetView` """ if isinstance(name, str): v = _DataSetView(self, name, **plotopts) else: v = name v.dataset = self self._views.append(v) return v def views(self): """Returns all the defined views in the dataset. :return: A list of view :rtype: List of :py:class:`iodata._DataSetView` """ return self._views def add_parameter(self, **kwargs): """Add a parameter to store with the dataset. :param kwargs: list of keywords with str values. These keywords are: * name: the name of the parameter. * group: the name of a group it belongs to. * value: the value of the parameter. * unit: the unit of the parameter. For example: .. code-block:: python from iodata import DataSet mydset = DataSet("Experiment") mydset.add_parameter(name='Spectrometer', group='misc', value='Omicron', unit='') """ self._parameters.append(kwargs) def parameters(self): """ Returns the list of defined parameters. :return: all parameters defined in the :py:class:`iodata.DataSet` object. :rtype: List of dict """ return self._parameters def get_parameter(self, group=None, name=None): """Retrieves all parameters for a given name and group. * If *name* is given and *group* is None, returns all parameters with such a *name* in all groups. * If *group* is given and *name* is None, returns all parameters in such a *group* * If both *name* and *group* are None. Returns all parameters (equivalent to :py:func:`iodata.DataSet.parameters`). :param group: The group name or None. :type group: str :param name: The parameter's name or None. :type name: str :return: A list of parameters. :rtype: List of dict """ p = [] for _ in self._parameters: if _['group'] == group or group == None: if _['name'] == name or name == None: p.append(_) return p[0] if len(p) == 1 else p def get_cluster(self): """Get all the atoms in the cluster. :return: The cluster :rtype: :py:class:`ase.Atoms` """ s = StringIO() s.write(self.get_parameter(group='Cluster', name='cluster')['value']) return ase.io.read(s, format='xyz') def select(self, *args, **kwargs): condition = kwargs.get('where', 'True') indices = [] def export(self, filename="", mode="w"): """Export the DataSet to the given *filename*. :param filename: The name of the file. :type filename: str .. warning:: Not yet implemented """ colnames = self.columns() with open(filename, mode) as fd: fd.write("# " + ("{:<20s}" * len(colnames)).format(*colnames ) + "\n") for i in range(len(self)): row = self[i] for key in row.columns(): value = row[key][0] fmt = '{:s}' #print value for t, f in list(self._formats.items()): if isinstance(value, t): fmt = f break #fd.write(' ') fd.write(fmt.format(value)) #fd.write(str(value) + ', ') fd.write('\n') def __getitem__(self, itemspec): if isinstance(itemspec, str): return getattr(self, itemspec) title = 'untitled' new = DataSet(title) new._col_names = self.columns() for arr in self._col_arrays: new._col_arrays.append(np.array(arr[itemspec]).flatten()) return new def __setstate__(self, state): self.__dict__ = state def __getstate__(self): return self.__dict__ def __getattr__(self, name): if name in self._col_names: i = self._col_names.index(name) return self._col_arrays[i] else: raise AttributeError("'{}' object has no attribute '{}'".format( self.__class__.__name__, name)) def __iter__(self): for i in range(len(self)): _ = {k: arr[i] for k, arr in zip(self._col_names, self._col_arrays)} point = _DataPoint(_) yield point def __len__(self): try: length = len(self._col_arrays[0]) except IndexError: length = 0 return length def __str__(self): max_len = 10 max_col = 10 ncols = min(max_col, len(self._col_arrays)) table_data = [self._col_names[:ncols]] table_data[0].insert(0, "") all_indices = np.arange(0, len(self)) indices = all_indices if len(self) > max_len: indices = list(range(max_len/2)) + list(range(-max_len/2, 0)) _i = 0 for i in indices: if i < _i: row = ['...' for _ in range(ncols + 1)] table_data.append(row) row = [str(all_indices[i]),] for j in range(ncols): arr = self._col_arrays[j] row.append(str(arr[i])) if len(self._col_names) > max_col: row.append('...') table_data.append(row) _i = i table = AsciiTable(table_data) table.outer_border = True table.title = self.title table.inner_column_border = False return table.table def __repr__(self): s = "<{}('{}')>".format(self.__class__.__name__, self.title) return s class Data(object): """Creates a new Data object to store DataSets. :param title: The title of the Data object. :type str: """ def __init__(self, title=''): self.title = title self._datasets = [] self._dirty = False def add_dset(self, title): """Adds a new DataSet in the Data object. :param title: The name of the DataSet. :type title: str :return: The newly created DataSet. :rtype: :py:class:`iodata.DataSet` """ titles = [d.title for d in self._datasets] if not title in titles: dset = DataSet(title) self._datasets.append(dset) self._dirty = True return dset else: raise NameError('A Dataset with that name already exists!') def delete_dset(self, title): """Removes a DataSet from the Data object. :param title: The DataSet name to be removed. :type title: str """ titles = [d.title for d in self._datasets] i = titles.index(title) self._datasets.pop(i) self._dirty = True def get_last_dset(self): """Get the last DataSet of the Data object. :return: The lastly created DataSet in the Data object :rtype: :py:class:`iodata.DataSet` """ return self._datasets[-1] def is_dirty(self): """Wether the Data object needs to be saved. :return: A boolean value to indicate if Data has changed since last dump to hard drive. :rtype: bool """ return self._dirty def save(self, filename, append=False): """Saves the current Data to the hard drive. The Data, all its content along with parameters, defined views... are saved to the hard drive in the HDF5 file format. Please see `hdfgroup `_ for more details about HDF5. :param filename: The name of the file to create or to append to. :type filename: str :param append: Wether to create a neww file or to append to an existing one. :type append: bool """ mode = 'a' if append else 'w' titles = [d.title for d in self._datasets] with h5py.File(filename, mode) as fd: if append: try: data_grp = fd['DATA'] meta_grp = fd['MsSpec viewer metainfo'] except Exception as err: fd.close() self.save(filename, append=False) return else: data_grp = fd.create_group('DATA') meta_grp = fd.create_group('MsSpec viewer metainfo') data_grp.attrs['title'] = self.title for dset in self._datasets: if dset.title in data_grp: LOGGER.warning('dataset \"{}\" already exists in file \"{}\", not overwritting'.format( dset.title, os.path.abspath(filename))) continue grp = data_grp.create_group(dset.title) grp.attrs['notes'] = dset.notes for col_name in dset.columns(): data = dset[col_name] grp.create_dataset(col_name, data=data) meta_grp.attrs['version'] = msspec.__version__ root = etree.Element('metainfo') # xmlize views for dset in self._datasets: views_node = etree.SubElement(root, 'views', dataset=dset.title) for view in dset.views(): view_el = etree.fromstring(view.to_xml()) views_node.append(view_el) # xmlize parameters for dset in self._datasets: param_node = etree.SubElement(root, 'parameters', dataset=dset.title) for p in dset.parameters(): child = etree.SubElement(param_node, 'parameter') for k, v in list(p.items()): child.attrib[k] = v xml_str = etree.tostring(root, pretty_print=False) try: del meta_grp['info'] except: meta_grp.create_dataset('info', data=np.array((xml_str,)).view('S1')) self._dirty = False LOGGER.info('Data saved in {}'.format(os.path.abspath(filename))) @staticmethod def load(filename): """Loads an HDF5 file from the disc. :param filename: The path to the file to laod. :type filename: str :return: A Data object. :rtype: :py:class:`iodata.Data` """ output = Data() with h5py.File(filename, 'r') as fd: parameters = {} views = {} output.title = fd['DATA'].attrs['title'] for dset_name in fd['DATA'] : parameters[dset_name] = [] views[dset_name] = [] dset = output.add_dset(dset_name) dset.notes = fd['DATA'][dset_name].attrs['notes'] for h5dset in fd['DATA'][dset_name]: dset.add_columns(**{h5dset: fd['DATA'][dset_name][h5dset].value}) try: vfile = LooseVersion(fd['MsSpec viewer metainfo'].attrs['version']) if vfile > LooseVersion(msspec.__version__): raise NameError('File was saved with a more recent format') xml = fd['MsSpec viewer metainfo']['info'].value.tostring() root = etree.fromstring(xml) for elt0 in root.iter('parameters'): dset_name = elt0.attrib['dataset'] for elt1 in elt0.iter('parameter'): parameters[dset_name].append(elt1.attrib) for elt0 in root.iter('views'): dset_name = elt0.attrib['dataset'] for elt1 in elt0.iter('view'): view = _DataSetView(None, "") view.from_xml(etree.tostring(elt1)) views[dset_name].append(view) except Exception as err: print(err) for dset in output: for v in views[dset.title]: dset.add_view(v) for p in parameters[dset.title]: dset.add_parameter(**p) output._dirty = False return output def __iter__(self): for dset in self._datasets: yield dset def __getitem__(self, key): try: titles = [d.title for d in self._datasets] i = titles.index(key) except ValueError: i = key return self._datasets[i] def __len__(self): return len(self._datasets) def __str__(self): s = str([dset.title for dset in self._datasets]) return s def __repr__(self): s = "".format(self.title) return s def view(self): """Pops up a grphical window to show all the defined views of the Data object. """ app = wx.App(False) app.SetAppName('MsSpec Data Viewer') frame = _DataWindow(self) frame.Show(True) app.MainLoop() class _DataSetView(object): def __init__(self, dset, name, **plotopts): self.dataset = dset self.title = name self._plotopts = dict( title='No title', xlabel='', ylabel='', grid=True, legend=[], colorbar=False, projection='rectilinear', xlim=[None, None], ylim=[None, None], scale='linear', marker=None, autoscale=False) self._plotopts.update(plotopts) self._selection_tags = [] self._selection_conditions = [] def set_plot_options(self, **kwargs): self._plotopts.update(kwargs) def select(self, *args, **kwargs): condition = kwargs.get('where', 'True') legend = kwargs.get('legend', '') self._selection_conditions.append(condition) self._selection_tags.append(args) self._plotopts['legend'].append(legend) def tags(self): return self._selection_tags def get_data(self): data = [] for condition, tags in zip(self._selection_conditions, self._selection_tags): indices = [] # replace all occurence of tags for tag in self.dataset.columns(): condition = condition.replace(tag, "p['{}']".format(tag)) for i, p in enumerate(self.dataset): if eval(condition): indices.append(i) values = [] for tag in tags: values.append(getattr(self.dataset[indices], tag)) data.append(values) return data def serialize(self): data = { 'name': self.title, 'selection_conditions': self._selection_conditions, 'selection_tags': self._selection_tags, 'plotopts': self._plotopts } root = etree.Element('root') return data def to_xml(self): plotopts = self._plotopts.copy() legends = plotopts.pop('legend') root = etree.Element('view', name=self.title) for key, value in list(plotopts.items()): root.attrib[key] = str(value) #root.attrib['dataset_name'] = self.dataset.title for tags, cond, legend in zip(self._selection_tags, self._selection_conditions, legends): curve = etree.SubElement(root, 'curve') curve.attrib['legend'] = legend curve.attrib['condition'] = cond axes = etree.SubElement(curve, 'axes') for tag in tags: variable = etree.SubElement(axes, 'axis', name=tag) return etree.tostring(root, pretty_print=False) def from_xml(self, xmlstr): root = etree.fromstring(xmlstr) self.title = root.attrib['name'] #self._plotopts['title'] = root.attrib['title'] #self._plotopts['xlabel'] = root.attrib['xlabel'] # self._plotopts['ylabel'] = root.attrib['ylabel'] # self._plotopts['grid'] = bool(root.attrib['grid']) # self._plotopts['colorbar'] = bool(root.attrib['colorbar']) # self._plotopts['projection'] = root.attrib['projection'] # self._plotopts['marker'] = root.attrib['marker'] for key in list(self._plotopts.keys()): try: self._plotopts[key] = eval(root.attrib.get(key)) except: self._plotopts[key] = root.attrib.get(key) legends = [] conditions = [] tags = [] for curve in root.iter("curve"): legends.append(curve.attrib['legend']) conditions.append(curve.attrib['condition']) variables = [] for var in curve.iter('axis'): variables.append(var.attrib['name']) tags.append(tuple(variables)) self._selection_conditions = conditions self._selection_tags = tags self._plotopts['legend'] = legends def __repr__(self): s = "<{}('{}')>".format(self.__class__.__name__, self.title) return s def __str__(self): try: dset_title = self.dataset.title except AttributeError: dset_title = "unknown" s = '{}:\n'.format(self.__class__.__name__) s += '\tname : %s\n' % self.title s += '\tdataset : %s\n' % dset_title s += '\ttags : %s\n' % str(self._selection_tags) s += '\tconditions : %s\n' % str(self._selection_conditions) return s class _GridWindow(wx.Frame): def __init__(self, dset, parent=None): title = 'Data: ' + dset.title wx.Frame.__init__(self, parent, title=title, size=(640, 480)) self.create_grid(dset) def create_grid(self, dset): grid = wx.grid.Grid(self, -1) grid.CreateGrid(len(dset), len(dset.columns())) for ic, c in enumerate(dset.columns()): grid.SetColLabelValue(ic, c) for iv, v in enumerate(dset[c]): grid.SetCellValue(iv, ic, str(v)) class _ParametersWindow(wx.Frame): def __init__(self, dset, parent=None): title = 'Parameters: ' + dset.title wx.Frame.__init__(self, parent, title=title, size=(400, 480)) self.create_tree(dset) def create_tree(self, dset): datatree = {} for p in dset.parameters(): is_hidden = p.get('hidden', "False") if is_hidden == "True": continue group = datatree.get(p['group'], []) #strval = str(p['value'] * p['unit'] if p['unit'] else p['value']) #group.append("{:s} = {:s}".format(p['name'], strval)) group.append("{} = {} {}".format(p['name'], p['value'], p['unit'])) datatree[p['group']] = group tree = wx.TreeCtrl(self, -1) root = tree.AddRoot('Parameters') for key in list(datatree.keys()): item0 = tree.AppendItem(root, key) for item in datatree[key]: tree.AppendItem(item0, item) tree.ExpandAll() tree.SelectItem(root) class _DataWindow(wx.Frame): def __init__(self, data): assert isinstance(data, (Data, DataSet)) if isinstance(data, DataSet): dset = data data = Data() data.first = dset self.data = data self._filename = None self._current_dset = None wx.Frame.__init__(self, None, title="", size=(640, 480)) self.Bind(wx.EVT_CLOSE, self.on_close) # Populate the menu bar self.create_menu() # Create the status bar statusbar = wx.StatusBar(self, -1) statusbar.SetFieldsCount(3) statusbar.SetStatusWidths([-2, -1, -1]) self.SetStatusBar(statusbar) # Add the notebook to hold all graphs self.notebooks = {} sizer = wx.BoxSizer(wx.VERTICAL) #sizer.Add(self.notebook) self.SetSizer(sizer) self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed) self.create_notebooks() self.update_title() def create_notebooks(self): for key in list(self.notebooks.keys()): nb = self.notebooks.pop(key) nb.Destroy() for dset in self.data: nb = wx.Notebook(self, -1) self.notebooks[dset.title] = nb self.GetSizer().Add(nb, 1, wx.ALL|wx.EXPAND) for view in dset.views(): self.create_page(nb, view) self.create_menu() self.show_dataset(self.data[0].title) def create_menu(self): menubar = wx.MenuBar() menu1 = wx.Menu() menu1.Append(110, "Open\tCtrl+O") menu1.Append(120, "Save\tCtrl+S") menu1.Append(130, "Save as...") menu1.Append(140, "Export\tCtrl+E") menu1.AppendSeparator() menu1.Append(199, "Close\tCtrl+Q") menu2 = wx.Menu() for i, dset in enumerate(self.data): menu_id = 201 + i menu2.AppendRadioItem(menu_id, dset.title) self.Bind(wx.EVT_MENU, self.on_menu_dataset, id=menu_id) self.Bind(wx.EVT_MENU, self.on_open, id=110) self.Bind(wx.EVT_MENU, self.on_save, id=120) self.Bind(wx.EVT_MENU, self.on_saveas, id=130) self.Bind(wx.EVT_MENU, self.on_close, id=199) menu3 = wx.Menu() menu3.Append(301, "Data") menu3.Append(302, "Cluster") menu3.Append(303, "Parameters") self.Bind(wx.EVT_MENU, self.on_viewdata, id=301) self.Bind(wx.EVT_MENU, self.on_viewcluster, id=302) self.Bind(wx.EVT_MENU, self.on_viewparameters, id=303) menubar.Append(menu1, "&File") menubar.Append(menu2, "&Datasets") menubar.Append(menu3, "&View") self.SetMenuBar(menubar) def on_open(self, event): if self.data.is_dirty(): mbx = wx.MessageDialog(self, ('Displayed data is unsaved. Do ' 'you wish to save before opening' 'another file ?'), 'Warning: Unsaved data', wx.YES_NO | wx.ICON_WARNING) if mbx.ShowModal() == wx.ID_YES: self.on_saveas(wx.Event()) mbx.Destroy() wildcard = "HDF5 files (*.hdf5)|*.hdf5" dlg = wx.FileDialog( self, message="Open a file...", defaultDir=os.getcwd(), defaultFile="", wildcard=wildcard, style=wx.OPEN ) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self._filename = path self.data = Data.load(path) self.create_notebooks() dlg.Destroy() self.update_title() def on_save(self, event): if self._filename: if self.data.is_dirty(): self.data.save(self._filename) else: self.on_saveas(event) def on_saveas(self, event): overwrite = True wildcard = "HDF5 files (*.hdf5)|*.hdf5|All files (*.*)|*.*" dlg = wx.FileDialog( self, message="Save file as ...", defaultDir=os.getcwd(), defaultFile='{}.hdf5'.format(self.data.title.replace(' ','_')), wildcard=wildcard, style=wx.SAVE) dlg.SetFilterIndex(0) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() if os.path.exists(path): mbx = wx.MessageDialog(self, ('This file already exists. ' 'Do you wish to overwrite it ?'), 'Warning: File exists', wx.YES_NO | wx.ICON_WARNING) if mbx.ShowModal() == wx.ID_NO: overwrite = False mbx.Destroy() if overwrite: self.data.save(path) self._filename = path dlg.Destroy() self.update_title() def on_viewdata(self, event): dset = self.data[self._current_dset] frame = _GridWindow(dset, parent=self) frame.Show() def on_viewcluster(self, event): win = wx.Frame(None, size=wx.Size(480, 340)) cluster_viewer = ClusterViewer(win, size=wx.Size(480, 340)) dset = self.data[self._current_dset] s = StringIO() s.write(dset.get_parameter(group='Cluster', name='cluster')['value']) atoms = ase.io.read(s, format='xyz') cluster_viewer.set_atoms(atoms, rescale=True, center=True) cluster_viewer.rotate_atoms(45., 45.) cluster_viewer.show_emitter(True) win.Show() def on_viewparameters(self, event): dset = self.data[self._current_dset] frame = _ParametersWindow(dset, parent=self) frame.Show() def on_close(self, event): if self.data.is_dirty(): mbx = wx.MessageDialog(self, ('Displayed data is unsaved. Do you ' 'really want to quit ?'), 'Warning: Unsaved data', wx.YES_NO | wx.ICON_WARNING) if mbx.ShowModal() == wx.ID_NO: mbx.Destroy() return self.Destroy() def on_menu_dataset(self, event): menu_id = event.GetId() dset_name = self.GetMenuBar().FindItemById(menu_id).GetText() self.show_dataset(dset_name) def show_dataset(self, name): for nb in list(self.notebooks.values()): nb.Hide() self.notebooks[name].Show() self.Layout() self.update_statusbar() self._current_dset = name def create_page(self, nb, view): opts = view._plotopts p = wx.Panel(nb, -1) figure = Figure() axes = None proj = opts['projection'] scale = opts['scale'] if proj == 'rectilinear': axes = figure.add_subplot(111, projection='rectilinear') elif proj in ('polar', 'ortho', 'stereo'): axes = figure.add_subplot(111, projection='polar') canvas = FigureCanvas(p, -1, figure) sizer = wx.BoxSizer(wx.VERTICAL) toolbar = NavigationToolbar2WxAgg(canvas) toolbar.Realize() sizer.Add(toolbar, 0, wx.ALL|wx.EXPAND) toolbar.update() sizer.Add(canvas, 5, wx.ALL|wx.EXPAND) p.SetSizer(sizer) p.Fit() p.Show() for values, label in zip(view.get_data(), opts['legend']): if proj in ('ortho', 'stereo'): theta, phi, Xsec = cols2matrix(*values) theta_ticks = np.arange(0, 91, 15) if proj == 'ortho': R = np.sin(np.radians(theta)) R_ticks = np.sin(np.radians(theta_ticks)) elif proj == 'stereo': R = 2 * np.tan(np.radians(theta/2.)) R_ticks = 2 * np.tan(np.radians(theta_ticks/2.)) #R = np.tan(np.radians(theta/2.)) X, Y = np.meshgrid(np.radians(phi), R) im = axes.pcolormesh(X, Y, Xsec) axes.set_yticks(R_ticks) axes.set_yticklabels(theta_ticks) figure.colorbar(im) elif proj == 'polar': values[0] = np.radians(values[0]) axes.plot(*values, label=label, picker=5, marker=opts['marker']) else: if scale == 'semilogx': pltcmd = axes.semilogx elif scale == 'semilogy': pltcmd = axes.semilogy elif scale == 'log': pltcmd = axes.loglog else: pltcmd = axes.plot pltcmd(*values, label=label, picker=5, marker=opts['marker']) axes.grid(opts['grid']) axes.set_title(opts['title']) axes.set_xlabel(opts['xlabel']) axes.set_ylabel(opts['ylabel']) axes.set_xlim(*opts['xlim']) axes.set_ylim(*opts['ylim']) if label: axes.legend() axes.autoscale(enable=opts['autoscale']) # MPL events figure.canvas.mpl_connect('motion_notify_event', self.on_mpl_motion) figure.canvas.mpl_connect('pick_event', self.on_mpl_pick) nb.AddPage(p, view.title) def update_statusbar(self): sb = self.GetStatusBar() menu_id = self.GetMenuBar().FindMenu('Datasets') menu = self.GetMenuBar().GetMenu(menu_id) for item in menu.GetMenuItems(): if item.IsChecked(): sb.SetStatusText("%s" % item.GetText(), 1) break def update_title(self): title = "MsSpec Data Viewer" if self.data.title: title += ": " + self.data.title if self._filename: title += " [" + os.path.basename(self._filename) + "]" self.SetTitle(title) def on_mpl_motion(self, event): sb = self.GetStatusBar() try: txt = "[{:.3f}, {:.3f}]".format(event.xdata, event.ydata) sb.SetStatusText(txt, 2) except Exception: pass def on_mpl_pick(self, event): print(event.artist) def on_page_changed(self, event): self.update_statusbar() if __name__ == "__main__": if False: data = Data('all my data') dset = data.add_dset('Dataset 0') X = np.arange(0, 20) Y = X**2 dset.add_columns(x=X, y=Y, z=X+2, w=Y**3) dset.add_parameter(name='truc', group='main', value='3.14', unit='eV') dset.add_parameter(name='machin', group='main', value='abc', unit='') # Z = [0,1] # # for z in Z: # for x, y in zip(X, Y): # dset.add_row(x=x, y=y, z=z, random=np.random.rand()) # # view = dset.add_view('my view', autoscale=True) view.select('x', 'y', where="z<10", legend=r"z = 0") view.select('x', 'y', where="z>10", legend=r"z = 1") print(dset.get_parameter(group='main')) constraint = lambda a, b: (a > 10 and a < 15) and b > 0 indices = list(map(constraint, dset.x, dset.w)) print(dset.y[indices]) #data.view() import sys data = Data.load(sys.argv[1]) data.view()