# -*- coding: utf-8 -*-
"""
Tools, functions and other funny things
"""
import copy
import logging
import os
import re
import numpy as np
logger = logging.getLogger(__name__)
__all__ = ["rotation_matrix_xyz", "get_resource"]
def sort_lists(a, b):
b = [x for (y, x) in sorted(zip(a, b))]
a = sorted(a)
return a, b
[docs]def get_resource(res_name, res_type="icons"):
"""
Build absolute path to specified resource within the package
Args:
res_name (str): name of the resource
res_type (str): subdir
Return:
str: path to resource
"""
own_path = os.path.dirname(__file__)
resource_path = os.path.abspath(os.path.join(own_path, "resources", res_type))
return os.path.join(resource_path, res_name)
def sort_tree(data_list, sort_key_path):
"""
Helper method for data sorting.
Takes a list of simulation results and sorts them into a tree whose index is
given by the sort_key_path.
Args:
data_list(list): List of simulation results
sort_key_path(list): List of dictionary keys to sort for.
Return:
dict: sorted dictionary
"""
result = {}
for elem in data_list:
temp_element = copy.deepcopy(elem)
sort_name = get_sub_value(temp_element, sort_key_path)
if sort_name not in result:
result.update({sort_name: {}})
while temp_element:
val, keys = _remove_deepest(temp_element)
if keys:
_add_sub_value(result[sort_name], keys, val)
return result
def get_sub_value(source, key_path):
sub_dict = source
for key in key_path:
sub_dict = sub_dict[key]
return sub_dict
def _remove_deepest(top_dict, keys=None):
"""
Iterates recursively over dict and removes deepest entry.
Args:
top_dict (dict): dictionary
keys (list): select entries to remove
Return:
tuple: entry and path to entry
"""
if not keys:
keys = []
for key in list(top_dict.keys()):
val = top_dict[key]
if isinstance(val, dict):
if val:
keys.append(key)
return _remove_deepest(val, keys)
else:
del top_dict[key]
continue
else:
del top_dict[key]
keys.append(key)
return val, keys
return None, None
def _add_sub_value(top_dict, keys, val):
if len(keys) == 1:
# we are here
if keys[0] in top_dict:
top_dict[keys[0]].append(val)
else:
top_dict.update({keys[0]: [val]})
return
# keep iterating
if keys[0] not in top_dict:
top_dict.update({keys[0]: {}})
_add_sub_value(top_dict[keys[0]], keys[1:], val)
return
[docs]def rotation_matrix_xyz(axis, angle, angle_dim):
"""
Calculate the rotation matrix for a rotation around a given axis with the angle :math:`\\varphi`.
Args:
axis (str): choose rotation axis "x", "y" or "z"
angle (int or float): rotation angle :math:`\\varphi`
angle_dim (str): choose "deg" for degree or "rad" for radiant
Return:
:obj:`numpy.ndarray`: rotation matrix
"""
assert angle_dim is "deg" or angle_dim is "rad"
assert axis is "x" or axis is "y" or axis is "z"
x = 0
y = 0
z = 0
if angle_dim is "deg":
a = np.deg2rad(angle)
else:
a = angle
if axis is "x":
x = 1
y = 0
z = 0
if axis is "y":
x = 0
y = 1
z = 0
if axis is "z":
x = 0
y = 0
z = 1
s = np.sin(a)
c = np.cos(a)
rotation_matrix = np.array([[c + x ** 2 * (1 - c), x * y * (1 - c) - z * s, x * z * (1 - c) + y * s],
[y * x * (1 - c) + z * s, c + y ** 2 * (1 - c), y * z * (1 - c) - x * s],
[z * x * (1 - c) - y * s, z * y * (1 - c) + x * s, c + z ** 2 * (1 - c)]])
return rotation_matrix
class PlainTextLogger(logging.Handler):
"""
Logging handler hat formats log data for line display
"""
def __init__(self, level=logging.NOTSET):
logging.Handler.__init__(self, level)
self.name = "PlainTextLogger"
formatter = logging.Formatter(
fmt="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
datefmt="%H:%M:%S")
self.setFormatter(formatter)
log_filter = PostFilter(invert=True)
self.addFilter(log_filter)
self.cb = None
def set_target_cb(self, cb):
self.cb = cb
def emit(self, record):
msg = self.format(record)
if self.cb:
self.cb(msg)
else:
logging.getLogger().error("No callback configured!")
class PostFilter(logging.Filter):
"""
Filter to sort out all not PostProcessing related log information
"""
def __init__(self, invert=False):
logging.Filter.__init__(self)
self._invert = invert
self.exp = re.compile(r"Post|Meta|Process")
def filter(self, record):
m = self.exp.match(record.name)
if self._invert:
return not bool(m)
else:
return bool(m)
def swap_cols(arr, frm, to):
""" Swap the column `frm` from a given index `to` the given index.
"""
arr[:, [frm, to]] = arr[:, [to, frm]]
return arr
def swap_rows(arr, frm, to):
""" Swap the rows `frm` from a given index `to` the given index.
"""
if len(arr.shape) == 1:
arr[[frm, to]] = arr[[to, frm]]
elif len(arr.shape) == 2:
arr[[frm, to], :] = arr[[to, frm], :]
return arr
class LengthList(object):
def __init__(self, maxLength):
self.maxLength = maxLength
self.ls = []
def push(self, st):
if len(self.ls) == self.maxLength:
self.ls.pop(0)
self.ls.append(st)
def get_list(self):
return self.ls
def __len__(self):
return len(self.ls)
def __getitem__(self, key):
return self.ls[key]
def get_figure_size(scale):
"""
calculate optimal figure size with the golden ratio
:param scale:
:return:
"""
# TODO: Get this from LaTeX using \the\textwidth
fig_width_pt = 448.13095
inches_per_pt = 1.0 / 72.27 # Convert pt to inch (stupid imperial system)
golden_ratio = (np.sqrt(5.0) - 1.0) / 2.0 # Aesthetic ratio
fig_width = fig_width_pt * inches_per_pt * scale # width in inches
fig_height = fig_width * golden_ratio # height in inches
fig_size = [fig_width, fig_height]
return fig_size