ccf-habitat 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pan Ziqi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,25 @@
1
+ ccf-habitat (CCF) — Third-Party Notices
2
+ =========================================
3
+
4
+ This package integrates the following third-party open-source component:
5
+
6
+ maskslic
7
+ --------
8
+ - Component: masked SLIC supervoxel generation (used by ccf.maskslic)
9
+ - Author/Copyright: Copyright (C) 2016-2019, Benjamin Irving
10
+ - License: BSD-3-Clause (redistribution and use in source and binary forms,
11
+ with or without modification, are permitted provided that the conditions of
12
+ the BSD-3-Clause license are met; see the license file distributed with the
13
+ maskslic package, e.g. maskslic-0.3.4.dist-info/LICENSE.txt)
14
+ - Source: https://pypi.org/project/maskslic/ | https://www.birving.com
15
+ - Upstream usage in this project: the maskslic scripts integrated from the
16
+ BESMNN biology-analysis kernel-validation package (s01_slic stage),
17
+ parameter conventions preserved as-is (human single-channel
18
+ compactness=10; BraTS19 multi-channel compactness=0.5).
19
+
20
+ The above notice is provided to comply with the BSD-3-Clause attribution
21
+ requirement. No modification is made to the maskslic algorithm itself;
22
+ ccf.maskslic only performs I/O, geometry preservation and parallel
23
+ orchestration around it.
24
+
25
+ ccf-habitat itself is distributed under the MIT License (see LICENSE).
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccf-habitat
3
+ Version: 0.3.0
4
+ Summary: Consistency-constrained framework for CT habitat clustering (BESP2/P2 algorithm package)
5
+ Author: Pan Ziqi
6
+ License: MIT
7
+ Requires-Python: >=3.9
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ License-File: NOTICE
11
+ Requires-Dist: numpy>=1.21
12
+ Requires-Dist: scipy>=1.7
13
+ Requires-Dist: pandas>=1.3
14
+ Requires-Dist: scikit-learn==1.6.1
15
+ Requires-Dist: SimpleITK>=2.0
16
+ Requires-Dist: PyRadiomics>=3.0
17
+ Requires-Dist: tqdm>=4.60
18
+ Requires-Dist: joblib>=1.0
19
+ Requires-Dist: psutil>=5.8
20
+ Requires-Dist: matplotlib>=3.5
21
+ Requires-Dist: pyyaml>=6.0
22
+ Requires-Dist: maskslic>=0.3
23
+ Dynamic: license-file
24
+
25
+ # ccf-habitat
26
+
27
+ 一致性约束的 CT 影像栖息地(habitat)亚区分割工具包。
28
+ 算法内核为 **BESP2**(BESMNN 谱聚类扩展版:超节点特征 → 迭代扩展 → 全局图 → 谱聚类)与
29
+ **P2**(五个冻结人源参考预测器,纯 NumPy),用于肺癌等肿瘤 CT 的亚区域无监督分割与跨物种判别。
30
+
31
+ 本包把原先"一键脚本 + 数据同包"的调用方式重构为标准 Python 包:
32
+
33
+ - `pip install -e .` 即可安装,提供 `ccf-train` / `ccf-predict` / `ccf-example` / `ccf-list-data` / `ccf-slic` 命令;
34
+ - 算法代码原样归档(`ccf/engine_besp2.py`、`ccf/selfsp.py`、`ccf/predictors.py`),**不重写逻辑**,
35
+ 因此结果与既有权威输出一致;
36
+ - **数据不捆绑**:约 35 GB 的影像/中间数据通过 `data_root` 指向外部目录(默认自动探测本机
37
+ `D:\Software-develop\BESMNN\biology-analysis\02_input`),GitHub 上只保留代码与配置。
38
+
39
+ ## 安装
40
+
41
+ ```bash
42
+ python -m pip install -e .
43
+ # 或直接以包根为工作目录:export PYTHONPATH=<包根>(Windows: $env:PYTHONPATH="<包根>")
44
+ ```
45
+
46
+ 依赖:numpy / scipy / pandas / scikit-learn==1.6.1 / SimpleITK / PyRadiomics / tqdm / joblib / psutil / matplotlib / pyyaml / maskslic。
47
+
48
+ ## 数据策略
49
+
50
+ | 内容 | 位置 |
51
+ |---|---|
52
+ | 代码 / 配置 / 示例 | 本仓库(GitHub 可公开) |
53
+ | 人源 539 例 CT/ROI/超体素 mask | `02_input/human/besp2_source/shared/{ct,sub,merged_masks}`(外部) |
54
+ | 人源 BESP2 工作套(split、模型、日志) | `02_input/human/besp2_source/BS{3,6,10}`(外部) |
55
+ | 鼠源(6 例)与病理基准 | `02_input/mouse*`、`02_input/mouse_pathology`(外部) |
56
+
57
+ `data_root` 解析优先级:命令行 `--data-root` / yaml 配置 > 环境变量 `CCF_DATA_ROOT` > 本机默认路径。
58
+
59
+ ## 用法
60
+
61
+ ### 命令行
62
+
63
+ ```bash
64
+ # 查看数据布局探测结果
65
+ ccf-list-data
66
+
67
+ # 冒烟:前 3 例跑通完整训练链(CPU 可跑,证明安装与调用链路)
68
+ ccf-train -k 3 -o ./out --subset 3
69
+
70
+ # 完整:全 539 例 K=6 训练+测试(重型,需 GPU 与较长时间)
71
+ ccf-train -k 6 -o ./out --mode train_test
72
+
73
+ # 判别(P2 预测器;reference 通过 CCF_DATA_REFERENCE 预置)
74
+ ccf-predict -m P2 -i embedding.npy -o ./out
75
+ ```
76
+
77
+ ### Python API
78
+
79
+ ```python
80
+ from ccf import CCFHabitatModel, CCFConfig
81
+
82
+ model = CCFHabitatModel(CCFConfig(k=3, output_dir="./out"))
83
+ fit = model.fit(subset=3) # 冒烟;去掉 subset=3 为全量
84
+ print(fit["train_data_path"])
85
+
86
+ from ccf.predictors import FrozenReference
87
+ ref = FrozenReference.from_model(spectral_model, train_index_mapping)
88
+ res = model.predict("P2", ref, embedding=mouse_embedding) # labels/scores/margin
89
+ ```
90
+
91
+ ## 与既有权威流程的关系(结果一致性)
92
+
93
+ | 组件 | 来源(biology-analysis 核验包) | 本包 |
94
+ |---|---|---|
95
+ | BESP2 主算法 | `01_code/s02_besp2_human/1.BESMNNCORRECT4.5.py` | `ccf/engine_besp2.py`(原样复制,改相对导入) |
96
+ | 谱聚类 | `01_code/s02_besp2_human/selfsp.py` | `ccf/selfsp.py` |
97
+ | P2 预测器 | `01_code/s02_besp2_human/new_predictors.py` | `ccf/predictors.py`(原样复制) |
98
+ | PyRadiomics 配置 | — | `ccf/configs/default_radiomics.yaml` |
99
+ | maskslic 超体素 | `01_code/s01_slic/0.Maskslic20-多线程_human.py`、`1.Maskslic100brats-多线程.py` | `ccf/maskslic.py`(参数口径原样保留) |
100
+
101
+ ## 超体素生成(ccf-slic,集成 maskslic)
102
+
103
+ 上游 `s01_slic` 阶段用 **PyPI maskslic 包**(Benjamin Irving,BSD-3-Clause,scikit-image SLIC 的
104
+ mask 约束变体)生成"仅肿瘤区域内"的超体素;本包把两套上游脚本的参数口径原样集成进
105
+ `ccf/maskslic.py`,只做 I/O、几何信息保留与并行编排,不修改算法。第三方许可声明见 `NOTICE`。
106
+
107
+ ```bash
108
+ # 单例:单模态(人源口径 compactness=10)或多模态(BraTS 口径 compactness=0.5 需显式指定)
109
+ ccf-slic --img ct.nii.gz --roi roi.nii.gz --out subregion_label.nii.gz
110
+ ccf-slic --img t1.nii.gz,t1ce.nii.gz,t2.nii.gz,flair.nii.gz --roi seg.nii.gz \
111
+ --out label.nii.gz --compactness 0.5
112
+
113
+ # 批量:img_dir 与 roi_dir 按文件名基线匹配,并行生成(对齐上游 execute_parallel)
114
+ ccf-slic --img-dir ./fuse --roi-dir ./roi2 --out-dir ./labels --workers 8
115
+ ```
116
+
117
+ ```python
118
+ from ccf.maskslic import supervoxels, batch_supervoxels
119
+ supervoxels("ct.nii.gz", "roi.nii.gz", "subregion_label.nii.gz") # 单模态
120
+ supervoxels(["t1.nii.gz", "flair.nii.gz"], "seg.nii.gz", "out.nii.gz",
121
+ compactness=0.5) # 多通道
122
+ ```
123
+
124
+ 既有 9 步全链核验(输入完整性自检、NMF 复现、表 1、三算法病理 KW、RNAseq 方向一致性、
125
+ 多重校正、表 5 二项检验)仍由 `D:\Software-develop\BESMNN\biology-analysis` 包 `run_all.py` 提供;
126
+ 本包负责把 BESP2/P2 调用标准化,供第三方复现与二次开发。
127
+
128
+ ## 目录结构
129
+
130
+ ```
131
+ ccf/
132
+ ├── pyproject.toml # 可安装包定义 + 命令行入口
133
+ ├── ccf/
134
+ │ ├── config.py # 数据路径/参数配置(yaml / 环境变量 / 默认值)
135
+ │ ├── dataset.py # 02_input 布局解析
136
+ │ ├── engine_besp2.py # BESP2 主算法(上游原样归档)
137
+ │ ├── selfsp.py # 谱聚类修改版(sklearn 1.6.1 扩展)
138
+ │ ├── predictors.py # P2 冻结人源参考预测器(P1-P5)
139
+ │ ├── model.py # CCFHabitatModel 高层封装(fit/predict)
140
+ │ ├── maskslic.py # maskslic 超体素生成(上游 s01_slic 集成)
141
+ │ ├── output.py # 报告输出
142
+ │ ├── cli.py # ccf-* 命令
143
+ │ └── configs/ # 默认 yaml
144
+ └── example/ # 小样本冒烟示例 + 数据指引
145
+ ```
146
+
147
+ ## 说明
148
+
149
+ - `02_input` 内数据目录保留上游原始命名(`BES`/`OK`/`BS3`/`5mouse` 等),与源逐文件 MD5 同批,
150
+ 勿改名(详见 `biology-analysis` 包 README 命名映射表)。
151
+ - K=3/6/10 均为论文口径;`run_mode="train"` 只训练、`"test"` 需已有 `train_data.npz`。
152
+ - 本包为科研复现用途;重跑全量需满足原始环境的依赖版本(尤其 scikit-learn==1.6.1)。
@@ -0,0 +1,128 @@
1
+ # ccf-habitat
2
+
3
+ 一致性约束的 CT 影像栖息地(habitat)亚区分割工具包。
4
+ 算法内核为 **BESP2**(BESMNN 谱聚类扩展版:超节点特征 → 迭代扩展 → 全局图 → 谱聚类)与
5
+ **P2**(五个冻结人源参考预测器,纯 NumPy),用于肺癌等肿瘤 CT 的亚区域无监督分割与跨物种判别。
6
+
7
+ 本包把原先"一键脚本 + 数据同包"的调用方式重构为标准 Python 包:
8
+
9
+ - `pip install -e .` 即可安装,提供 `ccf-train` / `ccf-predict` / `ccf-example` / `ccf-list-data` / `ccf-slic` 命令;
10
+ - 算法代码原样归档(`ccf/engine_besp2.py`、`ccf/selfsp.py`、`ccf/predictors.py`),**不重写逻辑**,
11
+ 因此结果与既有权威输出一致;
12
+ - **数据不捆绑**:约 35 GB 的影像/中间数据通过 `data_root` 指向外部目录(默认自动探测本机
13
+ `D:\Software-develop\BESMNN\biology-analysis\02_input`),GitHub 上只保留代码与配置。
14
+
15
+ ## 安装
16
+
17
+ ```bash
18
+ python -m pip install -e .
19
+ # 或直接以包根为工作目录:export PYTHONPATH=<包根>(Windows: $env:PYTHONPATH="<包根>")
20
+ ```
21
+
22
+ 依赖:numpy / scipy / pandas / scikit-learn==1.6.1 / SimpleITK / PyRadiomics / tqdm / joblib / psutil / matplotlib / pyyaml / maskslic。
23
+
24
+ ## 数据策略
25
+
26
+ | 内容 | 位置 |
27
+ |---|---|
28
+ | 代码 / 配置 / 示例 | 本仓库(GitHub 可公开) |
29
+ | 人源 539 例 CT/ROI/超体素 mask | `02_input/human/besp2_source/shared/{ct,sub,merged_masks}`(外部) |
30
+ | 人源 BESP2 工作套(split、模型、日志) | `02_input/human/besp2_source/BS{3,6,10}`(外部) |
31
+ | 鼠源(6 例)与病理基准 | `02_input/mouse*`、`02_input/mouse_pathology`(外部) |
32
+
33
+ `data_root` 解析优先级:命令行 `--data-root` / yaml 配置 > 环境变量 `CCF_DATA_ROOT` > 本机默认路径。
34
+
35
+ ## 用法
36
+
37
+ ### 命令行
38
+
39
+ ```bash
40
+ # 查看数据布局探测结果
41
+ ccf-list-data
42
+
43
+ # 冒烟:前 3 例跑通完整训练链(CPU 可跑,证明安装与调用链路)
44
+ ccf-train -k 3 -o ./out --subset 3
45
+
46
+ # 完整:全 539 例 K=6 训练+测试(重型,需 GPU 与较长时间)
47
+ ccf-train -k 6 -o ./out --mode train_test
48
+
49
+ # 判别(P2 预测器;reference 通过 CCF_DATA_REFERENCE 预置)
50
+ ccf-predict -m P2 -i embedding.npy -o ./out
51
+ ```
52
+
53
+ ### Python API
54
+
55
+ ```python
56
+ from ccf import CCFHabitatModel, CCFConfig
57
+
58
+ model = CCFHabitatModel(CCFConfig(k=3, output_dir="./out"))
59
+ fit = model.fit(subset=3) # 冒烟;去掉 subset=3 为全量
60
+ print(fit["train_data_path"])
61
+
62
+ from ccf.predictors import FrozenReference
63
+ ref = FrozenReference.from_model(spectral_model, train_index_mapping)
64
+ res = model.predict("P2", ref, embedding=mouse_embedding) # labels/scores/margin
65
+ ```
66
+
67
+ ## 与既有权威流程的关系(结果一致性)
68
+
69
+ | 组件 | 来源(biology-analysis 核验包) | 本包 |
70
+ |---|---|---|
71
+ | BESP2 主算法 | `01_code/s02_besp2_human/1.BESMNNCORRECT4.5.py` | `ccf/engine_besp2.py`(原样复制,改相对导入) |
72
+ | 谱聚类 | `01_code/s02_besp2_human/selfsp.py` | `ccf/selfsp.py` |
73
+ | P2 预测器 | `01_code/s02_besp2_human/new_predictors.py` | `ccf/predictors.py`(原样复制) |
74
+ | PyRadiomics 配置 | — | `ccf/configs/default_radiomics.yaml` |
75
+ | maskslic 超体素 | `01_code/s01_slic/0.Maskslic20-多线程_human.py`、`1.Maskslic100brats-多线程.py` | `ccf/maskslic.py`(参数口径原样保留) |
76
+
77
+ ## 超体素生成(ccf-slic,集成 maskslic)
78
+
79
+ 上游 `s01_slic` 阶段用 **PyPI maskslic 包**(Benjamin Irving,BSD-3-Clause,scikit-image SLIC 的
80
+ mask 约束变体)生成"仅肿瘤区域内"的超体素;本包把两套上游脚本的参数口径原样集成进
81
+ `ccf/maskslic.py`,只做 I/O、几何信息保留与并行编排,不修改算法。第三方许可声明见 `NOTICE`。
82
+
83
+ ```bash
84
+ # 单例:单模态(人源口径 compactness=10)或多模态(BraTS 口径 compactness=0.5 需显式指定)
85
+ ccf-slic --img ct.nii.gz --roi roi.nii.gz --out subregion_label.nii.gz
86
+ ccf-slic --img t1.nii.gz,t1ce.nii.gz,t2.nii.gz,flair.nii.gz --roi seg.nii.gz \
87
+ --out label.nii.gz --compactness 0.5
88
+
89
+ # 批量:img_dir 与 roi_dir 按文件名基线匹配,并行生成(对齐上游 execute_parallel)
90
+ ccf-slic --img-dir ./fuse --roi-dir ./roi2 --out-dir ./labels --workers 8
91
+ ```
92
+
93
+ ```python
94
+ from ccf.maskslic import supervoxels, batch_supervoxels
95
+ supervoxels("ct.nii.gz", "roi.nii.gz", "subregion_label.nii.gz") # 单模态
96
+ supervoxels(["t1.nii.gz", "flair.nii.gz"], "seg.nii.gz", "out.nii.gz",
97
+ compactness=0.5) # 多通道
98
+ ```
99
+
100
+ 既有 9 步全链核验(输入完整性自检、NMF 复现、表 1、三算法病理 KW、RNAseq 方向一致性、
101
+ 多重校正、表 5 二项检验)仍由 `D:\Software-develop\BESMNN\biology-analysis` 包 `run_all.py` 提供;
102
+ 本包负责把 BESP2/P2 调用标准化,供第三方复现与二次开发。
103
+
104
+ ## 目录结构
105
+
106
+ ```
107
+ ccf/
108
+ ├── pyproject.toml # 可安装包定义 + 命令行入口
109
+ ├── ccf/
110
+ │ ├── config.py # 数据路径/参数配置(yaml / 环境变量 / 默认值)
111
+ │ ├── dataset.py # 02_input 布局解析
112
+ │ ├── engine_besp2.py # BESP2 主算法(上游原样归档)
113
+ │ ├── selfsp.py # 谱聚类修改版(sklearn 1.6.1 扩展)
114
+ │ ├── predictors.py # P2 冻结人源参考预测器(P1-P5)
115
+ │ ├── model.py # CCFHabitatModel 高层封装(fit/predict)
116
+ │ ├── maskslic.py # maskslic 超体素生成(上游 s01_slic 集成)
117
+ │ ├── output.py # 报告输出
118
+ │ ├── cli.py # ccf-* 命令
119
+ │ └── configs/ # 默认 yaml
120
+ └── example/ # 小样本冒烟示例 + 数据指引
121
+ ```
122
+
123
+ ## 说明
124
+
125
+ - `02_input` 内数据目录保留上游原始命名(`BES`/`OK`/`BS3`/`5mouse` 等),与源逐文件 MD5 同批,
126
+ 勿改名(详见 `biology-analysis` 包 README 命名映射表)。
127
+ - K=3/6/10 均为论文口径;`run_mode="train"` 只训练、`"test"` 需已有 `train_data.npz`。
128
+ - 本包为科研复现用途;重跑全量需满足原始环境的依赖版本(尤其 scikit-learn==1.6.1)。
@@ -0,0 +1,17 @@
1
+ # -*- coding: utf-8 -*-
2
+ """ccf-habitat: 一致性约束的 CT 影像栖息地(habitat)聚类包。
3
+
4
+ 算法内核 = 上游 BESP2(BESMNN 谱聚类扩展版 + P2 冻结人源参考预测器)。
5
+ 本包为可安装、可移植的标准 Python 包封装:
6
+ - ``ccf-train``:人源 CT/ROI/超体素 mask -> 全局图 + 谱聚类 -> 亚区分割
7
+ - ``ccf-predict``:P2 冻结参考预测器判别接口
8
+ - ``ccf-example``:小样本一键冒烟示例(证明安装/调用链路可跑)
9
+
10
+ 数据不随包分发(约 35 GB),通过配置文件或环境变量指向外部数据目录,
11
+ 默认探测 ``D:\\Software-develop\\BESMNN\\biology-analysis\\02_input``。
12
+ """
13
+ from .config import CCFConfig, default_data_root
14
+ from .model import CCFHabitatModel
15
+
16
+ __version__ = "0.3.0"
17
+ __all__ = ["CCFConfig", "CCFHabitatModel", "default_data_root", "__version__"]
@@ -0,0 +1,176 @@
1
+ # -*- coding: utf-8 -*-
2
+ """命令行入口:ccf-train / ccf-predict / ccf-example / ccf-list-data。
3
+
4
+ 用法示例:
5
+ ccf-train -k 3 -o ./out --subset 3 # 冒烟:前 3 例跑通训练链
6
+ ccf-train -k 6 -o ./out # 完整:全 539 例(重型,需 GPU/长时间)
7
+ ccf-predict -m P2 -i embedding.npy # P2 收缩高斯判别(需 reference 预置)
8
+ ccf-example # 一键示例(小样本)
9
+ ccf-list-data # 查看数据布局探测结果
10
+ """
11
+ import argparse
12
+ import os
13
+ import sys
14
+
15
+ import numpy as np
16
+
17
+ from .config import CCFConfig
18
+ from .model import CCFHabitatModel
19
+ from . import dataset, output
20
+
21
+
22
+ def _common_args(ap):
23
+ ap.add_argument("--data-root", default=None, help="数据根目录(02_input)")
24
+ ap.add_argument("-o", "--output-dir", default=None, help="输出目录")
25
+ ap.add_argument("--config-yaml", default=None, help="yaml 配置覆盖")
26
+
27
+
28
+ def train_main(argv=None):
29
+ ap = argparse.ArgumentParser(prog="ccf-train", description="人源 BESP2 训练/测试链(CCF)")
30
+ _common_args(ap)
31
+ ap.add_argument("-k", "--n-clusters", type=int, default=3, choices=(3, 6, 10), help="聚类数")
32
+ ap.add_argument("--subset", default=None,
33
+ help="int=取前 N 例冒烟;逗号分隔 id 列表=指定样本")
34
+ ap.add_argument("--mode", default="train_test", choices=("train", "train_test", "test"),
35
+ help="运行模式")
36
+ args = ap.parse_args(argv)
37
+
38
+ kw = dict(k=args.n_clusters, run_mode=args.mode)
39
+ if args.data_root:
40
+ kw["data_root"] = args.data_root
41
+ if args.output_dir:
42
+ kw["output_dir"] = args.output_dir
43
+ if args.config_yaml:
44
+ cfg = CCFConfig.from_yaml(args.config_yaml, **kw)
45
+ else:
46
+ cfg = CCFConfig(**kw)
47
+
48
+ subset = None
49
+ if args.subset:
50
+ subset = int(args.subset) if args.subset.isdigit() else args.subset.split(",")
51
+
52
+ model = CCFHabitatModel(cfg)
53
+ fit = model.fit(subset=subset)
54
+ report = output.write_fit_report(fit, os.path.join(fit["output_dir"], "fit_report.txt"))
55
+ print(report)
56
+ print("\n完成。输出: %s" % fit["output_dir"])
57
+ return 0
58
+
59
+
60
+ def predict_main(argv=None):
61
+ ap = argparse.ArgumentParser(prog="ccf-predict", description="P2 冻结人源参考预测器判别")
62
+ _common_args(ap)
63
+ ap.add_argument("-m", "--method", required=True, choices=("P1", "P2", "P3", "P4", "P5"))
64
+ ap.add_argument("-i", "--embedding", default=None, help="npy 文件(P1-P4)")
65
+ ap.add_argument("--w-human-mouse", default=None, help="npz 文件中的跨物种图(P5)")
66
+ args = ap.parse_args(argv)
67
+
68
+ cfg = CCFConfig(**(dict(data_root=args.data_root) if args.data_root else {}))
69
+ model = CCFHabitatModel(cfg)
70
+
71
+ # reference:CLI 版简化——从环境/约定路径加载。完整用法见 example/README.md。
72
+ ref = _load_default_reference()
73
+ if ref is None:
74
+ print("未预置 FrozenReference。Python API 用法:\n"
75
+ " from ccf.predictors import FrozenReference\n"
76
+ " ref = FrozenReference.from_model(model, mapping)\n"
77
+ " CCFHabitatModel().predict('P2', ref, embedding=embedding)")
78
+ return 2
79
+
80
+ kw = {}
81
+ if args.method == "P5":
82
+ if not args.w_human_mouse:
83
+ print("P5 需要 --w-human-mouse <npz>")
84
+ return 2
85
+ kw["w_human_mouse"] = np.load(args.w_human_mouse)["w"]
86
+ else:
87
+ if not args.embedding:
88
+ print("P1-P4 需要 -i embedding.npy")
89
+ return 2
90
+ kw["embedding"] = np.load(args.embedding)
91
+
92
+ result = model.predict(args.method, ref, **kw)
93
+ out_dir = args.output_dir or os.path.join(os.getcwd(), "ccf_output")
94
+ os.makedirs(out_dir, exist_ok=True)
95
+ print(output.write_predict_report(result, os.path.join(out_dir, "predict_report.txt")))
96
+ return 0
97
+
98
+
99
+ def example_main(argv=None):
100
+ ap = argparse.ArgumentParser(prog="ccf-example", description="小样本一键冒烟示例")
101
+ _common_args(ap)
102
+ args = ap.parse_args(argv)
103
+
104
+ cfg = CCFConfig(**(dict(data_root=args.data_root) if args.data_root else {}))
105
+ print("数据根: %s" % cfg.data_root)
106
+ print(dataset.describe(cfg.data_root))
107
+ model = CCFHabitatModel(cfg)
108
+ fit = model.fit(subset=3, output_dir=args.output_dir)
109
+ print(output.write_fit_report(fit, os.path.join(fit["output_dir"], "fit_report.txt")))
110
+ print("\n冒烟完成。输出: %s" % fit["output_dir"])
111
+ return 0
112
+
113
+
114
+ def list_data_main(argv=None):
115
+ ap = argparse.ArgumentParser(prog="ccf-list-data", description="数据布局探测")
116
+ ap.add_argument("--data-root", default=None)
117
+ args = ap.parse_args(argv)
118
+ cfg = CCFConfig(**(dict(data_root=args.data_root) if args.data_root else {}))
119
+ print(dataset.describe(cfg.data_root))
120
+ return 0
121
+
122
+
123
+ def slic_main(argv=None):
124
+ """ccf-slic:maskslic 超体素生成(上游 s01_slic 阶段)。
125
+
126
+ 单例(多模态/单模态):
127
+ ccf-slic --img ct.nii.gz --roi roi.nii.gz --out out/label.nii.gz
128
+ ccf-slic --img t1.nii.gz,t1ce.nii.gz,t2.nii.gz,flair.nii.gz --roi seg.nii.gz --out out.nii.gz --compactness 0.5
129
+ 批量(img_dir 与 roi_dir 按文件名基线匹配):
130
+ ccf-slic --img-dir ./fuse --roi-dir ./roi2 --out-dir ./labels --workers 8
131
+ """
132
+ from . import maskslic as _ms
133
+
134
+ ap = argparse.ArgumentParser(prog="ccf-slic", description="maskslic 超体素生成")
135
+ ap.add_argument("--img", default=None, help="单模态文件或逗号分隔多模态列表")
136
+ ap.add_argument("--roi", default=None, help="肿瘤/ROI mask 文件")
137
+ ap.add_argument("--out", default=None, help="输出 NIfTI 路径(单例模式)")
138
+ ap.add_argument("--img-dir", default=None, help="批量模式:图像目录(单模态)")
139
+ ap.add_argument("--roi-dir", default=None, help="批量模式:ROI 目录")
140
+ ap.add_argument("--out-dir", default=None, help="批量模式:输出目录")
141
+ ap.add_argument("--n-segments", type=int, default=100, help="目标超体素数量(默认 100,对齐上游)")
142
+ ap.add_argument("--compactness", type=float, default=None,
143
+ help="紧凑度(默认自动:多通道 0.5 / 单通道 10,对齐上游口径)")
144
+ ap.add_argument("--workers", type=int, default=None, help="批量并行进程数(默认 CPU 核数)")
145
+ args = ap.parse_args(argv)
146
+
147
+ kw = dict(n_segments=args.n_segments)
148
+ if args.compactness is not None:
149
+ kw["compactness"] = args.compactness
150
+
151
+ if args.img and args.roi and args.out:
152
+ img_paths = args.img.split(",")
153
+ _ms.supervoxels(img_paths, args.roi, args.out, **kw)
154
+ print("完成: %s" % args.out)
155
+ return 0
156
+ if args.img_dir and args.roi_dir and args.out_dir:
157
+ ok, total = _ms.batch_supervoxels(args.img_dir, args.roi_dir, args.out_dir,
158
+ workers=args.workers, **kw)
159
+ print("批量完成: 成功 %d/%d" % (ok, total))
160
+ return 0 if ok == total else 1
161
+ ap.print_help()
162
+ return 2
163
+
164
+
165
+ def _load_default_reference():
166
+ """CLI 简化用:默认 reference 从 CCF_DATA_REFERENCE 环境变量指向的 npy 加载。"""
167
+ path = os.environ.get("CCF_DATA_REFERENCE", "")
168
+ if not path or not os.path.exists(path):
169
+ return None
170
+ from .predictors import FrozenReference
171
+ d = np.load(path, allow_pickle=True)
172
+ return FrozenReference(d["embedding"], d["labels"], d["patient_ids"])
173
+
174
+
175
+ if __name__ == "__main__":
176
+ sys.exit(train_main())
@@ -0,0 +1,94 @@
1
+ # -*- coding: utf-8 -*-
2
+ """配置:数据路径 + 运行参数,支持 yaml / 环境变量 / 默认值三级覆盖。
3
+
4
+ GitHub 友好原则:本包不捆绑数据。所有大体积数据(02_input 约 35 GB)
5
+ 通过 ``data_root`` 指向外部目录;找不到时给出明确报错与指引。
6
+ """
7
+ import os
8
+
9
+ try:
10
+ import yaml
11
+ except ImportError: # pragma: no cover
12
+ yaml = None
13
+
14
+ # 默认探测路径(本机现有biology-analysis 核验包数据根)
15
+ _DEFAULT_CANDIDATES = [
16
+ r"D:\Software-develop\BESMNN\biology-analysis\02_input",
17
+ ]
18
+
19
+ _PKG_DIR = os.path.dirname(os.path.abspath(__file__))
20
+ DEFAULT_RADIOMICS_YAML = os.path.join(_PKG_DIR, "configs", "default_radiomics.yaml")
21
+
22
+
23
+ def default_data_root():
24
+ """返回第一个存在的候选数据根;全部不存在时返回空字符串。"""
25
+ env = os.environ.get("CCF_DATA_ROOT", "").strip()
26
+ if env and os.path.isdir(env):
27
+ return env
28
+ for c in _DEFAULT_CANDIDATES:
29
+ if os.path.isdir(c):
30
+ return c
31
+ return ""
32
+
33
+
34
+ class CCFConfig:
35
+ """CCF 运行配置。
36
+
37
+ Parameters
38
+ ----------
39
+ data_root : str, optional
40
+ 数据根目录(含 human/mouse 等)。默认见 :func:`default_data_root`。
41
+ k : int
42
+ 聚类数(3/6/10,论文口径)。默认 3。
43
+ output_dir : str, optional
44
+ 结果输出目录。默认 ``./ccf_output``。
45
+ case_subset : int or list or None
46
+ None=全部样本;int=取前 N 例(冒烟测试);list=指定样本名。
47
+ run_mode : str
48
+ ``"train"`` / ``"train_test"`` / ``"test"``,透传 BESP2 引擎。
49
+ radiomics_yaml : str, optional
50
+ PyRadiomics 配置。默认包内 ``configs/default_radiomics.yaml``。
51
+ seed : int
52
+ 随机种子(BESP2 引擎沿用其内部逻辑;本字段留作记录)。
53
+ """
54
+
55
+ def __init__(self, data_root=None, k=3, output_dir=None,
56
+ case_subset=None, run_mode="train",
57
+ radiomics_yaml=None, seed=2027):
58
+ self.data_root = data_root if data_root is not None else default_data_root()
59
+ self.k = int(k)
60
+ self.output_dir = output_dir or os.path.join(os.getcwd(), "ccf_output")
61
+ self.case_subset = case_subset
62
+ self.run_mode = run_mode
63
+ self.radiomics_yaml = radiomics_yaml or DEFAULT_RADIOMICS_YAML
64
+ self.seed = seed
65
+ if not self.data_root:
66
+ raise ValueError(
67
+ "未找到数据根目录。请设置环境变量 CCF_DATA_ROOT,或传入 data_root="
68
+ "指向 02_input 数据目录(本机: D:\\Software-develop\\BESMNN\\biology-analysis\\02_input)。"
69
+ )
70
+
71
+ # ---- 序列化 ----
72
+ def to_dict(self):
73
+ return dict(data_root=self.data_root, k=self.k, output_dir=self.output_dir,
74
+ case_subset=self.case_subset, run_mode=self.run_mode,
75
+ radiomics_yaml=self.radiomics_yaml, seed=self.seed)
76
+
77
+ @classmethod
78
+ def from_yaml(cls, path, **overrides):
79
+ if yaml is None:
80
+ raise ImportError("需要 pyyaml: python -m pip install pyyaml")
81
+ with open(path, "r", encoding="utf-8") as f:
82
+ d = yaml.safe_load(f) or {}
83
+ d.update(overrides)
84
+ return cls(**d)
85
+
86
+ def save_yaml(self, path):
87
+ if yaml is None:
88
+ raise ImportError("需要 pyyaml: python -m pip install pyyaml")
89
+ with open(path, "w", encoding="utf-8") as f:
90
+ yaml.safe_dump(self.to_dict(), f, allow_unicode=True, sort_keys=False)
91
+
92
+ def __repr__(self):
93
+ return "CCFConfig(data_root=%s, k=%d, run_mode=%s)" % (
94
+ self.data_root, self.k, self.run_mode)
@@ -0,0 +1,8 @@
1
+ # ccf-habitat 默认配置(可被命令行参数覆盖)
2
+ data_root: "" # 留空则自动探测(环境变量 CCF_DATA_ROOT 或本机 02_input)
3
+ k: 3 # 聚类数 3/6/10
4
+ output_dir: ./ccf_output # 结果输出目录
5
+ run_mode: train_test # train / train_test / test
6
+ case_subset: null # null=全部;int=前 N 例冒烟;[ids]=指定样本
7
+ radiomics_yaml: "" # 留空用包内 configs/default_radiomics.yaml
8
+ seed: 2027
@@ -0,0 +1,22 @@
1
+ # Built-in default PyRadiomics parameter file.
2
+ # Replace with your study-specific radiomics.yaml using -r/--radiomics when needed.
3
+
4
+ imageType:
5
+ Original: {}
6
+
7
+ featureClass:
8
+ firstorder: []
9
+ shape: []
10
+ glcm: []
11
+ glrlm: []
12
+ glszm: []
13
+ gldm: []
14
+ ngtdm: []
15
+
16
+ setting:
17
+ binWidth: 25
18
+ interpolator: sitkBSpline
19
+ resampledPixelSpacing: null
20
+ normalize: false
21
+ removeOutliers: null
22
+ label: 1