# code adapted from https://github.com/aristoteleo/dynamo-release/blob/master/dynamo/configuration.py
import inspect
import logging
import os
import warnings
from functools import wraps
from typing import List, Optional, Tuple, Union
import colorcet
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from anndata import AnnData
from cycler import cycler
from matplotlib import cm, colors, rcParams
from scipy import sparse
from .errors import ConfigurationError
from .logging import logger_manager as lm
# Global tolerance value:
[docs]EPS = np.finfo(float).eps
[docs]MAX = np.finfo(np.float32).max
[docs]class SpateoConfig:
def __init__(
self,
logging_level: int = logging.INFO,
n_threads: int = os.cpu_count(),
):
self.logging_level = logging_level
self.n_threads = n_threads
@property
[docs] def logging_level(self):
return self.__logging_level
@property
[docs] def n_threads(self):
return self.__n_threads
@logging_level.setter
def logging_level(self, level: Union[str, int]):
lm.main_debug(f"Setting logging level to {level}.")
if isinstance(level, str):
level = level.lower()
if level == "debug":
level = logging.DEBUG
elif level == "info":
level = logging.INFO
elif level == "warning":
level = logging.WARNING
elif level == "error":
level = logging.ERROR
elif level == "critical":
level = logging.CRITICAL
lm.main_set_level(level)
self.__logging_level = level
@n_threads.setter
def n_threads(self, n: int):
lm.main_debug(f"Setting n_threads to {n}.")
try:
import torch
torch.set_num_threads(n)
except:
pass
try:
import cv2
cv2.setNumThreads(n)
except:
pass
try:
import tensorflow as tf
tf.config.threading.set_intra_op_parallelism_threads(n)
tf.config.threading.set_inter_op_parallelism_threads(n)
except:
pass
self.__n_threads = n
[docs]class SpateoAdataKeyManager:
# This key will be present in the .uns of the AnnData to indicate the type of
# information the AnnData holds.
[docs] ADATA_TYPE_KEY = "__type"
[docs] ADATA_DEFAULT_TYPE = None
[docs] ADATA_AGG_TYPE = "AGG" # This is an AnnData containing aggregated UMI counts
[docs] ADATA_UMI_TYPE = "UMI" # This is an obs x genes AnnData (canonical)
[docs] UNS_SPATIAL_KEY = "spatial"
[docs] UNS_SPATIAL_BINSIZE_KEY = "binsize"
[docs] UNS_SPATIAL_SCALE_KEY = "scale"
[docs] UNS_SPATIAL_SCALE_UNIT_KEY = "scale_unit"
[docs] UNS_SPATIAL_SEGMENTATION_KEY = "segmentation"
[docs] UNS_SPATIAL_ALIGNMENT_KEY = "alignment"
[docs] UNS_SPATIAL_QC_KEY = "qc"
[docs] SPLICED_LAYER_KEY = "spliced"
[docs] UNSPLICED_LAYER_KEY = "unspliced"
[docs] STAIN_LAYER_KEY = "stain"
[docs] LABELS_LAYER_KEY = "labels"
[docs] MARKERS_SUFFIX = "markers"
[docs] DISTANCES_SUFFIX = "distances"
[docs] LABELS_SUFFIX = "labels"
[docs] SCORES_SUFFIX = "scores"
[docs] EXPANDED_SUFFIX = "expanded"
[docs] AUGMENTED_SUFFIX = "augmented"
[docs] SELECTION_SUFFIX = "selection"
[docs] BOUNDARY_SUFFIX = "boundary"
[docs] def gen_new_layer_key(layer_name: str, key: str, sep: str = "_") -> str:
if layer_name == "":
return key
if layer_name[-1] == sep:
return layer_name + key
return sep.join([layer_name, key])
[docs] def select_layer_data(
adata: AnnData, layer: str, copy: bool = False, make_dense: bool = False
) -> Union[np.ndarray, sparse.spmatrix]:
lm.main_info(f"<select> {layer} layer in AnnData Object")
if layer is None:
layer = SpateoAdataKeyManager.X_LAYER
res_data = None
if layer == SpateoAdataKeyManager.X_LAYER:
res_data = adata.X
else:
res_data = adata.layers[layer]
if make_dense and sparse.issparse(res_data):
return res_data.toarray()
if copy:
return res_data.copy()
return res_data
[docs] def set_layer_data(
adata: AnnData, layer: str, vals: np.ndarray, var_indices: Optional[np.ndarray] = None, replace: bool = False
):
lm.main_info_insert_adata_layer(layer)
# Mostly for testing
if replace:
adata.layers[layer] = vals
return
if var_indices is None:
var_indices = slice(None)
if layer == SpateoAdataKeyManager.X_LAYER:
adata.X[:, var_indices] = vals
elif layer in adata.layers:
adata.layers[layer][:, var_indices] = vals
else:
# layer does not exist in adata
# ignore var_indices and set values as a new layer
adata.layers[layer] = vals
[docs] def get_adata_type(adata: AnnData) -> str:
return adata.uns[SpateoAdataKeyManager.ADATA_TYPE_KEY]
[docs] def adata_is_type(adata: AnnData, t: str) -> bool:
return SpateoAdataKeyManager.get_adata_type(adata) == t
[docs] def check_adata_is_type(t: str, argname: str = "adata", optional: bool = False):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# Get original, unwrapped function in case multiple decorators
# are applied.
unwrapped = inspect.unwrap(func)
# Obtain arguments by name.
call_args = inspect.getcallargs(unwrapped, *args, **kwargs)
adata = call_args[argname]
passing = (
all(SpateoAdataKeyManager.adata_is_type(_adata, t) for _adata in adata)
if isinstance(adata, (list, tuple))
else SpateoAdataKeyManager.adata_is_type(adata, t)
if type(adata) == AnnData
else False
)
if (not optional or adata is not None) and not passing:
if isinstance(adata, (list, tuple)):
raise ConfigurationError(
f"AnnDatas provided to `{argname}` argument must be of `{t}` type, but some are not."
)
elif type(adata) == AnnData:
raise ConfigurationError(
f"AnnData provided to `{argname}` argument must be of `{t}` type, but received "
f"`{SpateoAdataKeyManager.get_adata_type(adata)}` type."
)
else:
raise ConfigurationError(f"AnnData is not AnnData object, but {type(adata)}.")
return func(*args, **kwargs)
return wrapper
return decorator
[docs] def init_adata_type(adata: AnnData, t: Optional[str] = None):
lm.main_info_insert_adata_uns(SpateoAdataKeyManager.ADATA_TYPE_KEY)
if t is None:
t = SpateoAdataKeyManager.ADATA_DEFAULT_TYPE
adata.uns[SpateoAdataKeyManager.ADATA_TYPE_KEY] = t
[docs] def init_uns_pp_namespace(adata: AnnData):
lm.main_info_insert_adata_uns(SpateoAdataKeyManager.UNS_PP_KEY)
if SpateoAdataKeyManager.UNS_PP_KEY not in adata.uns:
adata.uns[SpateoAdataKeyManager.UNS_PP_KEY] = {}
[docs] def init_uns_spatial_namespace(adata: AnnData):
lm.main_info_insert_adata_uns(SpateoAdataKeyManager.UNS_SPATIAL_KEY)
if SpateoAdataKeyManager.UNS_SPATIAL_KEY not in adata.uns:
adata.uns[SpateoAdataKeyManager.UNS_SPATIAL_KEY] = {}
[docs] def set_uns_spatial_attribute(adata: AnnData, key: str, value: object):
if SpateoAdataKeyManager.UNS_SPATIAL_KEY not in adata.uns:
SpateoAdataKeyManager.init_uns_spatial_namespace(adata)
adata.uns[SpateoAdataKeyManager.UNS_SPATIAL_KEY][key] = value
[docs] def get_uns_spatial_attribute(adata: AnnData, key: str) -> object:
return adata.uns[SpateoAdataKeyManager.UNS_SPATIAL_KEY][key]
[docs] def has_uns_spatial_attribute(adata: AnnData, key: str) -> bool:
return key in adata.uns[SpateoAdataKeyManager.UNS_SPATIAL_KEY]
[docs] def get_agg_bounds(adata: AnnData) -> Tuple[int, int, int, int]:
"""Get (xmin, xmax, ymin, ymax) for AGG type anndatas."""
atype = SpateoAdataKeyManager.get_adata_type(adata)
if atype != SpateoAdataKeyManager.ADATA_AGG_TYPE:
raise ConfigurationError(f"AnnData has incorrect type: {atype}")
return int(adata.obs_names[0]), int(adata.obs_names[-1]), int(adata.var_names[0]), int(adata.var_names[-1])
[docs]SKM = SpateoAdataKeyManager
# Means to shift the scale of colormaps:
[docs]def shiftedColorMap(
cmap: mpl.colors.ListedColormap,
start: float = 0,
midpoint: float = 0.5,
stop: float = 1.0,
name: str = "shiftedcmap",
) -> mpl.colors.ListedColormap:
"""
Function to offset the "center" of a colormap. Useful for
data with a negative min and positive max, and you want the
middle of the colormap's dynamic range to be at zero.
Args:
cmap: The matplotlib colormap to be altered
start: Offset from the lowest point in the colormap's range.
Defaults to 0.0 (no lower offset). Should be between
0.0 and `midpoint`.
midpoint: The new center of the colormap. Defaults to
0.5 (no shift). Should be between 0.0 and 1.0. In
general, this should be 1 - vmax / (vmax + abs(vmin))
For example if your data range from -15.0 to +5.0, and
you want the center of the colormap at 0.0, `midpoint`
should be set to 1 - 5/(5 + 15)) or 0.75
stop: Offset from the highest point in the colormap's range.
Defaults to 1.0 (no upper offset). Should be between
`midpoint` and 1.0.
name: the colormap name of the shifted colormap that will be registered.
Returns:
newcmap: a new colormap that has the middle point of the colormap shifted.
"""
# Check for existing shifted colormap:
mpl.cm.ColormapRegistry.unregister(plt.colormaps, name="shiftedcmap")
cdict = {"red": [], "green": [], "blue": [], "alpha": []}
# regular index to compute the colors
reg_index = np.linspace(start, stop, 257)
# shifted index to match the data
shift_index = np.hstack(
[np.linspace(0.0, midpoint, 128, endpoint=False), np.linspace(midpoint, 1.0, 129, endpoint=True)]
)
for ri, si in zip(reg_index, shift_index):
r, g, b, a = cmap(ri)
cdict["red"].append((si, r, r))
cdict["green"].append((si, g, g))
cdict["blue"].append((si, b, b))
cdict["alpha"].append((si, a, a))
newcmap = mpl.colors.LinearSegmentedColormap(name, cdict)
mpl.colormaps.register(cmap=newcmap)
return newcmap
[docs]fire_cmap = mpl.colors.LinearSegmentedColormap.from_list("fire", colorcet.fire)
[docs]darkblue_cmap = mpl.colors.LinearSegmentedColormap.from_list("darkblue", colorcet.kbc)
[docs]darkgreen_cmap = mpl.colors.LinearSegmentedColormap.from_list("darkgreen", colorcet.kgy)
[docs]darkred_cmap = mpl.colors.LinearSegmentedColormap.from_list("darkred", colors=colorcet.linear_kry_5_95_c72[:192], N=256)
[docs]darkpurple_cmap = mpl.colors.LinearSegmentedColormap.from_list("darkpurple", colorcet.linear_bmw_5_95_c89)
# add gkr theme
[docs]div_blue_black_red_cmap = mpl.colors.LinearSegmentedColormap.from_list(
"div_blue_black_red", colorcet.diverging_gkr_60_10_c40
)
# add RdBu_r theme
[docs]div_blue_red_cmap = mpl.colors.LinearSegmentedColormap.from_list("div_blue_red", colorcet.diverging_bwr_55_98_c37)
# add glasbey_bw for cell annotation in white background
[docs]glasbey_white_cmap = mpl.colors.LinearSegmentedColormap.from_list("glasbey_white", colorcet.glasbey_bw_minc_20)
# add glasbey_bw_minc_20_maxl_70 theme for cell annotation in dark background
[docs]glasbey_dark_cmap = mpl.colors.LinearSegmentedColormap.from_list("glasbey_dark", colorcet.glasbey_bw_minc_20_maxl_70)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
if "fire" not in mpl.colormaps():
mpl.colormaps.register(cmap=fire_cmap, name="fire")
if "darkblue" not in mpl.colormaps():
mpl.colormaps.register(cmap=darkblue_cmap, name="darkblue")
if "darkgreen" not in mpl.colormaps():
mpl.colormaps.register(cmap=darkgreen_cmap, name="darkgreen")
if "darkred" not in mpl.colormaps():
mpl.colormaps.register(cmap=darkred_cmap, name="darkred")
if "darkpurple" not in mpl.colormaps():
mpl.colormaps.register(cmap=darkpurple_cmap, name="darkpurple")
if "div_blue_black_red" not in mpl.colormaps():
mpl.colormaps.register(cmap=div_blue_black_red_cmap, name="div_blue_black_red")
if "div_blue_red" not in mpl.colormaps():
mpl.colormaps.register(cmap=div_blue_red_cmap, name="div_blue_red")
if "glasbey_white" not in mpl.colormaps():
mpl.colormaps.register(cmap=glasbey_white_cmap, name="glasbey_white")
if "glasbey_dark" not in mpl.colormaps():
mpl.colormaps.register(cmap=glasbey_dark_cmap, name="glasbey_dark")
[docs]_themes = {
"fire": {
"cmap": "fire",
"color_key_cmap": "rainbow",
"background": "black",
"edge_cmap": "fire",
},
"viridis": {
"cmap": "viridis",
"color_key_cmap": "Spectral",
"background": "white",
"edge_cmap": "gray",
},
"inferno": {
"cmap": "inferno",
"color_key_cmap": "Spectral",
"background": "black",
"edge_cmap": "gray",
},
"blue": {
"cmap": "Blues",
"color_key_cmap": "tab20",
"background": "white",
"edge_cmap": "gray_r",
},
"red": {
"cmap": "Reds",
"color_key_cmap": "tab20b",
"background": "white",
"edge_cmap": "gray_r",
},
"green": {
"cmap": "Greens",
"color_key_cmap": "tab20c",
"background": "white",
"edge_cmap": "gray_r",
},
"darkblue": {
"cmap": "darkblue",
"color_key_cmap": "rainbow",
"background": "black",
"edge_cmap": "darkred",
},
"darkred": {
"cmap": "darkred",
"color_key_cmap": "rainbow",
"background": "black",
"edge_cmap": "darkblue",
},
"darkgreen": {
"cmap": "darkgreen",
"color_key_cmap": "rainbow",
"background": "black",
"edge_cmap": "darkpurple",
},
"div_blue_black_red": {
"cmap": "div_blue_black_red",
"color_key_cmap": "div_blue_black_red",
"background": "black",
"edge_cmap": "gray_r",
},
"div_blue_red": {
"cmap": "div_blue_red",
"color_key_cmap": "div_blue_red",
"background": "white",
"edge_cmap": "gray_r",
},
"glasbey_dark": {
"cmap": "glasbey_dark",
"color_key_cmap": "glasbey_dark",
"background": "black",
"edge_cmap": "gray",
},
"glasbey_white_zebrafish": {
"cmap": "zebrafish",
"color_key_cmap": "zebrafish",
"background": "white",
"edge_cmap": "gray_r",
},
"glasbey_white": {
"cmap": "glasbey_white",
"color_key_cmap": "glasbey_white",
"background": "white",
"edge_cmap": "gray_r",
},
}
[docs]def reset_rcParams():
"""Reset `matplotlib.rcParams` to defaults."""
from matplotlib import rcParamsDefault
rcParams.update(rcParamsDefault)
# create cmap
[docs]zebrafish_colors = [
"#4876ff",
"#85C7F2",
"#cd00cd",
"#911eb4",
"#000080",
"#808080",
"#008080",
"#ffc125",
"#262626",
"#3cb44b",
"#ff4241",
"#b77df9",
]
# https://github.com/vega/vega/wiki/Scales#scale-range-literals
[docs]cyc_10 = list(map(colors.to_hex, cm.tab10.colors))
[docs]cyc_20 = list(map(colors.to_hex, cm.tab20c.colors))
[docs]zebrafish_256 = list(map(colors.to_hex, zebrafish_colors))
[docs]def spateo_theme(background="white"):
# https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/mpl-data/stylelib/dark_background.mplstyle
if background == "black":
rcParams.update(
{
"lines.color": "w",
"patch.edgecolor": "w",
"text.color": "w",
"axes.facecolor": background,
"axes.edgecolor": "white",
"axes.labelcolor": "w",
"xtick.color": "w",
"ytick.color": "w",
"figure.facecolor": background,
"figure.edgecolor": background,
"savefig.facecolor": background,
"savefig.edgecolor": background,
"grid.color": "w",
"axes.grid": False,
}
)
else:
rcParams.update(
{
"lines.color": "k",
"patch.edgecolor": "k",
"text.color": "k",
"axes.facecolor": background,
"axes.edgecolor": "black",
"axes.labelcolor": "k",
"xtick.color": "k",
"ytick.color": "k",
"figure.facecolor": background,
"figure.edgecolor": background,
"savefig.facecolor": background,
"savefig.edgecolor": background,
"grid.color": "k",
"axes.grid": False,
}
)
[docs]def config_spateo_rcParams(
background: str = "white",
prop_cycle: List[str] = zebrafish_256,
fontsize: int = 8,
color_map: mpl.colors.ListedColormap = None,
frameon: Optional[bool] = None,
) -> None:
"""Configure matplotlib.rcParams to spateo defaults (based on ggplot style and scanpy).
Args:
background: The background color of the plot. By default, we use the white ground
which is suitable for producing figures for publication. Setting it to `black` background will
be great for presentation.
prop_cycle: A list with hex color codes
fontsize: Size of font
color_map: Color map
frameon: Whether to have frame for the figure.
Returns:
Nothing but configure the rcParams globally.
"""
# from http://www.huyng.com/posts/sane-color-scheme-for-matplotlib/
rcParams["patch.linewidth"] = 0.5
rcParams["patch.facecolor"] = "348ABD" # blue
rcParams["patch.edgecolor"] = "EEEEEE"
rcParams["patch.antialiased"] = True
rcParams["font.size"] = 10.0
rcParams["axes.facecolor"] = "E5E5E5"
rcParams["axes.edgecolor"] = "white"
rcParams["axes.linewidth"] = 1
rcParams["axes.grid"] = True
# rcParams['axes.titlesize'] = "x-large"
# rcParams['axes.labelsize'] = "large"
rcParams["axes.labelcolor"] = "555555"
rcParams["axes.axisbelow"] = True # grid/ticks are below elements (e.g., lines, text)
# rcParams['axes.prop_cycle'] = cycler('color', ['E24A33', '348ABD', '988ED5', '777777', 'FBC15E', '8EBA42', 'FFB5B8'])
# # E24A33 : red
# # 348ABD : blue
# # 988ED5 : purple
# # 777777 : gray
# # FBC15E : yellow
# # 8EBA42 : green
# # FFB5B8 : pink
# rcParams['xtick.color'] = "555555"
rcParams["xtick.direction"] = "out"
# rcParams['ytick.color'] = "555555"
rcParams["ytick.direction"] = "out"
rcParams["grid.color"] = "white"
rcParams["grid.linestyle"] = "-" # solid line
rcParams["figure.facecolor"] = "white"
rcParams["figure.edgecolor"] = "white" # 0.5
# the following code is modified from scanpy
# https://github.com/theislab/scanpy/blob/178a0981405ba8ccfd5031eb15bc07b3a45d2730/scanpy/plotting/_rcmod.py
# dpi options (mpl default: 100, 100)
rcParams["figure.dpi"] = 100
rcParams["savefig.dpi"] = 300
# figure (default: 0.125, 0.96, 0.15, 0.91)
rcParams["figure.figsize"] = (6, 4)
rcParams["figure.subplot.left"] = 0.18
rcParams["figure.subplot.right"] = 0.96
rcParams["figure.subplot.bottom"] = 0.15
rcParams["figure.subplot.top"] = 0.91
# lines (defaults: 1.5, 6, 1)
rcParams["lines.linewidth"] = 1.5 # the line width of the frame
rcParams["lines.markersize"] = 6
rcParams["lines.markeredgewidth"] = 1
# font
rcParams["font.sans-serif"] = [
"Arial",
"sans-serif",
"Helvetica",
"DejaVu Sans",
"Bitstream Vera Sans",
]
fontsize = fontsize
labelsize = 0.90 * fontsize
# fonsizes (default: 10, medium, large, medium)
rcParams["font.size"] = fontsize
rcParams["legend.fontsize"] = labelsize
rcParams["axes.titlesize"] = fontsize
rcParams["axes.labelsize"] = labelsize
# legend (default: 1, 1, 2, 0.8)
rcParams["legend.numpoints"] = 1
rcParams["legend.scatterpoints"] = 1
rcParams["legend.handlelength"] = 0.5
rcParams["legend.handletextpad"] = 0.4
# color cycle
rcParams["axes.prop_cycle"] = cycler(color=prop_cycle) # use tab20c by default
# lines
rcParams["axes.linewidth"] = 0.8
rcParams["axes.edgecolor"] = "black"
rcParams["axes.facecolor"] = "white"
# ticks (default: k, k, medium, medium)
rcParams["xtick.color"] = "k"
rcParams["ytick.color"] = "k"
rcParams["xtick.labelsize"] = labelsize
rcParams["ytick.labelsize"] = labelsize
# axes grid (default: False, #b0b0b0)
rcParams["axes.grid"] = False
rcParams["grid.color"] = ".8"
# color map
rcParams["image.cmap"] = "RdBu_r" if color_map is None else color_map
spateo_theme(background)
# frame (default: True)
frameon = False if frameon is None else frameon
global _frameon
_frameon = frameon
[docs]def set_pub_style(scaler: float = 1) -> None:
"""
formatting helper function that can be used to save publishable figures
Args:
scaler: The multiplier to universally increase or decrease the font sizes.
Returns:
Nothing but set up the configuration for saving publishable figures.
"""
set_figure_params("spateo", background="white")
mpl.use("cairo")
mpl.rcParams.update({"font.size": 6 * scaler})
params = {
"font.size": 6 * scaler,
"legend.fontsize": 6 * scaler,
"legend.handlelength": 0.5 * scaler,
"axes.labelsize": 8 * scaler,
"axes.titlesize": 8 * scaler,
"xtick.labelsize": 8 * scaler,
"ytick.labelsize": 8 * scaler,
"axes.titlepad": 1 * scaler,
"axes.labelpad": 1 * scaler,
}
mpl.rcParams.update(params)
[docs]def set_pub_style_mpltex():
"""formatting helper function based on mpltex package that can be used to save publishable figures"""
set_figure_params("spateo", background="white")
mpl.use("cairo")
# the following code is adapted from https://github.com/liuyxpp/mpltex
# latex_preamble = r"\usepackage{siunitx}\sisetup{detect-all}\usepackage{helvet}\usepackage[eulergreek,EULERGREEK]{sansmath}\sansmath"
params = {
"font.family": "sans-serif",
"font.serif": ["Times", "Computer Modern Roman"],
"font.sans-serif": [
"Arial",
"Helvetica",
"sans-serif",
"Computer Modern Sans serif",
],
"font.size": 6,
# "text.usetex": True,
# "text.latex.preamble": latex_preamble, # To force LaTeX use Helvetica
# "axes.prop_cycle": default_color_cycler,
"axes.titlesize": 8,
"axes.labelsize": 8,
"axes.linewidth": 1,
"figure.subplot.left": 0.125,
"figure.subplot.right": 0.95,
"figure.subplot.bottom": 0.1,
"figure.subplot.top": 0.95,
"savefig.dpi": 300,
"savefig.format": "pdf",
# "savefig.bbox": "tight",
# this will crop white spaces around images that will make
# width/height no longer the same as the specified one.
"legend.fontsize": 6,
"legend.frameon": False,
"legend.numpoints": 1,
"legend.handlelength": 0.5,
"legend.scatterpoints": 1,
"legend.labelspacing": 0.5,
"legend.markerscale": 0.9,
"legend.handletextpad": 0.5, # pad between handle and text
"legend.borderaxespad": 0.5, # pad between legend and axes
"legend.borderpad": 0.5, # pad between legend and legend content
"legend.columnspacing": 1, # pad between each legend column
# "text.fontsize" : 4,
"xtick.labelsize": 6,
"ytick.labelsize": 6,
"lines.linewidth": 1,
"lines.markersize": 6,
# "lines.markeredgewidth": 0,
# 0 will make line-type markers, such as "+", "x", invisible
# Revert some properties to mpl v1 which is more suitable for publishing
"axes.autolimit_mode": "round_numbers",
"axes.xmargin": 0,
"axes.ymargin": 0,
"xtick.direction": "in",
"xtick.top": True,
"ytick.direction": "in",
"ytick.right": True,
"axes.titlepad": 1,
"axes.labelpad": 1,
}
mpl.rcParams.update(params)