diff --git a/xfel/command_line/upload_mtz.py b/xfel/command_line/upload_mtz.py index 9b8d557788f..561f04650cc 100644 --- a/xfel/command_line/upload_mtz.py +++ b/xfel/command_line/upload_mtz.py @@ -27,6 +27,11 @@ .help = Id string of the destination folder. If the folder url is \ https://drive.google.com/drive/u/0/folders/1NlJkfL6CMd1NZIl6Duy23i4G1RM9cNH- , \ then the id is 1NlJkfL6CMd1NZIl6Duy23i4G1RM9cNH- . + lock_file = None + .type = path + .help = Path to lock file for coordinating concurrent uploads. \ +If None, defaults to ~/.upload_mtz.lock. Set this to a writable location \ +if the home directory is not accessible (e.g., on compute nodes). } input { mtz_file = None @@ -78,9 +83,14 @@ def _get_log_fname(mtz_fname): class Locker: """ See https://stackoverflow.com/a/60214222 """ + def __init__(self, lock_file_path=None): + if lock_file_path is None: + lock_file_path = os.path.expanduser('~/.upload_mtz.lock') + self.lock_file_path = lock_file_path + def __enter__(self): try: - self.fp = open(os.path.expanduser('~/.upload_mtz.lock'), 'wb') + self.fp = open(self.lock_file_path, 'wb') except FileNotFoundError: self.fp = None if fcntl and self.fp is not None: @@ -98,7 +108,7 @@ class pydrive2_interface: destination folder. """ - def __init__(self, cred_file, folder_id): + def __init__(self, cred_file, folder_id, lock_file=None): try: from pydrive2.auth import ServiceAccountCredentials, GoogleAuth from pydrive2.drive import GoogleDrive @@ -111,11 +121,12 @@ def __init__(self, cred_file, folder_id): ) self.drive = GoogleDrive(gauth) self.top_folder_id = folder_id + self.lock_file = lock_file def _fetch_or_create_folder(self, fname, parent_id): - with Locker(): + with Locker(self.lock_file): query = { "q": "'{}' in parents and title='{}'".format(parent_id, fname), "supportsTeamDrives": "true", @@ -209,7 +220,8 @@ def run_with_preparsed(params): drive = pydrive2_interface( params.drive.credential_file, - params.drive.shared_folder_id + params.drive.shared_folder_id, + lock_file=params.drive.lock_file ) folders = [dataset_root, version_str] files = [log_path] diff --git a/xfel/ui/__init__.py b/xfel/ui/__init__.py index 60dad313742..dd0062afd69 100644 --- a/xfel/ui/__init__.py +++ b/xfel/ui/__init__.py @@ -1,5 +1,5 @@ from __future__ import absolute_import, division, print_function -import os +import os, sys from iotbx.phil import parse from libtbx.utils import Sorry @@ -148,7 +148,6 @@ master_phil_scope = parse(master_phil_str + db_phil_str, process_includes=True) settings_dir = os.path.join(os.path.expanduser('~'), '.cctbx.xfel') -settings_file = os.path.join(settings_dir, 'settings.phil') known_dials_dispatchers = { 'cctbx.xfel.xtc_process': 'xfel.command_line.xtc_process', @@ -167,6 +166,13 @@ def load_phil_scope_from_dispatcher(dispatcher): return phil_scope def load_cached_settings(scope=None, extract=True): + # Determine which settings file to use + settings_file = os.environ.get('CCTBX_XFEL_SETTINGS') + if settings_file is None: + settings_file = os.path.join(settings_dir, 'settings.phil') + + print('Load settings from', settings_file) + if scope is None: scope = master_phil_scope if os.path.exists(settings_file): @@ -186,6 +192,11 @@ def load_cached_settings(scope=None, extract=True): return scope def save_cached_settings(params): + # Save to the file specified by environment or default + settings_file = os.environ.get('CCTBX_XFEL_SETTINGS') + if settings_file is None: + settings_file = os.path.join(settings_dir, 'settings.phil') + if not os.path.exists(settings_dir): os.makedirs(settings_dir) diff --git a/xfel/ui/components/submission_tracker.py b/xfel/ui/components/submission_tracker.py index 1bf6075aba4..1b0e1cf818e 100644 --- a/xfel/ui/components/submission_tracker.py +++ b/xfel/ui/components/submission_tracker.py @@ -170,6 +170,8 @@ def __init__(self, queueing_system): "queue interrogator not implemented for %s queueing system"%self.queueing_system) def read_result(self, log_path): + if not log_path: + return "Error reading log file, no path to log" result = easy_run.fully_buffered(command=self.command % log_path) status = "\n".join(result.stdout_lines) error = "\n".join(result.stderr_lines) diff --git a/xfel/ui/components/xfel_gui_controls.py b/xfel/ui/components/xfel_gui_controls.py index c79ee654c0d..504d00cade2 100644 --- a/xfel/ui/components/xfel_gui_controls.py +++ b/xfel/ui/components/xfel_gui_controls.py @@ -326,6 +326,44 @@ def __init__(self, parent, output_box.AddGrowableCol(1, 1) self.SetSizer(output_box) +class ComboButtonCtrl(CtrlBase): + ''' Generic panel that will place a combo control, with a label and an + optional large button, and an optional bitmap button''' + + def __init__(self, parent, + label='', label_size=(100, -1), + label_style='normal', + text_style=wx.TE_LEFT, + ctrl_size=(200, -1), + big_button=False, + big_button_label='Browse...', + big_button_size=wx.DefaultSize, + ghost_button=True, + value='', **kwargs): + + CtrlBase.__init__(self, parent=parent, label_style=label_style, **kwargs) + + output_box = wx.FlexGridSizer(1, 4, 0, 10) + self.txt = wx.StaticText(self, label=label, size=label_size) + self.txt.SetFont(self.font) + output_box.Add(self.txt) + + self.ctr = wx.ComboBox(self, name=self.Name + "_ctr", style=text_style, size=ctrl_size) + self.ctr.SetValue(value) + output_box.Add(self.ctr, flag=wx.EXPAND) + + self.btn_big = Button(self, name=self.Name + "_btn_big", label=big_button_label, size=big_button_size) + if ghost_button: + output_box.Add(self.btn_big, flag=wx.RESERVE_SPACE_EVEN_IF_HIDDEN) + else: + output_box.Add(self.btn_big) + + if not big_button: + self.btn_big.Hide() + + output_box.AddGrowableCol(1, 1) + self.SetSizer(output_box) + class TwoButtonCtrl(CtrlBase): ''' Generic panel that will place a text control, with a label and an optional large button, and an optional bitmap button''' diff --git a/xfel/ui/components/xfel_gui_dialogs.py b/xfel/ui/components/xfel_gui_dialogs.py index f8738aa02b0..a0d3eba124c 100644 --- a/xfel/ui/components/xfel_gui_dialogs.py +++ b/xfel/ui/components/xfel_gui_dialogs.py @@ -14,6 +14,7 @@ import wx from wx.lib.mixins.listctrl import TextEditMixin, getListCtrlSelection from wx.lib.scrolledpanel import ScrolledPanel +from iotbx.phil import parse from xfel.ui.db.task import task_types import numpy as np @@ -112,6 +113,21 @@ def __init__(self, parent, params, self.main_sizer = wx.BoxSizer(wx.VERTICAL) + # Project management control + self.project = gctr.ComboButtonCtrl(self, + name='project', + label='Project', + label_style='bold', + label_size=(150, -1), + big_button=True, + big_button_size=(160, -1), + ) +# value=self.params.experiment_tag if self.params.experiment_tag is not None else "") + self.main_sizer.Add(self.project, + flag=wx.EXPAND | wx.ALL, + border=10) + self.project.Hide() # WIP + # Experiment tag and DB Credentials button self.db_cred = gctr.TextButtonCtrl(self, name='db_cred', @@ -120,7 +136,7 @@ def __init__(self, parent, params, label_size=(150, -1), big_button=True, big_button_label='DB Credentials...', - big_button_size=(130, -1), + big_button_size=(160, -1), value=self.params.experiment_tag if self.params.experiment_tag is not None else "") self.main_sizer.Add(self.db_cred, flag=wx.EXPAND | wx.ALL, @@ -2102,7 +2118,7 @@ def __getattr__(self, item): elif item in ["extra_phil_str", "calib_dir", "dark_avg_path", "dark_stddev_path", "gain_map_path", "beamx", "beamy", "gain_mask_level", "untrusted_pixel_mask_path", "binning", "energy", "wavelength_offset", "spectrum_eV_per_pixel", "spectrum_eV_offset", - "comment", "config_str", "extra_format_str"]: + "comment", "extra_format_str"]: return None else: raise AttributeError(item) @@ -2132,13 +2148,6 @@ def __getattr__(self, item): # Run block start / end points (choice widgets) - if self.is_lcls: - self.config_panel = wx.Panel(self) - config_box = wx.StaticBox(self.config_panel, label='Configuration') - self.config_sizer = wx.StaticBoxSizer(config_box) - self.config_panel.SetSizer(self.config_sizer) - self.config_panel.Hide() - self.phil_panel = wx.Panel(self) phil_box = wx.StaticBox(self.phil_panel, label='Extra phil parameters') self.phil_sizer = wx.StaticBoxSizer(phil_box) @@ -2156,18 +2165,6 @@ def __getattr__(self, item): self.runblock_sizer = wx.BoxSizer(wx.VERTICAL) self.runblock_panel.SetSizer(self.runblock_sizer) - if self.is_lcls: - # Configuration text ctrl (user can put in anything they want) - self.config = gctr.PHILBox(self.config_panel, - btn_import=True, - btn_import_label='Import Config', - btn_export=False, - btn_default=True, - btn_default_label='Default Config', - ctr_size=(-1, 100), - ctr_value=str(block.config_str)) - self.config_sizer.Add(self.config, 1, flag=wx.EXPAND | wx.ALL, border=10) - # Extra phil self.phil = gctr.PHILBox(self.phil_panel, btn_import=True, @@ -2304,7 +2301,6 @@ def __getattr__(self, item): self.main_sizer.Add(self.phil_panel, flag=wx.EXPAND | wx.ALL, border=10) if self.is_lcls: - self.main_sizer.Add(self.config_panel, flag=wx.EXPAND | wx.ALL, border=10) self.main_sizer.Add(self.format_panel, flag=wx.EXPAND | wx.ALL, border=10) self.runblock_box_sizer.Add(self.runblock_panel) self.main_sizer.Add(self.runblock_box_sizer, flag=wx.EXPAND | wx.ALL, @@ -2325,7 +2321,6 @@ def __getattr__(self, item): id=self.untrusted_path.btn_big.GetId()) self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK) - self.fill_in_fields() self.configure_controls() self.Layout() @@ -2339,21 +2334,6 @@ def onAutoEnd(self, e): def onSpecifyEnd(self, e): self.runblocks_end.Enable() - def onImportConfig(self, e): - cfg_dlg = wx.FileDialog(self, - message="Load configuration file", - defaultDir=os.curdir, - defaultFile="*", - wildcard="*", - style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST, - ) - if cfg_dlg.ShowModal() == wx.ID_OK: - config_file = cfg_dlg.GetPaths()[0] - with open(config_file, 'r') as cfg: - cfg_contents = cfg.read() - self.config.ctr.SetValue(cfg_contents) - cfg_dlg.Destroy() - def onImportPhil(self, e): phil_dlg = wx.FileDialog(self, message="Load phil file", @@ -2384,10 +2364,6 @@ def onImportFormat(self, e): self.format.ctr.SetValue(phil_contents) phil_dlg.Destroy() - def onDefaultConfig(self, e): - # TODO: Generate default config parameters (re-do based on pickle / CBF) - pass - def onOK(self, e): try: first = int(self.runblocks_start.ctr.GetValue()) @@ -2439,7 +2415,6 @@ def is_none(string): rg_dict['wavelength_offset']=self.wavelength_offset.wavelength_offset.GetValue() rg_dict['binning']=self.bin_nrg_gain.binning.GetValue() rg_dict['detector_address']=self.address.ctr.GetValue() - rg_dict['config_str']=self.config.ctr.GetValue() rg_dict['extra_format_str']=self.format.ctr.GetValue() rg_dict['spectrum_eV_per_pixel']=self.spectrum_calibration.spectrum_eV_per_pixel.GetValue() rg_dict['spectrum_eV_offset']=self.spectrum_calibration.spectrum_eV_offset.GetValue() @@ -2494,7 +2469,6 @@ def fill_in_fields(self): self.phil.ctr.SetValue(str(last.extra_phil_str)) if self.is_lcls: self.address.ctr.SetValue(str(last.detector_address)) - self.config.ctr.SetValue(str(last.config_str)) self.format.ctr.SetValue(str(last.extra_format_str)) self.beam_xyz.DetZ.SetValue(str(last.detz_parameter)) self.beam_xyz.X.SetValue(str(last.beamx)) @@ -2675,7 +2649,7 @@ class TrialDialog(BaseDialog): def __init__(self, parent, db, new=True, trial=None, - label_style='bold', + label_style='normal', content_style='normal', *args, **kwargs): @@ -2704,19 +2678,136 @@ def __init__(self, parent, db, self.trial_info = gctr.TwoButtonCtrl(self, label='Trial number:', label_size=(100, -1), - label_style='bold', + label_style='normal', button1=True, button1_label='Import PHIL', button1_size=(120, -1), button2=True, - button2_label='Default PHIL', + button2_label='Edit PHIL' if new else 'Show PHIL', button2_size=(120, -1), value="{}".format(trial_number)) self.trial_comment = gctr.TextButtonCtrl(self, label='Comment:', label_size=(100, -1), - label_style='bold', + label_style='normal', + ghost_button=False) + + self.overall_panel = wx.Panel(self) + overall_box = wx.StaticBox(self.overall_panel, label='Overall parameters') + self.overall_sizer = wx.StaticBoxSizer(overall_box) + self.overall_panel.SetSizer(self.overall_sizer) + + self.chk_find_spots = wx.CheckBox(self.overall_panel, + label='Find spots') + self.chk_index = wx.CheckBox(self.overall_panel, + label='Index') + self.chk_integrate = wx.CheckBox(self.overall_panel, + label='Integrate') + self.min_spots = gctr.TextButtonCtrl(self.overall_panel, + label='Min spots', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.overall_ctrl_sizer = wx.FlexGridSizer(1, 4, 10, 20) + self.overall_ctrl_sizer.Add(self.chk_find_spots, flag=wx.ALL, border=10) + self.overall_ctrl_sizer.Add(self.chk_index, flag=wx.ALL, border=10) + self.overall_ctrl_sizer.Add(self.chk_integrate, flag=wx.ALL, border=10) + self.overall_ctrl_sizer.Add(self.min_spots, flag=wx.ALL, border=10) + self.overall_sizer.Add(self.overall_ctrl_sizer) + + self.spotfinding_panel = wx.Panel(self) + spotfinding_box = wx.StaticBox(self.spotfinding_panel, label='Spotfinding parameters') + self.spotfinding_sizer = wx.StaticBoxSizer(spotfinding_box) + self.spotfinding_panel.SetSizer(self.spotfinding_sizer) + + self.min_spot_size = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Min spot size', + label_size=(-1, -1), + label_style='normal', ghost_button=False) + self.max_spot_size = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Max spot size', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.sigma_background = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Sigma background', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.sigma_strong = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Sigma strong', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.global_threshold = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Global threshold', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.gain = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Gain', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.kernel_size = gctr.TextButtonCtrl(self.spotfinding_panel, + label='Kernel size', + label_size=(-1, -1), + label_style='normal', + ghost_button=False) + self.threshold_algorithm = gctr.ChoiceCtrl(self.spotfinding_panel, + label='Threshold algorithm:', + label_size=(200, -1), + label_style='normal', + ctrl_size=(200, -1), + choices=['dispersion', 'dispersion_extended', 'radial_profile']) + + self.spotfinding_ctrl_sizer = wx.FlexGridSizer(4, 2, 10, 10) + self.spotfinding_ctrl_sizer.Add(self.min_spot_size, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.max_spot_size, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.sigma_background, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.sigma_strong, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.global_threshold, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.gain, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.kernel_size, flag=wx.ALL, border=10) + self.spotfinding_ctrl_sizer.Add(self.threshold_algorithm, flag=wx.ALL, border=10) + self.spotfinding_sizer.Add(self.spotfinding_ctrl_sizer) + + self.indexing_panel = wx.Panel(self) + indexing_box = wx.StaticBox(self.indexing_panel, label='Indexing parameters') + self.indexing_sizer = wx.StaticBoxSizer(indexing_box) + self.indexing_panel.SetSizer(self.indexing_sizer) + + self.unit_cell = gctr.TextButtonCtrl(self.indexing_panel, + label='Unit cell:', + label_size=(100, -1), + label_style='normal', + ghost_button=False) + self.space_group = gctr.TextButtonCtrl(self.indexing_panel, + label='Space group:', + label_size=(150, -1), + label_style='normal', + ghost_button=False) + self.d_min_indexing = gctr.TextButtonCtrl(self.indexing_panel, + label='d_min indexing:', + label_size=(150, -1), + label_style='normal', + ghost_button=False) + self.max_lattices = gctr.TextButtonCtrl(self.indexing_panel, + label='Max lattices:', + label_size=(150, -1), + label_style='normal', + ghost_button=False) + self.chk_subsampling = wx.CheckBox(self.indexing_panel, + label='Reflection subsampling') + + self.indexing_ctrl_sizer = wx.FlexGridSizer(3, 2, 10, 10) + self.indexing_ctrl_sizer.Add(self.unit_cell, flag=wx.ALL, border=10) + self.indexing_ctrl_sizer.Add(self.space_group, flag=wx.ALL, border=10) + self.indexing_ctrl_sizer.Add(self.d_min_indexing, flag=wx.ALL, border=10) + self.indexing_ctrl_sizer.Add(self.max_lattices, flag=wx.ALL, border=10) + self.indexing_ctrl_sizer.Add(self.chk_subsampling, flag=wx.ALL, border=10) + self.indexing_sizer.Add(self.indexing_ctrl_sizer) self.phil_box = gctr.RichTextCtrl(self, style=wx.VSCROLL, size=(-1, 400)) @@ -2733,7 +2824,7 @@ def __init__(self, parent, db, name='trial_throttle', label='Percent events processed:', label_size=(180, -1), - label_style='bold', + label_style='normal', ctrl_size=(150, -1), ctrl_value='100', ctrl_min=1, @@ -2742,7 +2833,7 @@ def __init__(self, parent, db, name='trial_num_bins', label='Number of bins:', label_size=(180, -1), - label_style='bold', + label_style='normal', ctrl_size=(150, -1), ctrl_value='20', ctrl_min=1, @@ -2753,7 +2844,7 @@ def __init__(self, parent, db, label='High res. limit ({}):' ''.format(u'\N{ANGSTROM SIGN}'.encode('utf-8')), label_size=(180, -1), - label_style='bold', + label_style='normal', ctrl_size=(150, -1), ctrl_value=str(d_min), ctrl_min=0.1, @@ -2775,9 +2866,9 @@ def __init__(self, parent, db, self.main_sizer.Add(self.trial_comment, flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, border=10) - self.main_sizer.Add(self.phil_box, 1, - flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, - border=10) + self.main_sizer.Add(self.overall_panel, flag=wx.EXPAND | wx.ALL, border=10) + self.main_sizer.Add(self.spotfinding_panel, flag=wx.EXPAND | wx.ALL, border=10) + self.main_sizer.Add(self.indexing_panel, flag=wx.EXPAND | wx.ALL, border=10) self.main_sizer.Add(self.option_sizer, flag=wx.EXPAND | wx.ALL, border=10) @@ -2814,10 +2905,8 @@ def __init__(self, parent, db, # Disable controls for viewing self.trial_info.button1.Disable() - self.trial_info.button2.Disable() self.trial_info.ctr.SetEditable(False) self.copy_runblocks.Hide() - self.phil_box.SetEditable(False) self.throttle.ctr.Disable() self.num_bins.ctr.Disable() self.d_min.ctr.Disable() @@ -2826,14 +2915,72 @@ def __init__(self, parent, db, target_phil_str = "" if process_percent is None: process_percent = 100 - self.phil_box.SetValue(target_phil_str) + + dispatcher = self.db.params.dispatcher + from xfel.ui import load_phil_scope_from_dispatcher + self.phil_scope = load_phil_scope_from_dispatcher(dispatcher) + self.working_phil_scope = self.phil_scope.fetch(parse(target_phil_str)) + + self.sync_controls() + self.throttle.ctr.SetValue(process_percent) # Bindings self.Bind(wx.EVT_BUTTON, self.onBrowse, self.trial_info.button1) - self.Bind(wx.EVT_BUTTON, self.onDefault, self.trial_info.button2) + self.Bind(wx.EVT_BUTTON, self.onEdit, self.trial_info.button2) self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK) + def sync_controls(self): + params = self.working_phil_scope.extract() + self.chk_find_spots.SetValue(params.dispatch.find_spots) + self.chk_index.SetValue(params.dispatch.index) + self.chk_integrate.SetValue(params.dispatch.integrate) + set_value(self.min_spots.ctr, params.dispatch.hit_finder.minimum_number_of_reflections) + + if params.indexing.known_symmetry.unit_cell: + self.unit_cell.ctr.SetValue(str(params.indexing.known_symmetry.unit_cell).strip('()')) + else: + self.unit_cell.ctr.SetValue("") + if params.indexing.known_symmetry.space_group: + self.space_group.ctr.SetValue(str(params.indexing.known_symmetry.space_group)) + else: + self.space_group.ctr.SetValue("") + + def sync_phil_scope(self): + trial_phil = f""" + dispatch {{ + find_spots = {self.chk_find_spots.GetValue()} + index = {self.chk_index.GetValue()} + integrate = {self.chk_integrate.GetValue()} + hit_finder {{ + minimum_number_of_reflections = {str_or_none(self.min_spots.ctr)} + }} + }} + indexing {{ + known_symmetry {{ + unit_cell = {self.unit_cell.ctr.GetValue()} + space_group = {self.space_group.ctr.GetValue()} + }} + }} + """ + params, msg = self.parse_trial_phil(trial_phil) + + if msg is None: + unit_cell = params.indexing.known_symmetry.unit_cell + space_group = params.indexing.known_symmetry.space_group + if unit_cell and space_group and space_group.group().is_compatible_unit_cell(unit_cell): + return True + else: + msg = "Unit cell is incompatible with space group" + + msg += '\nFix the parameters and try again' + msgdlg = wx.MessageDialog(self, + message=msg, + caption='Warning', + style=wx.OK | wx.ICON_EXCLAMATION) + msgdlg.ShowModal() + return False + def onBrowse(self, e): ''' Open dialog for selecting PHIL file ''' @@ -2848,48 +2995,62 @@ def onBrowse(self, e): target_file = load_dlg.GetPaths()[0] with open(target_file, 'r') as phil_file: phil_file_contents = phil_file.read() - self.phil_box.SetValue(phil_file_contents) - load_dlg.Destroy() + _, msg = self.parse_trial_phil(phil_file_contents) - def onDefault(self, e): - # TODO: Generate default PHIL parameters - pass + if msg is not None: + self.sync_controls(phil_file_contents) + else: + msg += '\nFix the parameters in the file and reload' + msgdlg = wx.MessageDialog(self, + message=msg, + caption='Warning', + style=wx.OK | wx.ICON_EXCLAMATION) + msgdlg.ShowModal() - def onOK(self, e): - if self.new: - target_phil_str = self.phil_box.GetValue() + load_dlg.Destroy() - # Parameter validation - dispatcher = self.db.params.dispatcher - from xfel.ui import load_phil_scope_from_dispatcher - phil_scope = load_phil_scope_from_dispatcher(dispatcher) + def onEdit(self, e): + if self.new and not self.sync_phil_scope(): + return + edit_phil_dlg = EditPhilDialog(self, + db=self.db, + read_only=not self.new, + phil_scope=self.phil_scope, + working_phil_scope=self.working_phil_scope) + edit_phil_dlg.Fit() + + if edit_phil_dlg.ShowModal() == wx.ID_OK: + self.parse_trial_phil(edit_phil_dlg.phil_box.GetValue()) + self.sync_controls() + + def parse_trial_phil(self, target_phil_str): + # Parameter validation + msg = None + try: + working_phil_scope, unused = self.working_phil_scope.fetch(parse(target_phil_str), track_unused_definitions = True) + except Exception as e: + msg = '\nParameters incompatible with %s dispatcher:\n%s\n' % (self.db.params.dispatcher, str(e)) + else: + if len(unused) > 0: + msg = [str(item) for item in unused] + msg = '\n'.join([' %s' % line for line in msg]) + msg = 'The following definitions were not recognized:\n%s\n' % msg - from iotbx.phil import parse - msg = None try: - trial_params, unused = phil_scope.fetch(parse(target_phil_str), track_unused_definitions = True) + params = working_phil_scope.extract() except Exception as e: - msg = '\nParameters incompatible with %s dispatcher:\n%s\n' % (dispatcher, str(e)) + if msg is None: msg = "" + msg += '\nOne or more values could not be parsed:\n%s\n' % str(e) + params = None else: - if len(unused) > 0: - msg = [str(item) for item in unused] - msg = '\n'.join([' %s' % line for line in msg]) - msg = 'The following definitions were not recognized:\n%s\n' % msg - - try: - params = trial_params.extract() - except Exception as e: - if msg is None: msg = "" - msg += '\nOne or more values could not be parsed:\n%s\n' % str(e) + self.working_phil_scope = working_phil_scope + return params, msg - if msg is not None: - msg += '\nFix the parameters and press OK again' - msgdlg = wx.MessageDialog(self, - message=msg, - caption='Warning', - style=wx.OK | wx.ICON_EXCLAMATION) - msgdlg.ShowModal() + def onOK(self, e): + if self.new: + if not self.sync_phil_scope(): return + target_phil_str = self.phil_scope.fetch_diff(self.working_phil_scope).as_str() comment = self.trial_comment.ctr.GetValue() process_percent = int(self.throttle.ctr.GetValue()) @@ -2925,6 +3086,92 @@ def onOK(self, e): self.trial.comment = self.trial_comment.ctr.GetValue() e.Skip() +class EditPhilDialog(BaseDialog): + def __init__(self, parent, db, + read_only=False, + phil_scope=None, + working_phil_scope=None, + label_style='normal', + content_style='normal', + *args, **kwargs): + + self.db = db + self.read_only = read_only + self.phil_scope = phil_scope + self.working_phil_scope = working_phil_scope + + BaseDialog.__init__(self, parent, + label_style=label_style, + content_style=content_style, + size=(600, 600), + *args, **kwargs) + + self.phil_box = gctr.RichTextCtrl(self, style=wx.VSCROLL, size=(400, 400)) + + self.main_sizer.Add(self.phil_box, 1, + flag=wx.EXPAND | wx.TOP | wx.LEFT | wx.RIGHT, + border=10) + + # Dialog control + if self.read_only: + dialog_box = self.CreateSeparatedButtonSizer(wx.OK) + else: + dialog_box = self.CreateSeparatedButtonSizer(wx.OK | wx.CANCEL) + + self.main_sizer.Add(dialog_box, + flag=wx.EXPAND | wx.ALL, + border=10) + + self.Layout() + + self.SetTitle('Settings') + if self.read_only: + # Disable controls for viewing + self.phil_box.SetEditable(False) + + self.phil_box.SetValue(phil_scope.fetch_diff(working_phil_scope).as_str()) + + # Bindings + self.Bind(wx.EVT_BUTTON, self.onOK, id=wx.ID_OK) + + def parse_trial_phil(self, target_phil_str): + # Parameter validation + msg = None + params = None + try: + working_phil_scope, unused = self.working_phil_scope.fetch(parse(target_phil_str), track_unused_definitions = True) + except Exception as e: + msg = '\nParameters incompatible with %s dispatcher:\n%s\n' % (self.db.params.dispatcher, str(e)) + else: + if len(unused) > 0: + msg = [str(item) for item in unused] + msg = '\n'.join([' %s' % line for line in msg]) + msg = 'The following definitions were not recognized:\n%s\n' % msg + + try: + params = working_phil_scope.extract() + except Exception as e: + if msg is None: msg = "" + msg += '\nOne or more values could not be parsed:\n%s\n' % str(e) + else: + self.working_phil_scope = working_phil_scope + return params, msg + + def onOK(self, e): + if not self.read_only: + target_phil_str = self.phil_box.GetValue() + _, msg = self.parse_trial_phil(target_phil_str) + + if msg is not None: + msg += '\nFix the parameters and press OK again' + msgdlg = wx.MessageDialog(self, + message=msg, + caption='Warning', + style=wx.OK | wx.ICON_EXCLAMATION) + msgdlg.ShowModal() + return + e.Skip() + class DatasetDialog(BaseDialog): def __init__(self, parent, db, new=True, @@ -3124,7 +3371,6 @@ def onOK(self, e): # Parameter validation from xfel.ui.db.task import Task - from iotbx.phil import parse dispatcher, phil_scope = Task.get_phil_scope(self.db, task_type) if phil_scope is not None: diff --git a/xfel/ui/db/__init__.py b/xfel/ui/db/__init__.py index dbace530397..ec481fbdbf1 100644 --- a/xfel/ui/db/__init__.py +++ b/xfel/ui/db/__init__.py @@ -10,6 +10,8 @@ def get_run_path(rootpath, trial, rungroup, run, task=None): import os + if trial is None or run is None: + return try: p = os.path.join(rootpath, "r%04d"%int(run.run), "%03d_rg%03d"%(trial.trial, rungroup.id)) except ValueError: @@ -65,7 +67,7 @@ def get_db_connection(params, block=True, autocommit=True): password = params.db.password retry_count = 0 - retry_max = 20 + retry_max = 10 sleep_time = 0.1 while retry_count < retry_max: try: @@ -75,7 +77,8 @@ def get_db_connection(params, block=True, autocommit=True): host=params.db.host, db=params.db.name, port=params.db.port, - autocommit=autocommit + autocommit=autocommit, + connect_timeout=1, ) return dbobj except Exception as e: @@ -89,6 +92,7 @@ def get_db_connection(params, block=True, autocommit=True): print("MySQL can't create a new thread. Retry", retry_count) else: raise e + print(str(e)) import time time.sleep(sleep_time) sleep_time *= 2 diff --git a/xfel/ui/db/job.py b/xfel/ui/db/job.py index 73f25d7fdb3..2c164c0d6c5 100644 --- a/xfel/ui/db/job.py +++ b/xfel/ui/db/job.py @@ -70,7 +70,7 @@ def __setattr__(self, name, value): def get_log_path(self): run_path = get_run_path(self.app.params.output_folder, self.trial, self.rungroup, self.run) - return os.path.join(run_path, "stdout", "out.log") + return os.path.join(run_path, "stdout", "log.out") if run_path else None def submit(self, previous_job = None): raise NotImplementedError("Override me!") diff --git a/xfel/ui/db/xfel_db.py b/xfel/ui/db/xfel_db.py index 246ffc3f7e3..68200ed943e 100644 --- a/xfel/ui/db/xfel_db.py +++ b/xfel/ui/db/xfel_db.py @@ -390,6 +390,7 @@ def execute_query(self, query, commit=True): raise e retry_count += 1 print("Couldn't connect to MYSQL, retry", retry_count) + print(str(e)) time.sleep(sleep_time) sleep_time *= 2 except Exception as e: