From 1fdd20542df14a70fce213ec0a1a257fddb1a325 Mon Sep 17 00:00:00 2001 From: Ricardo_MI Date: Sat, 9 Aug 2025 02:02:12 +0800 Subject: [PATCH 1/6] refactor(bar): Remove the legacy `plot_one_group_violin_figure_old` function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `plot_one_group_violin_figure_old` function is officially deprecated. It is replaced by the new `plot_one_group_violin_figure` function. --- 重构(bar): 移除旧版`plot_one_violin_figure_old`函数 正式弃用`plot_one_violin_figure_old`函数。 用新函数`plot_one_violin_figure`函数代替。 --- src/plotfig/__init__.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plotfig/__init__.py b/src/plotfig/__init__.py index 8587c4c..b76b6ab 100644 --- a/src/plotfig/__init__.py +++ b/src/plotfig/__init__.py @@ -1,7 +1,6 @@ from .bar import ( plot_one_group_bar_figure, plot_one_group_violin_figure, - plot_one_group_violin_figure_old, plot_multi_group_bar_figure, ) from .correlation import plot_correlation_figure @@ -23,7 +22,6 @@ __all__ = [ "plot_one_group_bar_figure", "plot_one_group_violin_figure", - "plot_one_group_violin_figure_old", "plot_multi_group_bar_figure", "plot_correlation_figure", "plot_matrix_figure", From 30802811422e5a9ddc0df38ae987d3f6811450c7 Mon Sep 17 00:00:00 2001 From: Ricardo_MI Date: Sat, 9 Aug 2025 02:08:18 +0800 Subject: [PATCH 2/6] docs(bar): Update function comments and fix static analysis errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Attempted to add type annotations for functions, but the task is not yet fully completed. --- 文档(bar): 更新函数注释,修复静态检查错误 尝试为函数添加类型注释,但是还没有完全完成 --- src/plotfig/bar.py | 925 +++++++++++++++++++++++++++------------------ 1 file changed, 550 insertions(+), 375 deletions(-) diff --git a/src/plotfig/bar.py b/src/plotfig/bar.py index e116166..559af37 100644 --- a/src/plotfig/bar.py +++ b/src/plotfig/bar.py @@ -1,26 +1,29 @@ import warnings -from typing import Any +from typing import Sequence import matplotlib.pyplot as plt import numpy as np -import numpy.typing as npt +from numpy.typing import NDArray from matplotlib.axes import Axes -from matplotlib.ticker import FuncFormatter, ScalarFormatter +from matplotlib.ticker import FuncFormatter, ScalarFormatter, FormatStrFormatter from matplotlib.colors import LinearSegmentedColormap, to_rgba -from matplotlib.patches import Polygon +from matplotlib.patches import Polygon, Rectangle from scipy import stats # 设置警告过滤器,显示所有警告 warnings.simplefilter("always") # 类型别名 -Num = int | float # 可同时接受int和float的类型 -NumArray = list[Num] | npt.NDArray[np.float64] # 数字数组类型 +Num = int | float | np.integer | np.floating +DataType = ( + np.ndarray # 三维 ndarray + | Sequence[np.ndarray] # list[二维 ndarray] + | Sequence[Sequence[Sequence[Num] | np.ndarray]] # 纯 list 嵌套数字 +) __all__ = [ "plot_one_group_bar_figure", "plot_one_group_violin_figure", - "plot_one_group_violin_figure_old", "plot_multi_group_bar_figure", ] @@ -28,7 +31,24 @@ RNG = np.random.default_rng(seed=1998) -def compute_summary(data: NumArray) -> tuple[float, float, float]: +def _is_valid_data(data): + if isinstance(data, np.ndarray): + return data.ndim == 2 + if isinstance(data, (list, tuple)): + for x in data: + if isinstance(x, np.ndarray): + if x.ndim != 1: + return False + elif isinstance(x, (list, tuple)): + if not all(isinstance(i, (int, float, np.floating)) for i in x): + return False + else: + return False + return True + return False + + +def _compute_summary(data): """计算均值、标准差、标准误""" mean = np.mean(data) sd = np.std(data, ddof=1) @@ -36,13 +56,13 @@ def compute_summary(data: NumArray) -> tuple[float, float, float]: return mean, sd, se -def add_scatter( - ax: Axes, - x_pos: Num, - data: NumArray, - color: str, - dots_size: Num = 35, -) -> None: +def _add_scatter( + ax, + x_pos, + data, + color, + dots_size, +): """添加散点""" ax.scatter( x_pos, @@ -55,51 +75,47 @@ def add_scatter( ) -def set_yaxis( - ax: Axes, - data: NumArray, - options: dict[str, Any] | None, -) -> None: +def _set_yaxis( + ax, + data, + y_lim, + ax_bottom_is_0, + y_max_tick_is_1, + math_text, + one_decimal_place, + percentage, +): """设置Y轴格式""" - if options.get("y_lim"): - ax.set_ylim(*options["y_lim"]) + if y_lim: + ax.set_ylim(y_lim) else: y_min, y_max = np.min(data), np.max(data) y_range = y_max - y_min golden_ratio = 5**0.5 - 1 - ax_min = ( - 0 - if options.get("ax_bottom_is_0") - else y_min - (y_range / golden_ratio - y_range / 2) - ) + ax_min = 0 if ax_bottom_is_0 else y_min - (y_range / golden_ratio - y_range / 2) ax_max = y_max + (y_range / golden_ratio - y_range / 2) ax.set_ylim(ax_min, ax_max) - if options.get("y_max_tick_is_1"): - ticks = [ - tick - for tick in ax.get_yticks() - if tick <= options.get("y_max_tick_is_1", 1) - ] + if y_max_tick_is_1: + ticks = [tick for tick in ax.get_yticks() if tick <= 1] ax.set_yticks(ticks) - if options.get("math_text", True) and (np.min(data) < 0.1 or np.max(data) > 100): + if math_text and (np.min(data) < 0.1 or np.max(data) > 100): formatter = ScalarFormatter(useMathText=True) formatter.set_powerlimits((-2, 2)) ax.yaxis.set_major_formatter(formatter) - if options.get("one_decimal_place"): - if options.get("math_text", True): + if one_decimal_place: + if math_text: warnings.warn( "“one_decimal_place”会与“math_text”冲突,请关闭“math_text”后再开启!", UserWarning, - stacklevel=2, ) else: - ax.yaxis.set_major_formatter(plt.FormatStrFormatter("%.1f")) + ax.yaxis.set_major_formatter(FormatStrFormatter("%.1f")) - if options.get("percentage"): - if options.get("math_text", True): + if percentage: + if math_text: warnings.warn( "“percentage”会与“math_text”冲突,请关闭“math_text”后再开启!", UserWarning, @@ -110,12 +126,12 @@ def set_yaxis( # 统计相关 -def perform_stat_test( - data1: NumArray | None = None, - data2: NumArray | None = None, - popmean: NumArray | None = None, - method: str = "ttest_ind", -) -> tuple[float, float]: +def _perform_stat_test( + data1=None, + data2=None, + popmean=None, + method="ttest_ind", +): """执行统计检验""" # 使用字典映射替代多个elif分支,提高可读性和可扩展性 test_methods = { @@ -134,39 +150,41 @@ def perform_stat_test( return stat, p -def determine_test_modle(data, method, p_list=None, popmean=0): +def _determine_test_modle(data, method, p_list=None, popmean=0): comparisons = [] idx = 0 if method != "ttest_1samp": for i in range(len(data)): for j in range(i + 1, len(data)): if method == "external": + if p_list is None: + raise ValueError("p_list参数不能为空") p = p_list[idx] idx += 1 else: - _, p = perform_stat_test( + _, p = _perform_stat_test( data1=data[i], data2=data[j], method=method ) if p <= 0.05: comparisons.append((i, j, p)) else: for i in range(len(data)): - _, p = perform_stat_test(data1=data[i], popmean=popmean, method=method) + _, p = _perform_stat_test(data1=data[i], popmean=popmean, method=method) if p <= 0.05: comparisons.append((i, p)) return comparisons -def annotate_significance( - ax: Axes, - comparisons: list[tuple[int, int, float]], - y_base: Num, - interval: Num, - line_color: str, - star_offset: Num, - fontsize: Num, - color: str, -) -> None: +def _annotate_significance( + ax, + comparisons, + y_base, + interval, + line_color, + star_offset, + fontsize, + color, +): """添加显著性星号和连线""" def _stars(pval, i, y, color, fontsize): @@ -199,7 +217,7 @@ def _stars(pval, i, y, color, fontsize): _stars(pval, i, y + star_offset, color, fontsize) -def statistics( +def _statistics( data, test_method, p_list, @@ -219,7 +237,7 @@ def statistics( ) for method in test_method: - comparisons = determine_test_modle(data, method, p_list, popmean) + comparisons = _determine_test_modle(data, method, p_list, popmean) if not comparisons: return @@ -232,7 +250,7 @@ def statistics( else asterisk_color ) - annotate_significance( + _annotate_significance( ax, comparisons, np.max(all_values), @@ -248,13 +266,13 @@ def statistics( DeprecationWarning, stacklevel=1, ) - comparisons = determine_test_modle(data, test_method, p_list, popmean) + comparisons = _determine_test_modle(data, test_method, p_list, popmean) if not comparisons: return y_max = ax.get_ylim()[1] interval = (y_max - np.max(all_values)) / (len(comparisons) + 1) - annotate_significance( + _annotate_significance( ax, comparisons, np.max(all_values), @@ -267,87 +285,171 @@ def statistics( # 可调用接口函数 +# TODO: 增加不显示散点的flag def plot_one_group_bar_figure( - data: list[NumArray], + data: np.ndarray | Sequence[Sequence[Num] | np.ndarray], ax: Axes | None = None, labels_name: list[str] | None = None, - width: Num = 0.5, colors: list[str] | None = None, - color_alpha: Num = 1, edgecolor: str | None = None, gradient_color: bool = False, - colors_start=None, - colors_end=None, - dots_size: Num = 35, + colors_start: list[str] | None = None, + colors_end: list[str] | None = None, dots_color: list[list[str]] | None = None, + y_lim: list[Num] | tuple[Num, Num] | None = None, + width: Num = 0.5, + color_alpha: Num = 1, + dots_size: Num = 35, + errorbar_type: str = "sd", title_name: str = "", + title_fontsize: Num = 12, + title_pad: Num = 10, x_label_name: str = "", + x_label_ha: str = "center", + x_label_fontsize: Num = 10, + x_tick_fontsize: Num = 8, + x_tick_rotation: Num = 0, y_label_name: str = "", - errorbar_type: str = "sd", + y_label_fontsize: Num = 10, + y_tick_fontsize: Num = 8, + y_tick_rotation: Num = 0, statistic: bool = False, - test_method: str = "ttest_ind", - popmean: Num = 0, + test_method: list[str] = ["ttest_ind"], p_list: list[float] | None = None, + popmean: Num = 0, statistical_line_color: str = "0.5", asterisk_fontsize: Num = 10, asterisk_color: str = "k", - **kwargs: Any, -) -> None: + ax_bottom_is_0: bool = False, + y_max_tick_is_1: bool = False, + math_text: bool = True, + one_decimal_place: bool = False, + percentage: bool = False, +) -> Axes | None: """绘制单组柱状图,包含散点、误差条和统计显著性标记。 Args: - data (list[NumArray]): 包含多个数据集的列表,每个数据集是一个数字数组。 - ax (Axes | None, optional): matplotlib 的 Axes 对象,用于绘图。默认为 None,使用当前 Axes。 - labels_name (list[str] | None, optional): 每个数据集对应的标签。默认为 None,使用索引作为标签。 - width (Num, optional): 柱子的宽度。默认为 0.5。 - colors (list[str] | None, optional): 每个柱子的颜色列表。若为 None,使用默认灰色。 - color_alpha (Num, optional): 颜色透明度,取值范围为 0(完全透明)到 1(完全不透明)。使用 gradient_color 时该参数无效。默认为 1。 - edgecolor (str | None, optional): 柱子的边缘颜色。默认为 None,即不特别设置。 - gradient_color (bool, optional): 是否为柱子启用渐变色填充。默认为 False。 - colors_start (list[str] | None, optional): 渐变色的起始颜色列表。用于 gradient_color=True。 - colors_end (list[str] | None, optional): 渐变色的结束颜色列表。用于 gradient_color=True。 - dots_size (Num, optional): 每个散点的大小。默认为 35。 - dots_color (list[list[str]] | None, optional): 每组数据中每个散点的颜色(二维列表)。默认为 None,使用灰色。 - title_name (str, optional): 图表的标题文字。默认为空字符串。 - x_label_name (str, optional): X 轴的标签。默认为空字符串。 - y_label_name (str, optional): Y 轴的标签。默认为空字符串。 - errorbar_type (str, optional): 误差条类型。支持 "sd"(标准差)或 "se"(标准误)。默认为 "sd"。 - statistic (bool, optional): 是否进行统计检验并在柱状图上标记显著性。默认为 False。 - test_method (str, optional): 统计检验方法。支持 "ttest_ind"、"ttest_rel"、"ttest_1samp"、"mannwhitneyu" 或 "external"。默认为 "ttest_ind"。 - popmean (Num): 总体均值假设值,用于单样本t检验(ttest_1samp)。默认为 0。 - p_list (list[float] | None, optional): 提供的 p 值列表,用于 "external" 检验。默认为None。 - statistical_line_color (str, optional): 统计显著性标记连线的颜色。默认为 "0.5"(灰色)。 - asterisk_fontsize (Num, optional): 显著性星号的字体大小。默认为 10。 - asterisk_color (str, optional): 显著性星号的颜色。默认为 "k"(黑色)。 - **kwargs (Any): 其他 matplotlib 参数,用于进一步定制图表样式。 + data (np.ndarray | Sequence[Sequence[Num] | np.ndarray]): + 输入数据,可以是二维numpy数组或嵌套序列,每个子序列代表一个柱状图的数据点 + ax (Axes | None, optional): + matplotlib的坐标轴对象,如果为None则使用当前坐标轴. Defaults to None. + labels_name (list[str] | None, optional): + 柱状图的标签名称列表. Defaults to None. + colors (list[str] | None, optional): + 柱状图的颜色列表. Defaults to None. + edgecolor (str | None, optional): + 柱状图边缘颜色. Defaults to None. + gradient_color (bool, optional): + 是否使用渐变颜色填充柱状图. Defaults to False. + colors_start (list[str] | None, optional): + 渐变色的起始颜色列表. Defaults to None. + colors_end (list[str] | None, optional): + 渐变色的结束颜色列表. Defaults to None. + dots_color (list[list[str]] | None, optional): + 散点的颜色列表. Defaults to None. + y_lim (list[Num] | tuple[Num, Num] | None, optional): + Y轴的范围限制. Defaults to None. + width (Num, optional): + 柱状图的宽度. Defaults to 0.5. + color_alpha (Num, optional): + 柱状图颜色的透明度. Defaults to 1. + dots_size (Num, optional): + 散点的大小. Defaults to 35. + errorbar_type (str, optional): + 误差条类型,可选 "sd"(标准差) 或 "se"(标准误). Defaults to "sd". + title_name (str, optional): + 图表标题. Defaults to "". + title_fontsize (Num, optional): + 标题字体大小. Defaults to 12. + title_pad (Num, optional): + 标题与图表的间距. Defaults to 10. + x_label_name (str, optional): + X轴标签名称. Defaults to "". + x_label_ha (str, optional): + X轴标签的水平对齐方式. Defaults to "center". + x_label_fontsize (Num, optional): + X轴标签字体大小. Defaults to 10. + x_tick_fontsize (Num, optional): + X轴刻度字体大小. Defaults to 8. + x_tick_rotation (Num, optional): + X轴刻度旋转角度. Defaults to 0. + y_label_name (str, optional): + Y轴标签名称. Defaults to "". + y_label_fontsize (Num, optional): + Y轴标签字体大小. Defaults to 10. + y_tick_fontsize (Num, optional): + Y轴刻度字体大小. Defaults to 8. + y_tick_rotation (Num, optional): + Y轴刻度旋转角度. Defaults to 0. + statistic (bool, optional): + 是否进行统计显著性分析. Defaults to False. + test_method (list[str], optional): + 统计检验方法列表. Defaults to ["ttest_ind"]. + p_list (list[float] | None, optional): + 预计算的p值列表,用于显著性标记. Defaults to None. + popmean (Num, optional): + 单样本t检验的假设均值. Defaults to 0. + statistical_line_color (str, optional): + 显著性标记线的颜色. Defaults to "0.5". + asterisk_fontsize (Num, optional): + 显著性星号的字体大小. Defaults to 10. + asterisk_color (str, optional): + 显著性星号的颜色. Defaults to "k". + ax_bottom_is_0 (bool, optional): + Y轴是否从0开始. Defaults to False. + y_max_tick_is_1 (bool, optional): + Y轴最大刻度是否限制为1. Defaults to False. + math_text (bool, optional): + 是否将Y轴显示为科学计数法格式. Defaults to True. + one_decimal_place (bool, optional): + Y轴刻度是否只保留一位小数. Defaults to False. + percentage (bool, optional): + 是否将Y轴显示为百分比格式. Defaults to False. + + Raises: + ValueError: 当data数据格式无效时抛出 + ValueError: 当errorbar_type不是"sd"或"se"时抛出 Returns: - None + Axes | None: 返回matplotlib的坐标轴对象或None """ + # 处理None值 + if not _is_valid_data(data): + raise ValueError("无效的 data") + ax = ax or plt.gca() + labels_name = labels_name or [str(i) for i in range(len(data))] + colors = colors or ["gray"] * len(data) + # 统一参数型 + width = float(width) + color_alpha = float(color_alpha) + dots_size = float(dots_size) + title_fontsize = float(title_fontsize) + title_pad = float(title_pad) + x_label_fontsize = float(x_label_fontsize) + x_tick_fontsize = float(x_tick_fontsize) + x_tick_rotation = float(x_tick_rotation) + y_label_fontsize = float(y_label_fontsize) + y_tick_fontsize = float(y_tick_fontsize) + y_tick_rotation = float(y_tick_rotation) + popmean = float(popmean) + asterisk_fontsize = float(asterisk_fontsize) - if ax is None: - ax = plt.gca() - if labels_name is None: - labels_name = [str(i) for i in range(len(data))] - if colors is None: - colors = ["gray"] * len(data) - - means, sds, ses = [], [], [] x_positions = np.arange(len(labels_name)) + means, sds, ses = [], [], [] scatter_positions = [] - for i, d in enumerate(data): - mean, sd, se = compute_summary(d) + mean, sd, se = _compute_summary(d) means.append(mean) sds.append(sd) ses.append(se) scatter_x = RNG.normal(i, 0.1, len(d)) scatter_positions.append(scatter_x) - if errorbar_type == "sd": error_values = sds elif errorbar_type == "se": error_values = ses + else: + raise ValueError("errorbar_type 只能是 'sd' 或者 'se'") # 绘制柱子 if gradient_color: @@ -360,7 +462,7 @@ def plot_one_group_bar_figure( cmap = LinearSegmentedColormap.from_list("grad_cmap", [c1, "white", c2]) gradient = np.linspace(0, 1, 100).reshape(1, -1) # 横向渐变 # 计算渐变矩形位置:跟bar完全对齐 - extent = [x - width / 2, x + width / 2, 0, h] + extent = (float(x - width / 2), float(x + width / 2), 0, h) # 叠加渐变矩形(imshow) ax.imshow(gradient, aspect="auto", cmap=cmap, extent=extent, zorder=0) else: @@ -386,41 +488,50 @@ def plot_one_group_bar_figure( # 绘制散点 for i, d in enumerate(data): if dots_color is None: - add_scatter(ax, scatter_positions[i], d, ["gray"] * len(d), dots_size) + _add_scatter(ax, scatter_positions[i], d, ["gray"] * len(d), dots_size) else: - add_scatter(ax, scatter_positions[i], d, dots_color[i], dots_size) + _add_scatter(ax, scatter_positions[i], d, dots_color[i], dots_size) # 美化 ax.spines[["top", "right"]].set_visible(False) ax.set_title( title_name, - fontsize=kwargs.get("title_fontsize", 10), - pad=kwargs.get("title_pad", 10), + fontsize=title_fontsize, + pad=float(title_pad), ) # x轴 - ax.set_xlim(min(x_positions) - 0.5, max(x_positions) + 0.5) - ax.set_xlabel(x_label_name, fontsize=kwargs.get("x_label_fontsize", 10)) + ax.set_xlim(np.min(x_positions) - 0.5, np.max(x_positions) + 0.5) + ax.set_xlabel(x_label_name, fontsize=x_label_fontsize) ax.set_xticks(x_positions) ax.set_xticklabels( labels_name, - ha=kwargs.get("x_label_ha", "center"), + fontsize=x_tick_fontsize, + rotation=x_tick_rotation, + ha=x_label_ha, rotation_mode="anchor", - fontsize=kwargs.get("x_tick_fontsize", 10), - rotation=kwargs.get("x_tick_rotation", 0), ) # y轴 ax.tick_params( axis="y", - labelsize=kwargs.get("y_tick_fontsize", 10), - rotation=kwargs.get("y_tick_rotation", 0), + labelsize=y_tick_fontsize, + rotation=y_tick_rotation, + ) + ax.set_ylabel(y_label_name, fontsize=y_label_fontsize) + all_values = np.concatenate([np.asarray(x) for x in data]).ravel() + _set_yaxis( + ax, + all_values, + y_lim=y_lim, + ax_bottom_is_0=ax_bottom_is_0, + y_max_tick_is_1=y_max_tick_is_1, + math_text=math_text, + one_decimal_place=one_decimal_place, + percentage=percentage, ) - ax.set_ylabel(y_label_name, fontsize=kwargs.get("y_label_fontsize", 10)) - all_values = np.concatenate(data) - set_yaxis(ax, all_values, kwargs) # 添加统计显著性标记 if statistic: - statistics( + _statistics( data, test_method, p_list, @@ -431,71 +542,151 @@ def plot_one_group_bar_figure( asterisk_fontsize, asterisk_color, ) + return ax def plot_one_group_violin_figure( - data: list[NumArray], + data: Sequence[list[float] | NDArray[np.float64]], ax: Axes | None = None, + labels_name: list[str] | None = None, width: Num = 0.8, colors: list[str] | None = None, color_alpha: Num = 1, gradient_color: bool = False, colors_start: list[str] | None = None, colors_end: list[str] | None = None, - labels_name: list[str] | None = None, - x_label_name: str = "", - y_label_name: str = "", - title_name: str = "", - title_pad: Num = 10, show_dots: bool = False, dots_size: Num = 35, + title_name: str = "", + title_fontsize: Num = 12, + title_pad: Num = 10, + x_label_name: str = "", + x_label_ha: str = "center", + x_label_fontsize: Num = 10, + x_tick_fontsize: Num = 8, + x_tick_rotation: Num = 0, + y_label_name: str = "", + y_label_fontsize: Num = 10, + y_tick_fontsize: Num = 8, + y_tick_rotation: Num = 0, statistic: bool = False, - test_method: str = "ttest_ind", + test_method: list[str] = ["ttest_ind"], popmean: Num = 0, p_list: list[float] | None = None, statistical_line_color: str = "0.5", asterisk_fontsize: Num = 10, asterisk_color: str = "k", - **kwargs: Any, -) -> None: + y_lim: list[Num] | tuple[Num, Num] | None = None, + ax_bottom_is_0: bool = False, + y_max_tick_is_1: bool = False, + math_text: bool = True, + one_decimal_place: bool = False, + percentage: bool = False, +) -> Axes | None: """绘制单组小提琴图,可选散点叠加、渐变填色和统计显著性标注。 Args: - data (list[NumArray]): 包含多个数据集的列表,每个数据集是一个数值数组。 - ax (Axes | None, optional): matplotlib 的 Axes 对象,用于绘图。默认为 None,使用当前 Axes。 - width (Num, optional): 小提琴图的总宽度。默认为 0.8。 - colors (list[str] | None, optional): 每个小提琴的颜色。若为 None,使用默认灰色。 - color_alpha (Num, optional): 颜色透明度,取值范围为 0(完全透明)到 1(完全不透明)。使用 gradient_color 时该参数无效。默认为 1。 - gradient_color (bool, optional): 是否启用渐变色填充。默认为 False。 - colors_start (list[str] | None, optional): 渐变起始颜色列表,对应每组数据。 - colors_end (list[str] | None, optional): 渐变结束颜色列表,对应每组数据。 - labels_name (list[str] | None, optional): 每个数据集的标签名称。默认为 None,使用索引作为标签。 - x_label_name (str, optional): X 轴的标签。默认为空字符串。 - y_label_name (str, optional): Y 轴的标签。默认为空字符串。 - title_name (str, optional): 图表标题。默认为空字符串。 - title_pad (Num, optional): 标题与图之间的垂直距离。默认为 10。 - show_dots (bool, optional): 是否在小提琴图上叠加散点。默认为 False。 - dots_size (Num, optional): 散点大小。默认为 35。 - statistic (bool, optional): 是否进行统计检验并标注显著性。默认为 False。 - test_method (str, optional): 统计检验方法。支持 "ttest_ind"、"ttest_rel"、"mannwhitneyu" 或 "external"。默认为 "ttest_ind"。 - popmean (Num): 总体均值假设值,用于单样本t检验(ttest_1samp)。默认为 0。 - p_list (list[float] | None, optional): 外部提供的 p 值列表。默认为 None。 - statistical_line_color (str, optional): 统计显著性标记连线的颜色。默认为 "0.5"(灰色)。 - asterisk_fontsize (Num, optional): 显著性星号的字体大小。默认为 10。 - asterisk_color (str, optional): 显著性星号的颜色。默认为 "k"(黑色)。 - **kwargs (Any): 其他 matplotlib 参数,用于进一步定制图表样式。 + data (Sequence[list[float] | NDArray[np.float64]]): + 输入数据,可以是二维numpy数组或嵌套序列,每个子序列代表一个小提琴的数据点 + ax (Axes | None, optional): + matplotlib的坐标轴对象,如果为None则使用当前坐标轴. Defaults to None. + labels_name (list[str] | None, optional): + 小提琴图的标签名称列表. Defaults to None. + width (Num, optional): + 小提琴图的宽度. Defaults to 0.8. + colors (list[str] | None, optional): + 小提琴图的颜色列表. Defaults to None. + color_alpha (Num, optional): + 小提琴图颜色的透明度. Defaults to 1. + gradient_color (bool, optional): + 是否使用渐变颜色填充小提琴图. Defaults to False. + colors_start (list[str] | None, optional): + 渐变色的起始颜色列表. Defaults to None. + colors_end (list[str] | None, optional): + 渐变色的结束颜色列表. Defaults to None. + show_dots (bool, optional): + 是否显示散点. Defaults to False. + dots_size (Num, optional): + 散点的大小. Defaults to 35. + title_name (str, optional): + 图表标题. Defaults to "". + title_fontsize (Num, optional): + 标题字体大小. Defaults to 12. + title_pad (Num, optional): + 标题与图表的间距. Defaults to 10. + x_label_name (str, optional): + X轴标签名称. Defaults to "". + x_label_ha (str, optional): + X轴标签的水平对齐方式. Defaults to "center". + x_label_fontsize (Num, optional): + X轴标签字体大小. Defaults to 10. + x_tick_fontsize (Num, optional): + X轴刻度字体大小. Defaults to 8. + x_tick_rotation (Num, optional): + X轴刻度旋转角度. Defaults to 0. + y_label_name (str, optional): + Y轴标签名称. Defaults to "". + y_label_fontsize (Num, optional): + Y轴标签字体大小. Defaults to 10. + y_tick_fontsize (Num, optional): + Y轴刻度字体大小. Defaults to 8. + y_tick_rotation (Num, optional): + Y轴刻度旋转角度. Defaults to 0. + statistic (bool, optional): + 是否进行统计显著性分析. Defaults to False. + test_method (list[str], optional): + 统计检验方法列表. Defaults to ["ttest_ind"]. + popmean (Num, optional): + 单样本t检验的假设均值. Defaults to 0. + p_list (list[float] | None, optional): + 预计算的p值列表,用于显著性标记. Defaults to None. + statistical_line_color (str, optional): + 显著性标记线的颜色. Defaults to "0.5". + asterisk_fontsize (Num, optional): + 显著性星号的字体大小. Defaults to 10. + asterisk_color (str, optional): + 显著性星号的颜色. Defaults to "k". + y_lim (list[Num] | tuple[Num, Num] | None, optional): + Y轴的范围限制. Defaults to None. + ax_bottom_is_0 (bool, optional): + Y轴是否从0开始. Defaults to False. + y_max_tick_is_1 (bool, optional): + Y轴最大刻度是否限制为1. Defaults to False. + math_text (bool, optional): + 是否将Y轴显示为科学计数法格式. Defaults to True. + one_decimal_place (bool, optional): + Y轴刻度是否只保留一位小数. Defaults to False. + percentage (bool, optional): + 是否将Y轴显示为百分比格式. Defaults to False. + + Raises: + ValueError: 当data数据格式无效时抛出 Returns: - None + Axes | None: 返回matplotlib的坐标轴对象或None """ - + # 处理None值 + if not _is_valid_data(data): + raise ValueError("无效的 data") ax = ax or plt.gca() labels_name = labels_name or [str(i) for i in range(len(data))] colors = colors or ["gray"] * len(data) - - def _draw_gradient_violin( - ax, data, pos, width=width, c1="red", c2="blue", color_alpha=1 - ): + # 统一参数型 + width = float(width) + color_alpha = float(color_alpha) + dots_size = float(dots_size) + title_fontsize = float(title_fontsize) + title_pad = float(title_pad) + x_label_fontsize = float(x_label_fontsize) + x_tick_fontsize = float(x_tick_fontsize) + x_tick_rotation = float(x_tick_rotation) + y_label_fontsize = float(y_label_fontsize) + y_tick_fontsize = float(y_tick_fontsize) + y_tick_rotation = float(y_tick_rotation) + popmean = float(popmean) + asterisk_fontsize = float(asterisk_fontsize) + + def _draw_gradient_violin(ax, data, pos, width, c1, c2, color_alpha): # KDE估计 kde = stats.gaussian_kde(data) buffer = (max(data) - min(data)) / 5 @@ -548,9 +739,9 @@ def _draw_gradient_violin( median = np.median(data) # 添加 IQR box(黑色矩形) ax.add_patch( - plt.Rectangle( + Rectangle( (pos - width / 16, q1), # 左下角坐标 - width / 8, # 宽度 + float(width / 8), # 宽度 q3 - q1, # 高度 facecolor="black", alpha=0.7, @@ -563,13 +754,16 @@ def _draw_gradient_violin( ymax_lst, ymin_lst = [], [] for i, d in enumerate(data): if gradient_color: + if colors_start is None: + colors_start = ["#e38a48"] * len(data) + if colors_end is None: # 默认颜色 + colors_end = ["#4573a5"] * len(data) c1 = colors_start[i] c2 = colors_end[i] else: c1 = c2 = colors[i] - ymax, ymin = _draw_gradient_violin( - ax, d, pos=i, c1=c1, c2=c2, color_alpha=color_alpha - ) + ymax, ymin = _draw_gradient_violin(ax, d, i, width, c1, c2, color_alpha) + ymax_lst.append(ymax) ymin_lst.append(ymin) ymax = max(ymax_lst) @@ -579,33 +773,44 @@ def _draw_gradient_violin( if show_dots: scatter_positions = [RNG.normal(i, 0.1, len(d)) for i, d in enumerate(data)] for i, d in enumerate(data): - add_scatter(ax, scatter_positions[i], d, colors[i], dots_size) + _add_scatter(ax, scatter_positions[i], d, colors[i], dots_size) # 美化 - ax.set_title(title_name, fontsize=kwargs.get("title_fontsize", 10), pad=title_pad) ax.spines[["top", "right"]].set_visible(False) + ax.set_title(title_name, fontsize=title_fontsize, pad=title_pad) # x轴 ax.set_xlim(-0.5, len(data) - 0.5) - ax.set_xlabel(x_label_name, fontsize=kwargs.get("x_label_fontsize", 10)) + ax.set_xlabel(x_label_name, fontsize=x_label_fontsize) ax.set_xticks(np.arange(len(data))) ax.set_xticklabels( labels_name, - fontsize=kwargs.get("x_tick_fontsize", 10), - rotation=kwargs.get("x_tick_rotation", 0), + fontsize=x_tick_fontsize, + rotation=x_tick_rotation, + ha=x_label_ha, + rotation_mode="anchor", ) # y轴 ax.tick_params( axis="y", - labelsize=kwargs.get("y_tick_fontsize", 10), - rotation=kwargs.get("y_tick_rotation", 0), + labelsize=y_tick_fontsize, + rotation=y_tick_rotation, ) - ax.set_ylabel(y_label_name, fontsize=kwargs.get("y_label_fontsize", 10)) + ax.set_ylabel(y_label_name, fontsize=y_label_fontsize) all_values = [ymin, ymax] - set_yaxis(ax, all_values, kwargs) + _set_yaxis( + ax, + all_values, + y_lim=y_lim, + ax_bottom_is_0=ax_bottom_is_0, + y_max_tick_is_1=y_max_tick_is_1, + math_text=math_text, + one_decimal_place=one_decimal_place, + percentage=percentage, + ) # 添加统计标记(复用现有函数) if statistic: - statistics( + _statistics( data, test_method, p_list, @@ -617,152 +822,12 @@ def _draw_gradient_violin( asterisk_color, ) - return - - -def plot_one_group_violin_figure_old( - data: list[NumArray], - ax: Axes | None = None, - width: Num = 0.8, - show_extrema: bool = True, - colors: list[str] | None = None, - labels_name: list[str] | None = None, - x_label_name: str = "", - y_label_name: str = "", - title_name: str = "", - title_pad: Num = 10, - show_dots: bool = False, - dots_size: Num = 35, - statistic: bool = False, - test_method: str = "ttest_ind", - p_list: list[float] | None = None, - **kwargs: Any, -) -> None: - warnings.warn( - "plot_one_group_violin_figure_old 即将弃用,请使用 plot_one_group_violin_figure 替代。未来版本将移除本函数。", - DeprecationWarning, - stacklevel=2, - ) - """绘制单组小提琴图,包含散点和统计显著性标记。 - - Args: - data (list[NumArray]): 包含多个数据集的列表,每个数据集是一个数字数组。 - ax (Axes | None, optional): matplotlib 的 Axes 对象,用于绘图。默认为 None,使用当前的 Axes。 - width (Num, optional): 小提琴图的宽度。默认为 0.8。 - show_extrema (bool, optional): 是否显示极值线。默认为 True。 - colors (list[str] | None, optional): 每个小提琴图的颜色。默认为 None,使用灰色。 - labels_name (list[str] | None, optional): 每个数据集的标签名称。默认为 None,使用索引作为标签。 - x_label_name (str, optional): X 轴的标签。默认为空字符串。 - y_label_name (str, optional): Y 轴的标签。默认为空字符串。 - title_name (str, optional): 图表的标题。默认为空字符串。 - title_pad (Num, optional): 标题与图之间的垂直距离。默认为 10。 - show_dots (bool, optional): 是否显示散点。默认为 False。 - dots_size (Num, optional): 散点的大小。默认为 35。 - statistic (bool, optional): 是否进行统计检验并标注显著性标记。默认为 False。 - test_method (str, optional): 统计检验的方法,支持 "ttest_ind"、"ttest_rel" 和 "mannwhitneyu"。默认为 "ttest_ind"。 - p_list (list[float] | None, optional): 外部提供的 p 值列表,用于统计检验。默认为 None。 - **kwargs (Any): 其他可选参数,用于进一步定制图表样式。 - - Returns: - None - """ - - ax = ax or plt.gca() - labels_name = labels_name or [str(i) for i in range(len(data))] - colors = colors or ["gray"] * len(data) - - # 绘制小提琴图 - parts = ax.violinplot( - dataset=list(data), - positions=np.arange(len(data)), - widths=width, - showextrema=show_extrema, - ) - # 添加 box 元素 - for i, d in enumerate(data): - # 计算统计量 - q1 = np.percentile(d, 25) - q3 = np.percentile(d, 75) - median = np.median(d) - # 添加 IQR box(黑色矩形) - ax.add_patch( - plt.Rectangle( - (i - width / 16, q1), # 左下角坐标 - width / 8, # 宽度 - q3 - q1, # 高度 - facecolor="black", - alpha=0.7, - ) - ) - # 添加白色中位数点 - ax.plot(i, median, "o", color="white", markersize=3, zorder=3) - - # 设置小提琴颜色(修改默认样式) - for pc, color in zip(parts["bodies"], colors): - pc.set_facecolor(color) - pc.set_edgecolor("black") - pc.set_alpha(1) - - # 修改内部线条颜色 - if show_extrema: - parts["cmins"].set_color("black") # 最小值线 - parts["cmaxes"].set_color("black") # 最大值线 - parts["cbars"].set_color("black") # 中线(median) - - # 绘制散点(复用现有函数) - if show_dots: - scatter_positions = [RNG.normal(i, 0.1, len(d)) for i, d in enumerate(data)] - for i, d in enumerate(data): - add_scatter(ax, scatter_positions[i], d, colors[i], dots_size) - - # 美化坐标轴(复用现有函数) - ax.spines[["top", "right"]].set_visible(False) - ax.set_title(title_name, fontsize=kwargs.get("title_fontsize", 10), pad=title_pad) - ax.set_xlabel(x_label_name, fontsize=kwargs.get("x_label_fontsize", 10)) - ax.set_ylabel(y_label_name, fontsize=kwargs.get("y_label_fontsize", 10)) - ax.set_xticks(np.arange(len(data))) - ax.set_xticklabels( - labels_name, - fontsize=kwargs.get("x_tick_fontsize", 10), - rotation=kwargs.get("x_tick_rotation", 0), - ) - - # 设置Y轴(复用现有函数) - all_values = np.concatenate(data) - set_yaxis(ax, all_values, kwargs) - - # 添加统计标记(复用现有函数) - if statistic: - comparisons = [] - idx = 0 - for i in range(len(data)): - for j in range(i + 1, len(data)): - if test_method == "external": - p = p_list[idx] if p_list else 1.0 - idx += 1 - else: - _, p = perform_stat_test(data[i], data[j], test_method) - if p <= 0.05: - comparisons.append((i, j, p)) - - if comparisons: - y_max = ax.get_ylim()[1] - interval = (y_max - np.max(all_values)) / (len(comparisons) + 1) - annotate_significance( - ax, - comparisons, - np.max(all_values), - interval, - line_color=kwargs.get("line_color", "0.5"), - star_offset=interval / 5, - fontsize=kwargs.get("asterisk_fontsize", 10), - color=kwargs.get("asterisk_color", "k"), - ) + return ax def plot_multi_group_bar_figure( - data: list[list[NumArray]], - ax: plt.Axes | None = None, + data: DataType, + ax: Axes | None = None, group_labels: list[str] | None = None, bar_labels: list[str] | None = None, bar_width: Num = 0.2, @@ -771,45 +836,146 @@ def plot_multi_group_bar_figure( errorbar_type: str = "sd", dots_color: str = "gray", dots_size: int = 35, + legend: bool = True, + legend_position: tuple[Num, Num] = (1.2, 1), title_name: str = "", + title_fontsize=12, + title_pad=10, x_label_name: str = "", + x_label_ha="center", + x_label_fontsize=10, + x_tick_fontsize=8, + x_tick_rotation=0, y_label_name: str = "", + y_label_fontsize=10, + y_tick_fontsize=8, + y_tick_rotation=0, statistic: bool = False, test_method: str = "external", p_list: list[list[Num]] | None = None, - legend: bool = True, - legend_position: tuple[Num, Num] = (1.2, 1), - **kwargs: Any, -) -> None: - """ - 绘制多组柱状图,包含散点、误差条、显著性标注和图例等。 + line_color="0.5", + asterisk_fontsize=10, + asterisk_color="k", + y_lim: list[Num] | tuple[Num, Num] | None = None, + ax_bottom_is_0: bool = False, + y_max_tick_is_1: bool = False, + math_text: bool = True, + one_decimal_place: bool = False, + percentage: bool = False, +) -> Axes: + """绘制多组柱状图,包含散点、误差条、显著性标注和图例等。 Args: - data (list[list[NumArray]]): 多组数据,每组是一个包含若干数值数组的列表。 - ax (plt.Axes | None, optional): matplotlib 的 Axes 对象。默认为 None,自动使用当前的 Axes。 - group_labels (list[str] | None, optional): 每组的标签名。默认为 None,使用 "Group i"。 - bar_labels (list[str] | None, optional): 每组内每个柱子的标签。默认为 None,使用 "Bar i"。 - bar_width (Num, optional): 单个柱子的宽度。默认为 0.2。 - bar_gap (Num, optional): 每组柱子之间的间距。默认为 0.1。 - bar_color (list[str] | None, optional): 每个柱子的颜色列表。默认为 None,使用灰色。 - errorbar_type (str, optional): 误差条类型,支持 "sd"(标准差)或 "se"(标准误)。默认为 "sd"。 - dots_color (str, optional): 散点的颜色。默认为 "gray"。 - dots_size (int, optional): 散点的大小。默认为 35。 - title_name (str, optional): 图标题。默认为空字符串。 - x_label_name (str, optional): X 轴标签。默认为空字符串。 - y_label_name (str, optional): Y 轴标签。默认为空字符串。 - statistic (bool, optional): 是否执行统计检验并显示显著性标注。默认为 False。 - test_method (str, optional): 统计检验方法。支持 "external"(外部 p 值)或其他方法。默认为 "external"。 - p_list (list[list[Num]] | None, optional): 外部提供的显著性 p 值列表。默认为 None。 - legend (bool, optional): 是否显示图例。默认为 True。 - legend_position (tuple[Num, Num], optional): 图例在坐标系中的位置。默认为 (1.2, 1)。 - **kwargs (Any): 其他可选参数,用于进一步定制图表样式。 + data (DataType): + 输入数据,可以是三维numpy数组、二维numpy数组列表或嵌套序列 + ax (Axes | None, optional): + matplotlib的坐标轴对象,如果为None则使用当前坐标轴. Defaults to None. + group_labels (list[str] | None, optional): + 组标签名称列表. Defaults to None. + bar_labels (list[str] | None, optional): + 柱状图标签名称列表. Defaults to None. + bar_width (Num, optional): + 柱状图的宽度. Defaults to 0.2. + bar_gap (Num, optional): + 柱状图之间的间隔. Defaults to 0.1. + bar_color (list[str] | None, optional): + 柱状图的颜色列表. Defaults to None. + errorbar_type (str, optional): + 误差条类型,可选 "sd"(标准差) 或 "se"(标准误). Defaults to "sd". + dots_color (str, optional): + 散点的颜色. Defaults to "gray". + dots_size (int, optional): + 散点的大小. Defaults to 35. + legend (bool, optional): + 是否显示图例. Defaults to True. + legend_position (tuple[Num, Num], optional): + 图例位置坐标. Defaults to (1.2, 1). + title_name (str, optional): + 图表标题. Defaults to "". + title_fontsize (int, optional): + 标题字体大小. Defaults to 12. + title_pad (int, optional): + 标题与图表的间距. Defaults to 10. + x_label_name (str, optional): + X轴标签名称. Defaults to "". + x_label_ha (str, optional): + X轴标签的水平对齐方式. Defaults to "center". + x_label_fontsize (int, optional): + X轴标签字体大小. Defaults to 10. + x_tick_fontsize (int, optional): + X轴刻度字体大小. Defaults to 8. + x_tick_rotation (int, optional): + X轴刻度旋转角度. Defaults to 0. + y_label_name (str, optional): + Y轴标签名称. Defaults to "". + y_label_fontsize (int, optional): + Y轴标签字体大小. Defaults to 10. + y_tick_fontsize (int, optional): + Y轴刻度字体大小. Defaults to 8. + y_tick_rotation (int, optional): + Y轴刻度旋转角度. Defaults to 0. + statistic (bool, optional): + 是否进行统计显著性分析. Defaults to False. + test_method (str, optional): + 统计检验方法,目前仅支持"external". Defaults to "external". + p_list (list[list[Num]] | None, optional): + 预计算的p值列表,用于显著性标记. Defaults to None. + line_color (str, optional): + 显著性标记线的颜色. Defaults to "0.5". + asterisk_fontsize (int, optional): + 显著性星号的字体大小. Defaults to 10. + asterisk_color (str, optional): + 显著性星号的颜色. Defaults to "k". + y_lim (list[Num] | tuple[Num, Num] | None, optional): + Y轴的范围限制. Defaults to None. + ax_bottom_is_0 (bool, optional): + Y轴是否从0开始. Defaults to False. + y_max_tick_is_1 (bool, optional): + Y轴最大刻度是否限制为1. Defaults to False. + math_text (bool, optional): + 是否将Y轴显示为科学计数法格式. Defaults to True. + one_decimal_place (bool, optional): + Y轴刻度是否只保留一位小数. Defaults to False. + percentage (bool, optional): + 是否将Y轴显示为百分比格式. Defaults to False. + + Raises: + ValueError: 当data数据格式无效时抛出 + ValueError: 当test_method不是"external"时抛出(多组数据统计测试方法暂时仅支持external方法) Returns: - None + Axes: 返回matplotlib的坐标轴对象 """ + def _is_valid_data_for_multi_group(data): + if not data: + raise ValueError("data 不能为空") + NumberTypes = (int, float, np.integer, np.floating) + # 1) 3D ndarray + if isinstance(data, np.ndarray): + return data.ndim == 3 + # 必须是外层序列 + if not isinstance(data, (list, tuple)): + return False + # 2) 平铺的 list,其中每个元素都是 2D ndarray + if all(isinstance(x, np.ndarray) and x.ndim == 2 for x in data): + return True + # 3) 外层是 groups:每个 group 是序列,group 内的 bar 要么 1D ndarray 要么数字序列 + for group in data: + if not isinstance(group, (list, tuple)): + return False + for bar in group: + if isinstance(bar, np.ndarray): + if bar.ndim != 1: + return False + elif isinstance(bar, (list, tuple)): + if not all(isinstance(v, NumberTypes) for v in bar): + return False + else: + return False + return True + if not _is_valid_data_for_multi_group(data): + raise ValueError("无效的 data") - # 动态参数 ax = ax or plt.gca() group_labels = group_labels or [f"Group {i + 1}" for i in range(len(data))] n_groups = len(data) @@ -834,13 +1000,15 @@ def plot_multi_group_bar_figure( x_positions_all.append(x_positions) # 计算均值、标准差、标准误 - means = [compute_summary(group_data[i])[0] for i in range(n_bars)] - sds = [compute_summary(group_data[i])[1] for i in range(n_bars)] - ses = [compute_summary(group_data[i])[2] for i in range(n_bars)] + means = [_compute_summary(group_data[i])[0] for i in range(n_bars)] + sds = [_compute_summary(group_data[i])[1] for i in range(n_bars)] + ses = [_compute_summary(group_data[i])[2] for i in range(n_bars)] if errorbar_type == "sd": error_values = sds elif errorbar_type == "se": error_values = ses + else: + raise ValueError("errorbar_type 只能是 'sd' 或者 'se'") # 绘制柱子 bars = ax.bar( x_positions, means, width=bar_width, color=bar_color, alpha=1, edgecolor="k" @@ -859,7 +1027,7 @@ def plot_multi_group_bar_figure( dot_x_pos = RNG.normal( x_positions[index_bar], scale=bar_width / 7, size=len(dot) ) - add_scatter(ax, dot_x_pos, dot, dots_color, dots_size=dots_size) + _add_scatter(ax, dot_x_pos, dot, dots_color, dots_size=dots_size) if legend: ax.legend(bars, bar_labels, bbox_to_anchor=legend_position) @@ -867,27 +1035,36 @@ def plot_multi_group_bar_figure( ax.spines[["top", "right"]].set_visible(False) ax.set_title( title_name, - fontsize=kwargs.get("title_fontsize", 15), - pad=kwargs.get("title_pad", 10), + fontsize=title_fontsize, + pad=title_pad, ) # x轴 - ax.set_xlabel(x_label_name, fontsize=kwargs.get("x_label_fontsize", 10)) + ax.set_xlabel(x_label_name, fontsize=x_label_fontsize) ax.set_xticks(np.arange(n_groups)) ax.set_xticklabels( group_labels, - ha=kwargs.get("x_label_ha", "center"), + ha=x_label_ha, rotation_mode="anchor", - fontsize=kwargs.get("x_tick_fontsize", 10), - rotation=kwargs.get("x_tick_rotation", 0), + fontsize=x_tick_fontsize, + rotation=x_tick_rotation, ) # y轴 ax.tick_params( axis="y", - labelsize=kwargs.get("y_tick_fontsize", 10), - rotation=kwargs.get("y_tick_rotation", 0), + labelsize=y_tick_fontsize, + rotation=y_tick_rotation, + ) + ax.set_ylabel(y_label_name, fontsize=y_label_fontsize) + _set_yaxis( + ax, + all_values, + y_lim, + ax_bottom_is_0, + y_max_tick_is_1, + math_text, + one_decimal_place, + percentage, ) - ax.set_ylabel(y_label_name, fontsize=kwargs.get("y_label_fontsize", 10)) - set_yaxis(ax, all_values, kwargs) # 添加统计显著性标记 if statistic: @@ -898,27 +1075,25 @@ def plot_multi_group_bar_figure( for i in range(len(group_data)): for j in range(i + 1, len(group_data)): if test_method == "external": + if p_list is None: + raise ValueError("p_list不能为空") p = p_list[index_group][idx] idx += 1 + else: + raise ValueError("多组数据统计测试方法暂时仅支持 external方法") if p <= 0.05: comparisons.append((x_positions[i], x_positions[j], p)) y_max = ax.get_ylim()[1] interval = (y_max - np.max(all_values)) / (len(comparisons) + 1) - annotate_significance( + _annotate_significance( ax, comparisons, np.max(all_values), interval, - line_color=kwargs.get("line_color", "0.5"), + line_color=line_color, star_offset=interval / 5, - fontsize=kwargs.get("asterisk_fontsize", 10), - color=kwargs.get("asterisk_color", "k"), + fontsize=asterisk_fontsize, + color=asterisk_color, ) - -def main(): - pass - - -if __name__ == "__main__": - main() + return ax From 54881e3615414c887351a6d2d0e128baee968660 Mon Sep 17 00:00:00 2001 From: Ricardo_T8 Date: Mon, 11 Aug 2025 16:16:08 +0800 Subject: [PATCH 3/6] style(bar): Format code and remove trailing whitespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 风格(bar): 格式化代码,移除末尾空格 --- src/plotfig/bar.py | 224 +++++++++++++++++++++++---------------------- 1 file changed, 113 insertions(+), 111 deletions(-) diff --git a/src/plotfig/bar.py b/src/plotfig/bar.py index 559af37..005999b 100644 --- a/src/plotfig/bar.py +++ b/src/plotfig/bar.py @@ -329,81 +329,81 @@ def plot_one_group_bar_figure( """绘制单组柱状图,包含散点、误差条和统计显著性标记。 Args: - data (np.ndarray | Sequence[Sequence[Num] | np.ndarray]): + data (np.ndarray | Sequence[Sequence[Num] | np.ndarray]): 输入数据,可以是二维numpy数组或嵌套序列,每个子序列代表一个柱状图的数据点 - ax (Axes | None, optional): + ax (Axes | None, optional): matplotlib的坐标轴对象,如果为None则使用当前坐标轴. Defaults to None. - labels_name (list[str] | None, optional): + labels_name (list[str] | None, optional): 柱状图的标签名称列表. Defaults to None. - colors (list[str] | None, optional): + colors (list[str] | None, optional): 柱状图的颜色列表. Defaults to None. - edgecolor (str | None, optional): + edgecolor (str | None, optional): 柱状图边缘颜色. Defaults to None. - gradient_color (bool, optional): + gradient_color (bool, optional): 是否使用渐变颜色填充柱状图. Defaults to False. - colors_start (list[str] | None, optional): + colors_start (list[str] | None, optional): 渐变色的起始颜色列表. Defaults to None. - colors_end (list[str] | None, optional): + colors_end (list[str] | None, optional): 渐变色的结束颜色列表. Defaults to None. - dots_color (list[list[str]] | None, optional): + dots_color (list[list[str]] | None, optional): 散点的颜色列表. Defaults to None. - y_lim (list[Num] | tuple[Num, Num] | None, optional): + y_lim (list[Num] | tuple[Num, Num] | None, optional): Y轴的范围限制. Defaults to None. - width (Num, optional): + width (Num, optional): 柱状图的宽度. Defaults to 0.5. - color_alpha (Num, optional): + color_alpha (Num, optional): 柱状图颜色的透明度. Defaults to 1. - dots_size (Num, optional): + dots_size (Num, optional): 散点的大小. Defaults to 35. - errorbar_type (str, optional): + errorbar_type (str, optional): 误差条类型,可选 "sd"(标准差) 或 "se"(标准误). Defaults to "sd". - title_name (str, optional): + title_name (str, optional): 图表标题. Defaults to "". - title_fontsize (Num, optional): + title_fontsize (Num, optional): 标题字体大小. Defaults to 12. - title_pad (Num, optional): + title_pad (Num, optional): 标题与图表的间距. Defaults to 10. - x_label_name (str, optional): + x_label_name (str, optional): X轴标签名称. Defaults to "". - x_label_ha (str, optional): + x_label_ha (str, optional): X轴标签的水平对齐方式. Defaults to "center". - x_label_fontsize (Num, optional): + x_label_fontsize (Num, optional): X轴标签字体大小. Defaults to 10. - x_tick_fontsize (Num, optional): + x_tick_fontsize (Num, optional): X轴刻度字体大小. Defaults to 8. - x_tick_rotation (Num, optional): + x_tick_rotation (Num, optional): X轴刻度旋转角度. Defaults to 0. - y_label_name (str, optional): + y_label_name (str, optional): Y轴标签名称. Defaults to "". - y_label_fontsize (Num, optional): + y_label_fontsize (Num, optional): Y轴标签字体大小. Defaults to 10. - y_tick_fontsize (Num, optional): + y_tick_fontsize (Num, optional): Y轴刻度字体大小. Defaults to 8. - y_tick_rotation (Num, optional): + y_tick_rotation (Num, optional): Y轴刻度旋转角度. Defaults to 0. - statistic (bool, optional): + statistic (bool, optional): 是否进行统计显著性分析. Defaults to False. - test_method (list[str], optional): + test_method (list[str], optional): 统计检验方法列表. Defaults to ["ttest_ind"]. - p_list (list[float] | None, optional): + p_list (list[float] | None, optional): 预计算的p值列表,用于显著性标记. Defaults to None. - popmean (Num, optional): + popmean (Num, optional): 单样本t检验的假设均值. Defaults to 0. - statistical_line_color (str, optional): + statistical_line_color (str, optional): 显著性标记线的颜色. Defaults to "0.5". - asterisk_fontsize (Num, optional): + asterisk_fontsize (Num, optional): 显著性星号的字体大小. Defaults to 10. - asterisk_color (str, optional): + asterisk_color (str, optional): 显著性星号的颜色. Defaults to "k". - ax_bottom_is_0 (bool, optional): + ax_bottom_is_0 (bool, optional): Y轴是否从0开始. Defaults to False. - y_max_tick_is_1 (bool, optional): + y_max_tick_is_1 (bool, optional): Y轴最大刻度是否限制为1. Defaults to False. - math_text (bool, optional): + math_text (bool, optional): 是否将Y轴显示为科学计数法格式. Defaults to True. - one_decimal_place (bool, optional): + one_decimal_place (bool, optional): Y轴刻度是否只保留一位小数. Defaults to False. - percentage (bool, optional): + percentage (bool, optional): 是否将Y轴显示为百分比格式. Defaults to False. Raises: @@ -586,77 +586,77 @@ def plot_one_group_violin_figure( """绘制单组小提琴图,可选散点叠加、渐变填色和统计显著性标注。 Args: - data (Sequence[list[float] | NDArray[np.float64]]): + data (Sequence[list[float] | NDArray[np.float64]]): 输入数据,可以是二维numpy数组或嵌套序列,每个子序列代表一个小提琴的数据点 - ax (Axes | None, optional): + ax (Axes | None, optional): matplotlib的坐标轴对象,如果为None则使用当前坐标轴. Defaults to None. - labels_name (list[str] | None, optional): + labels_name (list[str] | None, optional): 小提琴图的标签名称列表. Defaults to None. - width (Num, optional): + width (Num, optional): 小提琴图的宽度. Defaults to 0.8. - colors (list[str] | None, optional): + colors (list[str] | None, optional): 小提琴图的颜色列表. Defaults to None. - color_alpha (Num, optional): + color_alpha (Num, optional): 小提琴图颜色的透明度. Defaults to 1. - gradient_color (bool, optional): + gradient_color (bool, optional): 是否使用渐变颜色填充小提琴图. Defaults to False. - colors_start (list[str] | None, optional): + colors_start (list[str] | None, optional): 渐变色的起始颜色列表. Defaults to None. - colors_end (list[str] | None, optional): + colors_end (list[str] | None, optional): 渐变色的结束颜色列表. Defaults to None. - show_dots (bool, optional): + show_dots (bool, optional): 是否显示散点. Defaults to False. - dots_size (Num, optional): + dots_size (Num, optional): 散点的大小. Defaults to 35. - title_name (str, optional): + title_name (str, optional): 图表标题. Defaults to "". - title_fontsize (Num, optional): + title_fontsize (Num, optional): 标题字体大小. Defaults to 12. - title_pad (Num, optional): + title_pad (Num, optional): 标题与图表的间距. Defaults to 10. - x_label_name (str, optional): + x_label_name (str, optional): X轴标签名称. Defaults to "". - x_label_ha (str, optional): + x_label_ha (str, optional): X轴标签的水平对齐方式. Defaults to "center". - x_label_fontsize (Num, optional): + x_label_fontsize (Num, optional): X轴标签字体大小. Defaults to 10. - x_tick_fontsize (Num, optional): + x_tick_fontsize (Num, optional): X轴刻度字体大小. Defaults to 8. - x_tick_rotation (Num, optional): + x_tick_rotation (Num, optional): X轴刻度旋转角度. Defaults to 0. - y_label_name (str, optional): + y_label_name (str, optional): Y轴标签名称. Defaults to "". - y_label_fontsize (Num, optional): + y_label_fontsize (Num, optional): Y轴标签字体大小. Defaults to 10. - y_tick_fontsize (Num, optional): + y_tick_fontsize (Num, optional): Y轴刻度字体大小. Defaults to 8. - y_tick_rotation (Num, optional): + y_tick_rotation (Num, optional): Y轴刻度旋转角度. Defaults to 0. - statistic (bool, optional): + statistic (bool, optional): 是否进行统计显著性分析. Defaults to False. - test_method (list[str], optional): + test_method (list[str], optional): 统计检验方法列表. Defaults to ["ttest_ind"]. - popmean (Num, optional): + popmean (Num, optional): 单样本t检验的假设均值. Defaults to 0. - p_list (list[float] | None, optional): + p_list (list[float] | None, optional): 预计算的p值列表,用于显著性标记. Defaults to None. - statistical_line_color (str, optional): + statistical_line_color (str, optional): 显著性标记线的颜色. Defaults to "0.5". - asterisk_fontsize (Num, optional): + asterisk_fontsize (Num, optional): 显著性星号的字体大小. Defaults to 10. - asterisk_color (str, optional): + asterisk_color (str, optional): 显著性星号的颜色. Defaults to "k". - y_lim (list[Num] | tuple[Num, Num] | None, optional): + y_lim (list[Num] | tuple[Num, Num] | None, optional): Y轴的范围限制. Defaults to None. - ax_bottom_is_0 (bool, optional): + ax_bottom_is_0 (bool, optional): Y轴是否从0开始. Defaults to False. - y_max_tick_is_1 (bool, optional): + y_max_tick_is_1 (bool, optional): Y轴最大刻度是否限制为1. Defaults to False. - math_text (bool, optional): + math_text (bool, optional): 是否将Y轴显示为科学计数法格式. Defaults to True. - one_decimal_place (bool, optional): + one_decimal_place (bool, optional): Y轴刻度是否只保留一位小数. Defaults to False. - percentage (bool, optional): + percentage (bool, optional): 是否将Y轴显示为百分比格式. Defaults to False. Raises: @@ -866,77 +866,77 @@ def plot_multi_group_bar_figure( """绘制多组柱状图,包含散点、误差条、显著性标注和图例等。 Args: - data (DataType): + data (DataType): 输入数据,可以是三维numpy数组、二维numpy数组列表或嵌套序列 - ax (Axes | None, optional): + ax (Axes | None, optional): matplotlib的坐标轴对象,如果为None则使用当前坐标轴. Defaults to None. - group_labels (list[str] | None, optional): + group_labels (list[str] | None, optional): 组标签名称列表. Defaults to None. - bar_labels (list[str] | None, optional): + bar_labels (list[str] | None, optional): 柱状图标签名称列表. Defaults to None. - bar_width (Num, optional): + bar_width (Num, optional): 柱状图的宽度. Defaults to 0.2. - bar_gap (Num, optional): + bar_gap (Num, optional): 柱状图之间的间隔. Defaults to 0.1. - bar_color (list[str] | None, optional): + bar_color (list[str] | None, optional): 柱状图的颜色列表. Defaults to None. - errorbar_type (str, optional): + errorbar_type (str, optional): 误差条类型,可选 "sd"(标准差) 或 "se"(标准误). Defaults to "sd". - dots_color (str, optional): + dots_color (str, optional): 散点的颜色. Defaults to "gray". - dots_size (int, optional): + dots_size (int, optional): 散点的大小. Defaults to 35. - legend (bool, optional): + legend (bool, optional): 是否显示图例. Defaults to True. - legend_position (tuple[Num, Num], optional): + legend_position (tuple[Num, Num], optional): 图例位置坐标. Defaults to (1.2, 1). - title_name (str, optional): + title_name (str, optional): 图表标题. Defaults to "". - title_fontsize (int, optional): + title_fontsize (int, optional): 标题字体大小. Defaults to 12. - title_pad (int, optional): + title_pad (int, optional): 标题与图表的间距. Defaults to 10. - x_label_name (str, optional): + x_label_name (str, optional): X轴标签名称. Defaults to "". - x_label_ha (str, optional): + x_label_ha (str, optional): X轴标签的水平对齐方式. Defaults to "center". - x_label_fontsize (int, optional): + x_label_fontsize (int, optional): X轴标签字体大小. Defaults to 10. - x_tick_fontsize (int, optional): + x_tick_fontsize (int, optional): X轴刻度字体大小. Defaults to 8. - x_tick_rotation (int, optional): + x_tick_rotation (int, optional): X轴刻度旋转角度. Defaults to 0. - y_label_name (str, optional): + y_label_name (str, optional): Y轴标签名称. Defaults to "". - y_label_fontsize (int, optional): + y_label_fontsize (int, optional): Y轴标签字体大小. Defaults to 10. - y_tick_fontsize (int, optional): + y_tick_fontsize (int, optional): Y轴刻度字体大小. Defaults to 8. - y_tick_rotation (int, optional): + y_tick_rotation (int, optional): Y轴刻度旋转角度. Defaults to 0. - statistic (bool, optional): + statistic (bool, optional): 是否进行统计显著性分析. Defaults to False. - test_method (str, optional): + test_method (str, optional): 统计检验方法,目前仅支持"external". Defaults to "external". - p_list (list[list[Num]] | None, optional): + p_list (list[list[Num]] | None, optional): 预计算的p值列表,用于显著性标记. Defaults to None. - line_color (str, optional): + line_color (str, optional): 显著性标记线的颜色. Defaults to "0.5". - asterisk_fontsize (int, optional): + asterisk_fontsize (int, optional): 显著性星号的字体大小. Defaults to 10. - asterisk_color (str, optional): + asterisk_color (str, optional): 显著性星号的颜色. Defaults to "k". - y_lim (list[Num] | tuple[Num, Num] | None, optional): + y_lim (list[Num] | tuple[Num, Num] | None, optional): Y轴的范围限制. Defaults to None. - ax_bottom_is_0 (bool, optional): + ax_bottom_is_0 (bool, optional): Y轴是否从0开始. Defaults to False. - y_max_tick_is_1 (bool, optional): + y_max_tick_is_1 (bool, optional): Y轴最大刻度是否限制为1. Defaults to False. - math_text (bool, optional): + math_text (bool, optional): 是否将Y轴显示为科学计数法格式. Defaults to True. - one_decimal_place (bool, optional): + one_decimal_place (bool, optional): Y轴刻度是否只保留一位小数. Defaults to False. - percentage (bool, optional): + percentage (bool, optional): 是否将Y轴显示为百分比格式. Defaults to False. Raises: @@ -946,8 +946,9 @@ def plot_multi_group_bar_figure( Returns: Axes: 返回matplotlib的坐标轴对象 """ + def _is_valid_data_for_multi_group(data): - if not data: + if not data.any(): raise ValueError("data 不能为空") NumberTypes = (int, float, np.integer, np.floating) # 1) 3D ndarray @@ -973,6 +974,7 @@ def _is_valid_data_for_multi_group(data): else: return False return True + if not _is_valid_data_for_multi_group(data): raise ValueError("无效的 data") From f20f4620fc25e22dc037f76be664b789aeb33118 Mon Sep 17 00:00:00 2001 From: Ricardo_T8 Date: Mon, 11 Aug 2025 22:55:17 +0800 Subject: [PATCH 4/6] test(connec): added some test scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 测试(connec): 增加一些测试用的脚本 --- .gitignore | 3 +- tests/test.ipynb | 174 +++++++++++++++++++++++++++++++++++++++++++++++ tests/test.py | 18 +++++ 3 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 tests/test.ipynb create mode 100644 tests/test.py diff --git a/.gitignore b/.gitignore index e67cc6b..7e91538 100644 --- a/.gitignore +++ b/.gitignore @@ -18,8 +18,7 @@ notebooks/ # docs docs/changelog.md -# tests -tests/ +tests/figures/* # utils utils/ diff --git a/tests/test.ipynb b/tests/test.ipynb new file mode 100644 index 0000000..7234fcf --- /dev/null +++ b/tests/test.ipynb @@ -0,0 +1,174 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "84d45348", + "metadata": {}, + "source": [ + "# 测试brain_connection" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "ce1c6a22", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "\n", + "def generate_sparse_connectome_v1(size: int = 31, sparsity: float = 0.9, seed=None):\n", + " \"\"\"\n", + " 生成稀疏对称矩阵\n", + "\n", + " Parameters:\n", + " size: 矩阵大小\n", + " sparsity: 稀疏度,0表示没有0元素,1表示全部为0元素\n", + " \"\"\"\n", + " if seed is not None:\n", + " np.random.seed(seed)\n", + " # 生成随机矩阵\n", + " connectome = np.random.rand(size, size)\n", + "\n", + " # 应用稀疏度 - 随机将一些元素设为0\n", + " mask = np.random.rand(size, size) < sparsity\n", + " connectome[mask] = 0\n", + "\n", + " # 确保矩阵对称\n", + " connectome = np.triu(connectome) + np.triu(connectome, 1).T\n", + "\n", + " # 确保对角线为0\n", + " np.fill_diagonal(connectome, 0)\n", + "\n", + " return connectome\n", + "\n", + "\n", + "def generate_sparse_connectome_v2(\n", + " size: int = 31, density: float = 0.1, seed: int | None = None\n", + ") -> np.ndarray:\n", + " \"\"\"\n", + " 生成一个对称的方阵,满足:\n", + " - 对角线为0\n", + " - 有正负值\n", + " - 稀疏程度由 density 控制(非零元素比例)\n", + "\n", + " 参数:\n", + " size: 方阵大小(行列数)\n", + " density: 非零元素比例,0~1,越小越稀疏\n", + " seed: 随机种子,方便复现\n", + "\n", + " 返回:\n", + " np.ndarray 方阵\n", + " \"\"\"\n", + " if seed is not None:\n", + " np.random.seed(seed)\n", + "\n", + " # 先生成上三角(不含对角线)随机稀疏矩阵\n", + " upper = np.random.choice(\n", + " [0, 1], size=(size, size), p=[1 - density, density]\n", + " ) * np.random.uniform(-1, 1, size=(size, size))\n", + "\n", + " # 让对角线为0\n", + " np.fill_diagonal(upper, 0)\n", + "\n", + " # 只保留上三角部分(含对角线0)\n", + " upper = np.triu(upper, k=1)\n", + "\n", + " # 下三角镜像上三角,保证对称\n", + " mat = upper + upper.T\n", + "\n", + " return mat\n" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "88082368", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2025-08-11 22:50:10.596\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mplotfig.brain_connection\u001b[0m:\u001b[36mplot_brain_connection_figure\u001b[0m:\u001b[36m267\u001b[0m - \u001b[1m未指定保存路径,默认保存在当前文件夹下的2025-08-11_22-50-10.html中。\u001b[0m\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "from plotfig import plot_brain_connection_figure\n", + "\n", + "lh_surfgii_file = r\"e:\\6_Self\\plot_self_brain_connectivity\\103818.L.midthickness.32k_fs_LR.surf.gii\"\n", + "rh_surfgii_file = r\"e:\\6_Self\\plot_self_brain_connectivity\\103818.R.midthickness.32k_fs_LR.surf.gii\"\n", + "niigz_file = r\"e:\\6_Self\\plot_self_brain_connectivity\\human_Self_processing_network.nii.gz\"\n", + "output_file = r\"E:\\git_repositories\\plotfig\\tests\\test.html\"\n", + "\n", + "connectome = generate_sparse_connectome_v1(seed=42)\n", + "# connectome = generate_sparse_connectome_v2(seed=42)\n", + "# print(np.max(connectome), np.min(connectome))\n", + "\n", + "fig = plot_brain_connection_figure(\n", + " connectome,\n", + " lh_surfgii_file=lh_surfgii_file,\n", + " rh_surfgii_file=rh_surfgii_file,\n", + " niigz_file=niigz_file,\n", + " # \n", + " line_color=\"green\",\n", + " scale_method=\"width_color\"\n", + "\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36628f32", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|██████████| 36/36 [02:34<00:00, 4.30s/it]\n", + "\u001b[32m2025-08-11 22:52:55.854\u001b[0m | \u001b[1mINFO \u001b[0m | \u001b[36mplotfig.brain_connection\u001b[0m:\u001b[36msave_brain_connection_frames\u001b[0m:\u001b[36m320\u001b[0m - \u001b[1m保存了 36 张图片在 E:/git_repositories/plotfig/tests/brain_connec_frames\u001b[0m\n" + ] + } + ], + "source": [ + "from plotfig import save_brain_connection_frames\n", + "\n", + "save_brain_connection_frames(fig, \"E:/git_repositories/plotfig/tests/figures/brain_connec_frames\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8b7c94cf", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "plotfig (3.11.11)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tests/test.py b/tests/test.py new file mode 100644 index 0000000..363a4ba --- /dev/null +++ b/tests/test.py @@ -0,0 +1,18 @@ +import random +from plotfig import * + +input_file = r"e:\git_repositories\plotfig\src\plotfig\data\neurodata\atlases\macaque_D99\label.txt" +output_file = r"e:\git_repositories\plotfig\src\plotfig\data\neurodata\atlases\macaque_D99\label1.txt" + +with open(input_file, "r", encoding="utf-8") as fin, \ + open(output_file, "w", encoding="utf-8") as fout: + + for idx, line in enumerate(fin, start=1): + region_name = line.rstrip("\n") + fout.write(region_name + "\n") + + r = random.randint(0, 255) + g = random.randint(0, 255) + b = random.randint(0, 255) + # 写格式:序号, r, g, b, 255 + fout.write(f"{idx} {r} {g} {b} 255\n") From 4ed3e3a758bb9090425d6f349da2b2f761049fcf Mon Sep 17 00:00:00 2001 From: Ricardo_T8 Date: Mon, 11 Aug 2025 22:57:59 +0800 Subject: [PATCH 5/6] build(deps): update dependencies to ensure correct saving of brain connection frames MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To guarantee proper operation of `save_brain_connection_frames`: - update plotly to >=6.1.1 - add kaleido >=1.0.0 - add loguru >=0.7.3 --- 构建(deps): 更新依赖使正确保存brain connec frames 为保证save_brain_connection_frames正确运行: - 更新plotly>=6.1.1 - 安装kaleido>=1.0.0 - 安装loguru>=0.7.3 --- README.md | 14 ++-- docs/installation.md | 4 +- pyproject.toml | 4 +- uv.lock | 185 +++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 194 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 24b54fd..24392e1 100644 --- a/README.md +++ b/README.md @@ -51,13 +51,15 @@ pip install . `plotfig` 依赖若干核心库,这些依赖将在安装过程中自动处理: -- [matplotlib](https://matplotlib.org/) ≥ 3.10. -- [mne-connectivity](https://mne.tools/mne-connectivity/stable/index.html) ≥ 0.7. -- [nibabel](https://nipy.org/nibabel/) ≥ 5.3. +- [matplotlib](https://matplotlib.org/) ≥ 3.10.1 +- [mne-connectivity](https://mne.tools/mne-connectivity/stable/index.html) ≥ 0.7.0 +- [nibabel](https://nipy.org/nibabel/) ≥ 5.3.2 - [numpy](https://numpy.org/) ≥ 2.2.4 -- [pandas](https://pandas.pydata.org/) ≥ 2.2. -- [plotly](https://plotly.com/) ≥ 6.0.1 -- [scipy](https://scipy.org/) ≥ 1.15. +- [pandas](https://pandas.pydata.org/) ≥ 2.2.3 +- [plotly](https://plotly.com/) ≥ 6.1.1 +- [kaleido](https://github.com/plotly/Kaleido) ≥ 1.0.0 +- [scipy](https://scipy.org/) ≥ 1.15.2 +- [loguru](https://loguru.readthedocs.io/en/stable/) ≥ 0.7.3 - [surfplot](https://github.com/danjgale/surfplot) 需使用其 GitHub 仓库中的最新版,而非 PyPI 上的版本,因后者尚未包含所需功能。 > ⚠️ **指定 `surfplot` 版本** diff --git a/docs/installation.md b/docs/installation.md index 7dfc446..080ff22 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -27,8 +27,10 @@ pip install . - [nibabel](https://nipy.org/nibabel/) ≥ 5.3.2 - [numpy](https://numpy.org/) ≥ 2.2.4 - [pandas](https://pandas.pydata.org/) ≥ 2.2.3 -- [plotly](https://plotly.com/) ≥ 6.0.1 +- [plotly](https://plotly.com/) ≥ 6.1.1 +- [kaleido](https://github.com/plotly/Kaleido) ≥ 1.0.0 - [scipy](https://scipy.org/) ≥ 1.15.2 +- [loguru](https://loguru.readthedocs.io/en/stable/) ≥ 0.7.3 - [surfplot](https://github.com/danjgale/surfplot) 需使用其 GitHub 仓库中的最新版,而非 PyPI 上的版本,因后者尚未包含所需功能。 !!! warning "指定 `surfplot` 版本" diff --git a/pyproject.toml b/pyproject.toml index 92b2833..e9bf621 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,12 +9,14 @@ authors = [ { name = "Ricardo Ryn", email = "ricardoRyn1317@gmail.com" } ] dependencies = [ + "kaleido>=1.0.0", + "loguru>=0.7.3", "matplotlib>=3.10.1", "mne-connectivity>=0.7.0", "nibabel>=5.3.2", "numpy>=2.2.4", "pandas>=2.2.3", - "plotly>=6.0.1", + "plotly>=6.1.1", "scipy>=1.15.2", "surfplot", ] diff --git a/uv.lock b/uv.lock index 254a5c6..f8f80cb 100644 --- a/uv.lock +++ b/uv.lock @@ -196,6 +196,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767, upload-time = "2024-12-24T18:12:32.852Z" }, ] +[[package]] +name = "choreographer" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "logistro" }, + { name = "simplejson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3e/2f/1de552325ce03732cca29534ca3e91136721f500666ea4fa2eb348eb237b/choreographer-1.0.9.tar.gz", hash = "sha256:36423b4b049cf2ec2a68fa4024bdd22d0c417d5421913ef62b6c0e89595b6895", size = 40462, upload-time = "2025-06-10T19:14:30.327Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/da/38f4a19fadd86416551871fa1cc385df61b0f54656e1def4892c148cfd8f/choreographer-1.0.9-py3-none-any.whl", hash = "sha256:b3277e30953843a83d3d730e49958a6be82013885d2a4f54b3950a3715191d7f", size = 51262, upload-time = "2025-06-10T19:14:29.313Z" }, +] + [[package]] name = "click" version = "8.2.1" @@ -565,6 +578,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2f/57/6bffd4b20b88da3800c5d691e0337761576ee688eb01299eae865689d2df/jupyter_core-5.8.1-py3-none-any.whl", hash = "sha256:c28d268fc90fb53f1338ded2eb410704c5449a358406e8a948b75706e24863d0", size = 28880, upload-time = "2025-05-27T07:38:15.137Z" }, ] +[[package]] +name = "kaleido" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "choreographer" }, + { name = "logistro" }, + { name = "orjson" }, + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/dc/fd2d955884f45f852152d44f5ecf79de3cb58da0f995b6f6f9acfc80dd94/kaleido-1.0.0.tar.gz", hash = "sha256:502d8ba64737924efaf5e94c2736745bcc7c926e6cc535838be36c0fc06330bd", size = 49400, upload-time = "2025-06-19T15:50:39.357Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/0e/853df00cac4823eaa8f35114d0d0ad1e2ec6b8243ce4acf5f8ac5235c517/kaleido-1.0.0-py3-none-any.whl", hash = "sha256:a7e8bd95648378d2746f6c86084d419d15f95b1ec7bb0ec810289b7feb25b18d", size = 51479, upload-time = "2025-06-19T15:50:37.884Z" }, +] + [[package]] name = "kiwisolver" version = "1.4.8" @@ -643,6 +671,28 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, ] +[[package]] +name = "logistro" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/27/c1/aa8bc9e07e4b4bd9a3bc05804c483ba3f334c94dcd54995da856103a204d/logistro-1.1.0.tar.gz", hash = "sha256:ad51f0efa2bc705bea7c266e8a759cf539457cf7108202a5eec77bdf6300d774", size = 8269, upload-time = "2025-04-26T20:14:11.012Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/df/e51691ab004d74fa25b751527d041ad1b4d84ee86cbcb8630ab0d7d5188e/logistro-1.1.0-py3-none-any.whl", hash = "sha256:4f88541fe7f3c545561b754d86121abd9c6d4d8b312381046a78dcd794fddc7c", size = 7894, upload-time = "2025-04-26T20:14:09.363Z" }, +] + +[[package]] +name = "loguru" +version = "0.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "win32-setctime", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/05/a1dae3dffd1116099471c643b8924f5aa6524411dc6c63fdae648c4f1aca/loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6", size = 63559, upload-time = "2024-12-06T11:20:56.608Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c", size = 61595, upload-time = "2024-12-06T11:20:54.538Z" }, +] + [[package]] name = "markdown" version = "3.8.1" @@ -1060,6 +1110,70 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119, upload-time = "2025-03-16T18:20:03.94Z" }, ] +[[package]] +name = "orjson" +version = "3.11.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/3b/fd9ff8ff64ae3900f11554d5cfc835fb73e501e043c420ad32ec574fe27f/orjson-3.11.1.tar.gz", hash = "sha256:48d82770a5fd88778063604c566f9c7c71820270c9cc9338d25147cbf34afd96", size = 5393373, upload-time = "2025-07-25T14:33:52.898Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a5/92/7ab270b5b3df8d5b0d3e572ddf2f03c9f6a79726338badf1ec8594e1469d/orjson-3.11.1-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:15e2a57ce3b57c1a36acffcc02e823afefceee0a532180c2568c62213c98e3ef", size = 240918, upload-time = "2025-07-25T14:32:11.021Z" }, + { url = "https://files.pythonhosted.org/packages/80/41/df44684cfbd2e2e03bf9b09fdb14b7abcfff267998790b6acfb69ad435f0/orjson-3.11.1-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:17040a83ecaa130474af05bbb59a13cfeb2157d76385556041f945da936b1afd", size = 129386, upload-time = "2025-07-25T14:32:12.361Z" }, + { url = "https://files.pythonhosted.org/packages/c1/08/958f56edd18ba1827ad0c74b2b41a7ae0864718adee8ccb5d1a5528f8761/orjson-3.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a68f23f09e5626cc0867a96cf618f68b91acb4753d33a80bf16111fd7f9928c", size = 132508, upload-time = "2025-07-25T14:32:13.917Z" }, + { url = "https://files.pythonhosted.org/packages/cc/b6/5e56e189dacbf51e53ba8150c20e61ee746f6d57b697f5c52315ffc88a83/orjson-3.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47e07528bb6ccbd6e32a55e330979048b59bfc5518b47c89bc7ab9e3de15174a", size = 128501, upload-time = "2025-07-25T14:32:15.13Z" }, + { url = "https://files.pythonhosted.org/packages/fe/de/f6c301a514f5934405fd4b8f3d3efc758c911d06c3de3f4be1e30d675fa4/orjson-3.11.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3807cce72bf40a9d251d689cbec28d2efd27e0f6673709f948f971afd52cb09", size = 130465, upload-time = "2025-07-25T14:32:17.355Z" }, + { url = "https://files.pythonhosted.org/packages/47/08/f7dbaab87d6f05eebff2d7b8e6a8ed5f13b2fe3e3ae49472b527d03dbd7a/orjson-3.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b2dc7e88da4ca201c940f5e6127998d9e89aa64264292334dad62854bc7fc27", size = 132416, upload-time = "2025-07-25T14:32:18.933Z" }, + { url = "https://files.pythonhosted.org/packages/43/3f/dd5a185273b7ba6aa238cfc67bf9edaa1885ae51ce942bc1a71d0f99f574/orjson-3.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3091dad33ac9e67c0a550cfff8ad5be156e2614d6f5d2a9247df0627751a1495", size = 134924, upload-time = "2025-07-25T14:32:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/db/ef/729d23510eaa81f0ce9d938d99d72dcf5e4ed3609d9d0bcf9c8a282cc41a/orjson-3.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ed0fce2307843b79a0c83de49f65b86197f1e2310de07af9db2a1a77a61ce4c", size = 130938, upload-time = "2025-07-25T14:32:21.769Z" }, + { url = "https://files.pythonhosted.org/packages/82/96/120feb6807f9e1f4c68fc842a0f227db8575eafb1a41b2537567b91c19d8/orjson-3.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5a31e84782a18c30abd56774c0cfa7b9884589f4d37d9acabfa0504dad59bb9d", size = 130811, upload-time = "2025-07-25T14:32:22.931Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/4695e946a453fa22ff945da4b1ed0691b3f4ec86b828d398288db4a0ff79/orjson-3.11.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:26b6c821abf1ae515fbb8e140a2406c9f9004f3e52acb780b3dee9bfffddbd84", size = 404272, upload-time = "2025-07-25T14:32:25.238Z" }, + { url = "https://files.pythonhosted.org/packages/cd/7b/1c953e2c9e55af126c6cb678a30796deb46d7713abdeb706b8765929464c/orjson-3.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f857b3d134b36a8436f1e24dcb525b6b945108b30746c1b0b556200b5cb76d39", size = 146196, upload-time = "2025-07-25T14:32:26.909Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c2/bef5d3bc83f2e178592ff317e2cf7bd38ebc16b641f076ea49f27aadd1d3/orjson-3.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df146f2a14116ce80f7da669785fcb411406d8e80136558b0ecda4c924b9ac55", size = 135336, upload-time = "2025-07-25T14:32:28.22Z" }, + { url = "https://files.pythonhosted.org/packages/92/95/bc6006881ebdb4608ed900a763c3e3c6be0d24c3aadd62beb774f9464ec6/orjson-3.11.1-cp311-cp311-win32.whl", hash = "sha256:d777c57c1f86855fe5492b973f1012be776e0398571f7cc3970e9a58ecf4dc17", size = 136665, upload-time = "2025-07-25T14:32:29.976Z" }, + { url = "https://files.pythonhosted.org/packages/59/c3/1f2b9cc0c60ea2473d386fed2df2b25ece50aeb73c798d4669aadff3061e/orjson-3.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:e9a5fd589951f02ec2fcb8d69339258bbf74b41b104c556e6d4420ea5e059313", size = 131388, upload-time = "2025-07-25T14:32:31.595Z" }, + { url = "https://files.pythonhosted.org/packages/b0/e5/40c97e5a6b85944022fe54b463470045b8651b7bb2f1e16a95c42812bf97/orjson-3.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:4cddbe41ee04fddad35d75b9cf3e3736ad0b80588280766156b94783167777af", size = 126786, upload-time = "2025-07-25T14:32:32.787Z" }, + { url = "https://files.pythonhosted.org/packages/98/77/e55513826b712807caadb2b733eee192c1df105c6bbf0d965c253b72f124/orjson-3.11.1-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2b7c8be96db3a977367250c6367793a3c5851a6ca4263f92f0b48d00702f9910", size = 240955, upload-time = "2025-07-25T14:32:34.056Z" }, + { url = "https://files.pythonhosted.org/packages/c9/88/a78132dddcc9c3b80a9fa050b3516bb2c996a9d78ca6fb47c8da2a80a696/orjson-3.11.1-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:72e18088f567bd4a45db5e3196677d9ed1605e356e500c8e32dd6e303167a13d", size = 129294, upload-time = "2025-07-25T14:32:35.323Z" }, + { url = "https://files.pythonhosted.org/packages/09/02/6591e0dcb2af6bceea96cb1b5f4b48c1445492a3ef2891ac4aa306bb6f73/orjson-3.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d346e2ae1ce17888f7040b65a5a4a0c9734cb20ffbd228728661e020b4c8b3a5", size = 132310, upload-time = "2025-07-25T14:32:36.53Z" }, + { url = "https://files.pythonhosted.org/packages/e9/36/c1cfbc617bcfa4835db275d5e0fe9bbdbe561a4b53d3b2de16540ec29c50/orjson-3.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4bda5426ebb02ceb806a7d7ec9ba9ee5e0c93fca62375151a7b1c00bc634d06b", size = 128529, upload-time = "2025-07-25T14:32:37.817Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bd/91a156c5df3aaf1d68b2ab5be06f1969955a8d3e328d7794f4338ac1d017/orjson-3.11.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10506cebe908542c4f024861102673db534fd2e03eb9b95b30d94438fa220abf", size = 130925, upload-time = "2025-07-25T14:32:39.03Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4c/a65cc24e9a5f87c9833a50161ab97b5edbec98bec99dfbba13827549debc/orjson-3.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45202ee3f5494644e064c41abd1320497fb92fd31fc73af708708af664ac3b56", size = 132432, upload-time = "2025-07-25T14:32:40.619Z" }, + { url = "https://files.pythonhosted.org/packages/2e/4d/3fc3e5d7115f4f7d01b481e29e5a79bcbcc45711a2723242787455424f40/orjson-3.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5adaf01b92e0402a9ac5c3ebe04effe2bbb115f0914a0a53d34ea239a746289", size = 135069, upload-time = "2025-07-25T14:32:41.84Z" }, + { url = "https://files.pythonhosted.org/packages/dc/c6/7585aa8522af896060dc0cd7c336ba6c574ae854416811ee6642c505cc95/orjson-3.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6162a1a757a1f1f4a94bc6ffac834a3602e04ad5db022dd8395a54ed9dd51c81", size = 131045, upload-time = "2025-07-25T14:32:43.085Z" }, + { url = "https://files.pythonhosted.org/packages/6a/4e/b8a0a943793d2708ebc39e743c943251e08ee0f3279c880aefd8e9cb0c70/orjson-3.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:78404206977c9f946613d3f916727c189d43193e708d760ea5d4b2087d6b0968", size = 130597, upload-time = "2025-07-25T14:32:44.336Z" }, + { url = "https://files.pythonhosted.org/packages/72/2b/7d30e2aed2f585d5d385fb45c71d9b16ba09be58c04e8767ae6edc6c9282/orjson-3.11.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:db48f8e81072e26df6cdb0e9fff808c28597c6ac20a13d595756cf9ba1fed48a", size = 404207, upload-time = "2025-07-25T14:32:45.612Z" }, + { url = "https://files.pythonhosted.org/packages/1b/7e/772369ec66fcbce79477f0891918309594cd00e39b67a68d4c445d2ab754/orjson-3.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0c1e394e67ced6bb16fea7054d99fbdd99a539cf4d446d40378d4c06e0a8548d", size = 146628, upload-time = "2025-07-25T14:32:46.981Z" }, + { url = "https://files.pythonhosted.org/packages/b4/c8/62bdb59229d7e393ae309cef41e32cc1f0b567b21dfd0742da70efb8b40c/orjson-3.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e7a840752c93d4eecd1378e9bb465c3703e127b58f675cd5c620f361b6cf57a4", size = 135449, upload-time = "2025-07-25T14:32:48.727Z" }, + { url = "https://files.pythonhosted.org/packages/02/47/1c99aa60e19f781424eabeaacd9e999eafe5b59c81ead4273b773f0f3af1/orjson-3.11.1-cp312-cp312-win32.whl", hash = "sha256:4537b0e09f45d2b74cb69c7f39ca1e62c24c0488d6bf01cd24673c74cd9596bf", size = 136653, upload-time = "2025-07-25T14:32:50.622Z" }, + { url = "https://files.pythonhosted.org/packages/31/9a/132999929a2892ab07e916669accecc83e5bff17e11a1186b4c6f23231f0/orjson-3.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:dbee6b050062540ae404530cacec1bf25e56e8d87d8d9b610b935afeb6725cae", size = 131426, upload-time = "2025-07-25T14:32:51.883Z" }, + { url = "https://files.pythonhosted.org/packages/9c/77/d984ee5a1ca341090902e080b187721ba5d1573a8d9759e0c540975acfb2/orjson-3.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:f55e557d4248322d87c4673e085c7634039ff04b47bfc823b87149ae12bef60d", size = 126635, upload-time = "2025-07-25T14:32:53.2Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e9/880ef869e6f66279ce3a381a32afa0f34e29a94250146911eee029e56efc/orjson-3.11.1-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:53cfefe4af059e65aabe9683f76b9c88bf34b4341a77d329227c2424e0e59b0e", size = 240835, upload-time = "2025-07-25T14:32:54.507Z" }, + { url = "https://files.pythonhosted.org/packages/f0/1f/52039ef3d03eeea21763b46bc99ebe11d9de8510c72b7b5569433084a17e/orjson-3.11.1-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:93d5abed5a6f9e1b6f9b5bf6ed4423c11932b5447c2f7281d3b64e0f26c6d064", size = 129226, upload-time = "2025-07-25T14:32:55.908Z" }, + { url = "https://files.pythonhosted.org/packages/ee/da/59fdffc9465a760be2cd3764ef9cd5535eec8f095419f972fddb123b6d0e/orjson-3.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dbf06642f3db2966df504944cdd0eb68ca2717f0353bb20b20acd78109374a6", size = 132261, upload-time = "2025-07-25T14:32:57.538Z" }, + { url = "https://files.pythonhosted.org/packages/bb/5c/8610911c7e969db7cf928c8baac4b2f1e68d314bc3057acf5ca64f758435/orjson-3.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dddf4e78747fa7f2188273f84562017a3c4f0824485b78372513c1681ea7a894", size = 128614, upload-time = "2025-07-25T14:32:58.808Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a1/a1db9d4310d014c90f3b7e9b72c6fb162cba82c5f46d0b345669eaebdd3a/orjson-3.11.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa3fe8653c9f57f0e16f008e43626485b6723b84b2f741f54d1258095b655912", size = 130968, upload-time = "2025-07-25T14:33:00.038Z" }, + { url = "https://files.pythonhosted.org/packages/56/ff/11acd1fd7c38ea7a1b5d6bf582ae3da05931bee64620995eb08fd63c77fe/orjson-3.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6334d2382aff975a61f6f4d1c3daf39368b887c7de08f7c16c58f485dcf7adb2", size = 132439, upload-time = "2025-07-25T14:33:01.354Z" }, + { url = "https://files.pythonhosted.org/packages/70/f9/bb564dd9450bf8725e034a8ad7f4ae9d4710a34caf63b85ce1c0c6d40af0/orjson-3.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3d0855b643f259ee0cb76fe3df4c04483354409a520a902b067c674842eb6b8", size = 135299, upload-time = "2025-07-25T14:33:03.079Z" }, + { url = "https://files.pythonhosted.org/packages/94/bb/c8eafe6051405e241dda3691db4d9132d3c3462d1d10a17f50837dd130b4/orjson-3.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0eacdfeefd0a79987926476eb16e0245546bedeb8febbbbcf4b653e79257a8e4", size = 131004, upload-time = "2025-07-25T14:33:04.416Z" }, + { url = "https://files.pythonhosted.org/packages/a2/40/bed8d7dcf1bd2df8813bf010a25f645863a2f75e8e0ebdb2b55784cf1a62/orjson-3.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0ed07faf9e4873518c60480325dcbc16d17c59a165532cccfb409b4cdbaeff24", size = 130583, upload-time = "2025-07-25T14:33:05.768Z" }, + { url = "https://files.pythonhosted.org/packages/57/e7/cfa2eb803ad52d74fbb5424a429b5be164e51d23f1d853e5e037173a5c48/orjson-3.11.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:d6d308dd578ae3658f62bb9eba54801533225823cd3248c902be1ebc79b5e014", size = 404218, upload-time = "2025-07-25T14:33:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/d5/21/bc703af5bc6e9c7e18dcf4404dcc4ec305ab9bb6c82d3aee5952c0c56abf/orjson-3.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c4aa13ca959ba6b15c0a98d3d204b850f9dc36c08c9ce422ffb024eb30d6e058", size = 146605, upload-time = "2025-07-25T14:33:08.55Z" }, + { url = "https://files.pythonhosted.org/packages/8f/fe/d26a0150534c4965a06f556aa68bf3c3b82999d5d7b0facd3af7b390c4af/orjson-3.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:be3d0653322abc9b68e5bcdaee6cfd58fcbe9973740ab222b87f4d687232ab1f", size = 135434, upload-time = "2025-07-25T14:33:09.967Z" }, + { url = "https://files.pythonhosted.org/packages/89/b6/1cb28365f08cbcffc464f8512320c6eb6db6a653f03d66de47ea3c19385f/orjson-3.11.1-cp313-cp313-win32.whl", hash = "sha256:4dd34e7e2518de8d7834268846f8cab7204364f427c56fb2251e098da86f5092", size = 136596, upload-time = "2025-07-25T14:33:11.333Z" }, + { url = "https://files.pythonhosted.org/packages/f9/35/7870d0d3ed843652676d84d8a6038791113eacc85237b673b925802826b8/orjson-3.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:d6895d32032b6362540e6d0694b19130bb4f2ad04694002dce7d8af588ca5f77", size = 131319, upload-time = "2025-07-25T14:33:12.614Z" }, + { url = "https://files.pythonhosted.org/packages/b7/3e/5bcd50fd865eb664d4edfdaaaff51e333593ceb5695a22c0d0a0d2b187ba/orjson-3.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:bb7c36d5d3570fcbb01d24fa447a21a7fe5a41141fd88e78f7994053cc4e28f4", size = 126613, upload-time = "2025-07-25T14:33:13.927Z" }, + { url = "https://files.pythonhosted.org/packages/61/d8/0a5cd31ed100b4e569e143cb0cddefc21f0bcb8ce284f44bca0bb0e10f3d/orjson-3.11.1-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7b71ef394327b3d0b39f6ea7ade2ecda2731a56c6a7cbf0d6a7301203b92a89b", size = 240819, upload-time = "2025-07-25T14:33:15.223Z" }, + { url = "https://files.pythonhosted.org/packages/b9/95/7eb2c76c92192ceca16bc81845ff100bbb93f568b4b94d914b6a4da47d61/orjson-3.11.1-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:77c0fe28ed659b62273995244ae2aa430e432c71f86e4573ab16caa2f2e3ca5e", size = 129218, upload-time = "2025-07-25T14:33:16.637Z" }, + { url = "https://files.pythonhosted.org/packages/da/84/e6b67f301b18adbbc346882f456bea44daebbd032ba725dbd7b741e3a7f1/orjson-3.11.1-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:1495692f1f1ba2467df429343388a0ed259382835922e124c0cfdd56b3d1f727", size = 132238, upload-time = "2025-07-25T14:33:17.934Z" }, + { url = "https://files.pythonhosted.org/packages/84/78/a45a86e29d9b2f391f9d00b22da51bc4b46b86b788fd42df2c5fcf3e8005/orjson-3.11.1-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:08c6a762fca63ca4dc04f66c48ea5d2428db55839fec996890e1bfaf057b658c", size = 130998, upload-time = "2025-07-25T14:33:19.282Z" }, + { url = "https://files.pythonhosted.org/packages/ea/8f/6eb3ee6760d93b2ce996a8529164ee1f5bafbdf64b74c7314b68db622b32/orjson-3.11.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9e26794fe3976810b2c01fda29bd9ac7c91a3c1284b29cc9a383989f7b614037", size = 130559, upload-time = "2025-07-25T14:33:20.589Z" }, + { url = "https://files.pythonhosted.org/packages/1b/78/9572ae94bdba6813917c9387e7834224c011ea6b4530ade07d718fd31598/orjson-3.11.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:4b4b4f8f0b1d3ef8dc73e55363a0ffe012a42f4e2f1a140bf559698dca39b3fa", size = 404231, upload-time = "2025-07-25T14:33:22.019Z" }, + { url = "https://files.pythonhosted.org/packages/1f/a3/68381ad0757e084927c5ee6cfdeab1c6c89405949ee493db557e60871c4c/orjson-3.11.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:848be553ea35aa89bfefbed2e27c8a41244c862956ab8ba00dc0b27e84fd58de", size = 146658, upload-time = "2025-07-25T14:33:23.675Z" }, + { url = "https://files.pythonhosted.org/packages/00/db/fac56acf77aab778296c3f541a3eec643266f28ecd71d6c0cba251e47655/orjson-3.11.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c964c29711a4b1df52f8d9966f015402a6cf87753a406c1c4405c407dd66fd45", size = 135443, upload-time = "2025-07-25T14:33:25.04Z" }, + { url = "https://files.pythonhosted.org/packages/76/b1/326fa4b87426197ead61c1eec2eeb3babc9eb33b480ac1f93894e40c8c08/orjson-3.11.1-cp314-cp314-win32.whl", hash = "sha256:33aada2e6b6bc9c540d396528b91e666cedb383740fee6e6a917f561b390ecb1", size = 136643, upload-time = "2025-07-25T14:33:26.449Z" }, + { url = "https://files.pythonhosted.org/packages/0f/8e/2987ae2109f3bfd39680f8a187d1bc09ad7f8fb019dcdc719b08c7242ade/orjson-3.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:68e10fd804e44e36188b9952543e3fa22f5aa8394da1b5283ca2b423735c06e8", size = 131324, upload-time = "2025-07-25T14:33:27.896Z" }, + { url = "https://files.pythonhosted.org/packages/21/5f/253e08e6974752b124fbf3a4de3ad53baa766b0cb4a333d47706d307e396/orjson-3.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:f3cf6c07f8b32127d836be8e1c55d4f34843f7df346536da768e9f73f22078a1", size = 126605, upload-time = "2025-07-25T14:33:29.244Z" }, +] + [[package]] name = "packaging" version = "24.2" @@ -1209,9 +1323,11 @@ wheels = [ [[package]] name = "plotfig" -version = "1.4.0" +version = "1.5.0" source = { editable = "." } dependencies = [ + { name = "kaleido" }, + { name = "loguru" }, { name = "matplotlib" }, { name = "mne-connectivity" }, { name = "nibabel" }, @@ -1233,12 +1349,14 @@ dev = [ [package.metadata] requires-dist = [ + { name = "kaleido", specifier = ">=1.0.0" }, + { name = "loguru", specifier = ">=0.7.3" }, { name = "matplotlib", specifier = ">=3.10.1" }, { name = "mne-connectivity", specifier = ">=0.7.0" }, { name = "nibabel", specifier = ">=5.3.2" }, { name = "numpy", specifier = ">=2.2.4" }, { name = "pandas", specifier = ">=2.2.3" }, - { name = "plotly", specifier = ">=6.0.1" }, + { name = "plotly", specifier = ">=6.1.1" }, { name = "scipy", specifier = ">=1.15.2" }, { name = "surfplot", git = "https://github.com/danjgale/surfplot.git?rev=9d59fa06c941b5ac2a8759b472f5db24883c8a48" }, ] @@ -1254,15 +1372,15 @@ dev = [ [[package]] name = "plotly" -version = "6.0.1" +version = "6.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "narwhals" }, { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c7/cc/e41b5f697ae403f0b50e47b7af2e36642a193085f553bf7cc1169362873a/plotly-6.0.1.tar.gz", hash = "sha256:dd8400229872b6e3c964b099be699f8d00c489a974f2cfccfad5e8240873366b", size = 8094643, upload-time = "2025-03-17T15:02:23.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/7c/f396bc817975252afbe7af102ce09cd12ac40a8e90b8699a857d1b15c8a3/plotly-6.1.1.tar.gz", hash = "sha256:84a4f3d36655f1328fa3155377c7e8a9533196697d5b79a4bc5e905bdd09a433", size = 7543694, upload-time = "2025-05-20T20:09:31.935Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/02/65/ad2bc85f7377f5cfba5d4466d5474423a3fb7f6a97fd807c06f92dd3e721/plotly-6.0.1-py3-none-any.whl", hash = "sha256:4714db20fea57a435692c548a4eb4fae454f7daddf15f8d8ba7e1045681d7768", size = 14805757, upload-time = "2025-03-17T15:02:18.73Z" }, + { url = "https://files.pythonhosted.org/packages/75/f3/f8cb7066f761e2530e1280889e3413769891e349fca35ee7290e4ace35f5/plotly-6.1.1-py3-none-any.whl", hash = "sha256:9cca7167406ebf7ff541422738402159ec3621a608ff7b3e2f025573a1c76225", size = 16118469, upload-time = "2025-05-20T20:09:26.196Z" }, ] [[package]] @@ -1605,6 +1723,54 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0a/c8/b3f566db71461cabd4b2d5b39bcc24a7e1c119535c8361f81426be39bb47/scipy-1.15.2-cp313-cp313t-win_amd64.whl", hash = "sha256:fe8a9eb875d430d81755472c5ba75e84acc980e4a8f6204d402849234d3017db", size = 40477705, upload-time = "2025-02-17T00:34:43.619Z" }, ] +[[package]] +name = "simplejson" +version = "3.20.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/92/51b417685abd96b31308b61b9acce7ec50d8e1de8fbc39a7fd4962c60689/simplejson-3.20.1.tar.gz", hash = "sha256:e64139b4ec4f1f24c142ff7dcafe55a22b811a74d86d66560c8815687143037d", size = 85591, upload-time = "2025-02-15T05:18:53.15Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/59/74bc90d1c051bc2432c96b34bd4e8036875ab58b4fcbe4d6a5a76985f853/simplejson-3.20.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:325b8c107253d3217e89d7b50c71015b5b31e2433e6c5bf38967b2f80630a8ca", size = 92132, upload-time = "2025-02-15T05:16:15.743Z" }, + { url = "https://files.pythonhosted.org/packages/71/c7/1970916e0c51794fff89f76da2f632aaf0b259b87753c88a8c409623d3e1/simplejson-3.20.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88a7baa8211089b9e58d78fbc1b0b322103f3f3d459ff16f03a36cece0d0fcf0", size = 74956, upload-time = "2025-02-15T05:16:17.062Z" }, + { url = "https://files.pythonhosted.org/packages/c8/0d/98cc5909180463f1d75fac7180de62d4cdb4e82c4fef276b9e591979372c/simplejson-3.20.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:299b1007b8101d50d95bc0db1bf5c38dc372e85b504cf77f596462083ee77e3f", size = 74772, upload-time = "2025-02-15T05:16:19.204Z" }, + { url = "https://files.pythonhosted.org/packages/e1/94/a30a5211a90d67725a3e8fcc1c788189f2ae2ed2b96b63ed15d0b7f5d6bb/simplejson-3.20.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ec618ed65caab48e81e3ed29586236a8e57daef792f1f3bb59504a7e98cd10", size = 143575, upload-time = "2025-02-15T05:16:21.337Z" }, + { url = "https://files.pythonhosted.org/packages/ee/08/cdb6821f1058eb5db46d252de69ff7e6c53f05f1bae6368fe20d5b51d37e/simplejson-3.20.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2cdead1d3197f0ff43373cf4730213420523ba48697743e135e26f3d179f38", size = 153241, upload-time = "2025-02-15T05:16:22.859Z" }, + { url = "https://files.pythonhosted.org/packages/4c/2d/ca3caeea0bdc5efc5503d5f57a2dfb56804898fb196dfada121323ee0ccb/simplejson-3.20.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3466d2839fdc83e1af42e07b90bc8ff361c4e8796cd66722a40ba14e458faddd", size = 141500, upload-time = "2025-02-15T05:16:25.068Z" }, + { url = "https://files.pythonhosted.org/packages/e1/33/d3e0779d5c58245e7370c98eb969275af6b7a4a5aec3b97cbf85f09ad328/simplejson-3.20.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d492ed8e92f3a9f9be829205f44b1d0a89af6582f0cf43e0d129fa477b93fe0c", size = 144757, upload-time = "2025-02-15T05:16:28.301Z" }, + { url = "https://files.pythonhosted.org/packages/54/53/2d93128bb55861b2fa36c5944f38da51a0bc6d83e513afc6f7838440dd15/simplejson-3.20.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f924b485537b640dc69434565463fd6fc0c68c65a8c6e01a823dd26c9983cf79", size = 144409, upload-time = "2025-02-15T05:16:29.687Z" }, + { url = "https://files.pythonhosted.org/packages/99/4c/dac310a98f897ad3435b4bdc836d92e78f09e38c5dbf28211ed21dc59fa2/simplejson-3.20.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e8eacf6a3491bf76ea91a8d46726368a6be0eb94993f60b8583550baae9439e", size = 146082, upload-time = "2025-02-15T05:16:31.064Z" }, + { url = "https://files.pythonhosted.org/packages/ee/22/d7ba958cfed39827335b82656b1c46f89678faecda9a7677b47e87b48ee6/simplejson-3.20.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:d34d04bf90b4cea7c22d8b19091633908f14a096caa301b24c2f3d85b5068fb8", size = 154339, upload-time = "2025-02-15T05:16:32.719Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c8/b072b741129406a7086a0799c6f5d13096231bf35fdd87a0cffa789687fc/simplejson-3.20.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:69dd28d4ce38390ea4aaf212902712c0fd1093dc4c1ff67e09687c3c3e15a749", size = 147915, upload-time = "2025-02-15T05:16:34.291Z" }, + { url = "https://files.pythonhosted.org/packages/6c/46/8347e61e9cf3db5342a42f7fd30a81b4f5cf85977f916852d7674a540907/simplejson-3.20.1-cp311-cp311-win32.whl", hash = "sha256:dfe7a9da5fd2a3499436cd350f31539e0a6ded5da6b5b3d422df016444d65e43", size = 73972, upload-time = "2025-02-15T05:16:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/01/85/b52f24859237b4e9d523d5655796d911ba3d46e242eb1959c45b6af5aedd/simplejson-3.20.1-cp311-cp311-win_amd64.whl", hash = "sha256:896a6c04d7861d507d800da7642479c3547060bf97419d9ef73d98ced8258766", size = 75595, upload-time = "2025-02-15T05:16:36.957Z" }, + { url = "https://files.pythonhosted.org/packages/8d/eb/34c16a1ac9ba265d024dc977ad84e1659d931c0a700967c3e59a98ed7514/simplejson-3.20.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f31c4a3a7ab18467ee73a27f3e59158255d1520f3aad74315edde7a940f1be23", size = 93100, upload-time = "2025-02-15T05:16:38.801Z" }, + { url = "https://files.pythonhosted.org/packages/41/fc/2c2c007d135894971e6814e7c0806936e5bade28f8db4dd7e2a58b50debd/simplejson-3.20.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:884e6183d16b725e113b83a6fc0230152ab6627d4d36cb05c89c2c5bccfa7bc6", size = 75464, upload-time = "2025-02-15T05:16:40.905Z" }, + { url = "https://files.pythonhosted.org/packages/0f/05/2b5ecb33b776c34bb5cace5de5d7669f9b60e3ca13c113037b2ca86edfbd/simplejson-3.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03d7a426e416fe0d3337115f04164cd9427eb4256e843a6b8751cacf70abc832", size = 75112, upload-time = "2025-02-15T05:16:42.246Z" }, + { url = "https://files.pythonhosted.org/packages/fe/36/1f3609a2792f06cd4b71030485f78e91eb09cfd57bebf3116bf2980a8bac/simplejson-3.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:000602141d0bddfcff60ea6a6e97d5e10c9db6b17fd2d6c66199fa481b6214bb", size = 150182, upload-time = "2025-02-15T05:16:43.557Z" }, + { url = "https://files.pythonhosted.org/packages/2f/b0/053fbda38b8b602a77a4f7829def1b4f316cd8deb5440a6d3ee90790d2a4/simplejson-3.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af8377a8af78226e82e3a4349efdde59ffa421ae88be67e18cef915e4023a595", size = 158363, upload-time = "2025-02-15T05:16:45.748Z" }, + { url = "https://files.pythonhosted.org/packages/d1/4b/2eb84ae867539a80822e92f9be4a7200dffba609275faf99b24141839110/simplejson-3.20.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15c7de4c88ab2fbcb8781a3b982ef883696736134e20b1210bca43fb42ff1acf", size = 148415, upload-time = "2025-02-15T05:16:47.861Z" }, + { url = "https://files.pythonhosted.org/packages/e0/bd/400b0bd372a5666addf2540c7358bfc3841b9ce5cdbc5cc4ad2f61627ad8/simplejson-3.20.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:455a882ff3f97d810709f7b620007d4e0aca8da71d06fc5c18ba11daf1c4df49", size = 152213, upload-time = "2025-02-15T05:16:49.25Z" }, + { url = "https://files.pythonhosted.org/packages/50/12/143f447bf6a827ee9472693768dc1a5eb96154f8feb140a88ce6973a3cfa/simplejson-3.20.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fc0f523ce923e7f38eb67804bc80e0a028c76d7868500aa3f59225574b5d0453", size = 150048, upload-time = "2025-02-15T05:16:51.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ea/dd9b3e8e8ed710a66f24a22c16a907c9b539b6f5f45fd8586bd5c231444e/simplejson-3.20.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76461ec929282dde4a08061071a47281ad939d0202dc4e63cdd135844e162fbc", size = 151668, upload-time = "2025-02-15T05:16:53Z" }, + { url = "https://files.pythonhosted.org/packages/99/af/ee52a8045426a0c5b89d755a5a70cc821815ef3c333b56fbcad33c4435c0/simplejson-3.20.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ab19c2da8c043607bde4d4ef3a6b633e668a7d2e3d56f40a476a74c5ea71949f", size = 158840, upload-time = "2025-02-15T05:16:54.851Z" }, + { url = "https://files.pythonhosted.org/packages/68/db/ab32869acea6b5de7d75fa0dac07a112ded795d41eaa7e66c7813b17be95/simplejson-3.20.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2578bedaedf6294415197b267d4ef678fea336dd78ee2a6d2f4b028e9d07be3", size = 154212, upload-time = "2025-02-15T05:16:56.318Z" }, + { url = "https://files.pythonhosted.org/packages/fa/7a/e3132d454977d75a3bf9a6d541d730f76462ebf42a96fea2621498166f41/simplejson-3.20.1-cp312-cp312-win32.whl", hash = "sha256:339f407373325a36b7fd744b688ba5bae0666b5d340ec6d98aebc3014bf3d8ea", size = 74101, upload-time = "2025-02-15T05:16:57.746Z" }, + { url = "https://files.pythonhosted.org/packages/bc/5d/4e243e937fa3560107c69f6f7c2eed8589163f5ed14324e864871daa2dd9/simplejson-3.20.1-cp312-cp312-win_amd64.whl", hash = "sha256:627d4486a1ea7edf1f66bb044ace1ce6b4c1698acd1b05353c97ba4864ea2e17", size = 75736, upload-time = "2025-02-15T05:16:59.017Z" }, + { url = "https://files.pythonhosted.org/packages/c4/03/0f453a27877cb5a5fff16a975925f4119102cc8552f52536b9a98ef0431e/simplejson-3.20.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:71e849e7ceb2178344998cbe5ade101f1b329460243c79c27fbfc51c0447a7c3", size = 93109, upload-time = "2025-02-15T05:17:00.377Z" }, + { url = "https://files.pythonhosted.org/packages/74/1f/a729f4026850cabeaff23e134646c3f455e86925d2533463420635ae54de/simplejson-3.20.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b63fdbab29dc3868d6f009a59797cefaba315fd43cd32ddd998ee1da28e50e29", size = 75475, upload-time = "2025-02-15T05:17:02.544Z" }, + { url = "https://files.pythonhosted.org/packages/e2/14/50a2713fee8ff1f8d655b1a14f4a0f1c0c7246768a1b3b3d12964a4ed5aa/simplejson-3.20.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1190f9a3ce644fd50ec277ac4a98c0517f532cfebdcc4bd975c0979a9f05e1fb", size = 75112, upload-time = "2025-02-15T05:17:03.875Z" }, + { url = "https://files.pythonhosted.org/packages/45/86/ea9835abb646755140e2d482edc9bc1e91997ed19a59fd77ae4c6a0facea/simplejson-3.20.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1336ba7bcb722ad487cd265701ff0583c0bb6de638364ca947bb84ecc0015d1", size = 150245, upload-time = "2025-02-15T05:17:06.899Z" }, + { url = "https://files.pythonhosted.org/packages/12/b4/53084809faede45da829fe571c65fbda8479d2a5b9c633f46b74124d56f5/simplejson-3.20.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e975aac6a5acd8b510eba58d5591e10a03e3d16c1cf8a8624ca177491f7230f0", size = 158465, upload-time = "2025-02-15T05:17:08.707Z" }, + { url = "https://files.pythonhosted.org/packages/a9/7d/d56579468d1660b3841e1f21c14490d103e33cf911886b22652d6e9683ec/simplejson-3.20.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a6dd11ee282937ad749da6f3b8d87952ad585b26e5edfa10da3ae2536c73078", size = 148514, upload-time = "2025-02-15T05:17:11.323Z" }, + { url = "https://files.pythonhosted.org/packages/19/e3/874b1cca3d3897b486d3afdccc475eb3a09815bf1015b01cf7fcb52a55f0/simplejson-3.20.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab980fcc446ab87ea0879edad41a5c28f2d86020014eb035cf5161e8de4474c6", size = 152262, upload-time = "2025-02-15T05:17:13.543Z" }, + { url = "https://files.pythonhosted.org/packages/32/84/f0fdb3625292d945c2bd13a814584603aebdb38cfbe5fe9be6b46fe598c4/simplejson-3.20.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f5aee2a4cb6b146bd17333ac623610f069f34e8f31d2f4f0c1a2186e50c594f0", size = 150164, upload-time = "2025-02-15T05:17:15.021Z" }, + { url = "https://files.pythonhosted.org/packages/95/51/6d625247224f01eaaeabace9aec75ac5603a42f8ebcce02c486fbda8b428/simplejson-3.20.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:652d8eecbb9a3b6461b21ec7cf11fd0acbab144e45e600c817ecf18e4580b99e", size = 151795, upload-time = "2025-02-15T05:17:16.542Z" }, + { url = "https://files.pythonhosted.org/packages/7f/d9/bb921df6b35be8412f519e58e86d1060fddf3ad401b783e4862e0a74c4c1/simplejson-3.20.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8c09948f1a486a89251ee3a67c9f8c969b379f6ffff1a6064b41fea3bce0a112", size = 159027, upload-time = "2025-02-15T05:17:18.083Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/5950605e4ad023a6621cf4c931b29fd3d2a9c1f36be937230bfc83d7271d/simplejson-3.20.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cbbd7b215ad4fc6f058b5dd4c26ee5c59f72e031dfda3ac183d7968a99e4ca3a", size = 154380, upload-time = "2025-02-15T05:17:20.334Z" }, + { url = "https://files.pythonhosted.org/packages/66/ad/b74149557c5ec1e4e4d55758bda426f5d2ec0123cd01a53ae63b8de51fa3/simplejson-3.20.1-cp313-cp313-win32.whl", hash = "sha256:ae81e482476eaa088ef9d0120ae5345de924f23962c0c1e20abbdff597631f87", size = 74102, upload-time = "2025-02-15T05:17:22.475Z" }, + { url = "https://files.pythonhosted.org/packages/db/a9/25282fdd24493e1022f30b7f5cdf804255c007218b2bfaa655bd7ad34b2d/simplejson-3.20.1-cp313-cp313-win_amd64.whl", hash = "sha256:1b9fd15853b90aec3b1739f4471efbf1ac05066a2c7041bf8db821bb73cd2ddc", size = 75736, upload-time = "2025-02-15T05:17:24.122Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/00f02a0a921556dd5a6db1ef2926a1bc7a8bbbfb1c49cfed68a275b8ab2b/simplejson-3.20.1-py3-none-any.whl", hash = "sha256:8a6c1bbac39fa4a79f83cbf1df6ccd8ff7069582a9fd8db1e52cea073bc2c697", size = 57121, upload-time = "2025-02-15T05:18:51.243Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -1780,6 +1946,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166, upload-time = "2024-01-06T02:10:55.763Z" }, ] +[[package]] +name = "win32-setctime" +version = "1.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/8f/705086c9d734d3b663af0e9bb3d4de6578d08f46b1b101c2442fd9aecaa2/win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0", size = 4867, upload-time = "2024-12-07T15:28:28.314Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/07/c6fe3ad3e685340704d314d765b7912993bcb8dc198f0e7a89382d37974b/win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390", size = 4083, upload-time = "2024-12-07T15:28:26.465Z" }, +] + [[package]] name = "xarray" version = "2025.3.0" From 7a372ebd4eb628d55ab2f8d7255269057c52af7c Mon Sep 17 00:00:00 2001 From: Ricardo_T8 Date: Mon, 11 Aug 2025 22:58:36 +0800 Subject: [PATCH 6/6] fix(connec): fix issue with line_color display under color scale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When `scale_method` is set to `color` or `width_color` and `line_color` is specified, the color was not applied correctly. This fix ensures the color is displayed as intended. --- 修复(connec): 修复 color scale 下 line_color 显示异常问题 当 `scale_method` 设置为 `color` 或 `width_color` 且同时指定了 `line_color` 时, 颜色未能正确应用。该问题已修复,确保颜色显示符合预期。 --- src/plotfig/brain_connection.py | 237 +++++++++++++++++++++----------- 1 file changed, 156 insertions(+), 81 deletions(-) diff --git a/src/plotfig/brain_connection.py b/src/plotfig/brain_connection.py index bd3f39f..90c69a5 100644 --- a/src/plotfig/brain_connection.py +++ b/src/plotfig/brain_connection.py @@ -1,16 +1,18 @@ -import os -import os.path as op import datetime +from pathlib import Path +from typing import Literal +from collections.abc import Sequence + import numpy as np +import numpy.typing as npt import nibabel as nib -from scipy.ndimage import center_of_mass import plotly.graph_objects as go import plotly.io as pio -import matplotlib.colors as mcolors -import matplotlib.cm as cm +from matplotlib.colors import LinearSegmentedColormap, to_hex +from scipy.ndimage import center_of_mass from tqdm import tqdm -from pathlib import Path -import numpy.typing as npt + +from loguru import logger Num = int | float @@ -19,15 +21,30 @@ "save_brain_connection_frames", ] -def _load_surface(file: str | Path): - '''加载 .surf.gii 文件,提取顶点和面''' - gii = nib.load(file) - vertices = gii.darrays[0].data - faces = gii.darrays[1].data - return vertices, faces + +def _validate_connectome(connectome): + """检测数据是否为有效的对称且对角线为0的连接矩阵""" + # 1. 判断是否二维方阵 + if connectome.ndim != 2 or connectome.shape[0] != connectome.shape[1]: + raise ValueError("connectome 必须是二维方阵") + # 2. 判断是否对称矩阵 + if not np.allclose(connectome, connectome.T, atol=1e-8): + raise ValueError("connectome 必须是对称矩阵") + # 3. 判断对角线是否全为0 + if not np.allclose(np.diag(connectome), 0, atol=1e-8): + raise ValueError("connectome 对角线必须全部为0") + # 4. 判断是否全0矩阵,警告但不抛异常 + if np.allclose(connectome, 0, atol=1e-8): + logger.warning("connectome 矩阵所有元素均为0,可能没有有效连接数据") + + +def _load_surface(file): + """加载 .surf.gii 文件,提取顶点和面""" + return nib.load(file).darrays[0].data, nib.load(file).darrays[1].data + def _create_mesh(vertices, faces, name): - ''' 创建 plotly 的 Mesh3d 图层''' + """创建 plotly 的 Mesh3d 图层""" return go.Mesh3d( x=vertices[:, 0], y=vertices[:, 1], @@ -42,29 +59,33 @@ def _create_mesh(vertices, faces, name): name=name, ) + def _get_node_indices(connectome, show_all_nodes): - ''' 判断哪些节点需要显示''' + """判断是否显示无任何连接的节点""" if not show_all_nodes: row_is_zero = np.any(connectome != 0, axis=1) return np.where(row_is_zero)[0] else: return np.arange(connectome.shape[0]) -def _get_centroids_real(niigz_file: str | Path): - '''读取 NIfTI 图集并计算ROI质心''' - img = nib.load(niigz_file) - atlas_data = img.get_fdata() - affine = img.affine +def _get_centroids_real(niigz_file): + """读取 NIfTI 图集并计算ROI质心""" + atlas_data = nib.load(niigz_file).get_fdata() + affine = nib.load(niigz_file).affine roi_labels = np.unique(atlas_data) roi_labels = roi_labels[roi_labels != 0] - - centroids_voxel = [center_of_mass((atlas_data == label).astype(int)) for label in roi_labels] + centroids_voxel = [ + center_of_mass((atlas_data == label).astype(int)) for label in roi_labels + ] centroids_real = [np.dot(affine, [*coord, 1])[:3] for coord in centroids_voxel] return np.array(centroids_real) -def _add_nodes_to_fig(fig, centroids_real, node_indices, nodes_name, nodes_size, nodes_color): - '''将节点(球)添加到图中''' + +def _add_nodes_to_fig( + fig, centroids_real, node_indices, nodes_name, nodes_size, nodes_color +): + """将节点(球)添加到图中""" for i in node_indices: fig.add_trace( go.Scatter3d( @@ -74,7 +95,7 @@ def _add_nodes_to_fig(fig, centroids_real, node_indices, nodes_name, nodes_size, mode="markers+text", marker={ "size": nodes_size[i], - "color": nodes_color, + "color": nodes_color[i], "colorscale": "Rainbow", "opacity": 0.8, "line": {"width": 2, "color": "black"}, @@ -85,8 +106,25 @@ def _add_nodes_to_fig(fig, centroids_real, node_indices, nodes_name, nodes_size, ) ) -def _add_edges_to_fig(fig, connectome, centroids_real, nodes_name, scale_method, line_width, line_color="#ff0000"): - '''将连接线绘制到图中''' + +def _add_edges_to_fig( + fig, + connectome, + centroids_real, + nodes_name, + scale_method, + line_width, + line_color, +): + """将连接线绘制到图中""" + + def _get_gradient_color(value, color): + """获取渐变色""" + assert 0 <= value <= 1, "value 必须在0和1之间" + cmap = LinearSegmentedColormap.from_list("grad_cmap", ["white", color]) + rgba = cmap(value) + return to_hex(rgba[:3]) + nodes_num = connectome.shape[0] if np.all(connectome == 0): return @@ -100,24 +138,28 @@ def _add_edges_to_fig(fig, connectome, centroids_real, nodes_name, scale_method, continue match scale_method: + case "": + each_line_color = line_color if value > 0 else "#0000ff" + each_line_width = line_width case "width": each_line_color = line_color if value > 0 else "#0000ff" each_line_width = abs(value / max_strength) * line_width case "color": norm_value = value / max_strength - each_line_color = mcolors.to_hex(cm.bwr(mcolors.Normalize(vmin=-1, vmax=1)(norm_value))) + each_line_color = _get_gradient_color(norm_value, line_color) each_line_width = line_width case "width_color" | "color_width": norm_value = value / max_strength each_line_width = abs(norm_value) * line_width - each_line_color = mcolors.to_hex(cm.bwr(mcolors.Normalize(vmin=-1, vmax=1)(norm_value))) - case "": - each_line_color = "#ff0000" if value > 0 else "#0000ff" - each_line_width = line_width + each_line_color = _get_gradient_color(norm_value, line_color) case _: - raise ValueError("scale_method must be '', 'width', 'color', 'width_color', or 'color_width'") + raise ValueError( + "scale_method 必须为 '', 'width', 'color', 'width_color', or 'color_width'中的一种" + ) - connection_line = np.array([centroids_real[i], centroids_real[j], [None] * 3]) + connection_line = np.array( + [centroids_real[i], centroids_real[j], [None] * 3] + ) fig.add_trace( go.Scatter3d( x=connection_line[:, 0], @@ -125,16 +167,21 @@ def _add_edges_to_fig(fig, connectome, centroids_real, nodes_name, scale_method, z=connection_line[:, 2], mode="lines", line={"color": each_line_color, "width": each_line_width}, - hoverinfo="none", + hoverinfo="name", name=f"{nodes_name[i]}-{nodes_name[j]}", ) ) + def _finalize_figure(fig): - '''调整图形布局与视觉样式''' + """调整图形布局与视觉样式""" fig.update_traces( selector={"mode": "markers"}, - marker={"size": 10, "colorscale": "Viridis", "line": {"width": 3, "color": "black"}}, + marker={ + "size": 10, + "colorscale": "Viridis", + "line": {"width": 3, "color": "black"}, + }, ) fig.update_layout( title="Connection", @@ -147,48 +194,75 @@ def _finalize_figure(fig): margin={"l": 0, "r": 0, "b": 0, "t": 30}, ) + def plot_brain_connection_figure( connectome: npt.NDArray, lh_surfgii_file: str | Path, rh_surfgii_file: str | Path, niigz_file: str | Path, + output_file: str | Path | None = None, + show_all_nodes: bool = False, + nodes_size: Sequence[Num] | npt.NDArray | None = None, nodes_name: list[str] | None = None, - nodes_size=None, nodes_color: list[str] | None = None, - output_file: str | Path | None = None, - scale_method: str = "", + scale_method: Literal["", "width", "color", "width_color", "color_width"] = "", line_width: Num = 10, - show_all_nodes: bool = False, - line_color: str = "#ff0000", -) -> None: + line_color: str = "red", +) -> go.Figure: """绘制大脑连接图,保存在指定的html文件中 Args: - connectome (npt.NDArray): 连接矩阵 - lh_surfgii_file (str | Path): 左脑surf.gii文件. - rh_surfgii_file (str | Path): 右脑surf.gii文件. - niigz_file (str | Path): 图集nii文件. - nodes_name (List[str] | None, optional): 节点名称. Defaults to None. - nodes_size (Num, optional): 节点大小. Defaults to 5. - nodes_color (List[str] | None, optional): 节点颜色. Defaults to None. - output_file (str | Path | None, optional): 保存的完整路径及文件名. Defaults to None. - scale_method (str, optional): 连接scale的形式. Defaults to "". - line_width (Num, optional): 连接粗细. Defaults to 10. - - Raises: - ValueError: 参数参数取值不合法时抛出. + connectome (npt.NDArray): + 大脑连接矩阵,形状为 (n, n),其中 n 是脑区数量。 + 矩阵中的值表示脑区之间的连接强度,正值表示正相关连接,负值表示负相关连接,0表示无连接。 + lh_surfgii_file (str | Path): + 左半脑表面几何文件路径 (.surf.gii 格式),用于绘制左半脑表面 + rh_surfgii_file (str | Path): + 右半脑表面几何文件路径 (.surf.gii 格式),用于绘制右半脑表面 + niigz_file (str | Path): + NIfTI格式的脑区图谱文件路径 (.nii.gz 格式),用于定位脑区节点的三维坐标 + output_file (str | Path | None, optional): + 输出HTML文件路径。如果未指定,则使用当前时间戳生成文件名。默认为None + show_all_nodes (bool, optional): + 是否显示所有脑区节点。如果为False,则只显示有连接的节点。默认为False + nodes_size (Sequence[Num] | npt.NDArray | None, optional): + 每个节点的大小,长度应与脑区数量一致。默认为None,即所有节点大小为5 + nodes_name (list[str] | None, optional): + 每个节点的名称标签,长度应与脑区数量一致。默认为None,即不显示名称 + nodes_color (list[str] | None, optional): + 每个节点的颜色,长度应与脑区数量一致。默认为None,即所有节点为白色 + scale_method (Literal["", "width", "color", "width_color", "color_width"], optional): + 连接线的缩放方法: + - "" : 所有连接线宽度和颜色固定 + - "width" : 根据连接强度调整线宽,正连接为红色,负连接为蓝色 + - "color" : 根据连接强度调整颜色(使用蓝白红颜色映射),线宽固定 + - "width_color" or "color_width" : 同时根据连接强度调整线宽和颜色 + 默认为 "" + line_width (Num, optional): + 连接线的基本宽度。当scale_method包含"width"时,此值作为最大宽度参考。默认为10 + line_color (str, optional): + 连接线的基本颜色。当scale_method不包含"color"时生效。默认为"#ff0000"(红色) + + Returns: + go.Figure: Plotly图形对象,包含绘制的大脑连接图 """ + _validate_connectome(connectome) + + if np.any(connectome < 0): + logger.warning( + "由于 connectome 存在负值,连线颜色无法自定义,只能正值显示红色,负值显示蓝色" + ) + line_color = "#ff0000" + nodes_num = connectome.shape[0] - if nodes_name is None: - nodes_name = [f"ROI-{i}" for i in range(nodes_num)] - if nodes_color is None: - nodes_color = ["white"] * nodes_num - if nodes_size is None: - nodes_size = [5] * nodes_num + nodes_name = nodes_name or [""] * nodes_num + nodes_color = nodes_color or ["white"] * nodes_num + nodes_size = nodes_size or [5] * nodes_num + if output_file is None: timestamp = datetime.datetime.now().strftime("%Y-%m-%d_%H-%M-%S") - output_file = op.join(f"./{timestamp}.html") - print(f"未指定保存路径,默认保存为:{output_file}") + output_file = Path(f"{timestamp}.html") + logger.info(f"未指定保存路径,默认保存在当前文件夹下的{output_file}中。") node_indices = _get_node_indices(connectome, show_all_nodes) vertices_L, faces_L = _load_surface(lh_surfgii_file) @@ -196,11 +270,22 @@ def plot_brain_connection_figure( mesh_L = _create_mesh(vertices_L, faces_L, "Left Hemisphere") mesh_R = _create_mesh(vertices_R, faces_R, "Right Hemisphere") + fig = go.Figure(data=[mesh_L, mesh_R]) centroids_real = _get_centroids_real(niigz_file) - _add_nodes_to_fig(fig, centroids_real, node_indices, nodes_name, nodes_size, nodes_color) - _add_edges_to_fig(fig, connectome, centroids_real, nodes_name, scale_method, line_width, line_color) + _add_nodes_to_fig( + fig, centroids_real, node_indices, nodes_name, nodes_size, nodes_color + ) + _add_edges_to_fig( + fig, + connectome, + centroids_real, + nodes_name, + scale_method, + line_width, + line_color, + ) _finalize_figure(fig) fig.write_html(output_file) @@ -208,19 +293,17 @@ def plot_brain_connection_figure( def save_brain_connection_frames( - fig: go.Figure, - output_dir: str, - n_frames: int = 36 + fig: go.Figure, output_dir: str | Path, n_frames: int = 36 ) -> None: """ - 生成不同角度的静态图片帧,用于制作旋转大脑连接图的 GIF 或视频。 + 生成不同角度的静态图片帧,可用于制作旋转大脑连接图的 GIF。 Args: fig (go.Figure): Plotly 的 Figure 对象,包含大脑表面和连接图。 - output_dir (str): 图片保存的文件夹路径,会自动创建文件夹。 + output_dir (str): 图片保存的文件夹路径,若文件夹不存在则自动创建。 n_frames (int, optional): 旋转帧的数量。默认 36,即每 10 度一帧。 """ - os.makedirs(output_dir, exist_ok=True) + Path(output_dir).mkdir(parents=True, exist_ok=True) angles = np.linspace(0, 360, n_frames, endpoint=False) for i, angle in tqdm(enumerate(angles), total=len(angles)): camera = dict( @@ -230,12 +313,4 @@ def save_brain_connection_frames( ) fig.update_layout(scene_camera=camera) pio.write_image(fig, f"{output_dir}/frame_{i:03d}.png", width=800, height=800) - print(f"保存了 {n_frames} 张图片在 {output_dir}") - - -def main(): - pass - - -if __name__ == "__main__": - main() \ No newline at end of file + logger.info(f"保存了 {n_frames} 张图片在 {output_dir}")