"""
Setting Global config for whole processing level
"""
from ast import literal_eval
from pathlib import Path
from typing import List, Optional, Sequence
from anndata import AnnData
from rich.console import Console
from rich.table import Table
console = Console()
class History(object):
key = "spatialtis_analysis_history"
def __init__(self, data: AnnData):
self.data = data
if self.key in data.uns_keys():
self.storage = data.uns[self.key]
else:
self.storage = {}
data.uns[self.key] = self.storage
def add(self, task_name: str, last_used_key: str):
self.storage[task_name] = last_used_key
self.data.uns[self.key] = self.storage
[docs]class _Config(object):
"""Global configurations for spatialtis
Don't directly import this class, import the instance created for you
>>> from spatialtis import Config
This allows you to set global configuration, so that you don't have to repeatly pass the same
parameters in every function.
To set a config, simple set the attribute
>>> Config.auto_save = True # auto save to current directory
>>> Config.auto_save = "my_spatialtis_result" # save to custom directory
To view your current configs
>>> Config.view()
Current configurations of SpatialTis
┌─────────────────────────┬───────────────┬───────┐
│ Options │ Attributes │ Value │
├─────────────────────────┼───────────────┼───────┤
│ Multiprocessing │ mp │ True │
│ Verbose │ verbose │ True │
│ Progress bar │ progress_bar │ False │
│ Auto save │ auto_save │ False │
│ Experiment observations │ exp_obs │ None │
│ ROI key │ roi_key │ │
│ Cell type key │ cell_type_key │ │
│ Marker key │ marker_key │ │
│ Centroid key │ centroid_key │ │
│ Shape key │ shape_key │ │
└─────────────────────────┴───────────────┴───────┘
Attributes:
exp_obs: **REQUIRED**, (Default: `None`); The columns in `AnnData.obs` that tells how your experiments organized,
for example, you have different columns ["patients", "sex", "organ_part", "roi_id"], the last one will
be used as `roi_key` by default.
roi_key: *OPTIONAL*, (Default: `None`); Overwrite the `roi_key`
centroid_key: **REQUIRED**, (Default: `None`); The column in `AnnData.obs` that store cell coordination,
must be in wkt format, you can use `spatialtis.transform_points` to transform you data into wkt.
shape_key: *OPTIONAL*, (Default: `None`);The columns in `AnnData.obs` that store cell shape,
must be in wkt format, you can use `spatialtis.transform_shapes` to transform you data into wkt.
cell_type_key: *OPTIONAL*, (Default: `None`); The columns in `AnnData.obs` that store cell type name,
some analysis require cell type name to proceed
marker_key: *OPTIONAL*, (Default: `None`); The columns in `AnnData.var` that store protein/gene/transcript... name,
if not specific, will use `AnnData.var.index`. some analysis require marker name to proceed
mp: *OPTIONAL*, (Default: `True`); To turn on/off multiprocessing. From v0.4.0, It only affects `spatial_coexp`,
other analysis are implemented in Rust with default parallel processing on.
verbose: *OPTIONAL*, (Default: `True`); Control the printed message
progress_bar: *OPTIONAL*, (Default: `True`); Control the progress bar
auto_save: *OPTIONAL*, (Default: `None`); Set to `True` will automatically save all images in
`spatialtis_result` fold created at current directory, or set a path to store else where.
"""
def __init__(self):
self._exp_obs: Optional[List[str]] = None
self._cell_type_key: Optional[str] = None
self._verbose: bool = True
self.save_path: Optional[Path] = None
self.progress_bar: bool = False
self._roi_key: Optional[str] = None
self.mp: bool = True
# used key name to store info in anndata
self.centroid_key: Optional[str] = None
self.shape_key: Optional[str] = None
self.marker_key: Optional[str] = None
def view(self):
"""Print a table with current configurations"""
table = Table(title="Current configurations of SpatialTis")
table.add_column("Options", style="bright_black")
table.add_column("Attributes", style="cyan")
table.add_column("Value", style="magenta")
# add content
table.add_row("Multiprocessing", "mp", str(self.mp))
table.add_row("Verbose", "verbose", str(self.verbose))
table.add_row("Progress bar", "progress_bar", str(self.progress_bar))
table.add_row(
"Auto save",
"auto_save",
str(self.save_path) if self.auto_save else str(self.auto_save),
)
table.add_row("Experiment observations", "exp_obs", str(self.exp_obs))
table.add_row("ROI key", "roi_key", self.roi_key)
table.add_row("Cell type key", "cell_type_key", self.cell_type_key)
table.add_row("Marker key", "marker_key", self.marker_key)
table.add_row("Centroid key", "centroid_key", self.centroid_key)
table.add_row("Shape key", "shape_key", self.shape_key)
console.print(table)
def __repr__(self):
self.view()
return "SpatialTis Global Configurations"
def _to_dict(self):
return dict(
mp=self.mp,
verbose=self.verbose,
progress_bar=self.progress_bar,
save_path=self.save_path,
exp_obs=self.exp_obs,
roi_key=self.roi_key,
cell_type_key=self.cell_type_key,
marker_key=self.marker_key,
centroid_key=self.centroid_key,
shape_key=self.shape_key,
)
@property
def roi_key(self):
return self._roi_key
@roi_key.setter
def roi_key(self, key):
if self.exp_obs is None:
self.exp_obs = [key]
if key not in self.exp_obs:
raise ValueError("The `roi_key` is not in your `exp_obs`")
else:
if self.exp_obs[-1] != key:
exp_obs = self.exp_obs
exp_obs.remove(key)
exp_obs.append(key)
self.exp_obs = exp_obs
else:
self._roi_key = key
@property
def exp_obs(self):
return self._exp_obs
@exp_obs.setter
def exp_obs(self, obs):
if isinstance(obs, (str, int, float)):
self._exp_obs = [obs]
self._roi_key = self._exp_obs[-1]
elif isinstance(obs, Sequence):
self._exp_obs = list(obs)
self._roi_key = self._exp_obs[-1]
elif obs is None:
self._exp_obs = None
else:
raise TypeError(f"Couldn't set `exp_obs` with type {type(obs)}")
@property
def cell_type_key(self):
return self._cell_type_key
@cell_type_key.setter
def cell_type_key(self, type_key):
if isinstance(type_key, (str, int, float)):
self._cell_type_key = type_key
elif type_key is None:
self._cell_type_key = None
else:
raise TypeError(f"Couldn't set CELL_TYPE_KEY with type {type(type_key)}")
@property
def verbose(self):
return self._verbose
@verbose.setter
def verbose(self, v):
if not isinstance(v, bool):
raise ValueError("CONFIG.verbose only accept bool value")
self._verbose = v
self.progress_bar = v
@property
def auto_save(self):
if self.save_path is not None:
return True
else:
return False
@auto_save.setter
def auto_save(self, path):
if isinstance(path, (str, Path)):
path = Path(path)
path.mkdir(exist_ok=True)
elif isinstance(path, bool):
if path:
path = Path().cwd() / "spatialtis-result"
path.mkdir(exist_ok=True)
else:
path = None
else:
raise TypeError("You can set a directory or set it True/False.")
self.save_path = path
def dumps(self, data: AnnData):
"""Save configurations to anndata
Args:
data: The `AnnData` object to save the Config
"""
data.uns["spatialtis_config"] = str(self._to_dict())
def loads(self, data: AnnData):
"""Load configurations from anndata
Args:
data: The `AnnData` object to load the Config
"""
config = literal_eval(data.uns["spatialtis_config"])
self.mp = config["mp"]
self.exp_obs = config["exp_obs"]
self.roi_key = config["roi_key"]
self.verbose = config["verbose"]
self.progress_bar = config["progress_bar"]
self.save_path = config["save_path"]
self.roi_key = config["roi_key"]
self.cell_type_key = config["cell_type_key"]
self.marker_key = config["marker_key"]
self.centroid_key = config["centroid_key"]
self.shape_key = config["shape_key"]
def reset(self):
"""Reset to default"""
self._verbose = True
self._exp_obs = None
self._roi_key = None
self._cell_type_key = None
self.mp = True
self.progress_bar = True
self.auto_save = False
self.marker_key = None
self.centroid_key = None
self.shape_key = None
# two state variable that don't need to reload
# need to init in the same place
Config = _Config()