def show_remote(self): """ """ dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() tree = ParameterTree() # tree.setMinimumWidth(400) tree.setMinimumHeight(500) tree.setParameters(self.remote_params, showTop=False) vlayout.addWidget(tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Save', buttonBox.AcceptRole) buttonBox.accepted.connect(dialog.accept) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.rejected.connect(dialog.reject) vlayout.addWidget(buttonBox) dialog.setWindowTitle('Fill in information about the actions and their shortcuts') res = dialog.exec() if res == dialog.Accepted: # save preset parameters in a xml file ioxml.parameter_to_xml_file( self.remote_params, os.path.join(remote_path, self.remote_params.child('filename').value()))
def show_file_attributes(self, type_info='dataset'): """ """ dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() tree = ParameterTree() tree.setMinimumWidth(400) tree.setMinimumHeight(500) if type_info == 'scan': tree.setParameters(self.scan_attributes, showTop=False) elif type_info == 'dataset': tree.setParameters(self.dataset_attributes, showTop=False) vlayout.addWidget(tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.addButton('Apply', buttonBox.AcceptRole) buttonBox.rejected.connect(dialog.reject) buttonBox.accepted.connect(dialog.accept) vlayout.addWidget(buttonBox) dialog.setWindowTitle( 'Fill in information about this {}'.format(type_info)) res = dialog.exec() return res
def show_overshoot(self): """ """ dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() tree = ParameterTree() tree.setMinimumWidth(400) tree.setMinimumHeight(500) tree.setParameters(self.overshoot_params, showTop=False) vlayout.addWidget(tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Save', buttonBox.AcceptRole) buttonBox.accepted.connect(dialog.accept) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.rejected.connect(dialog.reject) vlayout.addWidget(buttonBox) dialog.setWindowTitle('Fill in information about this managers') res = dialog.exec() if res == dialog.Accepted: # save managers parameters in a xml file # start = os.path.split(os.path.split(os.path.realpath(__file__))[0])[0] # start = os.path.join("..",'daq_scan') ioxml.parameter_to_xml_file( self.overshoot_params, os.path.join(overshoot_path, self.overshoot_params.child('filename').value()))
def show_preset(self): """ """ dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() tree = ParameterTree() tree.setMinimumWidth(400) tree.setMinimumHeight(500) tree.setParameters(self.preset_params, showTop=False) vlayout.addWidget(tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Save', buttonBox.AcceptRole) buttonBox.accepted.connect(dialog.accept) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.rejected.connect(dialog.reject) vlayout.addWidget(buttonBox) dialog.setWindowTitle('Fill in information about this preset') res = dialog.exec() if self.pid_type: path = pid_path else: path = preset_path if res == dialog.Accepted: # save preset parameters in a xml file #start = os.path.split(os.path.split(os.path.realpath(__file__))[0])[0] #start = os.path.join("..",'daq_scan') custom_tree.parameter_to_xml_file( self.preset_params, os.path.join(path, self.preset_params.child(('filename')).value())) if not self.pid_type: #check if overshoot configuration and layout configuration with same name exists => delete them if yes overshoot_path = os.path.join(local_path, 'overshoot_configurations') file = os.path.splitext( self.preset_params.child(('filename')).value())[0] file = os.path.join(overshoot_path, file + '.xml') if os.path.isfile(file): os.remove(file) layout_path = os.path.join(local_path, 'layout') file = os.path.splitext( self.preset_params.child(('filename')).value())[0] file = os.path.join(layout_path, file + '.dock') if os.path.isfile(file): os.remove(file)
def show_file_attributes(self, type_info='dataset'): """ Switch the type_info value. In case of : * *scan* : Set parameters showing top false * *dataset* : Set parameters showing top false * *managers* : Set parameters showing top false. Add the save/cancel buttons to the accept/reject dialog (to save managers parameters in a xml file). Finally, in case of accepted managers type info, save the managers parameters in a xml file. =============== =========== ==================================== **Parameters** **Type** **Description** *type_info* string The file type information between * scan * dataset * managers =============== =========== ==================================== See Also -------- custom_tree.parameter_to_xml_file, create_menu """ dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() tree = ParameterTree() tree.setMinimumWidth(400) tree.setMinimumHeight(500) if type_info == 'scan': tree.setParameters(self.scan_attributes, showTop=False) elif type_info == 'dataset': tree.setParameters(self.dataset_attributes, showTop=False) vlayout.addWidget(tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.addButton('Apply', buttonBox.AcceptRole) buttonBox.rejected.connect(dialog.reject) buttonBox.accepted.connect(dialog.accept) vlayout.addWidget(buttonBox) dialog.setWindowTitle( 'Fill in information about this {}'.format(type_info)) res = dialog.exec() return res
class DbLoggerGUI(DbLogger, QtCore.QObject): params = [ { 'title': 'Database:', 'name': 'database_type', 'type': 'list', 'value': 'PostgreSQL', 'values': [ 'PostgreSQL', ] }, { 'title': 'Server IP:', 'name': 'server_ip', 'type': 'str', 'value': config['network']['logging']['sql']['ip'] }, { 'title': 'Server port:', 'name': 'server_port', 'type': 'int', 'value': config['network']['logging']['sql']['port'] }, { 'title': 'Connect:', 'name': 'connect_db', 'type': 'bool_push', 'value': False }, { 'title': 'Connected:', 'name': 'connected_db', 'type': 'led', 'value': False, 'readonly': True }, ] + dashboard_submodules_params def __init__(self, database_name): DbLogger.__init__(self, database_name, ip_address=config['network']['logging']['sql']['ip'], port=config['network']['logging']['sql']['port'], save2D=False) QtCore.QObject.__init__(self) self.settings = Parameter.create(title='DB settings', name='db_settings', type='group', children=self.params) self.settings.child(('do_save')).hide() self.settings_tree = ParameterTree() self.settings_tree.setMinimumHeight(310) self.settings_tree.setParameters(self.settings, showTop=False) self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed) def parameter_tree_changed(self, param, changes): """ Check for changes in the given (parameter,change,information) tuple list. In case of value changed, update the DAQscan_settings tree consequently. =============== ============================================ ============================== **Parameters** **Type** **Description** *param* instance of pyqtgraph parameter the parameter to be checked *changes* (parameter,change,information) tuple list the current changes state =============== ============================================ ============================== """ for param, change, data in changes: path = self.settings.childPath(param) if path is not None: childName = '.'.join(path) else: childName = param.name() if change == 'childAdded': pass elif change == 'value': if param.name() == 'server_ip': self.ip_address = param.value() elif param.name() == 'server_port': self.port = param.value() elif param.name() == 'connect_db': status = self.connect_db() self.settings.child(('connected_db')).setValue(status) elif param.name() == 'save_2D': self.save2D = param.value() elif change == 'parent': pass
class H5Saver(QObject): """QObject containing all methods in order to save datas in a *hdf5 file* with a hierachy compatible with the H5Browser. The saving parameters are contained within a **Parameter** object: self.settings that can be displayed on a UI (see :numref:`other_settings`) using the widget self.settings_tree. At the creation of a new file, a node group named **Raw_datas** and represented by the attribute ``raw_group`` is created and set with a metadata attribute: * 'type' given by the **save_type** class parameter The root group of the file is then set with a few metadata: * 'pymodaq_version' the current pymodaq version, e.g. 1.6.2 * 'file' the file name * 'date' the current date * 'time' the current time All datas will then be saved under this node in various groups See Also -------- H5Browser Parameters ---------- h5_file: pytables hdf5 file object used to save all datas and metadas h5_file_path: str or Path pyqtSignal signal represented by a float. Is emitted each time the hardware reached the target position within the epsilon precision (see comon_parameters variable) save_type: str an element of the list module attribute save_types = ['scan', 'detector', 'custom'] * 'scan' is used for DAQ_Scan module and should be used for similar application * 'detector' is used for DAQ_Viewer module and should be used for similar application * 'custom' should be used for customized applications Attributes ---------- status_sig: pyqtSignal emits a signal of type Threadcommand in order to senf log information to a main UI new_file_sig: pyqtSignal emits a boolean signal to let the program know when the user pressed the new file button on the UI settings: Parameter Parameter instance (pyqtgraph) containing all settings (could be represented using the settings_tree widget) settings_tree: ParameterTree Widget representing as a Tree structure, all the settings defined in the class preamble variable ``params`` """ status_sig = pyqtSignal(utils.ThreadCommand) new_file_sig = pyqtSignal(bool) params = [ { 'title': 'Save type:', 'name': 'save_type', 'type': 'list', 'values': save_types, 'readonly': True }, { 'title': 'Save 2D datas:', 'name': 'save_2D', 'type': 'bool', 'value': True }, { 'title': 'Save raw datas only:', 'name': 'save_raw_only', 'type': 'bool', 'value': True, 'tooltip': 'if True, will not save extracted ROIs used to do live plotting, only raw datas and the scan \ result will be saved' }, { 'title': 'Do Save:', 'name': 'do_save', 'type': 'bool', 'default': False, 'value': False }, { 'title': 'N saved:', 'name': 'N_saved', 'type': 'int', 'default': 0, 'value': 0, 'visible': False }, { 'title': 'custom_name?:', 'name': 'custom_name', 'type': 'bool', 'default': False, 'value': False }, { 'title': 'show file content?:', 'name': 'show_file', 'type': 'bool', 'default': False, 'value': False }, { 'title': 'Base path:', 'name': 'base_path', 'type': 'browsepath', 'value': 'C:\Data', 'filetype': False, 'readonly': True, }, { 'title': 'Base name:', 'name': 'base_name', 'type': 'str', 'value': 'Scan', 'readonly': True }, { 'title': 'Current scan:', 'name': 'current_scan_name', 'type': 'str', 'value': '', 'readonly': True }, { 'title': 'Current path:', 'name': 'current_scan_path', 'type': 'text', 'value': 'C:\Data', 'readonly': True, 'visible': False }, { 'title': 'h5file:', 'name': 'current_h5_file', 'type': 'text_pb', 'value': '', 'readonly': True }, { 'title': 'Compression options:', 'name': 'compression_options', 'type': 'group', 'children': [ { 'title': 'Compression library:', 'name': 'h5comp_library', 'type': 'list', 'value': 'zlib', 'values': ['zlib', 'lzo', 'bzip2', 'blosc'] }, { 'title': 'Compression level:', 'name': 'h5comp_level', 'type': 'int', 'value': 5, 'min': 0, 'max': 9 }, ] }, ] def __init__(self, h5_file_path=None, h5_file=None, save_type='scan'): """Initialize the H5Saver object Creates the ``setting`` and ``settings_tree`` object """ super(H5Saver, self).__init__() if save_type not in save_types: raise Exception('Invalid saving type') self.h5_file = h5_file self.h5_file_path = h5_file_path self.h5_file_name = None self.logger_array = None self.current_group = None self.current_scan_group = None self.current_scan_name = None self.raw_group = None self.settings = Parameter.create(title='Saving settings', name='save_settings', type='group', children=self.params) self.settings_tree = ParameterTree() self.settings_tree.setMinimumHeight(310) self.settings_tree.setParameters(self.settings, showTop=False) self.settings.child( ('current_h5_file' )).sigActivated.connect(lambda: self.emit_new_file(True)) self.settings.child(('save_type')).setValue(save_type) self.filters = tables.Filters( complevel=self.settings.child('compression_options', 'h5comp_level').value(), complib=self.settings.child('compression_options', 'h5comp_library').value()) # self.settings.child('saving_options', 'save_independent').show(save_type == 'scan') # self.settings.child('saving_options', 'do_save').show(not save_type == 'scan') # self.settings.child('saving_options', 'current_scan_name').show(save_type == 'scan') self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed ) # any changes on the settings will update accordingly the detector def emit_new_file(self, status): """Emits the new_file_sig Parameters ---------- status: bool emits True if a new file has been asked by the user pressing the new file button on the UI """ self.new_file_sig.emit(status) def init_file(self, update_h5=False, custom_naming=False, addhoc_file_path=None): """Initializes a new h5 file. Could set the h5_file attributes as: * a file with a name following a template if ``custom_naming`` is ``False`` and ``addhoc_file_path`` is ``None`` * a file within a name set using a file dialog popup if ``custom_naming`` is ``True`` * a file with a custom name if ``addhoc_file_path`` is a ``Path`` object or a path string Parameters ---------- update_h5: bool create a new h5 file with name specified by other parameters if false try to open an existing file and will append new data to it custom_naming: bool if True, a selection file dialog opens to set a new file name addhoc_file_path: Path or str supplied name by the user for the new file Returns ------- update_h5: bool True if new file has been created, False otherwise """ date = datetime.datetime.now() if addhoc_file_path is None: if not os.path.isdir(self.settings.child(('base_path')).value()): os.mkdir(self.settings.child(('base_path')).value()) # set the filename and path base_name = self.settings.child(('base_name')).value() if not custom_naming: custom_naming = self.settings.child(('custom_name')).value() if not custom_naming: scan_type = self.settings.child( ('save_type')).value() == 'scan' scan_path, current_scan_name, save_path = self.update_file_paths( update_h5) self.current_scan_name = current_scan_name self.settings.child( ('current_scan_name')).setValue(current_scan_name) self.settings.child( ('current_scan_path')).setValue(str(scan_path)) if not scan_type: self.h5_file_path = save_path #will remove the dataset part used for DAQ_scan datas self.h5_file_name = base_name + date.strftime( '_%Y%m%d_%H_%M_%S.h5') else: self.h5_file_path = save_path self.h5_file_name = save_path.name + ".h5" else: self.h5_file_name = utils.select_file(start_path=base_name, save=True, ext='h5') self.h5_file_path = self.h5_file_name.parent else: if isinstance(addhoc_file_path, str): addhoc_file_path = Path(addhoc_file_path) self.h5_file_path = addhoc_file_path.parent self.h5_file_name = addhoc_file_path.name fullpathname = str(self.h5_file_path.joinpath(self.h5_file_name)) self.settings.child(('current_h5_file')).setValue(fullpathname) if update_h5: self.current_scan_group = None scan_group = None if self.current_scan_group is not None: scan_group = self.current_scan_group._v_name if update_h5: self.close_file() self.h5_file = tables.open_file(fullpathname, 'w', title='PyMoDAQ file') self.h5_file.root._v_attrs['pymodaq_version'] = get_version() else: self.close_file() self.h5_file = tables.open_file(fullpathname, 'a', title='PyMoDAQ file') self.raw_group = self.get_set_group(self.h5_file.root, 'Raw_datas', title='Data from PyMoDAQ modules') self.get_set_logger(self.raw_group) if scan_group is not None: self.current_scan_group = self.get_set_group( self.raw_group, scan_group) else: self.current_scan_group = self.get_last_scan() self.raw_group._v_attrs['type'] = self.settings.child( ('save_type')).value() self.h5_file.root._v_attrs['file'] = date.strftime(self.h5_file_name) if update_h5: self.h5_file.root._v_attrs['date'] = date.strftime('%Y%m%d') self.h5_file.root._v_attrs['time'] = date.strftime('%H:%M:%S') return update_h5 def update_file_paths(self, update_h5=False): """Apply the template depending on the 'save_type' settings child Parameters ---------- update_h5: bool if True, will increment the file name and eventually the current scan index if False, get the current scan index in the h5 file Returns ------- scan_path: Path current_filename: str dataset_path: Path See Also -------- :py:meth:`pymodaq.daq_utils.daq_utils.set_current_scan_path` """ try: # set the filename and path base_path = self.settings.child(('base_path')).value() base_name = self.settings.child(('base_name')).value() current_scan = self.settings.child(('current_scan_name')).value() scan_type = self.settings.child(('save_type')).value() == 'scan' if current_scan == '' or update_h5: next_scan_index = 0 update_h5 = True #just started the main program so one should create a new h5 else: next_scan_index = self.get_scan_index() scan_path, current_filename, dataset_path = utils.set_current_scan_path( base_path, base_name, update_h5, next_scan_index, create_dataset_folder=scan_type) self.settings.child(('current_scan_path')).setValue(str(scan_path)) return scan_path, current_filename, dataset_path except Exception as e: print(e) def get_last_scan(self): """Gets the last scan node within the h5_file and under the the **raw_group** Returns ------- scan_group: pytables group or None """ groups = [ group for group in list(self.raw_group._v_groups) if 'Scan' in group ] groups.sort() if len(groups) != 0: scan_group = self.h5_file.get_node(self.raw_group, groups[-1]) else: scan_group = None return scan_group def get_scan_index(self): """ return the scan group index in the "scan templating": Scan000, Scan001 as an integer """ try: if self.current_scan_group is None: return 0 else: groups = [ group for group in list(self.raw_group._v_groups) if 'Scan' in group ] groups.sort() flag = False for child in list( self.h5_file.get_node(self.raw_group, groups[-1])._v_groups): if 'scan' in child: return len(groups) return 0 except Exception as e: return 0 def load_file(self, base_path=None, file_path=None): """Opens a file dialog to select a h5file saved on disk to be used Parameters ---------- base_path file_path See Also -------- :py:meth:`init_file` :py:meth:`pymodaq.daq_utils.daq_utils.select_file` """ if base_path is None: base_path = self.settings.child('base_path').value() if not os.path.isdir(base_path): base_path = None if file_path is None: file_path = utils.select_file(base_path, save=False, ext='h5') if not isinstance(file_path, Path): file_path = Path(file_path) if 'h5' not in file_path.suffix: raise IOError('Invalid file type, should be a h5 file') self.init_file(addhoc_file_path=file_path) def close_file(self): """Flush data and close the h5file """ try: if self.h5_file is not None: self.h5_file.flush() if self.h5_file.isopen: self.h5_file.close() except Exception as e: print(e) #no big deal def is_node_in_group(self, where, name): """ Check if a given node with name is in the group defined by where (comparison on lower case strings) Parameters ---------- where: (str or node) path or parent node instance name: (str) group node name Returns ------- bool True if node exists, False otherwise """ nodes_names = [ node._v_name.lower() for node in self.h5_file.list_nodes(where) ] return name.lower() in nodes_names def get_set_logger(self, where): """ Retrieve or create (if absent) a logger enlargeable array to store logs Get attributed to the class attribute ``logger_array`` Parameters ---------- where: node location within the tree where to save or retrieve the array Returns ------- logger_array: vlarray enlargeable array accepting strings as elements """ logger = 'Logger' if not logger in list(self.h5_file.get_node(where)._v_children.keys()): # check if logger node exist text_atom = tables.atom.ObjectAtom() self.logger_array = self.h5_file.create_vlarray(where, logger, atom=text_atom) self.logger_array._v_attrs['type'] = 'log' else: self.logger_array = self.h5_file.get_node(where, name=logger) return self.logger_array def get_set_group(self, where, name, title=''): """Retrieve or create (if absent) a node group Get attributed to the class attribute ``current_group`` Parameters ---------- where: str or node path or parent node instance name: str group node name title: str node title Returns ------- group: group node """ if not name in list(self.h5_file.get_node(where)._v_children.keys()): self.current_group = self.h5_file.create_group(where, name, title) else: self.current_group = self.h5_file.get_node(where, name) return self.current_group def add_data_group(self, where, group_data_type, title='', settings_as_xml='', metadata=dict([])): """Creates a group node at given location in the tree Parameters ---------- where: group node where to create data group group_data_type: list of str either ['data0D', 'data1D', 'data2D'] title: str, optional a title for this node, will be saved as metadata settings_as_xml: str, optional XML string created from a Parameter object to be saved as metadata metadata: dict, optional will be saved as a new metadata attribute with name: key and value: dict value Returns ------- group: group node See Also -------- :py:meth:`àdd_group` """ if group_data_type not in group_data_types: raise Exception('Invalid data group type') group = self.add_group(group_data_type, '', where, title, settings_as_xml, metadata) return group def add_navigation_axis(self, data, parent_group, axis='x_axis', enlargeable=False, title='', metadata=dict([])): """ Create carray for navigation axis within a scan Parameters ---------- data: (ndarray) of dimension 1 parent_group: (str or node) parent node where to save new data axis: (str) either x_axis or y_axis """ if axis not in ['x_axis', 'y_axis', 'z_axis']: raise Exception('Invalid navigation axis name') array = self.add_array(parent_group, 'scan_{:s}'.format(axis), 'navigation_axis', data_shape=data.shape, data_dimension='1D', array_to_save=data, enlargeable=enlargeable, title=title, metadata=metadata) return array def add_data_live_scan(self, channel_group, data_dict, scan_type='scan1D', title=''): shape, dimension, size = utils.get_data_dimension( data_dict['data'], scan_type=scan_type, remove_scan_dimension=True) data_array = self.add_array(channel_group, 'Data', 'data', array_type=np.float, title=title, data_shape=shape, data_dimension=dimension, scan_type=scan_type, array_to_save=data_dict['data']) if 'x_axis' in data_dict: if not isinstance(data_dict['x_axis'], dict): array_to_save = data_dict['x_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['x_axis']) array_to_save = tmp_dict.pop('data') if 'x_axis' in data_dict: array = self.add_array(channel_group, 'x_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) if 'y_axis' in data_dict: if not isinstance(data_dict['y_axis'], dict): array_to_save = data_dict['y_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['y_axis']) array_to_save = tmp_dict.pop('data') if 'y_axis' in data_dict: array = self.add_array(channel_group, 'y_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) return data_array def add_data(self, channel_group, data_dict, scan_type='scan1D', scan_shape=[], title='', enlargeable=False, init=False, add_scan_dim=False): shape, dimension, size = utils.get_data_dimension(data_dict['data']) data_array = self.add_array(channel_group, 'Data', 'data', array_type=np.float, title=title, data_shape=shape, enlargeable=enlargeable, data_dimension=dimension, scan_type=scan_type, scan_shape=scan_shape, array_to_save=data_dict['data'], init=init, add_scan_dim=add_scan_dim) if 'x_axis' in data_dict: if not isinstance(data_dict['x_axis'], dict): array_to_save = data_dict['x_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['x_axis']) array_to_save = tmp_dict.pop('data') array = self.add_array(channel_group, 'x_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) if 'y_axis' in data_dict: if not isinstance(data_dict['y_axis'], dict): array_to_save = data_dict['y_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['y_axis']) array_to_save = tmp_dict.pop('data') array = self.add_array(channel_group, 'y_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) self.h5_file.flush() return data_array def add_array(self, where, name, data_type, data_shape=(1, ), data_dimension='0D', scan_type='', scan_shape=[], title='', array_to_save=None, array_type=np.float, enlargeable=False, metadata=dict([]), init=False, add_scan_dim=False): if data_dimension not in data_dimensions: raise Exception('Invalid data dimension') if data_type not in data_types: raise Exception('Invalid data type') if scan_type != '': scan_type = utils.uncapitalize(scan_type) if scan_type not in scan_types: raise Exception('Invalid scan type') if enlargeable: shape = [0] if data_shape != (1, ): shape.extend(data_shape) shape = tuple(shape) array = self.h5_file.create_earray(where, utils.capitalize(name), tables.Atom.from_dtype( np.dtype(array_type)), shape=shape, title=title, filters=self.filters) array._v_attrs['shape'] = shape else: if add_scan_dim: #means it is an array initialization to zero shape = scan_shape[:] shape.extend(data_shape) if init or array_to_save is None: array_to_save = np.zeros(shape) array = self.h5_file.create_carray(where, utils.capitalize(name), obj=array_to_save, title=title, filters=self.filters) array._v_attrs['shape'] = array_to_save.shape array._v_attrs['type'] = data_type array._v_attrs['data_dimension'] = data_dimension array._v_attrs['scan_type'] = scan_type for metadat in metadata: array._v_attrs[metadat] = metadata[metadat] return array def append(self, array, data): if not (isinstance(array, tables.vlarray.VLArray) or isinstance(array, tables.earray.EArray)): raise Exception('This array cannot be appended') if isinstance(data, np.ndarray): if data.shape != (1, ): shape = [1] shape.extend(data.shape) array.append(data.reshape(shape)) else: array.append(data) else: array.append(data) sh = list(array._v_attrs['shape']) sh[0] += 1 array._v_attrs['shape'] = tuple(sh) def add_group(self, group_name, group_type, where, title='', settings_as_xml='', metadata=dict([])): """ Add a node in the h5 file tree of the group type Parameters ---------- group_name: (str) a custom name for this group group_type: (str) one of the possible values of **group_types** where: (str or node) parent node where to create the new group settings_as_xml: (str) XML string containing Parameters representation (see custom_Tree) metadata: (dict) extra metadata to be saved with this new group node Returns ------- (node): newly created group node """ if group_type not in group_types: raise Exception('Invalid group type') try: node = self.h5_file.get_node(where, utils.capitalize(group_name)) except tables.NoSuchNodeError as e: node = None if node is None: node = self.get_set_group(where, utils.capitalize(group_name), title) node._v_attrs['settings'] = settings_as_xml node._v_attrs['type'] = group_type.lower() for metadat in metadata: node._v_attrs[metadat] = metadata[metadat] return node def add_incremental_group(self, group_type, where, title='', settings_as_xml='', metadata=dict([])): """ Add a node in the h5 file tree of the group type with an increment in the given name Parameters ---------- group_type: (str) one of the possible values of **group_types** where: (str or node) parent node where to create the new group settings_as_xml: (str) XML string containing Parameters representation (see custom_Tree) metadata: (dict) extra metadata to be saved with this new group node Returns ------- (node): newly created group node """ if group_type not in group_types: raise Exception('Invalid group type') nodes = list(self.h5_file.get_node(where)._v_children.keys()) nodes_tmp = [] for node in nodes: if utils.capitalize(group_type) in node: nodes_tmp.append(node) nodes_tmp.sort() if len(nodes_tmp) == 0: ind_group = -1 else: ind_group = int(nodes_tmp[-1][-3:]) group = self.get_set_group( where, utils.capitalize(group_type) + '{:03d}'.format(ind_group + 1), title) group._v_attrs['settings'] = settings_as_xml if group_type.lower() != 'ch': group._v_attrs['type'] = group_type.lower() else: group._v_attrs['type'] = '' for metadat in metadata: group._v_attrs[metadat] = metadata[metadat] return group def add_det_group(self, where, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type detector See Also ------- add_incremental_group """ group = self.add_incremental_group('detector', where, title, settings_as_xml, metadata) return group def add_CH_group(self, where, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type channel See Also ------- add_incremental_group """ group = self.add_incremental_group('ch', where, title, settings_as_xml, metadata) return group def add_live_scan_group(self, where, dimensionality, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type live scan See Also ------- add_incremental_group """ group = self.add_group('Live_scan_{:s}'.format(dimensionality), '', where, title=title, settings_as_xml=settings_as_xml, metadata=metadata) return group def add_scan_group(self, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type scan See Also ------- add_incremental_group """ if self.current_scan_group is not None: if list(self.current_scan_group._v_children) == []: new_scan = False else: new_scan = True else: new_scan = True if new_scan: self.current_scan_group = self.add_incremental_group( 'scan', self.raw_group, title, settings_as_xml, metadata) self.current_scan_group._v_attrs['description'] = '' self.settings.child(('current_scan_name')).setValue( self.current_scan_group._v_name) return self.current_scan_group def add_move_group(self, where, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type move See Also ------- add_incremental_group """ group = self.add_incremental_group('move', where, title, settings_as_xml, metadata) return group def parameter_tree_changed(self, param, changes): for param, change, data in changes: path = self.settings.childPath(param) if change == 'childAdded': pass elif change == 'value': if param.name() == 'show_file': param.setValue(False) self.show_file_content() elif param.name() == 'base_path': try: if not os.path.isdir(param.value()): os.mkdir(param.value()) except: self.update_status( "The base path couldn't be set, please check your options" ) elif param.name() in custom_tree.iter_children( self.settings.child(('compression_options')), []): self.filters = tables.Filters( complevel=self.settings.child('compression_options', 'h5comp_level').value(), complib=self.settings.child('compression_options', 'h5comp_library').value()) elif change == 'parent': pass def update_status(self, status): self.status_sig.emit( utils.ThreadCommand("Update_Status", [status, 'log'])) def show_file_content(self): form = QtWidgets.QWidget() if not self.h5_file.isopen: if self.h5_file_path.exists(): self.analysis_prog = H5Browser(form, h5file=self.h5_file_path) else: raise FileExistsError('no File presents') else: self.analysis_prog = H5Browser(form, h5file=self.h5_file) form.show()
class BeeActions(QObject): log_signal = pyqtSignal(str) def __init__(self, dockarea): super().__init__() self.dockarea = dockarea self.dockarea.dock_signal.connect(self.save_layout_state_auto) self.mainwindow = dockarea.parent() self.chrono = ChronoTimer(dockarea) self.author = 'Aurore Avargues' self.h5saver = H5Saver() self.h5saver.new_file_sig.connect(self.create_new_file) self.settings = None self.shortcut_file = None self.shortcuts = [] self.shortcut_manager = ShortCutManager(list_actions) self.timestamp_array = None self.action_array = None self.bee_array = None self.setup_ui() def setup_ui(self): #creating the menubar self.menubar = self.mainwindow.menuBar() self.create_menu(self.menubar) #disconnect normal chrono/timer behaviour with the start button self.chrono.start_pb.disconnect() self.chrono.start_pb.clicked.connect(self.set_scan) self.chrono.reset_pb.clicked.connect(self.stop_daq) self.settings_dock = Dock('Settings') self.dockarea.addDock(self.settings_dock, 'bottom', self.chrono.dock_controls) self.dock_daq = Dock('Data Acquisition') self.dockarea.addDock(self.dock_daq, 'right') self.logger_list = QtWidgets.QListWidget() self.logger_list.setMinimumWidth(300) self.dock_daq.addWidget(self.logger_list) self.init_tree = ParameterTree() self.init_tree.setMinimumWidth(300) self.init_tree.setMinimumHeight(150) self.settings_dock.addWidget(self.init_tree) self.settings_dock.addWidget(self.h5saver.settings_tree) self.h5saver.settings.child(('save_type')).hide() self.h5saver.settings.child(('save_2D')).hide() self.h5saver.settings.child(('save_raw_only')).hide() self.h5saver.settings.child(('do_save')).hide() self.h5saver.settings.child(('custom_name')).hide() self.settings = Parameter.create(name='init_settings', type='group', children=[ { 'title': 'Loaded presets', 'name': 'loaded_files', 'type': 'group', 'children': [ { 'title': 'Shortcut file', 'name': 'shortcut_file', 'type': 'str', 'value': '', 'readonly': True }, { 'title': 'Layout file', 'name': 'layout_file', 'type': 'str', 'value': '', 'readonly': True }, ] }, { 'title': 'Settings', 'name': 'settings', 'type': 'group', 'children': [ { 'title': 'Save Bee number', 'name': 'save_bee_number', 'type': 'bool', 'value': True }, ] }, { 'title': 'Shortcuts', 'name': 'shortcuts', 'type': 'group', 'children': [] }, ]) self.init_tree.setParameters(self.settings, showTop=False) self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed) #params about dataset attributes and scan attibutes date = QDateTime(QDate.currentDate(), QTime.currentTime()) params_dataset = [{ 'title': 'Dataset information', 'name': 'dataset_info', 'type': 'group', 'children': [{ 'title': 'Author:', 'name': 'author', 'type': 'str', 'value': self.author }, { 'title': 'Date/time:', 'name': 'date_time', 'type': 'date_time', 'value': date }, { 'title': 'Sample:', 'name': 'sample', 'type': 'str', 'value': '' }, { 'title': 'Experiment type:', 'name': 'experiment_type', 'type': 'str', 'value': '' }, { 'title': 'Description:', 'name': 'description', 'type': 'text', 'value': '' }] }] params_scan = [{ 'title': 'Scan information', 'name': 'scan_info', 'type': 'group', 'children': [ { 'title': 'Author:', 'name': 'author', 'type': 'str', 'value': self.author }, { 'title': 'Date/time:', 'name': 'date_time', 'type': 'date_time', 'value': date }, { 'title': 'Scan type:', 'name': 'scan_type', 'type': 'list', 'value': 'Scan1D', 'values': ['Scan1D', 'Scan2D'] }, { 'title': 'Scan name:', 'name': 'scan_name', 'type': 'str', 'value': '', 'readonly': True }, { 'title': 'Description:', 'name': 'description', 'type': 'text', 'value': '' }, ] }] self.dataset_attributes = Parameter.create(name='Attributes', type='group', children=params_dataset) self.scan_attributes = Parameter.create(name='Attributes', type='group', children=params_scan) def parameter_tree_changed(self, param, changes): """ | Check eventual changes in the changes list parameter. | | In case of changed values, emit the signal containing the current path and parameter via update_settings_signal to the connected hardware. =============== ==================================== ================================================== **Parameters** **Type** **Description** *param* instance of pyqtgraph parameter The parameter to be checked *changes* (parameter,change,infos) tuple list The (parameter,change,infos) list to be treated =============== ==================================== ================================================== """ for param, change, data in changes: path = self.settings.childPath(param) if path is not None: childName = '.'.join(path) else: childName = param.name() if change == 'childAdded': pass elif change == 'value': if param.name() in custom_tree.iter_children( self.settings.child(('shortcuts')), []): if param.parent().name() == 'shortcuts': param_index = custom_tree.iter_children( self.settings.child(('shortcuts')), []).index(param.name()) action = self.shortcut_manager.shortcut_params.child( ('actions')).children()[param_index].child( ('action')).value() self.activate_shortcut(self.shortcuts[param_index], action, activate=param.value()) elif change == 'parent': pass def activate_shortcut(self, shortcut, action='', activate=True): if activate: shortcut.activated.connect(self.create_activated_slot(action)) else: try: shortcut.activated.disconnect() except: pass def create_activated_slot(self, action): return lambda: self.log_data(action) def log_data(self, action=''): now = self.chrono.get_elapsed_time() if self.settings.child('settings', 'save_bee_number').value(): widget = QtWidgets.QWidget() index, res = QtWidgets.QInputDialog.getInt( widget, 'Bee number', 'Pick a number for this bee!') if res: new_item = QtWidgets.QListWidgetItem( f'Elapsed time: {int(now)} s, Bee {index} did :{action}') self.logger_list.insertItem(0, new_item) self.h5saver.append(self.action_array, action) self.h5saver.append(self.timestamp_array, np.array([now])) self.h5saver.append(self.bee_array, np.array([index])) else: new_item = QtWidgets.QListWidgetItem( f'Elapsed time: {int(now)} s:{action}') self.logger_list.insertItem(0, new_item) self.h5saver.append(self.action_array, action) self.h5saver.append(self.timestamp_array, np.array([now])) self.h5saver.flush() def create_shortcuts(self): pass def create_new_file(self, new_file): self.h5saver.init_file(update_h5=new_file) res = self.update_file_settings(new_file) return res def set_scan(self): """ Sets the current scan given the selected settings. Makes some checks, increments the h5 file scans. In case the dialog is cancelled, return False and aborts the scan """ try: if self.shortcut_file is not None: # set the filename and path res = self.create_new_file(False) if not res: return #create the arrays within the current scan group self.timestamp_array = self.h5saver.add_array( self.h5saver.current_scan_group, 'time_axis', 'data', scan_type='scan1D', enlargeable=True, array_to_save=np.array([ 0., ]), data_shape=(1, ), title='Timestamps', metadata=dict(units='seconds')) self.action_array = self.h5saver.add_string_array( self.h5saver.current_scan_group, 'actions', title='Actions', metadata=dict([])) if self.settings.child('settings', 'save_bee_number').value(): self.bee_array = self.h5saver.add_array( self.h5saver.current_scan_group, 'bees', 'data', scan_type='scan1D', enlargeable=True, array_to_save=np.array([ 0, ]), title='Bees', data_shape=(1, )) current_filename = self.h5saver.settings.child( ('current_scan_name')).value() self.init_tree.setEnabled(False) self.h5saver.settings_tree.setEnabled(False) self.logger_list.clear() self.h5saver.current_scan_group._v_attrs['scan_done'] = False # if all metadat steps have been validated, start the chrono self.chrono.start() return True else: mssg = QtWidgets.QMessageBox() mssg.setText( 'You have to load a shortcut file configuration before starting' ) mssg.exec() return False except Exception as e: self.update_status(getLineInfo() + str(e)) def stop_daq(self): self.h5saver.current_scan_group._v_attrs['scan_done'] = True self.init_tree.setEnabled(True) self.h5saver.settings_tree.setEnabled(True) self.h5saver.flush() def update_file_settings(self, new_file=False): try: if self.h5saver.current_scan_group is None: new_file = True if new_file: self.set_metadata_about_dataset() self.save_metadata(self.h5saver.raw_group, 'dataset_info') if self.h5saver.current_scan_name is None: self.h5saver.add_scan_group() elif not self.h5saver.is_node_in_group( self.h5saver.raw_group, self.h5saver.current_scan_name): self.h5saver.add_scan_group() #set attributes to the current group, such as scan_type.... self.scan_attributes.child('scan_info', 'scan_type').setValue('Scan1D') self.scan_attributes.child('scan_info', 'scan_name').setValue( self.h5saver.current_scan_group._v_name) self.scan_attributes.child('scan_info', 'description').setValue( self.h5saver.current_scan_group._v_attrs['description']) res = self.set_metadata_about_current_scan() self.save_metadata(self.h5saver.current_scan_group, 'scan_info') return res except Exception as e: self.update_status(getLineInfo() + str(e)) def set_metadata_about_current_scan(self): """ Set the date/time and author values of the scan_info child of the scan_attributes tree. Show the 'scan' file attributes. See Also -------- show_file_attributes """ date = QDateTime(QDate.currentDate(), QTime.currentTime()) self.scan_attributes.child('scan_info', 'date_time').setValue(date) self.scan_attributes.child('scan_info', 'author').setValue( self.dataset_attributes.child('dataset_info', 'author').value()) res = self.show_file_attributes('scan') return res def set_metadata_about_dataset(self): """ Set the date value of the data_set_info-date_time child of the data_set_attributes tree. Show the 'dataset' file attributes. See Also -------- show_file_attributes """ date = QDateTime(QDate.currentDate(), QTime.currentTime()) self.dataset_attributes.child('dataset_info', 'date_time').setValue(date) res = self.show_file_attributes('dataset') return res def show_file_attributes(self, type_info='dataset'): """ """ dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() tree = ParameterTree() tree.setMinimumWidth(400) tree.setMinimumHeight(500) if type_info == 'scan': tree.setParameters(self.scan_attributes, showTop=False) elif type_info == 'dataset': tree.setParameters(self.dataset_attributes, showTop=False) vlayout.addWidget(tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.addButton('Apply', buttonBox.AcceptRole) buttonBox.rejected.connect(dialog.reject) buttonBox.accepted.connect(dialog.accept) vlayout.addWidget(buttonBox) dialog.setWindowTitle( 'Fill in information about this {}'.format(type_info)) res = dialog.exec() return res def save_metadata(self, node, type_info='dataset_info'): """ """ attr = node._v_attrs if type_info == 'dataset_info': attr['type'] = 'dataset' params = self.dataset_attributes else: attr['type'] = 'scan' params = self.scan_attributes for child in params.child((type_info)).children(): if isinstance(child.value(), QDateTime): attr[child.name()] = child.value().toString( 'dd/mm/yyyy HH:MM:ss') else: attr[child.name()] = child.value() if type_info == 'dataset_info': #save contents of given parameter object into an xml string under the attribute settings settings_str = b'<All_settings title="All Settings" type="group">' settings_str += custom_tree.parameter_to_xml_string(params) settings_str += custom_tree.parameter_to_xml_string(self.settings) if hasattr(self.shortcut_manager, 'shortcut_params'): settings_str += custom_tree.parameter_to_xml_string( self.shortcut_manager.shortcut_params) settings_str += b'</All_settings>' attr.settings = settings_str elif type_info == 'scan_info': settings_str = b'<All_settings title="All Settings" type="group">' + \ custom_tree.parameter_to_xml_string(params) + \ custom_tree.parameter_to_xml_string(self.settings) + \ custom_tree.parameter_to_xml_string(self.h5saver.settings) + b'</All_settings>' attr.settings = settings_str def show_log(self): import webbrowser webbrowser.open(logging.getLoggerClass().root.handlers[0].baseFilename) def show_file(self): self.h5saver.flush() self.h5saver.show_file_content() def create_menu(self, menubar): menubar.clear() # %% create Settings menu self.file_menu = menubar.addMenu('File') self.file_menu.addAction('Show log file', self.show_log) self.file_menu.addAction('Show data file', self.show_file) self.file_menu.addSeparator() quit_action = self.file_menu.addAction('Quit') quit_action.triggered.connect(self.quit_fun) self.settings_menu = menubar.addMenu('Settings') docked_menu = self.settings_menu.addMenu('Docked windows') action_load = docked_menu.addAction('Load Layout') action_save = docked_menu.addAction('Save Layout') action_load.triggered.connect(self.load_layout_state) action_save.triggered.connect(self.save_layout_state) docked_menu.addSeparator() self.preset_menu = menubar.addMenu('Preset Shortcuts') action_new_preset = self.preset_menu.addAction('New preset') # action.triggered.connect(lambda: self.show_file_attributes(type_info='preset')) action_new_preset.triggered.connect(self.create_preset) action_modify_preset = self.preset_menu.addAction('Modify preset') action_modify_preset.triggered.connect(self.modify_shortcuts) self.preset_menu.addSeparator() load_preset = self.preset_menu.addMenu('Load presets') slots = dict([]) for ind_file, file in enumerate(os.listdir(shortcut_path)): if file.endswith(".xml"): (filesplited, ext) = os.path.splitext(file) slots[filesplited] = load_preset.addAction(filesplited) slots[filesplited].triggered.connect( self.create_menu_slot(os.path.join(shortcut_path, file))) def modify_shortcuts(self): try: path = select_file(start_path=shortcut_path, save=False, ext='xml') if path != '': self.shortcut_manager.set_file_preset(str(path)) mssg = QtWidgets.QMessageBox() mssg.setText( f'You have to restart the application to take the modifications into account! ' f'Quitting the application...') mssg.exec() self.quit_fun() else: # cancel pass except Exception as e: self.update_status(getLineInfo() + str(e)) def create_menu_slot(self, filename): return lambda: self.set_shortcut_mode(filename) def set_shortcut_mode(self, filename): #TODO: apply shortcuts to this widget tail, fileext = os.path.split(filename) file, ext = os.path.splitext(fileext) if ext == '.xml': self.shortcut_file = filename self.shortcut_manager.set_file_preset(filename, show=False) self.settings.child('loaded_files', 'shortcut_file').setValue(filename) self.author = self.shortcut_manager.shortcut_params.child( ('author')).value() self.dataset_attributes.child('dataset_info', 'author').setValue(self.author) self.scan_attributes.child('scan_info', 'author').setValue(self.author) path = os.path.join(layout_path, file + '.dock') if os.path.isfile(path): self.load_layout_state(path) #remove existing shorcuts while len(self.shortcuts): self.shortcuts.pop(0) for ind, shortcut in enumerate( self.shortcut_manager.shortcut_params.child( ('actions')).children()): stc = QtWidgets.QShortcut( QtGui.QKeySequence(shortcut.child(('shortcut')).value()), self.dockarea) self.settings.child(('shortcuts')).addChild({ 'title': f"Shortcut{ind:02d}: {shortcut.child(('action')).value()} {shortcut.child(('shortcut')).value()}:", 'name': f'shortcut{ind:02d}', 'type': 'led_push', 'value': True }) self.shortcuts.append(stc) self.activate_shortcut(stc, shortcut.child(('action')).value(), activate=True) def create_preset(self): try: self.shortcut_manager.set_new_preset() self.create_menu(self.menubar) except Exception as e: self.update_status(getLineInfo() + str(e)) def save_layout_state(self, file=None): """ Save the current layout state in the select_file obtained pathname file. Once done dump the pickle. See Also -------- utils.select_file """ try: dockstate = self.dockarea.saveState() if file is None: file = select_file(start_path=None, save=True, ext='dock') if file is not None: with open(str(file), 'wb') as f: pickle.dump(dockstate, f, pickle.HIGHEST_PROTOCOL) except: pass def save_layout_state_auto(self): if self.shortcut_file is not None: file = os.path.split(self.shortcut_file)[1] file = os.path.splitext(file)[0] path = os.path.join(layout_path, file + '.dock') self.save_layout_state(path) def load_layout_state(self, file=None): """ Load and restore a layout state from the select_file obtained pathname file. See Also -------- utils.select_file """ try: if file is None: file = select_file(save=False, ext='dock') if file is not None: with open(str(file), 'rb') as f: dockstate = pickle.load(f) self.dockarea.restoreState(dockstate) file = os.path.split(file)[1] self.settings.child('loaded_files', 'layout_file').setValue(file) except: pass def quit_fun(self): """ Quit the current instance of DAQ_scan and close on cascade move and detector modules. See Also -------- quit_fun """ try: areas = self.dockarea.tempAreas[:] for area in areas: area.win.close() QtWidgets.QApplication.processEvents() QThread.msleep(1000) QtWidgets.QApplication.processEvents() if hasattr(self, 'mainwindow'): self.mainwindow.close() except Exception as e: pass def update_status(self, txt): """ Show the txt message in the status bar with a delay of wait_time ms. =============== =========== ======================= **Parameters** **Type** **Description** *txt* string The message to show *wait_time* int the delay of showing *log_type* string the type of the log =============== =========== ======================= """ try: self.log_signal.emit(txt) logging.info(txt) except Exception as e: pass
class BatchManager: params = [{'title': 'Filename:', 'name': 'filename', 'type': 'str', 'value': 'batch_default'}, {'title': 'Scans', 'name': 'scans', 'type': 'group', 'children': []}] def __init__(self, msgbox=False, actuators=[], detectors=[], path=None): self.actuators = actuators self.detectors = detectors self.scans = OrderedDict([]) self.tree = ParameterTree() self.tree.setMinimumWidth(400) self.tree.setMaximumWidth(500) self.tree.setMinimumHeight(500) if path is None: path = batch_path else: assert isinstance(path, Path) self.batch_path = path self.settings = None if msgbox: msgBox = QtWidgets.QMessageBox() msgBox.setText("Scan Batch Manager?") msgBox.setInformativeText("What do you want to do?") cancel_button = msgBox.addButton(QtWidgets.QMessageBox.Cancel) new_button = msgBox.addButton("New", QtWidgets.QMessageBox.ActionRole) modify_button = msgBox.addButton('Modify', QtWidgets.QMessageBox.AcceptRole) msgBox.setDefaultButton(QtWidgets.QMessageBox.Cancel) ret = msgBox.exec() if msgBox.clickedButton() == new_button: self.set_new_batch() elif msgBox.clickedButton() == modify_button: self.set_file_batch() else: # cancel pass def get_act_dets(self): acts = dict([]) dets = dict([]) for name in self.scans: acts[name] = self.settings.child('scans', name, 'modules', 'actuators').value()['selected'] dets[name] = self.settings.child('scans', name, 'modules', 'detectors').value()['selected'] return acts, dets def set_file_batch(self, filename=None, show=True): """ """ if filename is None or filename == False: filename = pymodaq.daq_utils.gui_utils.file_io.select_file(start_path=self.batch_path, save=False, ext='xml') if filename == '': return status = False settings_tmp = Parameter.create(title='Batch', name='settings_tmp', type='group', children=ioxml.XML_file_to_parameter(str(filename))) children = settings_tmp.child('scans').children() self.settings = Parameter.create(title='Batch', name='settings', type='group', children=self.params) actuators = children[0].child('modules', 'actuators').value()['all_items'] if actuators != self.actuators: pymodaq.daq_utils.messenger.show_message('The loaded actuators from the batch file do not corresponds to the' ' dashboard actuators') return else: self.actuators = actuators detectors = children[0].child('modules', 'detectors').value()['all_items'] if detectors != self.detectors: pymodaq.daq_utils.messenger.show_message('The loaded detectors from the batch file do not corresponds to the' ' dashboard detectors') return else: self.detectors = detectors for child in children: self.add_scan(name=child.name(), title=child.opts['title']) self.settings.child('scans', child.name()).restoreState(child.saveState()) if show: status = self.show_tree() else: self.tree.setParameters(self.settings, showTop=False) return status def set_scans(self): infos = [] acts, dets = self.get_act_dets() for scan in self.scans: infos.append(f'{scan}: {acts[scan]} / {dets[scan]}') infos.append(f'{scan}: {self.scans[scan].set_scan()}') return infos def set_new_batch(self): self.settings = Parameter.create(title='Batch', name='settings', type='group', children=self.params) self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed) status = self.show_tree() return status def parameter_tree_changed(self, param, changes): """ Check for changes in the given (parameter,change,information) tuple list. In case of value changed, update the DAQscan_settings tree consequently. =============== ============================================ ============================== **Parameters** **Type** **Description** *param* instance of pyqtgraph parameter the parameter to be checked *changes* (parameter,change,information) tuple list the current changes state =============== ============================================ ============================== """ for param, change, data in changes: path = self.settings.childPath(param) if change == 'childAdded': pass elif change == 'value': pass elif change == 'parent': pass def show_tree(self): dialog = QtWidgets.QDialog() vlayout = QtWidgets.QVBoxLayout() add_scan = QtWidgets.QPushButton('Add Scan') add_scan.clicked.connect(self.add_scan) self.tree.setParameters(self.settings, showTop=False) vlayout.addWidget(add_scan) vlayout.addWidget(self.tree) dialog.setLayout(vlayout) buttonBox = QtWidgets.QDialogButtonBox(parent=dialog) buttonBox.addButton('Save', buttonBox.AcceptRole) buttonBox.accepted.connect(dialog.accept) buttonBox.addButton('Cancel', buttonBox.RejectRole) buttonBox.rejected.connect(dialog.reject) vlayout.addWidget(buttonBox) dialog.setWindowTitle('Fill in information about this Scan batch') res = dialog.exec() if res == dialog.Accepted: # save managers parameters in a xml file # start = os.path.split(os.path.split(os.path.realpath(__file__))[0])[0] # start = os.path.join("..",'daq_scan') ioxml.parameter_to_xml_file( self.settings, os.path.join(self.batch_path, self.settings.child('filename').value())) return res == dialog.Accepted def add_scan(self, name=None, title=None): if name is None or name is False: name_prefix = 'scan' child_indexes = [int(par.name()[len(name_prefix) + 1:]) for par in self.settings.child('scans').children()] if child_indexes == []: newindex = 0 else: newindex = max(child_indexes) + 1 name = f'{name_prefix}{newindex:02.0f}' title = f'Scan {newindex:02.0f}' child = {'title': title, 'name': name, 'type': 'group', 'removable': True, 'children': params} self.scans[name] = Scanner(actuators=[self.actuators[0]], adaptive_losses=adaptive_losses) self.settings.child('scans').addChild(child) self.settings.child('scans', name, 'modules', 'actuators').setValue(dict(all_items=self.actuators, selected=[self.actuators[0]])) self.settings.child('scans', name, 'modules', 'detectors').setValue(dict(all_items=self.detectors, selected=[self.detectors[0]])) self.settings.child('scans', name).addChild( self.scans[name].settings)
class H5Saver(QObject): status_sig = pyqtSignal(utils.ThreadCommand) new_file_sig = pyqtSignal(bool) params = [ { 'title': 'Save type:', 'name': 'save_type', 'type': 'list', 'values': save_types, 'readonly': True }, { 'title': 'Save 2D datas:', 'name': 'save_2D', 'type': 'bool', 'value': True }, { 'title': 'Save raw datas only:', 'name': 'save_raw_only', 'type': 'bool', 'value': True, 'tooltip': 'if True, will not save extracted ROIs used to do live plotting, only raw datas and the scan \ result will be saved' }, { 'title': 'Do Save:', 'name': 'do_save', 'type': 'bool', 'default': False, 'value': False }, { 'title': 'N saved:', 'name': 'N_saved', 'type': 'int', 'default': 0, 'value': 0, 'visible': False }, { 'title': 'custom_name?:', 'name': 'custom_name', 'type': 'bool', 'default': False, 'value': False }, { 'title': 'show file content?:', 'name': 'show_file', 'type': 'bool', 'default': False, 'value': False }, { 'title': 'Base path:', 'name': 'base_path', 'type': 'browsepath', 'value': 'C:\Data', 'filetype': False, 'readonly': True, }, { 'title': 'Base name:', 'name': 'base_name', 'type': 'str', 'value': 'Scan', 'readonly': True }, { 'title': 'Current scan:', 'name': 'current_scan_name', 'type': 'str', 'value': '', 'readonly': True }, { 'title': 'Current path:', 'name': 'current_scan_path', 'type': 'text', 'value': 'C:\Data', 'readonly': True, 'visible': False }, { 'title': 'h5file:', 'name': 'current_h5_file', 'type': 'text_pb', 'value': '', 'readonly': True }, { 'title': 'Compression options:', 'name': 'compression_options', 'type': 'group', 'children': [ { 'title': 'Compression library:', 'name': 'h5comp_library', 'type': 'list', 'value': 'zlib', 'values': ['zlib', 'lzo', 'bzip2', 'blosc'] }, { 'title': 'Compression level:', 'name': 'h5comp_level', 'type': 'int', 'value': 5, 'min': 0, 'max': 9 }, ] }, ] def __init__(self, h5_file_path=None, h5_file=None, save_type='scan'): """ Initialize the h5Saver object Parameters ---------- h5_file_path: (Path) Path object pointing to the h5_file h5_file: instance of a h5 file as opened using the pytables module save_type: (str) either 'scan', 'detector' or 'custom' """ super(H5Saver, self).__init__() if save_type not in save_types: raise Exception('Invalid saving type') self.h5_file = h5_file self.h5_file_path = h5_file_path self.h5_file_name = None self.logger_array = None self.current_group = None self.current_scan_group = None self.current_scan_name = None self.raw_group = None self.settings = Parameter.create(title='Saving settings', name='save_settings', type='group', children=self.params) self.settings_tree = ParameterTree() self.settings_tree.setMinimumHeight(310) self.settings_tree.setParameters(self.settings, showTop=False) self.settings.child( ('current_h5_file' )).sigActivated.connect(lambda: self.emit_new_file(True)) self.settings.child(('save_type')).setValue(save_type) self.filters = tables.Filters( complevel=self.settings.child('compression_options', 'h5comp_level').value(), complib=self.settings.child('compression_options', 'h5comp_library').value()) # self.settings.child('saving_options', 'save_independent').show(save_type == 'scan') # self.settings.child('saving_options', 'do_save').show(not save_type == 'scan') # self.settings.child('saving_options', 'current_scan_name').show(save_type == 'scan') self.settings.sigTreeStateChanged.connect( self.parameter_tree_changed ) # any changes on the settings will update accordingly the detector def emit_new_file(self, status): self.new_file_sig.emit(status) def init_file(self, update_h5=False, custom_naming=False, addhoc_file_path=None): """ init a new h5 file. Could be a file with a given name (addhoc_file_path) or following the template for scans (datasets) or for detectors or a box to set a custom name (custom naming) Parameters ---------- update_h5: (bool) create a new h5 file with name specified by other parameters if false try to open an existing file or create it if it doesn't exists custom_naming: (bool) if True, a selection file dialog opens to set a new file name addhoc_file_path: (Path) supplied name for the file Returns ------- bool: True if new file has been created, False otherwise """ date = datetime.datetime.now() if addhoc_file_path is None: if not os.path.isdir(self.settings.child(('base_path')).value()): os.mkdir(self.settings.child(('base_path')).value()) # set the filename and path base_name = self.settings.child(('base_name')).value() if not custom_naming: custom_naming = self.settings.child(('custom_name')).value() if not custom_naming: scan_type = self.settings.child( ('save_type')).value() == 'scan' scan_path, current_scan_name, save_path = self.update_file_paths( update_h5) self.current_scan_name = current_scan_name self.settings.child( ('current_scan_name')).setValue(current_scan_name) self.settings.child( ('current_scan_path')).setValue(str(scan_path)) if not scan_type: self.h5_file_path = save_path #will remove the dataset part used for DAQ_scan datas self.h5_file_name = base_name + date.strftime( '_%Y%m%d_%H_%M_%S.h5') else: self.h5_file_path = save_path self.h5_file_name = save_path.name + ".h5" else: self.h5_file_name = utils.select_file(start_path=base_name, save=True, ext='h5') self.h5_file_path = self.h5_file_name.parent else: if isinstance(addhoc_file_path, str): addhoc_file_path = Path(addhoc_file_path) self.h5_file_path = addhoc_file_path.parent self.h5_file_name = addhoc_file_path.name fullpathname = str(self.h5_file_path.joinpath(self.h5_file_name)) self.settings.child(('current_h5_file')).setValue(fullpathname) if update_h5: self.current_scan_group = None scan_group = None if self.current_scan_group is not None: scan_group = self.current_scan_group._v_name if update_h5: self.close_file() self.h5_file = tables.open_file(fullpathname, 'w', title='PyMoDAQ file') self.h5_file.root._v_attrs['pymodaq_version'] = get_version() else: self.close_file() self.h5_file = tables.open_file(fullpathname, 'a', title='PyMoDAQ file') self.raw_group = self.get_set_group(self.h5_file.root, 'Raw_datas', title='Data from PyMoDAQ modules') self.get_set_logger(self.raw_group) if scan_group is not None: self.current_scan_group = self.get_set_group( self.raw_group, scan_group) else: self.current_scan_group = self.get_last_scan() self.raw_group._v_attrs['type'] = self.settings.child( ('save_type')).value() self.h5_file.root._v_attrs['file'] = date.strftime(self.h5_file_name) if update_h5: self.h5_file.root._v_attrs['date'] = date.strftime('%Y%m%d') self.h5_file.root._v_attrs['time'] = date.strftime('%H:%M:%S') return update_h5 def update_file_paths(self, update_h5=False): """ """ try: # set the filename and path base_path = self.settings.child(('base_path')).value() base_name = self.settings.child(('base_name')).value() current_scan = self.settings.child(('current_scan_name')).value() scan_type = self.settings.child(('save_type')).value() == 'scan' if current_scan == '' or update_h5: next_scan_index = 0 update_h5 = True #just started the main program so one should create a new h5 else: next_scan_index = self.get_scan_index() scan_path, current_filename, dataset_path = utils.set_current_scan_path( base_path, base_name, update_h5, next_scan_index, create_dataset_folder=scan_type) self.settings.child(('current_scan_path')).setValue(str(scan_path)) return scan_path, current_filename, dataset_path except Exception as e: print(e) def get_last_scan(self): groups = [ group for group in list(self.raw_group._v_groups) if 'Scan' in group ] groups.sort() if len(groups) != 0: scan_group = self.h5_file.get_node(self.raw_group, groups[-1]) else: scan_group = None return scan_group def get_scan_index(self): try: if self.current_scan_group is None: return 0 else: groups = [ group for group in list(self.raw_group._v_groups) if 'Scan' in group ] groups.sort() flag = False for child in list( self.h5_file.get_node(self.raw_group, groups[-1])._v_groups): if 'scan' in child: return len(groups) return 0 except Exception as e: return 0 def load_file(self, base_path=None, file_path=None): if base_path is None: base_path = self.settings.child('base_path').value() if not os.path.isdir(base_path): base_path = None if file_path is None: file_path = utils.select_file(base_path, save=False, ext='h5') if not isinstance(file_path, Path): file_path = Path(file_path) if not file_path.suffix == 'h5': raise IOError('Invalid file type, should be a h5 file') self.init_file(addhoc_file_path=file_path) def close_file(self): try: if self.h5_file is not None: self.h5_file.flush() if self.h5_file.isopen: self.h5_file.close() except Exception as e: print(e) #no big deal def is_node_in_group(self, where, name): """ Check if a given node with name is in the group defined by where (comparison on lower case strings) Parameters ---------- where: (str or node) path or parent node instance name: (str) group node name Returns ------- bool: True if node exists, False otherwise """ nodes_names = [ node._v_name.lower() for node in self.h5_file.list_nodes(where) ] return name.lower() in nodes_names def get_set_logger(self, where): logger = 'Logger' if not logger in list(self.h5_file.get_node(where)._v_children.keys()): # check if logger node exist text_atom = tables.atom.ObjectAtom() self.logger_array = self.h5_file.create_vlarray(where, logger, atom=text_atom) self.logger_array._v_attrs['type'] = 'log' else: self.logger_array = self.h5_file.get_node(where, name=logger) return self.logger_array def get_set_group(self, where, name, title=''): """ Retrieve or create (if absent) a node group Parameters ---------- where: (str or node) path or parent node instance name: (str) group node name title: (str) node title Returns ------- node: group node """ if not name in list(self.h5_file.get_node(where)._v_children.keys()): self.current_group = self.h5_file.create_group(where, name, title) else: self.current_group = self.h5_file.get_node(where, name) return self.current_group def add_data_group(self, where, group_data_type, title='', settings_as_xml='', metadata=dict([])): if group_data_type not in group_data_types: raise Exception('Invalid data group type') group = self.add_group(group_data_type, '', where, title, settings_as_xml, metadata) return group def add_navigation_axis(self, data, parent_group, axis='x_axis', enlargeable=False, title='', metadata=dict([])): """ Create carray for navigation axis within a scan Parameters ---------- data: (ndarray) of dimension 1 parent_group: (str or node) parent node where to save new data axis: (str) either x_axis or y_axis """ if axis not in ['x_axis', 'y_axis', 'z_axis']: raise Exception('Invalid navigation axis name') array = self.add_array(parent_group, 'scan_{:s}'.format(axis), 'navigation_axis', data_shape=data.shape, data_dimension='1D', array_to_save=data, enlargeable=enlargeable, title=title, metadata=metadata) return array def add_data_live_scan(self, channel_group, data_dict, scan_type='scan1D', title=''): shape, dimension, size = utils.get_data_dimension( data_dict['data'], scan_type=scan_type, remove_scan_dimension=True) data_array = self.add_array(channel_group, 'Data', 'data', array_type=np.float, title=title, data_shape=shape, data_dimension=dimension, scan_type=scan_type, array_to_save=data_dict['data']) if 'x_axis' in data_dict: if not isinstance(data_dict['x_axis'], dict): array_to_save = data_dict['x_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['x_axis']) array_to_save = tmp_dict.pop('data') if 'x_axis' in data_dict: array = self.add_array(channel_group, 'x_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) if 'y_axis' in data_dict: if not isinstance(data_dict['y_axis'], dict): array_to_save = data_dict['y_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['y_axis']) array_to_save = tmp_dict.pop('data') if 'y_axis' in data_dict: array = self.add_array(channel_group, 'y_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) return data_array def add_data(self, channel_group, data_dict, scan_type='scan1D', scan_shape=[], title='', enlargeable=False, init=False, add_scan_dim=False): shape, dimension, size = utils.get_data_dimension(data_dict['data']) data_array = self.add_array(channel_group, 'Data', 'data', array_type=np.float, title=title, data_shape=shape, enlargeable=enlargeable, data_dimension=dimension, scan_type=scan_type, scan_shape=scan_shape, array_to_save=data_dict['data'], init=init, add_scan_dim=add_scan_dim) if 'x_axis' in data_dict: if not isinstance(data_dict['x_axis'], dict): array_to_save = data_dict['x_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['x_axis']) array_to_save = tmp_dict.pop('data') array = self.add_array(channel_group, 'x_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) if 'y_axis' in data_dict: if not isinstance(data_dict['y_axis'], dict): array_to_save = data_dict['y_axis'] tmp_dict = dict(label='', units='') else: tmp_dict = copy.deepcopy(data_dict['y_axis']) array_to_save = tmp_dict.pop('data') array = self.add_array(channel_group, 'y_axis', 'axis', array_type=np.float, array_to_save=array_to_save, enlargeable=False, data_dimension='1D', metadata=tmp_dict) self.h5_file.flush() return data_array def add_array(self, where, name, data_type, data_shape=(1, ), data_dimension='0D', scan_type='', scan_shape=[], title='', array_to_save=None, array_type=np.float, enlargeable=False, metadata=dict([]), init=False, add_scan_dim=False): if data_dimension not in data_dimensions: raise Exception('Invalid data dimension') if data_type not in data_types: raise Exception('Invalid data type') if scan_type != '': scan_type = utils.uncapitalize(scan_type) if scan_type not in scan_types: raise Exception('Invalid scan type') if enlargeable: shape = [0] if data_shape != (1, ): shape.extend(data_shape) shape = tuple(shape) array = self.h5_file.create_earray(where, utils.capitalize(name), tables.Atom.from_dtype( np.dtype(array_type)), shape=shape, title=title, filters=self.filters) array._v_attrs['shape'] = shape else: if add_scan_dim: #means it is an array initialization to zero shape = scan_shape[:] shape.extend(data_shape) if init or array_to_save is None: array_to_save = np.zeros(shape) array = self.h5_file.create_carray(where, utils.capitalize(name), obj=array_to_save, title=title, filters=self.filters) array._v_attrs['shape'] = array_to_save.shape array._v_attrs['type'] = data_type array._v_attrs['data_dimension'] = data_dimension array._v_attrs['scan_type'] = scan_type for metadat in metadata: array._v_attrs[metadat] = metadata[metadat] return array def append(self, array, data): if not (isinstance(array, tables.vlarray.VLArray) or isinstance(array, tables.earray.EArray)): raise Exception('This array cannot be appended') if isinstance(data, np.ndarray): if data.shape != (1, ): shape = [1] shape.extend(data.shape) array.append(data.reshape(shape)) else: array.append(data) else: array.append(data) sh = list(array._v_attrs['shape']) sh[0] += 1 array._v_attrs['shape'] = tuple(sh) def add_group(self, group_name, group_type, where, title='', settings_as_xml='', metadata=dict([])): """ Add a node in the h5 file tree of the group type Parameters ---------- group_name: (str) a custom name for this group group_type: (str) one of the possible values of **group_types** where: (str or node) parent node where to create the new group settings_as_xml: (str) XML string containing Parameters representation (see custom_Tree) metadata: (dict) extra metadata to be saved with this new group node Returns ------- (node): newly created group node """ if group_type not in group_types: raise Exception('Invalid group type') try: node = self.h5_file.get_node(where, utils.capitalize(group_name)) except tables.NoSuchNodeError as e: node = None if node is None: node = self.get_set_group(where, utils.capitalize(group_name), title) node._v_attrs['settings'] = settings_as_xml node._v_attrs['type'] = group_type.lower() for metadat in metadata: node._v_attrs[metadat] = metadata[metadat] return node def add_incremental_group(self, group_type, where, title='', settings_as_xml='', metadata=dict([])): """ Add a node in the h5 file tree of the group type with an increment in the given name Parameters ---------- group_type: (str) one of the possible values of **group_types** where: (str or node) parent node where to create the new group settings_as_xml: (str) XML string containing Parameters representation (see custom_Tree) metadata: (dict) extra metadata to be saved with this new group node Returns ------- (node): newly created group node """ if group_type not in group_types: raise Exception('Invalid group type') nodes = list(self.h5_file.get_node(where)._v_children.keys()) nodes_tmp = [] for node in nodes: if utils.capitalize(group_type) in node: nodes_tmp.append(node) nodes_tmp.sort() if len(nodes_tmp) == 0: ind_group = -1 else: ind_group = int(nodes_tmp[-1][-3:]) group = self.get_set_group( where, utils.capitalize(group_type) + '{:03d}'.format(ind_group + 1), title) group._v_attrs['settings'] = settings_as_xml if group_type.lower() != 'ch': group._v_attrs['type'] = group_type.lower() else: group._v_attrs['type'] = '' for metadat in metadata: group._v_attrs[metadat] = metadata[metadat] return group def add_det_group(self, where, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type detector See Also ------- add_incremental_group """ group = self.add_incremental_group('detector', where, title, settings_as_xml, metadata) return group def add_CH_group(self, where, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type channel See Also ------- add_incremental_group """ group = self.add_incremental_group('ch', where, title, settings_as_xml, metadata) return group def add_live_scan_group(self, where, dimensionality, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type live scan See Also ------- add_incremental_group """ group = self.add_group('Live_scan_{:s}'.format(dimensionality), '', where, title=title, settings_as_xml=settings_as_xml, metadata=metadata) return group def add_scan_group(self, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type scan See Also ------- add_incremental_group """ if self.current_scan_group is not None: if list(self.current_scan_group._v_children) == []: new_scan = False else: new_scan = True else: new_scan = True if new_scan: self.current_scan_group = self.add_incremental_group( 'scan', self.raw_group, title, settings_as_xml, metadata) self.current_scan_group._v_attrs['description'] = '' self.settings.child(('current_scan_name')).setValue( self.current_scan_group._v_name) return self.current_scan_group def add_move_group(self, where, title='', settings_as_xml='', metadata=dict([])): """ Add a new group of type move See Also ------- add_incremental_group """ group = self.add_incremental_group('move', where, title, settings_as_xml, metadata) return group def parameter_tree_changed(self, param, changes): for param, change, data in changes: path = self.settings.childPath(param) if change == 'childAdded': pass elif change == 'value': if param.name() == 'show_file': param.setValue(False) self.show_file_content() elif param.name() == 'base_path': try: if not os.path.isdir(param.value()): os.mkdir(param.value()) except: self.update_status( "The base path couldn't be set, please check your options" ) elif param.name() in custom_tree.iter_children( self.settings.child(('compression_options')), []): self.filters = tables.Filters( complevel=self.settings.child('compression_options', 'h5comp_level').value(), complib=self.settings.child('compression_options', 'h5comp_library').value()) elif change == 'parent': pass def update_status(self, status): self.status_sig.emit( utils.ThreadCommand("Update_Status", [status, 'log'])) def show_file_content(self): form = QtWidgets.QWidget() if not self.h5_file.isopen: if self.h5_file_path.exists(): self.analysis_prog = H5Browser(form, h5file=self.h5_file_path) else: raise FileExistsError('no File presents') else: self.analysis_prog = H5Browser(form, h5file=self.h5_file) form.show()