"""
TranSPHIRE is supposed to help with the cryo-EM data collection
Copyright (C) 2017 Markus Stabrin
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import re
import os
import datetime
import copy as copy_mod
import errno
import json
import sys
import shutil
import time
import numpy as np
import pandas as pd
from PyQt5.QtGui import QFont
from . import transphire_content as tc
from . import transphire_plot as tp
from . import transphire_import as ti
VERSION_RE = re.compile('(.*) >=v([\d.]+)')
[docs]def symlink_rel(src, dst):
rel_path_src = os.path.relpath(src, os.path.dirname(dst))
os.symlink(rel_path_src, dst)
return os.path.join(rel_path_src, dst)
[docs]def rerun_function_in_case_of_error(func):
def wrapper(*args, **kwargs):
error_code = []
for i in range(20):
try:
return_value = func(*args, **kwargs)
except OSError as e:
error_code.append(str(e))
print(e)
time.sleep(0.5)
else:
return return_value
raise BlockingIOError(' --- '.join(error_code))
return wrapper
[docs]def thread_safe(func):
def wrapper(self, *args, **kwargs):
self._DataFrame__get_df()
try:
return_value = func(self, *args, **kwargs)
except:
self._lock.release()
raise
else:
self._DataFrame__set_df()
return return_value
return wrapper
[docs]class DataFrame(object):
def __init__(self, manager, file_path):
super(DataFrame, self).__init__()
self._lock = manager.RLock()
self._namespace = manager.Namespace()
self._file_path = file_path
self._data_frame = None
self._namespace.df = None
self._increment = 5
self.load_df()
def __get_df(self):
self._lock.acquire()
self._data_frame = self._namespace.df
def __set_df(self):
self._namespace.df = self._data_frame
self._data_frame = None
self._lock.release()
def __add_df_column(self, name):
self._data_frame[name] = np.nan
self._data_frame[name] = self._data_frame[name].astype(object)
def __add_df_rows(self, length):
columns = self._data_frame.columns
new_data_frame = pd.DataFrame(index=range(length), columns=columns)
self._data_frame = self._data_frame.append(new_data_frame, ignore_index=True)
def __save_df(self):
self._data_frame.to_csv(self._file_path)
def __check_df(self, index, columns):
if index not in self._data_frame.index:
rows_to_add = self._increment * (index // self._increment + 1) - self._data_frame.shape[0]
self.__add_df_rows(rows_to_add)
for column in columns:
try:
self._data_frame[column]
except KeyError:
self.__add_df_column(column)
[docs] @thread_safe
def get_df(self):
return self._data_frame
[docs] @thread_safe
def set_df(self, data_frame):
self._data_frame = data_frame
self.__save_df()
[docs] @thread_safe
def save_df(self):
self._data_frame.to_csv(self._file_path)
[docs] @thread_safe
def load_df(self):
if os.path.exists(self._file_path):
df = pd.read_csv(self._file_path, index_col=0, dtype=object)
else:
df = pd.DataFrame(dtype=object)
self._data_frame = df
[docs] @thread_safe
def value_in_column(self, value, column):
return value in self._data_frame[column].values
[docs] @thread_safe
def get_values(self, index, columns):
if isinstance(columns, str):
columns = [columns]
self.__check_df(index, columns)
return self._data_frame.loc[index, list(columns)]
[docs] @thread_safe
def set_values(self, index, value_dict, do_save=True):
self.__check_df(index, value_dict.keys())
for column, value in value_dict.items():
self._data_frame.loc[index, column] = value
if do_save:
self.__save_df()
[docs] @thread_safe
def append_values(self, value_dict, do_save=True):
index = self._data_frame[next(iter(value_dict))].last_valid_index() + 1
self.set_values(index, value_dict, do_save)
[docs] @thread_safe
def get_index_where(self, column, value, func_type):
try:
col = self._data_frame[column].astype(float)
except:
col = self._data_frame[column]
if func_type == 'eq':
return_value = self._data_frame.index[col == value]
elif func_type == 'gg':
return_value = self._data_frame.index[col > value]
elif func_type == 'll':
return_value = self._data_frame.index[col < value]
elif func_type == 'ge':
return_value = self._data_frame.index[col >= value]
elif func_type == 'le':
return_value = self._data_frame.index[col <= value]
else:
raise NameError
return return_value
[docs]def get_unique_types():
valid_sub_items = np.array([value['typ'] for value in get_function_dict().values() if value['typ'] is not None])
_, unique_idx = np.unique(valid_sub_items, return_index=True)
return valid_sub_items[np.sort(unique_idx)]
[docs]def create_log(*args):
"""
Add a time string to print statement.
args - Args to print
Returns:
String with added time stemp.
"""
time_string = datetime.datetime.now().strftime('%Y/%m/%d - %H:%M:%S')
return '{0} => {1}'.format(time_string, ' '.join([str(entry) for entry in args]))
[docs]def find_latest_version(name, dictionary):
"""
Find the latest matching version key in a dictionary.
Raises an assertion error it the return_key is not valid.
name - Name of the program
dictionary - Dictionary to find the best matching version.
Returns:
The best matching version Key.
"""
valid_versions = []
for entry in dictionary:
if name not in entry:
continue
prog_name, version = VERSION_RE.search(entry).groups()
if prog_name == name:
valid_versions.append(
tuple(
[
tuple(
[
int(num) for num in version.split('.')
]
),
entry
]
)
)
return sorted(valid_versions)[-1][-1]
[docs]def find_best_match(name, dictionary):
"""
Find the best matching version key in a dictionary.
Raises an assertion error it the return_key is not valid.
name - Name of the current version.
dictionary - Dictionary to find the best matching version.
Returns:
The best matching version Key.
"""
prog_name, _ = VERSION_RE.search(name).groups()
valid_versions = [
tuple([tuple([int(num) for num in VERSION_RE.search(entry).group(2).split('.')]), entry])
for entry in dictionary.keys()
if prog_name in entry
]
return_key = None
for version, current_key in reversed(sorted(valid_versions)):
version_string = '.'.join([str(entry) for entry in version])
if is_higher_version(name, version_string):
return_key = current_key
break
assert return_key is not None, (name, prog_name, valid_versions, dictionary)
return return_key
[docs]def is_higher_version(name, version_ref):
"""
Compare the versions of software.
The versions do not need to match in digits.
v1 > v0, v0.0, v0.0.0
v1 >= v1
v1 < v1.0.1
name - Name of the Software containing the version string, e.g. v1, v1.1, v1.1.1, v1.1.1rc2
version_ref - reference version as string, e.g. 1, 1.1, 1.1.1
Returns:
True if the version is larger than the reference version
"""
version_comp = VERSION_RE.search(name).group(2)
version_tuple_comp = tuple([int(entry) for entry in version_comp.split('.')])
version_tuple_ref = tuple([int(entry) for entry in version_ref.split('.')])
return version_tuple_comp >= version_tuple_ref
[docs]def normalize_image(data, apix=1.0, min_res=30, real=True):
if real:
pass
else:
box_x = data.shape[0]
box_x_half = box_x / 2
box_y = data.shape[1]
box_y_half = box_y / 2
min_freq = (apix / min_res)**2
mask = np.ones((data.shape[0], data.shape[1]), dtype=bool)
for idx_x in range(mask.shape[0]):
for idx_y in range(mask.shape[1]):
x = (idx_x - box_x_half) / box_x
y = (idx_y - box_y_half) / box_y
radius = x**2+y**2
if radius < min_freq:
mask[idx_x, idx_y] = 0
elif np.abs(idx_x - box_x_half) < 0:
mask[idx_x, idx_y] = 0
elif np.abs(idx_y - box_y_half) < 0:
mask[idx_x, idx_y] = 0
data[~mask] = np.median(data[mask])
data = np.sqrt(data)
return data
[docs]def get_name(name):
"""
Remove the extension of a file and return the basename without the PATH
name - Name to remove the extension from
Returns:
The name of the file without PATH and extension.
"""
return os.path.basename(os.path.splitext(name)[0])
[docs]def copy(file_in, file_out):
"""
Copy file_in to a new location.
Arguments:
file_in - Input file
file_out - Output file
Return:
None
"""
if os.path.isfile(file_in):
mkdir_p(os.path.dirname(file_out))
try:
shutil.copy2(file_in, file_out)
except PermissionError:
print('Error with {0}! Switching to copyfile!'.format(file_in))
shutil.copyfile(file_in, file_out)
else:
umask = os.umask(0)
os.umask(umask)
os.chmod(file_out, 0o666 & ~umask)
elif os.path.isdir(file_in):
copytree(file_in, file_out)
[docs]def copytree(root_src_dir, root_dst_dir):
if os.path.exists(root_dst_dir):
root_dst_dir = os.path.join(root_dst_dir, os.path.basename(root_src_dir))
mkdir_p(root_dst_dir)
for src_dir, dirs, files in os.walk(root_src_dir):
dst_dir = src_dir.replace(root_src_dir, root_dst_dir, 1)
if not os.path.exists(dst_dir):
mkdir_p(dst_dir)
for file_ in files:
src_file = os.path.join(src_dir, file_)
dst_file = os.path.join(dst_dir, file_)
if os.path.exists(dst_file):
os.remove(dst_file)
copy(src_file, dst_file)
[docs]def get_function_dict():
"""
Return a dictionary containing the function to use for specific plots.
Arguments:
None
Return:
None
"""
function_dict = {}
### Compression programs
function_dict['Compress cmd'] = {
'content': tc.default_compress_command_line,
'executable': True,
'has_path': False,
'typ': 'Compress',
'license': False,
'category': 'External software',
'allow_empty': ['--command_uncompress'],
}
### Motion programs
function_dict['MotionCor2 >=v1.0.0'] = {
'plot': tp.update_motion,
'plot_data': ti.import_motion_cor_2_v1_0_0,
'content': tc.default_motion_cor_2_v1_0_0,
'executable': True,
'has_path': 'MotionCor2',
'typ': 'Motion',
'license': True,
'category': 'External software',
'allow_empty': ['-DefectFile', '-Gain', '-Dark'],
}
function_dict['MotionCor2 >=v1.0.5'] = copy_mod.deepcopy(function_dict['MotionCor2 >=v1.0.0'])
function_dict['MotionCor2 >=v1.0.5']['content'] = tc.default_motion_cor_2_v1_0_5
function_dict['MotionCor2 >=v1.1.0'] = copy_mod.deepcopy(function_dict['MotionCor2 >=v1.0.0'])
function_dict['MotionCor2 >=v1.1.0']['content'] = tc.default_motion_cor_2_v1_1_0
function_dict['MotionCor2 >=v1.2.6'] = copy_mod.deepcopy(function_dict['MotionCor2 >=v1.1.0'])
function_dict['MotionCor2 >=v1.3.0'] = copy_mod.deepcopy(function_dict['MotionCor2 >=v1.1.0'])
function_dict['MotionCor2 >=v1.3.0']['content'] = tc.default_motion_cor_2_v1_3_0
function_dict['Unblur >=v1.0.0'] = {
'plot': tp.update_motion,
'plot_data': ti.import_unblur_v1_0_0,
'content': tc.default_unblur_v1_0_0,
'executable': True,
'has_path': 'unblur',
'typ': 'Motion',
'license': False,
'category': 'External software',
'allow_empty': [],
}
### CTF Programs
function_dict['CTFFIND4 >=v4.1.8'] = {
'plot': tp.update_ctf,
'plot_data': ti.import_ctffind_v4_1_8,
'content': tc.default_ctffind_4_v4_1_8,
'executable': True,
'has_path': 'ctffind',
'typ': 'CTF',
'license': False,
'category': 'External software',
'allow_empty': ['Gain file'],
}
function_dict['CTFFIND4 >=v4.1.10'] = function_dict['CTFFIND4 >=v4.1.8']
function_dict['CTFFIND4 >=v4.1.13'] = copy_mod.deepcopy(function_dict['CTFFIND4 >=v4.1.8'])
function_dict['Gctf >=v1.06'] = {
'plot': tp.update_ctf,
'plot_data': ti.import_gctf_v1_06,
'content': tc.default_gctf_v1_06,
'executable': True,
'has_path': 'Gctf',
'typ': 'CTF',
'license': False,
'category': 'External software',
'allow_empty': [],
'old': False,
}
function_dict['Gctf >=v1.18'] = copy_mod.deepcopy(function_dict['Gctf >=v1.06'])
function_dict['Gctf >=v1.18']['content'] = tc.default_gctf_v1_18
function_dict['CTER >=v1.0'] = {
'plot': tp.update_ctf,
'plot_data': ti.import_cter_v1_0,
'content': tc.default_cter_v1_0,
'executable': True,
'has_path': 'sp_cter.py',
'typ': 'CTF',
'license': False,
'category': 'External software',
'allow_empty': [],
}
function_dict['CTER >=v1.3'] = copy_mod.deepcopy(function_dict['CTER >=v1.0'])
### Picking programs
function_dict['crYOLO >=v1.0.4'] = {
'plot': tp.update_cryolo_v1_0_4,
'plot_data': ti.import_cryolo_v1_0_4,
'content': tc.default_cryolo_v1_0_4,
'executable': True,
'has_path': 'cryolo_predict.py',
'typ': 'Picking',
'license': True,
'category': 'External software',
'allow_empty': [],
}
function_dict['crYOLO >=v1.0.5'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.0.4'])
function_dict['crYOLO >=v1.1.0'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.0.4'])
function_dict['crYOLO >=v1.1.0']['content'] = tc.default_cryolo_v1_1_0
function_dict['crYOLO >=v1.2.1'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.0.4'])
function_dict['crYOLO >=v1.2.1']['content'] = tc.default_cryolo_v1_2_1
function_dict['crYOLO >=v1.2.2'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.2.1'])
function_dict['crYOLO >=v1.2.2']['plot_data'] = ti.import_cryolo_v1_2_2
function_dict['crYOLO >=v1.4.1'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.2.2'])
function_dict['crYOLO >=v1.4.1']['content'] = tc.default_cryolo_v1_4_1
function_dict['crYOLO >=v1.5.8'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.2.2'])
function_dict['crYOLO >=v1.5.8']['content'] = tc.default_cryolo_v1_5_8
function_dict['crYOLO >=v1.8.0'] = copy_mod.deepcopy(function_dict['crYOLO >=v1.2.2'])
function_dict['crYOLO >=v1.8.0']['plot_data'] = ti.import_cryolo_v1_8_0
### Extract programs
function_dict['WINDOW >=v1.2'] = {
'plot': tp.update_micrograph,
'plot_data': ti.import_window_v1_2,
'content': tc.default_window_1_2,
'executable': True,
'has_path': 'sp_window.py',
'typ': 'Extract',
'license': False,
'category': 'External software',
'allow_empty': [''],
}
### 2D classification programs
function_dict['ISAC2 >=v1.2'] = {
'plot': tp.update_batch,
'plot_data': ti.import_isac_v1_2,
'content': tc.default_isac2_1_2,
'executable': True,
'has_path': 'sp_isac2_gpu.py',
'typ': 'Class2d',
'license': False,
'category': 'External software',
'allow_empty': [''],
}
### 2D selection programs
function_dict['Cinderella >=v0.3.1'] = {
'plot': tp.update_batch,
'plot_data': ti.import_cinderella_v0_3_1,
'content': tc.default_cinderella_v0_3_1,
'executable': True,
'has_path': 'sp_cinderella_predict.py',
'typ': 'Select2d',
'license': False,
'category': 'External software',
'allow_empty': [],
}
### 2D train programs
function_dict['crYOLO_train >=v1.5.4'] = {
'plot': tp.dummy,
'plot_data': ti.dummy,
'content': tc.default_cryolo_train_v1_5_4,
'executable': True,
'has_path': 'cryolo_train.py',
'typ': 'Train2d',
'license': True,
'category': 'External software',
'allow_empty': [],
}
function_dict['crYOLO_train >=v1.5.8'] = copy_mod.deepcopy(function_dict['crYOLO_train >=v1.5.4'])
function_dict['crYOLO_train >=v1.5.8']['content'] = tc.default_cryolo_train_v1_5_8
function_dict['crYOLO_train >=v1.7.4'] = copy_mod.deepcopy(function_dict['crYOLO_train >=v1.5.4'])
function_dict['crYOLO_train >=v1.7.4']['content'] = tc.default_cryolo_train_v1_7_4
### auto processing programs
function_dict['sp_auto >=v1.3'] = {
'plot': tp.update_batch,
'plot_data': ti.import_auto_sphire_v1_3,
'content': tc.default_auto_sphire_v1_3,
'executable': True,
'has_path': 'sp_auto.py',
'typ': 'Auto3d',
'license': False,
'category': 'External software',
'allow_empty': [
'SSH username',
'SSH password',
'--mtf',
'input_volume',
'input_mask',
'--rviper_addition',
'--adjust_rviper_addition',
'--mask_rviper_addition',
'--meridien_addition',
'--sharpening_meridien_addition',
],
}
### Other no executable stuff
function_dict['Pipeline'] = {
'plot': None,
'plot_data': None,
'content': tc.default_pipeline,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'Internal settings',
'allow_empty': [],
}
function_dict['Global'] = {
'plot': None,
'plot_data': None,
'content': tc.default_global,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'Internal settings',
'allow_empty': ['-Gain'],
}
function_dict['Input'] = {
'plot': None,
'plot_data': None,
'content': tc.default_input,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'Internal settings',
'allow_empty': [],
}
function_dict['Output'] = {
'plot': None,
'plot_data': None,
'content': tc.default_general,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'Internal settings',
'allow_empty': ['Rename suffix', 'Rename prefix'],
}
function_dict['Mount'] = {
'plot': None,
'plot_data': None,
'content': tc.default_mount,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
function_dict['Notification'] = {
'plot': None,
'plot_data': None,
'content': tc.default_notification,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
function_dict['Notification_widget'] = {
'plot': None,
'plot_data': None,
'content': tc.default_notification_widget,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
function_dict['Others'] = {
'plot': None,
'plot_data': None,
'content': tc.default_others,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
function_dict['Font'] = {
'plot': None,
'plot_data': None,
'content': tc.default_font,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
function_dict['Copy'] = {
'plot': None,
'plot_data': None,
'content': tc.default_copy,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
function_dict['Path'] = {
'plot': None,
'plot_data': None,
'content': tc.default_path,
'executable': False,
'has_path': False,
'typ': None,
'license': False,
'category': 'TranSPHIRE settings',
'allow_empty': [],
}
for key in function_dict:
if 'old' in function_dict[key]:
continue
try:
prog_name, _ = VERSION_RE.search(key).groups()
newest_key = find_latest_version(prog_name, function_dict)
except AttributeError:
newest_key = key
if newest_key == key:
function_dict[key]['old'] = False
else:
function_dict[key]['old'] = True
return function_dict
[docs]def mkdir_p(path):
"""
Create output directories recursively.
Arguments:
path - Directory path to create
Return:
None
"""
try:
os.makedirs(path)
except OSError as exc:
if exc.errno == errno.EEXIST and os.path.lexists(path):
pass
else:
raise
[docs]def message(text):
"""
Show a text in a message box.
Arguments:
text - Text shown in the message box
Return:
None
"""
final_text = split_maximum(text, 80, ' ')
dialog = MessageBox(is_question=False)
dialog.setText(None, final_text)
dialog.exec_()
[docs]def split_maximum(text, max_char, split_char=None):
"""
Split text into chunks of size max_char containing whole words.
Arguments:
text - Text to split
max_char - Maximum number of characters
Returns:
Splitted text
"""
if split_char is None:
add_char = ' '
else:
add_char = split_char
new_text = []
try:
current_line = [text.split(split_char)[0]]
except IndexError:
return text
except AttributeError:
print(text)
for entry in text.split(split_char)[1:]:
if sum(map(len, current_line)) + len(current_line) + len(entry) > max_char:
new_text.append(add_char.join(current_line))
current_line = [entry]
else:
current_line.append(entry)
new_text.append(add_char.join(current_line))
return '\n{0}'.join(new_text).format(add_char if add_char.strip() else '')
[docs]def question(head, text):
"""
Show a questions message box dialog.
Arguments:
head - Header of the window
text - Text with the questions
Return:
True if No, False if Yes
"""
message_box = MessageBox(is_question=True)
message_box.setText(head, text)
result = message_box.exec_()
return result
[docs]def get_exclude_set_path(content):
"""
Check the widget_2 variable, if the program should be loaded or not.
Argument:
content - Content as dictionary.
Return:
List of names to exclude
"""
exclude_list = []
for sub_content in content:
for entry2 in sub_content:
for key in entry2:
widget_2 = entry2[key][1]['widget_2']
if widget_2 is None:
continue
elif widget_2 == 'Main':
continue
elif widget_2 == 'Advanced' or widget_2 == 'Rare':
exclude_list.append(key)
exclude_list.append('Plot {0}'.format(key))
exclude_list.append('Plot {0}'.format('{0} feedback'.format(key)))
else:
print(
'TransphireUtils: widget_2 content unknown! Exit here!',
widget_2
)
sys.exit(1)
return set(exclude_list)
[docs]def reduce_programs(exclude_set=None):
"""
Reduce the number of programs to the users preferences.
Arguments:
exclude_set - Set of names to not consider
Return:
List of content for motion, List of content for ctf
"""
if exclude_set is None:
exclude_set = set([])
else:
exclude_set = exclude_set
content = {}
function_dict = get_function_dict()
for key in function_dict:
if key in exclude_set:
continue
elif function_dict[key]['executable']:
content.setdefault(function_dict[key]['typ'], []).append(key)
if function_dict[key]['typ'] is None:
print(key, 'is executable, but does not have a typ! Exit here!')
sys.exit(1)
else:
pass
for key, value in content.items():
content[key] = list(sorted(value))
return content
[docs]def reduce_copy_entries(exclude_set, content):
"""
Reduce the number of options based on the exclude_set
Arguments:
exclude_set - Set of names to not consider
content - Content of the widgets
Return:
None
"""
valid_sub_items = get_unique_types()
for item in content:
for sub_item in item:
values = None
for entry in valid_sub_items:
try:
values = sub_item[entry][1]['values']
except KeyError:
continue
else:
break
if values is not None:
for key in list(exclude_set):
if key in values:
values.remove(key)
if sorted(values) == ['False', 'Later']:
sub_item[list(sub_item)[0]][0] = 'False'
exclude_set.add(list(sub_item)[0])
else:
sub_item[list(sub_item)[0]][0] = values[0]
[docs]def get_key_names(settings_folder, name):
"""
Extract mount names from related settings file.
Arguments:
None
Return:
List of mount names
"""
default_file = '{0}/content_{1}.txt'.format(settings_folder, name.replace(' ', '_'))
if not os.path.isfile(default_file):
default_file = '{0}/SHARED/content_{1}.txt'.format(settings_folder, name.replace(' ', '_').replace('>=', ''))
try:
with open(default_file, 'r') as file_r:
data = json.load(file_r)
except FileNotFoundError:
data = []
return_dict = {
'Copy_to_work': [],
'Copy_to_backup': [],
'Copy_to_hdd': [],
'Import': [],
'Copy': [],
}
for entry in data:
name = None
typ = None
for widget in entry:
for key in widget:
if key == 'Mount name':
name = widget[key][0]
elif key == 'Typ':
typ = widget[key][0]
else:
pass
try:
return_dict[typ].append(name)
except KeyError:
return_dict[typ.replace('_', '_to_')].append(name)
if return_dict['Copy']:
print('')
print('Old Copy type detected! Please edit the TranSPHIRE settings and change them to Import')
print('')
return_dict['Import'].extend(return_dict['Copy'])
del return_dict['Copy']
return return_dict
[docs]def get_content_gui(content, template_name, n_feedbacks):
"""
Create content lists to load the GUI.
Arguments:
content - Content as dictionary.
template_name = Name of the template!
Return:
Content as list
"""
content_extern = reduce_programs()
gui_content = [
{
'name': 'Notification_widget',
'widget': NotificationContainer,
'content': content[template_name]['Notification_widget'],
'layout': 'h2',
},
{
'name': 'Button',
'widget': ButtonContainer,
'layout': 'h3',
},
{
'name': 'TAB1',
'widget': TabDocker,
'layout': 'h4',
},
{
'name': 'Mount',
'widget': MountContainer,
'content_mount': content[template_name]['Mount'],
'layout': 'TAB1',
},
{
'name': 'Settings',
'widget': TabDocker,
'layout': 'TAB1',
},
{
'name': 'Retrain',
'widget': SelectDialog,
'layout': 'TAB1',
},
{
'name': 'Visualisation',
'widget': TabDocker,
'layout': 'TAB1',
},
{
'name': 'Input',
'widget': SettingsContainer,
'content': content[template_name]['Input'],
'content_mount': content[template_name]['Mount'],
'layout': 'Settings',
},
{
'name': 'Output',
'widget': SettingsContainer,
'content': content[template_name]['Output'],
'content_others': content[template_name]['Others'],
'layout': 'Settings',
},
{
'name': 'Global',
'widget': SettingsContainer,
'content': content[template_name]['Global'],
'layout': 'Settings',
},
{
'name': 'Notification',
'widget': SettingsContainer,
'content': content[template_name]['Notification'],
'layout': 'Settings',
},
{
'name': 'Copy',
'widget': SettingsContainer,
'content': content[template_name]['Copy'],
'layout': 'Settings',
},
{
'name': 'Path',
'widget': SettingsContainer,
'content': content[template_name]['Path'],
'layout': 'Settings',
},
{
'name': 'Pipeline',
'widget': SettingsContainer,
'content': content[template_name]['Pipeline'],
'layout': 'Settings',
},
]
for entry in get_unique_types():
gui_content.append(
{
'name':entry,
'widget': TabDocker,
'layout': 'Settings',
},
)
gui_content.extend([
{
'name': 'Status',
'widget': StatusContainer,
'content': content[template_name]['Others'],
'content_mount': content[template_name]['Mount'],
'content_pipeline': content[template_name]['Pipeline'],
'content_font': content[template_name]['Font'],
'layout': 'v2',
},
])
all_content = []
for entry in get_unique_types():
all_content.append([entry, content_extern[entry]])
if not entry.startswith('Compress'):
gui_content.append(
{
'name': 'Plot {0}'.format(entry),
'widget': TabDocker,
'layout': 'Visualisation',
},
)
for typ, content_typ in all_content:
for input_content in content_typ:
gui_content.append({
'name': input_content,
'widget': SettingsContainer,
'content': content[template_name][input_content],
'layout': typ,
})
if not input_content.startswith('Compress'):
for index in range(n_feedbacks+1):
if index == 0:
feedback_content = input_content
else:
feedback_content = '{0} feedback {1}'.format(input_content, index)
gui_content.append({
'name': 'Plot {0}'.format(feedback_content),
'widget': TabDocker,
'layout': 'Plot {0}'.format(typ),
})
gui_content.append({
'name': 'Overview',
'widget': PlotContainer,
'content': 'overview',
'plot_type': '{0}_feedback_{1}'.format(typ, index),
'layout': 'Plot {0}'.format(feedback_content),
})
gui_content.append({
'name': 'Show images',
'widget': PlotContainer,
'content': 'image',
'plot_type': '{0}_feedback_{1}'.format(typ, index),
'layout': 'Plot {0}'.format(feedback_content),
})
gui_content.append({
'name': 'Plot per micrograph',
'widget': PlotContainer,
'content': 'values',
'plot_type': '{0}_feedback_{1}'.format(typ, index),
'layout': 'Plot {0}'.format(feedback_content),
})
gui_content.append({
'name': 'Plot histogram',
'widget': PlotContainer,
'content': 'histogram',
'plot_type': '{0}_feedback_{1}'.format(typ, index),
'layout': 'Plot {0}'.format(feedback_content),
})
#if typ == 'Motion':
# gui_content.append({
# 'name': 'Frames',
# 'widget': FrameContainer,
# 'layout': typ,
# })
return gui_content
[docs]def look_and_feel_small(app, font=None):
"""
Look and feel for the default settings dialog.
Arguments:
app - QApplication.
font - User provided font size (default None)
Return:
Style sheet
"""
if font is None:
font = 5
else:
font = float(font)
font_type = QFont('Verdana', font, 63)
font_type.setStyleStrategy(QFont.PreferAntialias)
app.setFont(font_type)
style_widgets = """
QWidget#central_raw {{
background-image: url("{0}");
}}
QWidget#central {{
background-color: {1};
border-radius: 15px;
}}
QWidget#settings {{
background-color: {2};
border-radius: 15px
}}
QWidget#tab {{
background-color: {2};
border-radius: 15px
}}
QTabWidget::pane {{
border-top: 2px solid #C2C7CB;
}}
QTabWidget::tab {{
min-width: 120px;
}}
QTabBar {{
background-color: #C2C7CB
}}
QTabBar::pane {{
border-top: 2px solid #C2C7CB;
}}
QTabBar::tab {{
min-width: 120px;
}}
QLineEdit {{ background-color: white }}
QLineEdit:disabled {{ background-color: rgba(125, 125, 125) }}
QComboBox {{ background-color: white }}
QComboBox:disabled {{ background-color: rgba(125, 125, 125) }}
QPushButton {{
background-color: qradialgradient(cx:0.5, cy:0.5, fx:0.5, fy:0.5, radius:1, stop:0 white, stop:1 #f9eeb4);
border-width: 1px;
border-style:inset;
padding: 1px;
border-radius: 5px
}}
QPushButton:checked {{
background-color: qradialgradient(cx:0.5, cy:0.5, fx:0.5, fy:0.5, radius:1, stop:0 white, stop:1 green);
border-width: 1px;
border-style:outset;
padding: 1px;
border-radius: 5px
}}
QPushButton:pressed {{
background-color: qradialgradient(cx:0.5, cy:0.5, fx:0.5, fy:0.5, radius:1, stop:0 white, stop:1 pink);
border-width: 1px;
border-style:outset;
padding: 1px;
border-radius: 5px
}}
""".format(
'{0}/images/sxgui_background.png'.format(os.path.dirname(__file__)),
'rgba(229, 229, 229, 192)',
'rgba(229, 229, 229, 120)',
)
return style_widgets
[docs]def look_and_feel(app, font=None, adjust_width=None, adjust_height=None, default=None):
"""
Look and feel.
Arguments:
app - QApplication.
font - User provided font size (default None)
adjust_width - User provided width adjustment (default None)
adjust_height - User provided height adjustment (default None)
default - Default values (default None)
Return:
Style sheet
"""
idx = 0
if font is not None:
font = float(font)
else:
font = float(default[0][idx]['Font'][0])
idx += 1
if adjust_width is not None:
adjust_width = float(adjust_width)
else:
adjust_width = float(default[0][idx]['Width adjustment'][0])
idx += 1
if adjust_height is not None:
adjust_height = float(adjust_height)
else:
adjust_height = float(default[0][idx]['Height adjustment'][0])
idx += 1
start_button_width = float(default[0][idx]['Start button'][0])
idx += 1
notification_edit_width = float(default[0][idx]['Notification edit'][0])
idx += 1
notification_check_width = float(default[0][idx]['Notification check'][0])
idx += 1
notification_button_width = float(default[0][idx]['Notification button'][0])
idx += 1
mount_button_width = float(default[0][idx]['Mount button'][0])
idx += 1
frame_entry_width = float(default[0][idx]['Frame entry'][0])
idx += 1
frame_button_width = float(default[0][idx]['Frame button'][0])
idx += 1
frame_label_width = float(default[0][idx]['Frame label'][0])
idx += 1
setting_widget_width = float(default[0][idx]['Setting widget'][0])
settinger_widget_width = float(default[0][idx]['Setting widget'][0])
settinger2_widget_width = float(default[0][idx]['Setting widget'][0])
idx += 1
setting_widget_width_large = float(default[0][idx]['Setting widget large'][0])
idx += 1
setting_widget_width_xlarge = float(default[0][idx]['Setting widget xlarge'][0])
idx += 1
status_name_width = float(default[0][idx]['Status name'][0])
idx += 1
status_info_width = float(default[0][idx]['Status info'][0])
idx += 1
status_quota_width = float(default[0][idx]['Status quota'][0])
idx += 1
tab_width = float(default[0][idx]['Tab width'][0])
idx += 1
widget_height = float(default[0][idx]['Widget height'][0])
idx += 1
tab_height = float(default[0][idx]['Tab height'][0])
font_type = QFont('Verdana', font, 63)
font_type.setStyleStrategy(QFont.PreferAntialias)
app.setFont(font_type)
start_button_width = '{0}px'.format(font * start_button_width * adjust_width)
notification_edit_width = '{0}px'.format(font * notification_edit_width * adjust_width)
notification_check_width = '{0}px'.format(font * notification_check_width * adjust_width)
notification_button_width = '{0}px'.format(font * notification_button_width * adjust_width)
mount_button_width = '{0}px'.format(font * mount_button_width * adjust_width)
frame_entry_width = '{0}px'.format(font * frame_entry_width * adjust_width)
frame_button_width = '{0}px'.format(font * frame_button_width * adjust_width)
frame_label_width = '{0}px'.format(font * frame_label_width * adjust_width)
setting_widget_width = '{0}px'.format(font * setting_widget_width * adjust_width)
settinger_widget_width = '{0}px'.format(font * settinger_widget_width * adjust_width * 0.9)
settinger2_widget_width = '{0}px'.format(font * settinger2_widget_width * adjust_width * 0.1)
setting_widget_width_large = '{0}px'.format(font * setting_widget_width_large * adjust_width)
setting_widget_width_xlarge = '{0}px'.format(font * setting_widget_width_xlarge * adjust_width)
status_name_width = '{0}px'.format(font * status_name_width * adjust_width)
status_info_width = '{0}px'.format(font * status_info_width * adjust_width)
status_quota_width = '{0}px'.format(font * status_quota_width * adjust_width)
tab_width = '{0}px'.format(font * tab_width * adjust_width)
widget_height_label = '{0}px'.format(font * widget_height * adjust_height / 2)
widget_height = '{0}px'.format(font * widget_height * adjust_height)
tab_height = '{0}px'.format(font * tab_height * adjust_height)
# Style sheet
style_widgets = """
QWidget#central_raw {{
background-image: url("{0}");
}}
QWidget#central {{
background-color: {1};
border-radius: 15px;
}}
QWidget#central_black {{
background-color: {3};
border-radius: 15px;
}}
QWidget#settings {{
background-color: {2};
border-radius: 15px
}}
QWidget#tab {{
background-color: {2};
border-radius: 15px
}}
QTabWidget::tab-bar {{
alignment: center;
}}
QTabWidget::pane {{
border-top: 2px solid #C2C7CB;
}}
QTabWidget::tab {{
min-width: 120px;
}}
QTabWidget#bot::pane {{
border-top: 0px solid #C2C7CB;
border-bottom: 2px solid #C2C7CB;
}}
QTabWidget#vertical::tab-bar {{
alignment: left;
}}
QTabWidget#vertical::pane {{
border-top: 0px solid #C2C7CB;
border-left: 2px solid #C2C7CB;
}}
QTabWidget#vertical::tab {{
min-width: 120px;
}}
QTabBar {{
alignment: center;
background-color: #C2C7CB
}}
QTabBar::pane {{
border-right: 2px solid #C2C7CB;
}}
QTabBar::tab {{
max-width: {4};
max-height: {5};
}}
QTabBar::tab:disabled {{
color: lightgrey;
background-color: darkgrey
}}
QTabBar#vertical::tab {{
max-width: {5};
max-height: {4};
}}
QMessageBox {{
background-image: url("{0}");
color: white;
}}
QFileDialog {{
background-color: {1}
}}
QScrollArea {{ background-color: transparent }}
QDockWidget {{ background-color: rgb(229, 229, 229) }}
""".format(
'{0}/images/sxgui_background.png'.format(os.path.dirname(__file__)),
'rgba(229, 229, 229, 192)',
'rgba(229, 229, 229, 120)',
'rgba(0, 0, 0, 153)',
tab_width,
tab_height,
)
button_style = """
QPushButton {{ min-height: {5}}}
QPushButton {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 #f9eeb4
);
border-width: 1px;
border-style: inset;
padding: 1px;
border-radius: 5px
}}
QPushButton#global:checked {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 green
);
border-width: 1px;
border-style: outset;
padding: 1px;
border-radius: 5px
}}
QPushButton:pressed {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 pink
);
border-width: 1px;
border-style: outset;
padding: 1px;
border-radius: 5px
}}
QPushButton#global {{
min-width: {6};
max-width: {6};
max-height: {5}
}}
QPushButton#start {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 green
);
min-width: {0};
max-width: {0};
max-height: {5}
}}
QPushButton#stop {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 #e34234
);
min-width: {0};
max-width: {0};
max-height: {5}
}}
QPushButton#button {{ min-width: {0}; max-width: {0} }}
QPushButton#frame {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 #68a3c3
);
max-width: {1};
min-width: {1}
}}
QPushButton#button_entry:enabled {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:1,
stop:0 white,
stop:1 #68a3c3
);
}}
QPushButton#unmount {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:8,
stop:0 white,
stop:1 #e34234
);
max-width: {3};
min-width: {3}
}}
QPushButton#mount {{
background-color: qradialgradient(
cx:0.5,
cy:0.5,
fx:0.5,
fy:0.5,
radius:8,
stop:0 white,
stop:1 green
);
max-width: {3};
min-width: {3}
}}
QPushButton#notification {{ max-width: {4}; min-width: {4} }}
QPushButton#sep {{
color: white;
background-color: black;
border-color: green;
border-width: 1px;
border-style: inset;
padding: 0px;
border-radius: 0px
}}
QPushButton#sep:checked {{
color: white;
background-color: black;
border-color: #e34234;
border-width: 1px;
border-style: outset;
padding: 0px;
border-radius: 0px
}}
""".format(
start_button_width,
frame_label_width,
frame_button_width,
mount_button_width,
notification_button_width,
widget_height,
settinger2_widget_width,
)
label_style = """
QLabel#picture {{ background-color: transparent }}
QLabel#important {{ font-weight: bold; color: red; background-color: white; qproperty-alignment: AlignCenter }}
QLabel#status_name {{ max-width: {1}; min-width: {1}; background-color: {0}; min-height: {5}; max-height: {5} }}
QLabel#status_info {{ max-width: {2}; min-width: {2}; background-color: {0}; min-height: {5}; max-height: {5} }}
QLabel#status_quota {{ max-width: {3}; min-width: {3}; background-color: {0}; min-height: {5}; max-height: {5} }}
QLabel#setting {{ max-width: {4}; min-width: {4}; background-color: {0}; min-height: {5}; max-height: {5} }}
QLabel#setting_large {{ max-width: {6}; min-width: {6}; background-color: {0}; min-height: {5}; max-height: {5} }}
QLabel#setting_xlarge {{ min-width: {7}; background-color: {0}; min-height: {5}; max-height: {5} }}
""".format(
'transparent',
status_name_width,
status_info_width,
status_quota_width,
setting_widget_width,
widget_height_label,
setting_widget_width_large,
setting_widget_width_xlarge,
)
edit_style = """
QLineEdit {{ max-height: {7}; min-height: {7}; background-color: white }}
QLineEdit#default_settings {{ min-width: {1}; max-width: 9999; background-color: white }}
QLineEdit:disabled {{ background-color: {6} }}
QLineEdit#settinger:enabled {{
max-width: {9}; min-width: {9}; background-color: {0}; min-height: {7}; max-height: {7}
}}
QLineEdit#setting:enabled {{
max-width: {1}; min-width: {1}; background-color: {0}; min-height: {7}; max-height: {7}
}}
QLineEdit#setting_large {{
max-width: {8}; min-width: {8}
}}
QLineEdit#setting_xlarge {{
min-width: {10}
}}
QLineEdit#setting_large:enabled {{
max-width: {8}; min-width: {8}; background-color: {0}; min-height: {7}; max-height: {7}
}}
QLineEdit#setting_xlarge:enabled {{
min-width: {10}; background-color: {0}; min-height: {7}; max-height: {7}
}}
QLineEdit#noti_edit:enabled {{
max-width: {2}; min-width: {2}; background-color: {5}; min-height: {7}; max-height: {7}
}}
QLineEdit#frame:enabled {{
max-width: {3}; min-width: {3}; background-color: {0}; min-height: {7}; max-height: {7}
}}
QLineEdit#entry:enabled {{
max-width: {4}; min-width: {4}; background-color: {0}; min-height: {7}; max-height: {7}
}}
""".format(
'white',
setting_widget_width,
notification_edit_width,
frame_label_width,
frame_entry_width,
'#5d995d',
'rgba(150,150,150)',
widget_height,
setting_widget_width_large,
settinger_widget_width,
setting_widget_width_xlarge,
)
check_style = """
QCheckBox {{ min-height: {1}; max-height: {1}; background-color: white }}
QCheckBox#noti_check {{ max-width: {0}; min-width: {0}; background-color: white }}
""".format(notification_check_width, widget_height)
combo_style = """
QComboBox {{ min-width: {4}; max-width: {4}; min-height: {3}; max-height: {3}; background-color: white }}
QComboBox#settinger {{ min-width: {5}; max-width: {5}; min-height: {3}; max-height: {3}; background-color: white }}
QComboBox#default_settings {{ min-width: {1}; max-width: 9999; background-color: white }}
QComboBox:disabled {{ background-color: {2} }}
QComboBox#settinger:disabled {{ background-color: {2} }}
QComboBox#default_settings:disabled {{ background-color: {2} }}
QComboBox QAbstractItemView {{
background-color: white; selection-color: black; selection-background-color: lightgray
}}
QComboBox#noti_edit:enabled {{ max-width: {1}; min-width: {1}; background-color: {0} }}
""".format(
'#5d995d',
notification_edit_width,
'rgba(150,150,150,200)',
widget_height,
setting_widget_width,
settinger_widget_width,
)
tool_style = """
QToolTip {0}
""".format(tooltip_style())
plain_style = """
QPlainTextEdit#status { background-color: rgba(229, 229, 229, 50); color: white }
QPlainTextEdit#dialog { background-color: rgba(229, 229, 229, 50); color: black }
"""
style = '\n'.join([style_widgets, button_style, label_style, edit_style, check_style, combo_style, tool_style, plain_style])
return style
[docs]def check_instance(value, typ):
"""
Check typ of value
Arguments:
value - Value to check
typ - Type to check
Return:
Bool
"""
if isinstance(typ, list):
value_split = value.split(' ')
for entry, var in zip(typ, value_split):
try:
entry(var)
except ValueError:
return False
else:
pass
else:
try:
typ(value)
except ValueError:
return False
else:
pass
return True
[docs]def get_color(typ):
"""
Color defined for the type
Arguments:
typ - Typ of color
Return:
Color string
"""
# Color table: https://davidmathlogic.com/colorblind/#%23E34234-%23FFFF00-%23FFFFFF-%23000000-%23B200FF-%2368A3C3-%23C3A368-%23D9D9D9-%23E34234-%2390EE90-%23FF336D-%23FF5C33
if typ == 'error':
color = '#e34234'
elif typ == 'global':
color = '#FFFF00'
elif typ == 'white':
color = '#FFFFFF'
elif typ == 'unchanged':
color = '#000000'
elif typ == 'changed':
color = 'B200FF'
elif typ == 'True':
color = '#68a3c3'
elif typ == 'False':
color = '#c3a368'
elif typ == 'Finished':
color = '#d9d9d9'
elif typ == 'Skipped':
color = '#d9d9d9'
elif typ == 'Later':
color = '#d9d9d9'
elif typ == 'Error':
color = '#FF5C33'
elif typ == 'Running':
color = '#90EE90'
elif typ == 'Waiting':
color = '#FFC14D'
elif typ == 'Stopped':
color = '#ff5c33'
else:
msg = 'Style not known! Go for black!'
print(msg, ":", typ)
message(msg)
color = '#000000'
return color
[docs]def get_style(typ):
"""
Style colores for the content of widgets.
Arguments:
typ - Typ of color
Return:
Color string
"""
color = get_color(typ)
return 'QPushButton {{color: {0}}} QLabel {{color: {0}}} QLineEdit {{color: {0}}} QComboBox {{color: {0}}}'.format(color)
[docs]def rebin(arr, new_shape):
shape = (new_shape[0], arr.shape[0] // new_shape[0],
new_shape[1], arr.shape[1] // new_shape[1])
return arr.reshape(shape).mean(-1).mean(1)
from .messagebox import MessageBox
from .mountcontainer import MountContainer
from .statuscontainer import StatusContainer
from .settingscontainer import SettingsContainer
from .buttoncontainer import ButtonContainer
from .notificationcontainer import NotificationContainer
from .plotcontainer import PlotContainer
from .tabdocker import TabDocker
from .selectdialog import SelectDialog