Source code for nrv.nmod._axon_population

"""
NRV-:class:`.axon_population` handling.
"""

from typing import Type, Iterable, Literal
import numpy as np
from pandas import DataFrame
from matplotlib import pyplot as plt, patches as ptc

from .utils._axon_pop_generator import (
    create_axon_population,
    load_axon_population,
    save_axon_population,
    get_stat_expected,
)

from .utils._packers import (
    Placer,
    axon_packer,
    get_circular_contour,
    expand_pop,
    remove_collision,
    get_ppop_info,
)

from ..backend._inouts import check_function_kwargs
from ..backend._log_interface import pass_info, rise_warning, rise_error
from ..utils.geom import (
    create_cshape,
    Ellipse,  # typing
    Circle,  # typing
)
from ..utils.geom._cshape import CShape  # typing
from ..utils.geom._popshape import PopShape
from ..utils._misc import get_MRG_parameters


[docs] class axon_population(PopShape): """ Instance of an axon population. """
[docs] def __init__(self, **kwgs): """ Initialize an axon-population container and optionally generate its content. Parameters ---------- **kwgs : dict Keyword arguments filtered and forwarded to :meth:`generate`. """ super().__init__() gen_kwg = check_function_kwargs(self.generate, kwgs) if len(gen_kwg): self.generate(**gen_kwg)
@property def axon_pop(self): """ Tabular representation of the stored axon population. Returns ------- pandas.DataFrame Dataframe containing axon properties and optional placement columns. """ return self._pop @property def n_ax(self): """ Number of axons stored in the population. Returns ------- int Number of rows in the population dataframe. """ if not self.has_pop: return 0 return len(self._pop) # --------------- # # Overall methods # # --------------- #
[docs] def generate( self, geometry: None | Type[CShape] = None, center: tuple[float, float] = None, radius: None | float | tuple[float, float] = None, rot: float = 0, diameter: None | float | tuple[float, float] = None, discard_placement: bool = False, data: tuple[np.ndarray] | np.ndarray | str = None, n_ax: int = None, FVF: None | float = None, percent_unmyel: float = 0.7, M_stat: str = "Schellens_1", U_stat: str = "Ochoa_U", pos: None | tuple[np.ndarray] | np.ndarray | str = None, method: Literal["default", "packing"] = "default", delta: float = 0.01, delta_trace: float | None = None, delta_in: float | None = None, n_iter: int = None, fit_to_size: bool = False, with_node_shift: bool = True, overwrite=False, ): """ Generate the complete geometry and/or axon population and placement. Parameters ---------- geometry : None | Type[CShape] if not None, geometry used for the population center : tuple[float, float], optional _description_, by default None r1 : float, optional _description_, by default 0 r2 : float, optional _description_, by default 0 rot : float, optional _description_, by default 0 data : tuple[np.ndarray] | np.ndarray | str data to used to create the population. Supported data: - `str`: of the file path and name where to load the population properties. - `tuple[np.ndarray]` containing the population properties. - `np.ndarray`: of dimention (2, n_ax) or (4, n_ax), arange in the following order: `types`, `diameters`, `y` (optional), `z` (optional). n_ax : int, optional Number of axon to generate for the population (Unmyelinated and myelinated), default 100 FVF : float Fiber Volume Fraction estimated for the area. If None, the value n_ax is used. By default set to None percent_unmyel : float ratio of unmyelinated axons in the population. Should be between 0 and 1. M_stat : str name of the statistic in the librairy or path to a new librairy in csv for myelinated diameters repartition U_stat : str name of the statistic in the librairy or path to a new librairy in csv for unmyelinated diameters repartition method : Literal["default", "packing"], optional method to use for the , by default "default" overwrite : bool, optional If True placement is skipped if the population is already paced, by default False delta : float axon-to-axon and axon to border minimal distance, by default .01 delta_trace : float | None, optional _description_, by default None delta_in : float | None, optional _description_, by default None fit_to_size : bool if true, the axon population is extended to fit within fascicle size, if not the population is kept as is n_iter : int number of interation for the packing algorithm if the y-x axon coordinates are not specified with_node_shift : bool if True also compute the Node of Ranviers shifts fname : str optional, if specified, name file to store the population generated Note ---- When `FVF` is set, an approximated value of `n_ax` is calculated from: .. math:: FVF = \\frac{n_{axons}*E_{d}}{A_{tot}} where $E_{d}$ is the espected diameters from the myelinated and unmyelinated fibers stats and $A_{tot}$ is geometry total area. Tip --- It goes for the previous definition that `FVF` will only be accurate for large axon population. This might be improved in future version but for now it is adviced to define small population using `n_ax` instead of `FVF` """ self.set_geometry( geometry=geometry, center=center, radius=radius, rot=rot, diameter=diameter, discard_placement=discard_placement, ) if n_ax is not None or FVF is not None: self.fill_geometry( data=data, n_ax=n_ax, FVF=FVF, percent_unmyel=percent_unmyel, M_stat=M_stat, U_stat=U_stat, pos=pos, method=method, delta=delta, delta_trace=delta_trace, delta_in=delta_in, n_iter=n_iter, fit_to_size=fit_to_size, with_node_shift=with_node_shift, overwrite=overwrite, )
[docs] def fill_geometry( self, data: tuple[np.ndarray] | np.ndarray | str = None, n_ax: int = 100, FVF: None | float = None, percent_unmyel: float = 0.7, M_stat: str = "Schellens_1", U_stat: str = "Ochoa_U", pos: None | tuple[np.ndarray] | np.ndarray | str = None, method: Literal["default", "packing"] = "default", delta: float = 0.01, delta_trace: float | None = None, delta_in: float | None = None, n_iter: int = None, fit_to_size: bool = False, with_node_shift: bool = True, overwrite: bool = False, fname: str = None, ): """ Fill a geometricaly defined contour with axons Parameters ---------- data : tuple[np.ndarray] | np.ndarray | str data to used to create the population. Supported data: - `str`: of the file path and name where to load the population properties. - `tuple[np.ndarray]` containing the population properties. - `np.ndarray`: of dimention (2, n_ax) or (4, n_ax), arange in the following order: `types`, `diameters`, `y` (optional), `z` (optional). n_ax : int, optional Number of axon to generate for the population (Unmyelinated and myelinated), default 100 FVF : float Fiber Volume Fraction estimated for the area. If None, the value n_ax is used. By default set to None percent_unmyel : float ratio of unmyelinated axons in the population. Should be between 0 and 1. M_stat : str name of the statistic in the librairy or path to a new librairy in csv for myelinated diameters repartition U_stat : str name of the statistic in the librairy or path to a new librairy in csv for unmyelinated diameters repartition method : Literal["default", "packing"], optional method to use for the , by default "default" overwrite : bool, optional If True placement is skipped if the population is already paced, by default False delta : float axon-to-axon and axon to border minimal distance, by default .01 delta_trace : float | None, optional _description_, by default None delta_in : float | None, optional _description_, by default None fit_to_size : bool if true, the axon population is extended to fit within fascicle size, if not the population is kept as is n_iter : int number of interation for the packing algorithm if the y-x axon coordinates are not specified with_node_shift : bool if True also compute the Node of Ranviers shifts fname : str optional, if specified, name file to store the population generated Note ---- When `FVF` is set, an approximated value of `n_ax` is calculated from: .. math:: FVF = \\frac{n_{axons}*E_{d}}{A_{tot}} where $E_{d}$ is the espected diameters from the myelinated and unmyelinated fibers stats and $A_{tot}$ is geometry total area. Tip --- It goes for the previous definition that `FVF` will only be accurate for large axon population. This might be improved in future version but for now it is adviced to define small population using `n_ax` instead of `FVF` """ if not self.has_geom: rise_warning( "set the geometry before filling the fascicle, nothing was set" ) else: self.create_population( data=data, percent_unmyel=percent_unmyel, n_ax=n_ax, FVF=FVF, M_stat=M_stat, U_stat=U_stat, overwrite=overwrite, ) # Need for the case of data.shape is (4,n) # to avoid calling place twice if not self.has_placed_pop or overwrite: self.place_population( pos=pos, method=method, delta=delta, delta_trace=delta_trace, delta_in=delta_in, n_iter=n_iter, fit_to_size=fit_to_size, with_node_shift=with_node_shift, overwrite=overwrite, ) if fname is not None: save_axon_population(fname, *self._pop.to_numpy())
[docs] def generate_from_deprected_fascicle(self, key_dic: dict): """ Generate the population from the data saved in a deprecated fascicle file. Warning ------- This function is mostly for internal use for retrocompatibility. If deprecated save file are found, it is adviced to uptated them using :func:`update_fascicle_file`. Parameters ---------- key_dic : dict Dictionary containing the loaded fascicle """ if len({"y_grav_center", "z_grav_center", "D"} - key_dic.keys()) == 0: self.set_geometry( center=(key_dic["y_grav_center"], key_dic["z_grav_center"]), diameter=key_dic["D"], ) if ( len( { "axons_type", "axons_diameter", "axons_y", "axons_z", "NoR_relative_position", } - key_dic.keys() ) == 0 ): if len(key_dic["axons_diameter"]) > 0: self.create_population( data=(key_dic["axons_type"], key_dic["axons_diameter"]) ) if len(key_dic["axons_y"]) > 0: self.place_population(pos=(key_dic["axons_y"], key_dic["axons_z"])) if len(key_dic["NoR_relative_position"]) == len(self): self.generate_NoR_position_from_data(key_dic["NoR_relative_position"]) elif self.has_pop: rise_warning( "Number of NoR positions in the file does not match with the population, regenerating" ) self.generate_random_NoR_position()
# ---------------- # # Geometry methods # # ---------------- #
[docs] def set_geometry( self, geometry: None | Type[CShape] = None, center: tuple[float, float] = None, radius: None | float | tuple[float, float] = None, rot: float = 0, degree: bool = False, diameter: None | float | tuple[float, float] = None, discard_placement: bool = False, ): """ Set the geometry of the population Parameters ---------- center : tuple[float, float], optional Center of the shape, by default (0, 0) radius : float | tuple[float, float], optional Radius of the shape, by default 10 rot : None | float, optional Rotation of the shape, by default None diameter : None | float | tuple[float, float], optional Diameter of the shape. If None, radius value is used to define the shape, by default None discard_placement : bool If true and a unplace an potential population already placed. Else, only discard the axon not fiting in the geometry, by default False """ if geometry is not None: self.geom = geometry else: if self.has_geom: center = center or self.geom.center radius = radius or self.geom.radius if isinstance(self.geom, Ellipse): rot = rot or self.geom.rot if center is not None and radius is not None or diameter is not None: self.geom = create_cshape( center=center, radius=radius, rot=rot, degree=degree, diameter=diameter, ) else: raise ValueError( "Either the geometry or its property must be use in argument" ) # Find points outside the new geometry if self.has_placed_pop: if discard_placement: self.clear_population_placement() else: self.check_placement
[docs] def reshape_geometry( self, geometry: None | Type[CShape] = None, center: tuple[float, float] = None, radius: float | tuple[float, float] = None, rot: float = None, discard_placement: bool = False, ): """ Update the population geometry while reusing unchanged geometry parameters. Parameters ---------- geometry : Type[CShape] | None, optional New geometry type or explicit geometry object. center : tuple[float, float] | None, optional New center coordinates. radius : float | tuple[float, float] | None, optional New radius or semi-axes. rot : float | None, optional New rotation angle. discard_placement : bool, optional If ``True``, drop the current axon placement after reshaping. """ if geometry is None and self.has_geom: # Reload previous geometry properties if unchange # ! Not ideal method see how to generalize if center is None: center = self.geom.center if radius: radius = self.geom.radius rot = self.geom.rot self.set_geometry( geometry=geometry, center=center, radius=radius, rot=rot, discard_placement=discard_placement, )
# -------------------- # # Population genrators # # -------------------- #
[docs] def create_population( self, data: tuple[np.ndarray] | np.ndarray | str = None, n_ax: int = 100, FVF: None | float = None, percent_unmyel: float = 0.7, M_stat: str = "Schellens_1", U_stat: str = "Ochoa_U", overwrite=False, ): """ Creat an placed axon population Parameters ---------- data : tuple[np.ndarray] | np.ndarray | str data to used to create the population. Supported data: - `str`: of the file path and name where to load the population properties. - `tuple[np.ndarray]` containing the population properties. - `np.ndarray`: of dimention (2, n_ax) or (4, n_ax), arange in the following order: `types`, `diameters`, `y` (optional), `z` (optional). n_ax : int, optional Number of axon to generate for the population (Unmyelinated and myelinated), default 100 FVF : float Fiber Volume Fraction estimated for the area. If None, the value n_ax is used. By default set to None percent_unmyel : float ratio of unmyelinated axons in the population. Should be between 0 and 1. M_stat : str name of the statistic in the librairy or path to a new librairy in csv for myelinated diameters repartition U_stat : str name of the statistic in the librairy or path to a new librairy in csv for unmyelinated diameters repartition Note ---- When `FVF` is set, an approximated value of `n_ax` is calculated from: .. math:: FVF = \\frac{n_{axons}*E_{d}}{A_{tot}} where $E_{d}$ is the espected diameters from the myelinated and unmyelinated fibers stats and $A_{tot}$ is geometry total area. Tip --- It goes for the previous definition that `FVF` will only be accurate for large axon population. This might be improved in future version but for now it is adviced to define small population using `n_ax` instead of `FVF` """ if not self.has_pop or overwrite: if data is not None: self.create_population_from_data(data=data) else: self.create_population_from_stat( n_ax=n_ax, FVF=FVF, percent_unmyel=percent_unmyel, M_stat=M_stat, U_stat=U_stat, )
[docs] def create_population_from_stat( self, n_ax: int, FVF: None | float = None, percent_unmyel: float = 0.7, M_stat: str = "Schellens_1", U_stat: str = "Ochoa_U", ): """ Create a virtual population of axons (no Neuron implementation, axon class not called) of a controled number, with controlled statistics. Parameters ---------- n_ax : int Number of axon to generate for the population (Unmyelinated and myelinated). FVF : float Fiber Volume Fraction estimated for the area. If None, the value n_ax is used. By default set to None. percent_unmyel : float ratio of unmyelinated axons in the population. Should be between 0 and 1. M_stat : str name of the statistic in the librairy or path to a new librairy in csv for myelinated diameters repartition. U_stat : str name of the statistic in the librairy or path to a new librairy in csv for unmyelinated diameters repartition. Note ---- When `FVF` is set, an approximated value of `n_ax` is calculated from: .. math:: FVF = \\frac{n_{axons}*E_{d}}{A_{tot}} where $E_{d}$ is the espected diameters from the myelinated and unmyelinated fibers stats and $A_{tot}$ is geometry total area. Tip --- It goes for the previous definition that `FVF` will only be accurate for large axon population. This might be improved in future version but for now it is adviced to define small population using `n_ax` instead of `FVF` """ if FVF is not None: e_d = percent_unmyel * get_stat_expected(U_stat) + ( 1 - percent_unmyel ) * get_stat_expected(M_stat) e_A = np.pi * (0.5 * e_d) ** 2 n_ax = round(FVF * self.geom.area / (2 * e_A)) #!BUG in stats `2*` not supposed to be their pass_info(f"A {n_ax} axons population will be generated") axons_diameters, axons_type, _, _ = create_axon_population( n_ax, percent_unmyel=percent_unmyel, M_stat=M_stat, U_stat=U_stat ) _data = { "types": axons_type, "diameters": axons_diameters, } self._pop = DataFrame(data=_data)
[docs] def create_population_from_data(self, data: tuple[np.ndarray] | np.ndarray | str): """ create the population directely from the values Note ---- If column are not precised, the order for the data must be: +-------+-----------+------------+------------+------------+ | Types | Diameters | y-position | z-position | node shift | + + + + + + | | | `optional` | `optional` | `optional` | +-------+-----------+------------+------------+------------+ For `str` indexed objects (:class:`dict` or :class:`pandas.DataFrame`) the respective indexes of the column above must be: `"types"`, `"diameters"`, `"y"` (optional), `"z"` (optional), `"node_shift"` (optional). Parameters ---------- data : tuple[np.ndarray] | np.ndarray | str data to used to create the population. Supported data: - `str`: of the file path and name where to load the population properties. - `tuple[np.ndarray]` containing the population properties. - `np.ndarray`: of dimention (2, n_ax) or (4, n_ax), arange in the following order: `types`, `diameters`, `y` (optional), `z` (optional), `node_shift` (optional). """ if isinstance(data, str): axons_diameters, axons_type, _, _, y_axons, z_axons = load_axon_population( data ) data = axons_diameters, axons_type if np.isnan(y_axons).all() or np.isnan(z_axons).all(): data += (y_axons, z_axons) if isinstance(data, dict) or isinstance(data, DataFrame): _keys = ["types", "diameters", "y", "z", "node_shift"] if isinstance(data, dict): _dkeys = data.keys() else: _dkeys = set(data.columns) if len(set(_keys) - _dkeys) > 0: _keys = _keys[:4] if len(set(_keys) - _dkeys) > 0: _keys = _keys[:2] if len(set(_keys) - _dkeys) > 0: rise_warning( f"Wrong data format to create_population.", f"If data is dict or DataFrames it must at least contains the keys: {_keys}", ) else: data = [data[k] for k in _keys] if np.iterable(data): n_col = len(data) self._pop = DataFrame(data=zip(*data[:2]), columns=["types", "diameters"]) if n_col == 4: self._pop = DataFrame( data=zip(*data), columns=["types", "diameters", "y", "z"] ) self.check_placement pass_info("Axon placed population generated from data") elif n_col == 5: self._pop = DataFrame( data=zip(*data), columns=["types", "diameters", "y", "z", "node_shift"], ) self.check_placement pass_info("Axon placed population generated from data") else: pass_info("Axon population generated from data")
# ----------------------- # # Place population method # # ----------------------- #
[docs] def place_population( self, pos: None | tuple[np.ndarray] | np.ndarray | str = None, method: Literal["default", "packing"] = "default", overwrite=False, delta: float = 0.01, delta_trace: float | None = None, delta_in: float | None = None, n_iter: int = None, fit_to_size: bool = False, with_node_shift: bool = True, ): """ Place the population. Parameters ---------- pos : None | tuple[np.ndarray] | np.ndarray if not None data to used to create the population. Supported data: - `str`: of the file path and name where to load the population properties. - `tuple[np.ndarray]` containing the population properties. - `np.ndarray`: of dimention (2, n_ax) method : Literal["default", "packing"], optional method to use for the , by default "default" overwrite : bool, optional If True placement is skipped if the population is already paced, by default False delta : float axon-to-axon and axon to border minimal distance, by default .01 delta_trace : float | None, optional _description_, by default None delta_in : float | None, optional _description_, by default None fit_to_size : bool if true, the axon population is extended to fit within fascicle size, if not the population is kept as is n_iter : int number of interation for the packing algorithm if the y-x axon coordinates are not specified with_node_shift : bool if True also compute the Node of Ranviers shifts """ if not self.has_pop or not self.has_geom: pass_info("Need to contain population and geometry to allow packing") return None if not overwrite and self.has_placed_pop: pass_info( "Population already placed, please precise if you want to overwrite" ) return None if pos is not None: self.place_population_from_data(pos=pos) if method == "packing": if n_iter is None: n_iter = 20_000 self._use_packer(delta=delta, n_iter=n_iter, fit_to_size=fit_to_size) else: if n_iter is None: n_iter = 500 self._use_placer( delta=delta, delta_trace=delta_trace, delta_in=delta_in, n_iter=n_iter ) if with_node_shift: self.generate_random_NoR_position()
[docs] def place_population_from_data(self, pos: tuple[np.ndarray] | np.ndarray | str): """_summary_ Parameters ---------- pos : tuple[np.ndarray] | np.ndarray data to used to create the population. Supported data: - `str`: of the file path and name where to load the population properties. - `tuple[np.ndarray]` containing the population properties. - `np.ndarray`: of dimention (2, n_ax) """ if not np.iterable(pos) or not len(pos) > 1: raise ValueError("pos must be iterable and of lenght at least equel to 2") self._pop["y"], self._pop["z"] = pos[0], pos[1]
def _use_placer( self, delta: float = 0.01, delta_trace: float | None = None, delta_in: float | None = None, n_iter: int = 500, ): """ Place population using :class:`Placer-class<.utils._packers.Placer>`. Parameters ---------- delta : float, optional axon-to-axon and axon to border minimal distance, by default .01 delta_trace : float | None, optional axon to border minimal distance, if None set to `delta, by default None delta_in : float | None, optional axon-to-axon minimal distance, if None set to `delta`, by default None n_iter : int, optional number of interation for the packing algorithm if the y-x axon coordinates are not specified, by default 500 """ _p = Placer( geom=self.geom, delta=delta, delta_trace=delta_trace, delta_in=delta_in, n_iter=n_iter, ) _, self.axon_pop["y"], self.axon_pop["z"], self.axon_pop["is_placed"] = ( _p.place_all(self.axon_pop["diameters"].to_numpy() / 2) ) def _use_packer( self, delta: float = 1, fit_to_size: bool = False, n_iter: int = 20_000, ) -> None: """ Place population using :class:`Placer-class<.utils._packers.Placer>`. Parameters ---------- delta : float axon-to-axon and axon to border minimal distance fit_to_size : bool if true, the axon population is extended to fit within fascicle size, if not the population is kept as is n_iter : int number of interation for the packing algorithm if the y-x axon coordinates are not specified """ axons_diameter = self.axon_pop["diameters"].to_numpy() N = len(axons_diameter) if not isinstance(self.geom, Circle): pass_info(NotImplemented) return self._use_placer(delta=delta, n_iter=n_iter) y_axons, z_axons = axon_packer(axons_diameter, delta=delta, n_iter=n_iter) if fit_to_size: if self.geom.radius is not None: d_pop = get_circular_contour(axons_diameter, y_axons, z_axons, delta) if d_pop < 2 * self.geom.radius: exp_factor = 0.99 * (2 * self.geom.radius / d_pop) y_axons, z_axons = expand_pop(y_axons, z_axons, exp_factor) else: rise_warning( "Can't fit population to size, fascicle diameter is not defined." ) self._pop["y"], self._pop["z"] = y_axons + self.geom.y, z_axons + self.geom.z _ok_in = remove_collision(axons_diameter, y_axons, z_axons, return_mask=True) _ok_trace = self.geom.is_inside((self._pop["y"], self._pop["z"])) self._pop["is_placed"] = _ok_in & _ok_trace
[docs] def get_ppop_info(self, verbose=False): """ Compute placement statistics for the current axon population. Parameters ---------- verbose : bool, optional If ``True``, print the computed statistics. """ y, z, r = ( self.axon_pop["y"].to_numpy(), self.axon_pop["z"].to_numpy(), (self.axon_pop["diameters"] / 2).to_numpy(), ) get_ppop_info(y, z, r, verbose=verbose)
# ---------------- # # NoR displacement # # ---------------- # @property def has_node_shift(self): """ Whether node-of-Ranvier shifts are available in the population table. Returns ------- bool ``True`` when the population contains a ``node_shift`` column. """ if not self.has_pop: return False return "node_shift" in self._pop.columns
[docs] def generate_random_NoR_position(self): """ Generates radom Node of Ranvier shifts to prevent from axons with the same diamters to be aligned. """ # also generated for unmyelinated but the meaningless value won't be used self._pop["node_shift"] = np.random.uniform(low=0.0, high=1.0, size=self.n_ax) self._pop["node_shift"] *= self._pop["types"]
[docs] def generate_ligned_NoR_position(self, x=0): """ Generates Node of Ranvier shifts to aligned a node of each axon to x postition. Parameters ---------- x : float x axsis value (um) on which node are lined, by default 0 """ # also generated for unmyelinated but the meaningless value won't be used node_lengths = get_MRG_parameters(self._pop["diameters"].to_numpy)[5] self._pop["node_shift"] = (x - 0.5) % node_lengths / node_lengths self._pop["node_shift"] *= self._pop["types"]
[docs] def generate_NoR_position_from_data(self, node_shift: np.ndarray): """ Generates Node of Ranvier shifts to aligned a node of each axon to x postition. Parameters ---------- x : float x axsis value (um) on which node are lined, by default 0 """ self._pop["node_shift"] = node_shift self._pop["node_shift"] *= self._pop["types"]
# ------- # # Plotter # # ------- #
[docs] def plot( self, axes: plt.Axes, expr: str | None = None, mask_labels: None | Iterable[str] | str = [], contour_color: str = "k", myel_color: str = "b", unmyel_color: str = "r", num: bool = False, **kwgs, ): """ Plot the population geometry and placed axons in the transverse plane. Parameters ---------- axes : matplotlib.axes.Axes Target axes. expr : str | None, optional Optional pandas expression selecting a sub-population. mask_labels : Iterable[str] | str | None, optional Existing mask labels selecting a sub-population. contour_color : str, optional Color used for the geometry contour. myel_color : str, optional Color used for myelinated axons. unmyel_color : str, optional Color used for unmyelinated axons. num : bool, optional If ``True``, annotate each drawn axon with its index. **kwgs : dict Additional plotting keyword arguments for the geometry contour. """ if self.has_geom: kwgs["color"] = contour_color axes.plot(*self.geom.get_trace(), **kwgs) if self.has_placed_pop: sub_pop = self.get_sub_population(expr=expr, mask_labels=mask_labels) for i_ax in sub_pop.index: c = ptc.Circle( (self._pop["y"][i_ax], self.axon_pop["z"][i_ax]), self._pop["diameters"][i_ax] / 2, ) if sub_pop["types"][i_ax]: c.set_color(myel_color) else: c.set_color(unmyel_color) axes.add_artist(c) if num: axes.text(self._pop["y"][i_ax], self._pop["z"][i_ax], str(i_ax)) axes.set_aspect("equal", adjustable="box")
[docs] def hist( self, axes: plt.Axes, expr: str | None = None, mask_labels: None | Iterable[str] | str = [], myel_color: str = "b", unmyel_color: str = "r", **kwgs, ): """ Plot an histogram of axon diamters in the population Parameters ---------- axes : matplotlib.Axes axes of the figure to display the histogram. expr : str | None, optional Subpopulation to plot, by default None. mask_labels : None | Iterable[str] | str, optional Subpopulation to plot, by default []. myel_color : str, optional Color of myelinated axons bins, by default "b". unmyel_color : str, optional Color of unmyelinated axons bins, by default "r". """ if self.has_pop: sub_pop = self.get_sub_population(expr=expr, mask_labels=mask_labels) u_diam = sub_pop.query("types==0")["diameters"] kwgs["color"] = unmyel_color axes.hist(u_diam, **kwgs) m_diam = sub_pop.query("types==1")["diameters"] kwgs["color"] = myel_color axes.hist(m_diam, **kwgs)