ctbodycomp
CT体成分2D/3D特征提取与独立校正。支持按类别或单个名称选择基础特征,以及独立的身高、体重、BSA和骨参照校正。
安装
在本项目目录运行python -m pip install .。开发时使用python -m pip install -e .。
支持Python 3.10及以上,依赖numpy、nibabel、pandas;测试需要pytest。
四类基础特征
| 类别 | 代码 | 数量 |
|---|---|---|
| 形态:面积或体积 | morphometry | 11 |
| 一阶统计:Mean、SD、P10、P50、P90、IQR | firstorder | 66 |
| 组成与分布:组织比值或占比,百分比 | composition | 12 |
| 复合:面积或体积×平均HU | composite | 11 |
| 合计 | 100 |
旧脚本的8项骨参照比和8项骨参照比×HU不再混入基础输出。
组织代码:SM骨骼肌(1)、SF皮下脂肪(4)、IMF肌间脂肪(2)、VF其余内脏脂肪(3)、PVAT血管周围脂肪(6)、EAT心外膜脂肪(7)、AorticPVAT主动脉周围脂肪(8)、Bone骨(5)、CVF(6/7/8)、VF_total(3/6/7/8)、TF(2/3/4/6/7/8)。基础标签互斥。
0.1.5 缺失脊柱标签输出
新增CSV诊断列diagnostics_missing_spine_labels,放在特征列前面,记录请求范围内缺失的脊柱标签。部分标签缺失继续计算;全部缺失仍失败并保留缺失列表。直接调用可读取extractor.last_diagnostics["missing_spine_labels"]。标签是否出现不代表扫描覆盖完整。
0.1.4 自定义标签和缺失组织
相比0.1.3,本版保留BodyCompositionExtractor(..., label_map=...)的调用方式、特征名及缺失处理规则,仅将内部实现改为直接读取原始标签。数量从原始标签计数中查询;HU统计按原始标签筛选CT值。默认标签和自定义标签均不生成重编号mask,但HU筛选和数值统计仍可能产生临时数组。
label_map按以下固定顺序填写使用者原始BC mask中的标签编号,编号由使用者决定,不要求为1–8:
| 位置 | 库的标准标签 | 组织 |
|---|---|---|
| 1 | 1 | SM 骨骼肌 |
| 2 | 2 | IMF 肌间脂肪 |
| 3 | 3 | VF 其余内脏脂肪 |
| 4 | 4 | SF 皮下脂肪 |
| 5 | 5 | Bone 骨 |
| 6 | 6 | PVAT 血管周围脂肪 |
| 7 | 7 | EAT 心外膜脂肪 |
| 8 | 8 | AorticPVAT 主动脉周围脂肪 |
from ctbodycomp import BodyCompositionExtractor
from ctbodycomp.batch import extract_csv
# 这些编号只是示例,请替换成自己的分割标签。
# 第三项为0表示没有VF标签,不能把背景0当成VF。
label_map = [2, 8, 0, 5, 4, 3, 6, 1]
extractor = BodyCompositionExtractor(mode="3D", label_map=label_map)
raw = extractor.execute("ct.nii.gz", "bc_mask.nii.gz")
# 同一个extractor可用于批量CSV,病例表格式不变:
# result = extract_csv("cases.csv", "features.csv", extractor)
# 2D有脊柱时,BC标签映射与脊柱标签分别设置:
extractor_2d = BodyCompositionExtractor(mode="2D", spine_label=7, label_map=label_map)
# raw_2d = extractor_2d.execute("ct.nii.gz", "bc_mask.nii.gz", spine_mask="spine.nii.gz")
不传label_map时沿用标准顺序[1, 2, 3, 4, 5, 6, 7, 8]。传入时必须为8项整数列表或元组;缺失组织填0,非零编号必须为正整数且不能重复。未映射的其他标签不参与组织计算。0.1.4按映射直接查询原始mask中的标签计数和对应CT值,不创建转换后的标签mask,不修改原mask,也不会修改脊柱标签或影像空间。整个批次使用同一映射,标签规范不同的病例应分批设置各自的映射。
**0.1.3改变了缺失组织的处理:**在实际选中的范围内先统计标签,所选特征的依赖标签全部存在才计算。缺失组织的面积、体积、一阶统计及原始复合均为NaN,CSV保存为空单元格;保留所有选定特征列,不按病例删除列。标签没有出现表示本次无法提供该组织的测量,不再用0表示缺失。
- CVF需要6、7、8全部存在;VF_total需要3、6、7、8全部存在;TF需要2、3、4、6、7、8全部存在。缺一个标签,对应复合组织的所有特征留空,不能用部分组织之和替代。
- 组成与分布特征检查分子、分母所依赖的全部标签。例如MFF需要1、2;SF_to_SM需要4、1;VF_total_to_SM需要3、6、7、8、1。缺少任意依赖就留空。
- 2D两层模式仍先逐层计算,再取等权平均;某特征在任一层缺少依赖时,最终该特征也留空。整幅影像其他层存在的标签不能补足当前测量范围的缺失。
- 后续校正保留这些缺失值:目标组织缺失时,其校正指数与校正复合留空;骨缺失时骨校正留空,但有有效身高等参数时其他校正仍可计算。特征值留空不等于病例提取失败,批量流程仍保留病例及状态。
extractor.last_diagnostics包含所用的label_map及选中范围出现的标准present_labels。它们记录映射和观察到的标签,不证明分割边界或组织定义与原项目相同。尤其VF需要排除单独分割的6–8区域,骨和脂肪组织的具体边界需符合相应输入规范;标签映射只更换编号。
先提取第一类,再校正
from ctbodycomp import BodyCompositionExtractor, FeatureNormalizer
extractor = BodyCompositionExtractor(
mode="3D",
features={"morphometry": ["SM_Volume", "SF_Volume", "Bone_Volume"]},
)
raw = extractor.execute(image="ct.nii.gz", mask="bc_mask.nii.gz")
# 只校正指定组织;原始结果与校正结果是两个独立字典。
normalizer = FeatureNormalizer(
mode="3D", methods=["height", "weight", "bsa", "bone"], tissues=["SM", "SF"]
)
corrected = normalizer.execute(raw, height_cm=170, weight_kg=65)
校正器根据第一类形态指标计算校正指数;开启include_composite=True时,同时读取一阶Mean并计算校正指数×Mean。不读取影像,不对HU本身或组成比值二次校正。可以把提取结果保存后,换一组校正方式再次计算。骨校正需提前提取同一范围的Bone_Volume或Bone_Area。
| 方法 | 公式 | 2D单位 | 3D单位 |
|---|---|---|---|
| height | 原始面积/体积 ÷ 身高(m)² | cm²/m² | cm³/m² |
| weight | 原始面积/体积 ÷ 体重(kg) | cm²/kg | cm³/kg |
| bsa | 原始面积/体积 ÷ BSA(m²) | cm²/m² | cm³/m² |
| bone | 原始面积/体积 ÷ 同范围骨面积/体积 | 无量纲 | 无量纲 |
2D和3D都使用身高²。默认BSA使用Mosteller公式:sqrt(height_cm * weight_kg / 3600),也可通过bsa_m2直接提供已知BSA;提供值优先,来源记录在normalizer.last_diagnostics。Mosteller公式参考:原始论文。
身高、体重、BSA需为有限正数;只选择骨校正时不需要身高体重。缺少所选方法需要的参数会抛出明确错误。骨量为0时骨校正输出NaN。CSV校正失败时保留原始特征及失败原因。
若提取全部11个形态特征且启用全部校正,height、weight、bsa各11项,bone对10个非骨组织输出,共43项。与100项基础特征分开存储。骨校正包括CVF和TF两个聚合组织,不输出恒为1的Bone/Bone。实际输出量由所选组织、特征和校正方式决定。
可选:校正指数 × 平均HU
从0.1.1开始,校正指数与校正后的复合特征统一由FeatureNormalizer管理。使用include_composite=True,同一次校正返回“指数”和“指数×同组织平均HU”,每一对相邻排列。两者均属于校正模块,与四类基础特征分开。默认不开启校正复合项。
from ctbodycomp import BodyCompositionExtractor, FeatureNormalizer
extractor = BodyCompositionExtractor(
mode="3D",
features={
"morphometry": ["SM_Volume", "Bone_Volume"],
"firstorder": ["SM_Mean"],
},
)
raw = extractor.execute("ct.nii.gz", "bc_mask.nii.gz")
normalizer = FeatureNormalizer(
mode="3D", methods=["height", "bone"], tissues=["SM"],
include_composite=True,
)
corrected = normalizer.execute(raw, height_cm=170)
# normalization_height_SM_Volume
# normalized_composite_height_SM_VolumeMean
# normalization_bone_SM_Volume
# normalized_composite_bone_SM_VolumeMean
units = normalizer.get_units(raw)
2D将模式改为2D,将第一类特征的Volume改为Area,输出为normalized_composite_height_SM_AreaMean等。
| 方法 | 公式,μ为同组织平均HU | 2D单位 | 3D单位 | 全部组织数量 |
|---|---|---|---|---|
| height | (面积/体积 ÷ 身高(m)²) × μ | cm²·HU/m² | cm³·HU/m² | 11 |
| weight | (面积/体积 ÷ 体重(kg)) × μ | cm²·HU/kg | cm³·HU/kg | 11 |
| bsa | (面积/体积 ÷ BSA(m²)) × μ | cm²·HU/m² | cm³·HU/m² | 11 |
| bone | (面积/体积 ÷ 同范围骨面积/体积) × μ | HU | HU | 10 |
共43项可选校正复合特征,不输出Bone/Bone×HU。每种模式最多有100项基础特征+43项校正指数+43项校正复合特征=186项。2D和3D是两种模式,不合并计数。负平均HU保留符号;指数或均值为NaN/无穷时结果为NaN,不解释为实际组织质量。
通过methods选择校正方式,通过tissues选择组织;tissues=None从输入已有的形态指标中选择组织。开启复合项需要同时提取目标组织的Mean。可用get_normalized_composite_catalog("3D")查询完整复合名称、单位、指数键和均值键。
对于已经保存的校正指数字典,仍支持NormalizedCompositeCalculator(mode, methods, tissues).execute(indices, raw)单独补算复合项;两个字典需来自同一病例、同一范围。
2D两层的计算顺序:先将两层面积与两层平均HU分别等权平均,面积平均值进行校正,再乘HU平均值。因此结果是“校正后的平均面积指数 × 平均HU”,与基础复合类的“逐层面积×HU后平均”定义不同。例如两层面积0.06、0.18 cm²,均值10、30 HU,身高1 m:校正复合为0.12×20=2.4 cm²·HU/m²,而基础复合为(0.06×10+0.18×30)/2=3 cm²·HU。
选择类别或单项
from ctbodycomp import BodyCompositionExtractor, get_feature_catalog
extractor = BodyCompositionExtractor(mode="2D", spine_label=7)
extractor.disableAllFeatures()
extractor.enableFeaturesByName(
morphometry=["SM_Area", "Bone_Area"],
firstorder=["SM_Mean", "SM_P50"],
composition=["MFF"],
)
raw = extractor.execute("ct.nii.gz", "bc_mask.nii.gz", spine_mask="spine.nii.gz")
# 按整个类别启用:
extractor.enableFeatureClassByName("firstorder")
# 查询当前模式下全部可用名称、英文输出键和单位:
catalog = get_feature_catalog("2D")
构造参数features=None默认启用全部四类;features={}不启用任何特征。映射中未列出的类别不提取,类别中的[]或None表示该类全部。enableFeaturesByName替换指定类别的选择,其他已启用类别保持不变,因此要只选择几项时先调用disableAllFeatures()。支持全部启用enableAllFeatures()和单类别关闭enableFeatureClassByName("firstorder", False)。
只输出选择的特征,内部依赖按需计算。例如只选Mean不计算分位数,只选形态不计算HU统计;只选一个组成比值时也不输出其内部依赖的组织面积/体积。
输入与选层
- 必需:CT与BC mask。接受文件路径或nibabel图像对象,图像需3D且空间匹配,尺寸以mm解释。
- 裁剪、选层和特征计算前,统一检查CT与BC mask的完整数组尺寸(shape)及空间变换矩阵(affine);提供Spine mask时也检查它与CT一致。affine包含体素尺寸、方向和原点,比较使用绝对容差
1e-4、相对容差0。不一致直接失败,不自动重采样或对齐。CSV批量提取会记录extraction_status=failed和具体原因,并继续其他病例。 - 可选:Spine mask。不提供时3D计算整幅CT,2D在整幅CT全部层中选中间层。
- 2D有Spine mask时需
spine_label,例如7或8。先找到该标签出现的最小、最大切片,以首末之间的整个连续区间为候选范围;奇数层取中间一层,偶数层取中间两层,即使中间层没有该脊柱标签也照常使用。 - 3D有Spine mask时默认范围
spine_range=(7, 18),可配置,取首末层之间的完整范围。新库依据affine识别上下方向,支持数组轴置换。 - 每个选中切片的组织ROI来自完整BC mask平面,Spine mask仅定位。斜位影像保持原切片平面。
- 2D两层基础特征先逐层计算再等权平均。独立校正使用这个平均后的形态值;骨校正因此是“平均组织面积/平均骨面积”,不同于旧脚本的“两层骨比值平均”。
- SD使用ddof=0;分位数为线性插值;非有限CT值不进入HU统计;缺失组织的面积/体积及其他特征均为NaN;存在组织但没有有限HU样本时,数量可计算而HU统计与乘HU项为NaN;任一层某基础指标NaN,则两层平均也为NaN。
CSV输出顺序为:原输入信息列 → diagnostics_scope、diagnostics_slices、diagnostics_missing_spine_labels、extraction_status → 所选特征列 → extraction_error。连续切片简写为42-374,单层为42;二维中间两层同样表示为42-43。extractor.last_diagnostics["selected_slices"]仍保留完整列表,只有CSV显示采用简写。
diagnostics_missing_spine_labels记录请求的脊柱标签中未在输入Spine mask出现的编号,例如[9, 12];已检查且没有缺失时为[],无Spine mask或尚未完成标签检查时为空。2D只检查spine_label;3D检查spine_range包含的全部整数标签。部分标签缺失仍按实际找到的首末切片计算;全部缺失仍失败,但缺失列表保留。该字段只检查标签是否出现,不判断扫描覆盖完整性或椎体边界截断。
直接路径或图像对象调用后,可从extractor.last_diagnostics["missing_spine_labels"]取得相同的整数列表;同时提供requested_spine_labels及present_spine_labels。未提供Spine mask时这三个列表均为[],应结合scope="full_image"解释。因目标标签全部缺失而失败时,异常仍会抛出,缺失列表保留在last_diagnostics中;若在读取文件或空间校验时已失败,则没有这些标签检查结果。
诊断信息在extractor.last_diagnostics,与纯数值特征分开。无Spine mask请传None;填写了错误文件路径不会自动退回全图计算。
CSV分两步运行
新库的CSV使用有意义的列名(区分大小写),不同于旧脚本按列位置读取:
ID,Image,Mask,SpineMask,Height_cm,Weight_kg
001,ct.nii.gz,bc_mask.nii.gz,,170,65
必需Image、Mask,其他列可选;SpineMask可省略或留空。相对路径相对于输入CSV目录。Height_cm、Weight_kg、可选BSA_m2由校正步骤读取,逐病例值优先于命令行公共值。
# 查询特征名
ctbodycomp features --mode 3D
# 仅提取肌肉、皮下脂肪和骨的体积
ctbodycomp extract cases.csv raw.csv --mode 3D --config examples/morphometry_3d.json
# 对提取结果进行校正,原始列也保留在输出中
ctbodycomp normalize raw.csv corrected.csv --mode 3D --methods height weight bsa bone --tissues SM SF
# 2D单标签、部分特征
ctbodycomp extract cases.csv raw_2d.csv --mode 2D --label 7 --config examples/selected_2d.json
ctbodycomp normalize raw_2d.csv corrected_2d.csv --mode 2D --methods bone --tissues SM
# 自定义BC标签:按SM、IMF、VF、SF、Bone、PVAT、EAT、AorticPVAT顺序;0表示缺失
ctbodycomp extract cases.csv raw_mapped.csv --mode 3D --label-map 2 8 0 5 4 3 6 1
需要校正复合时,提取配置必须包含目标组织的firstorder_*_Mean。在同一校正步骤增加--include-composite即可:
# 示例配置包含肌肉、骨体积和肌肉Mean
ctbodycomp extract cases.csv raw.csv --mode 3D --config examples/normalized_composite_3d.json
ctbodycomp normalize raw.csv corrected.csv --mode 3D --methods height weight bsa bone --tissues SM --include-composite
# 查询校正复合特征及所需输入
ctbodycomp features --mode 3D --normalized-composite
校正CSV保留基础特征,按“校正指数、对应复合项”的顺序追加列,使用统一的normalization_status、normalization_error记录结果。缺少所需Mean列时记录失败;Mean数值为空时对应复合项为NaN。影像不存在也不影响已成功提取结果的后续计算。
如需对已有校正CSV补算复合项,也保留单独命令:ctbodycomp normalized-composite corrected.csv products.csv --mode 3D --methods height bone --tissues SM。
配置JSON直接写“类别→特征名称列表”。命令返回0表示成功,1表示输出中存在失败病例,参数错误返回2。输出目录需存在,输出不能覆盖输入文件;重复输出路径会替换先前结果。
完整使用示例
源码包中的 examples/reference_usage/README.md 提供五种完整提取示例:2D T12、2D无脊柱、3D T12、3D T1–T12、3D无脊柱,以及独立校正示例。每种情况同时提供可运行的Python脚本和Markdown教程,不依赖额外本地辅助脚本。
输入使用 cases.example.csv 和 cases_without_spine.example.csv 通用模板。模板不包含实际病例数据,使用前替换为自己的影像路径。示例计算结果写入示例目录下的 results/,不会纳入发布包。
测试和构建
在项目根目录运行:
python -m pip install -e '.[test]'
python -m pytest -q -p no:cacheprovider
python -m pip install --upgrade build twine
python -m build
python -m twine check dist/ctbodycomp-0.1.5-py3-none-any.whl dist/ctbodycomp-0.1.5.tar.gz
wheel包含库模块、许可证和安装元数据;源码包另外包含测试与使用教程。虚拟环境、缓存、本地影像、实际病例表和计算结果不纳入发布文件。PUBLISHING.md 仅保留在开发目录供维护者参考,不随发布包分发。
许可证
本版本采用 PolyForm Noncommercial License 1.0.0,完整原文随发布包附于 LICENSE。该许可证不包含两年后自动转换为MIT的条款;不在其许可范围内的商业用途需版权持有人另行授权。
本许可允许在许可范围内修改和分发代码,未设“禁止修改”限制。教育机构、公共研究机构、公共卫生或健康组织等的使用,被其机构条款明确列为允许用途,不因资金来源或相关资金义务改变;不能把本许可理解为所有医院收费场景都必须另行申请。实际允许范围以完整原文为准。
本项目为公开源码、限制用途的软件库,不声明为OSI认可的开源许可证。作者姓名及邮箱元数据仍未填写。
Metadata
Release files for ctbodycomp 0.1.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| ctbodycomp-0.1.5.tar.gz | 53.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ctbodycomp-0.1.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 80.5 kB
Release files / ctbodycomp-0.1.5.tar.gz
| Download URL | ctbodycomp-0.1.5.tar.gz |
|---|---|
| Size | 53.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2725abe9462bc51542c59f5c5b49d7a77b072d49ea55be514bed1ec18e805f0f
|
|
BLAKE2b-256 checksum How to use checksums |
1180f9763fc52648aeaa922afc5a5d86584c38c719b68b4ad0273db50522e3de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|
Release files / ctbodycomp-0.1.5-py3-none-any.whl
| Download URL | ctbodycomp-0.1.5-py3-none-any.whl |
|---|---|
| Size | 27.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
671ca0529db26afd29887fe5df11a5fbcc84e5b399a9894675a6a41fd09b20d2
|
|
BLAKE2b-256 checksum How to use checksums |
f54c2a5065dd42cd724caad971a1a1cd4f5426104c1b7287b9330cde078ff1b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.9
|