Add UPP STL asset indexing workflow
This commit is contained in:
92
UPP_数据库构建/01_UPP资产索引建表.sql
Normal file
92
UPP_数据库构建/01_UPP资产索引建表.sql
Normal file
@@ -0,0 +1,92 @@
|
||||
CREATE TABLE IF NOT EXISTS upp_exam_assets (
|
||||
ct_number text NOT NULL PRIMARY KEY,
|
||||
list_present boolean NOT NULL DEFAULT false,
|
||||
stl_present boolean NOT NULL DEFAULT false,
|
||||
patient_name text,
|
||||
patient_sex text,
|
||||
patient_age text,
|
||||
patient_id_masked text,
|
||||
exam_date timestamptz,
|
||||
task_created_at timestamptz,
|
||||
exam_description text,
|
||||
exam_device text,
|
||||
algorithm_model text,
|
||||
upp_status text,
|
||||
list_record_count integer NOT NULL DEFAULT 0,
|
||||
selected_list_record jsonb,
|
||||
list_records jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
selected_source_case_dir text,
|
||||
selected_source_stl_dir text,
|
||||
processed_stl_dir text,
|
||||
stl_case_name text,
|
||||
stl_sequence_no integer,
|
||||
stl_file_count integer NOT NULL DEFAULT 0,
|
||||
stl_total_bytes bigint NOT NULL DEFAULT 0,
|
||||
stl_files jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
stl_candidates jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CONSTRAINT ck_upp_exam_assets_ct_number_present CHECK (btrim(ct_number) <> ''),
|
||||
CONSTRAINT ck_upp_exam_assets_ct_number_format CHECK (ct_number ~ '^D?CT[0-9]{8,}$')
|
||||
);
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF to_regclass('public.upp_stl_files') IS NOT NULL
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public'
|
||||
AND table_name = 'upp_stl_files'
|
||||
AND column_name = 'file_names'
|
||||
)
|
||||
THEN
|
||||
CREATE TABLE IF NOT EXISTS upp_stl_files_row_detail_backup AS TABLE upp_stl_files;
|
||||
DROP TABLE upp_stl_files;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS upp_stl_files (
|
||||
ct_number text NOT NULL PRIMARY KEY REFERENCES upp_exam_assets(ct_number) ON DELETE CASCADE,
|
||||
file_count integer NOT NULL DEFAULT 0,
|
||||
total_bytes bigint NOT NULL DEFAULT 0,
|
||||
segment_names jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
segment_families jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
segment_categories jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
file_names jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
source_file_paths jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
processed_file_paths jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
files jsonb NOT NULL DEFAULT '[]'::jsonb,
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE upp_exam_assets ALTER COLUMN ct_number SET NOT NULL;
|
||||
ALTER TABLE upp_stl_files ALTER COLUMN ct_number SET NOT NULL;
|
||||
ALTER TABLE upp_stl_files ADD COLUMN IF NOT EXISTS segment_families jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
ALTER TABLE upp_stl_files ADD COLUMN IF NOT EXISTS segment_categories jsonb NOT NULL DEFAULT '[]'::jsonb;
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_list_present ON upp_exam_assets(list_present);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_stl_present ON upp_exam_assets(stl_present);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_patient_name ON upp_exam_assets(patient_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_exam_date ON upp_exam_assets(exam_date);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_list_records_gin ON upp_exam_assets USING gin (list_records);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_exam_assets_stl_files_gin ON upp_exam_assets USING gin (stl_files);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_stl_files_segment_names_gin ON upp_stl_files USING gin (segment_names);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_stl_files_segment_families_gin ON upp_stl_files USING gin (segment_families);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_stl_files_segment_categories_gin ON upp_stl_files USING gin (segment_categories);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_stl_files_file_names_gin ON upp_stl_files USING gin (file_names);
|
||||
CREATE INDEX IF NOT EXISTS idx_upp_stl_files_files_gin ON upp_stl_files USING gin (files);
|
||||
|
||||
COMMENT ON TABLE upp_exam_assets IS 'UPP列表、STL重建结果、未来CT数据的CT号唯一资产索引';
|
||||
COMMENT ON COLUMN upp_exam_assets.ct_number IS 'CT检查号,唯一索引键';
|
||||
COMMENT ON COLUMN upp_exam_assets.list_records IS '来自UPP列表OCR的同CT号全部记录';
|
||||
COMMENT ON COLUMN upp_exam_assets.selected_list_record IS '同CT号列表记录中按任务创建时间/检查时间选择的新记录';
|
||||
COMMENT ON COLUMN upp_exam_assets.stl_candidates IS '同CT号全部候选STL目录,保留去重选择依据';
|
||||
COMMENT ON COLUMN upp_exam_assets.stl_files IS '最终选中STL目录的文件清单';
|
||||
COMMENT ON COLUMN upp_exam_assets.processed_stl_dir IS '规范化后的STL目录,目录名为CT号';
|
||||
COMMENT ON TABLE upp_stl_files IS 'UPP最终选中STL文件聚合表,每个CT号唯一一行';
|
||||
COMMENT ON COLUMN upp_stl_files.ct_number IS 'CT检查号,主键并关联upp_exam_assets.ct_number';
|
||||
COMMENT ON COLUMN upp_stl_files.segment_names IS '最终选中STL文件的分割名称JSON数组';
|
||||
COMMENT ON COLUMN upp_stl_files.segment_families IS '与segment_names同顺序的训练family JSON数组';
|
||||
COMMENT ON COLUMN upp_stl_files.segment_categories IS '与segment_names同顺序的粗分类JSON数组';
|
||||
COMMENT ON COLUMN upp_stl_files.file_names IS '最终选中STL文件名JSON数组';
|
||||
COMMENT ON COLUMN upp_stl_files.files IS '最终选中STL文件完整明细JSON数组';
|
||||
672
UPP_数据库构建/02_同步UPP_STL资产.py
Executable file
672
UPP_数据库构建/02_同步UPP_STL资产.py
Executable file
@@ -0,0 +1,672 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""整理UPP STL目录,并把CT号唯一资产索引同步到PostgreSQL。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unicodedata
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_STL_ROOT = BASE_DIR / "UPP_STL处理" / "待处理STL数据"
|
||||
DEFAULT_PROCESSED_ROOT = BASE_DIR / "UPP_STL处理" / "已处理STL数据"
|
||||
DEFAULT_LIST_JSON = BASE_DIR / "UPP列表处理" / "数据处理结果区" / "全量分片结果" / "合并_图片表格_结构化.json"
|
||||
DEFAULT_SCHEMA = BASE_DIR / "UPP_数据库构建" / "01_UPP资产索引建表.sql"
|
||||
DEFAULT_REPORT = BASE_DIR / "UPP_数据库构建" / "UPP_STL资产同步报告.json"
|
||||
CT_PATTERN = re.compile(r"(D?CT\d{8,})", re.IGNORECASE)
|
||||
VALID_CT_PATTERN = re.compile(r"^D?CT\d{8,}$")
|
||||
|
||||
|
||||
ASSET_FIELDS = [
|
||||
"ct_number",
|
||||
"list_present",
|
||||
"stl_present",
|
||||
"patient_name",
|
||||
"patient_sex",
|
||||
"patient_age",
|
||||
"patient_id_masked",
|
||||
"exam_date",
|
||||
"task_created_at",
|
||||
"exam_description",
|
||||
"exam_device",
|
||||
"algorithm_model",
|
||||
"upp_status",
|
||||
"list_record_count",
|
||||
"selected_list_record",
|
||||
"list_records",
|
||||
"selected_source_case_dir",
|
||||
"selected_source_stl_dir",
|
||||
"processed_stl_dir",
|
||||
"stl_case_name",
|
||||
"stl_sequence_no",
|
||||
"stl_file_count",
|
||||
"stl_total_bytes",
|
||||
"stl_files",
|
||||
"stl_candidates",
|
||||
]
|
||||
|
||||
STL_FIELDS = [
|
||||
"ct_number",
|
||||
"file_count",
|
||||
"total_bytes",
|
||||
"segment_names",
|
||||
"segment_families",
|
||||
"segment_categories",
|
||||
"file_names",
|
||||
"source_file_paths",
|
||||
"processed_file_paths",
|
||||
"files",
|
||||
]
|
||||
|
||||
MODEL_VALUES = {"肝胆模型", "泌尿模型", "胸外模型"}
|
||||
DEVICE_VALUES = {"CT", "MR", "DR", "CR", "DX", "US", "XA", "NM", "PT"}
|
||||
|
||||
|
||||
def normalize_text(value: Any) -> str:
|
||||
if value is None:
|
||||
return ""
|
||||
text = unicodedata.normalize("NFKC", str(value)).replace("\u3000", " ")
|
||||
return re.sub(r"\s+", " ", text).strip()
|
||||
|
||||
|
||||
def normalize_ct(value: Any) -> str:
|
||||
return re.sub(r"\s+", "", normalize_text(value)).upper()
|
||||
|
||||
|
||||
def valid_ct(value: str) -> bool:
|
||||
return bool(VALID_CT_PATTERN.fullmatch(value))
|
||||
|
||||
|
||||
def extract_ct(text: str) -> str:
|
||||
match = CT_PATTERN.search(text)
|
||||
if not match:
|
||||
return ""
|
||||
ct_number = normalize_ct(match.group(1))
|
||||
return ct_number if valid_ct(ct_number) else ""
|
||||
|
||||
|
||||
def json_dump(value: Any) -> str:
|
||||
return json.dumps(value, ensure_ascii=False, separators=(",", ":"))
|
||||
|
||||
|
||||
def path_text(path: Path) -> str:
|
||||
return str(path.expanduser().absolute())
|
||||
|
||||
|
||||
def parse_time(value: Any) -> datetime | None:
|
||||
text = normalize_text(value)
|
||||
if not text:
|
||||
return None
|
||||
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S", "%Y-%m-%d", "%Y/%m/%d"):
|
||||
try:
|
||||
return datetime.strptime(text, fmt)
|
||||
except ValueError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def normalize_status(value: Any) -> str:
|
||||
text = normalize_text(value).replace("\ufe0f", "")
|
||||
compact = re.sub(r"\s+", "", text)
|
||||
if not compact:
|
||||
return ""
|
||||
if "部分重建成功" in compact:
|
||||
return "√ 部分重建成功"
|
||||
if "重建失败" in compact or compact.startswith(("×", "✗")):
|
||||
return "× 重建失败"
|
||||
if "重建成功" in compact:
|
||||
return "√ 重建成功"
|
||||
if "已报告" in compact:
|
||||
return "√ 已报告"
|
||||
return text
|
||||
|
||||
|
||||
def status_like(value: Any) -> bool:
|
||||
text = normalize_text(value)
|
||||
return bool(re.search(r"(重建成功|重建失败|部分重建成功|已报告)", text))
|
||||
|
||||
|
||||
def classify_segment(segment_name: str) -> tuple[str, str]:
|
||||
if segment_name in {"liver", "liver_left", "liver_right"}:
|
||||
return "肝脏主体", segment_name
|
||||
if re.fullmatch(r"liver_segment_S[1-8]", segment_name):
|
||||
return "肝段", segment_name
|
||||
if segment_name in {"liver_artery", "liver_vein", "portal_vein", "bile_duct"}:
|
||||
return "血管胆管", segment_name
|
||||
if segment_name in {"pancreas", "spleen", "cholecyst"}:
|
||||
return "腹部脏器", segment_name
|
||||
if segment_name in {"skin", "rib", "vertebrae", "sternum", "hipbone", "sacrum"}:
|
||||
return "体表骨骼", segment_name
|
||||
if re.fullmatch(r"liver_tumor_\d+", segment_name):
|
||||
return "肝脏肿瘤", "liver_tumor_*"
|
||||
if re.fullmatch(r"liver_cyst_\d+", segment_name):
|
||||
return "肝囊肿", "liver_cyst_*"
|
||||
if re.fullmatch(r"liver_hemangioma_\d+", segment_name):
|
||||
return "肝血管瘤", "liver_hemangioma_*"
|
||||
if re.fullmatch(r"pancreas_tumor_\d+", segment_name):
|
||||
return "胰腺肿瘤", "pancreas_tumor_*"
|
||||
if re.fullmatch(r"Segment_\d+", segment_name):
|
||||
return "未命名分割", "Segment_*"
|
||||
return "其他", segment_name
|
||||
|
||||
|
||||
def clean_record_info(info: dict[str, Any]) -> dict[str, Any]:
|
||||
cleaned = dict(info)
|
||||
exam_description = normalize_text(cleaned.get("检查描述"))
|
||||
exam_device = normalize_text(cleaned.get("检查设备"))
|
||||
algorithm_model = normalize_text(cleaned.get("算法模型"))
|
||||
if exam_description in DEVICE_VALUES and exam_device in MODEL_VALUES and status_like(algorithm_model):
|
||||
cleaned["检查描述"] = ""
|
||||
cleaned["检查设备"] = exam_description
|
||||
cleaned["算法模型"] = exam_device
|
||||
cleaned["状态"] = normalize_status(algorithm_model)
|
||||
elif exam_device in MODEL_VALUES and status_like(algorithm_model) and normalize_text(cleaned.get("状态")) in {"土", "士", "↓"}:
|
||||
cleaned["检查设备"] = "CT"
|
||||
cleaned["算法模型"] = exam_device
|
||||
cleaned["状态"] = normalize_status(algorithm_model)
|
||||
else:
|
||||
cleaned["状态"] = normalize_status(cleaned.get("状态"))
|
||||
return cleaned
|
||||
|
||||
|
||||
def clean_record(record: dict[str, Any]) -> dict[str, Any]:
|
||||
cleaned = dict(record)
|
||||
info = cleaned.get("记录信息")
|
||||
if isinstance(info, dict):
|
||||
cleaned["记录信息"] = clean_record_info(info)
|
||||
return cleaned
|
||||
|
||||
|
||||
def sql_quote_path(path: Path) -> str:
|
||||
return "'" + str(path.expanduser().absolute()).replace("'", "''") + "'"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class StlCandidate:
|
||||
ct_number: str
|
||||
source_case_dir: Path
|
||||
source_stl_dir: Path
|
||||
sequence_no: int | None
|
||||
files: tuple[Path, ...]
|
||||
|
||||
@property
|
||||
def file_count(self) -> int:
|
||||
return len(self.files)
|
||||
|
||||
@property
|
||||
def total_bytes(self) -> int:
|
||||
return sum(file.stat().st_size for file in self.files)
|
||||
|
||||
def score(self) -> tuple[int, int, str, str]:
|
||||
return (
|
||||
self.file_count,
|
||||
self.sequence_no if self.sequence_no is not None else -1,
|
||||
self.source_case_dir.name,
|
||||
self.source_stl_dir.name,
|
||||
)
|
||||
|
||||
def summary(self) -> dict[str, Any]:
|
||||
return {
|
||||
"source_case_dir": path_text(self.source_case_dir),
|
||||
"source_stl_dir": path_text(self.source_stl_dir),
|
||||
"case_name": self.source_case_dir.name,
|
||||
"sequence_no": self.sequence_no,
|
||||
"file_count": self.file_count,
|
||||
"total_bytes": self.total_bytes,
|
||||
}
|
||||
|
||||
|
||||
def sequence_no(case_dir: Path, stl_dir: Path) -> int | None:
|
||||
values: list[int] = []
|
||||
for pattern in (r"-(\d{3,6})_D?CT", r"^(\d{3,6})-STL$"):
|
||||
for text in (case_dir.name, stl_dir.name):
|
||||
values.extend(int(item) for item in re.findall(pattern, text, re.IGNORECASE))
|
||||
return max(values) if values else None
|
||||
|
||||
|
||||
def scan_stl_candidates(stl_root: Path) -> tuple[dict[str, list[StlCandidate]], list[str]]:
|
||||
candidates: dict[str, list[StlCandidate]] = {}
|
||||
no_ct_dirs: list[str] = []
|
||||
for directory in sorted(stl_root.rglob("*")):
|
||||
if not directory.is_dir():
|
||||
continue
|
||||
files = tuple(sorted([item for item in directory.iterdir() if item.is_file() and item.suffix.lower() == ".stl"]))
|
||||
if not files:
|
||||
continue
|
||||
ct_number = extract_ct("/".join(directory.relative_to(stl_root).parts))
|
||||
if not ct_number:
|
||||
no_ct_dirs.append(path_text(directory))
|
||||
continue
|
||||
candidate = StlCandidate(
|
||||
ct_number=ct_number,
|
||||
source_case_dir=directory.parent,
|
||||
source_stl_dir=directory,
|
||||
sequence_no=sequence_no(directory.parent, directory),
|
||||
files=files,
|
||||
)
|
||||
candidates.setdefault(ct_number, []).append(candidate)
|
||||
return candidates, no_ct_dirs
|
||||
|
||||
|
||||
def choose_candidates(candidates: dict[str, list[StlCandidate]]) -> dict[str, StlCandidate]:
|
||||
return {ct_number: max(items, key=lambda item: item.score()) for ct_number, items in candidates.items()}
|
||||
|
||||
|
||||
def clear_directory(directory: Path) -> None:
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
for child in directory.iterdir():
|
||||
if child.is_dir() and not child.is_symlink():
|
||||
shutil.rmtree(child)
|
||||
else:
|
||||
child.unlink()
|
||||
|
||||
|
||||
def place_file(source: Path, destination: Path, mode: str) -> str:
|
||||
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||
if mode == "symlink":
|
||||
destination.symlink_to(source.expanduser().absolute())
|
||||
return "symlink"
|
||||
if mode == "copy":
|
||||
shutil.copy2(source, destination)
|
||||
return "copy"
|
||||
try:
|
||||
os.link(source, destination)
|
||||
return "hardlink"
|
||||
except OSError:
|
||||
shutil.copy2(source, destination)
|
||||
return "copy"
|
||||
|
||||
|
||||
def materialize_selected_stl(
|
||||
selected: dict[str, StlCandidate],
|
||||
processed_root: Path,
|
||||
link_mode: str,
|
||||
refresh_files: bool,
|
||||
) -> tuple[dict[str, list[dict[str, Any]]], dict[str, str]]:
|
||||
processed_files: dict[str, list[dict[str, Any]]] = {}
|
||||
link_methods: dict[str, str] = {}
|
||||
processed_root.mkdir(parents=True, exist_ok=True)
|
||||
for ct_number, candidate in sorted(selected.items()):
|
||||
ct_dir = processed_root / ct_number
|
||||
if refresh_files:
|
||||
clear_directory(ct_dir)
|
||||
else:
|
||||
ct_dir.mkdir(parents=True, exist_ok=True)
|
||||
file_rows: list[dict[str, Any]] = []
|
||||
used_methods: set[str] = set()
|
||||
for source in candidate.files:
|
||||
destination = ct_dir / source.name
|
||||
if destination.exists() or destination.is_symlink():
|
||||
destination.unlink()
|
||||
method = place_file(source, destination, link_mode)
|
||||
used_methods.add(method)
|
||||
category, family = classify_segment(source.stem)
|
||||
file_rows.append(
|
||||
{
|
||||
"segment_name": source.stem,
|
||||
"family": family,
|
||||
"category": category,
|
||||
"file_name": source.name,
|
||||
"source_file_path": path_text(source),
|
||||
"processed_file_path": path_text(destination),
|
||||
"size_bytes": source.stat().st_size,
|
||||
}
|
||||
)
|
||||
manifest = {
|
||||
"ct_number": ct_number,
|
||||
"selected_source_case_dir": path_text(candidate.source_case_dir),
|
||||
"selected_source_stl_dir": path_text(candidate.source_stl_dir),
|
||||
"processed_stl_dir": path_text(ct_dir),
|
||||
"selection_rule": "file_count_desc_then_sequence_no_desc",
|
||||
"stl_file_count": candidate.file_count,
|
||||
"stl_total_bytes": candidate.total_bytes,
|
||||
"files": file_rows,
|
||||
}
|
||||
(ct_dir / "manifest.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
processed_files[ct_number] = file_rows
|
||||
link_methods[ct_number] = "+".join(sorted(used_methods))
|
||||
return processed_files, link_methods
|
||||
|
||||
|
||||
def load_list_records(list_json: Path) -> tuple[dict[str, list[dict[str, Any]]], list[dict[str, Any]]]:
|
||||
data = json.loads(list_json.read_text(encoding="utf-8"))
|
||||
records_by_ct: dict[str, list[dict[str, Any]]] = {}
|
||||
invalid: list[dict[str, Any]] = []
|
||||
for index, record in enumerate(data.get("图片表格记录", []), start=1):
|
||||
record = clean_record(record)
|
||||
info = record.get("记录信息") or {}
|
||||
ct_number = normalize_ct(info.get("检查号", ""))
|
||||
if not valid_ct(ct_number):
|
||||
invalid.append({"row_index": index, "ct_number": ct_number, "record": record})
|
||||
continue
|
||||
records_by_ct.setdefault(ct_number, []).append(record)
|
||||
return records_by_ct, invalid
|
||||
|
||||
|
||||
def select_list_record(records: list[dict[str, Any]]) -> dict[str, Any]:
|
||||
def key(record: dict[str, Any]) -> tuple[datetime, datetime, int, int]:
|
||||
info = record.get("记录信息") or {}
|
||||
image = record.get("图片信息") or {}
|
||||
task_time = parse_time(info.get("任务创建时间")) or datetime.min
|
||||
exam_time = parse_time(info.get("检查日期")) or datetime.min
|
||||
page_no = -1
|
||||
seq = image.get("图片序号")
|
||||
if isinstance(seq, list):
|
||||
numbers = [item for item in seq if isinstance(item, int)]
|
||||
if numbers:
|
||||
page_no = numbers[0]
|
||||
row_no = int(image.get("图片内行号") or -1)
|
||||
return (task_time, exam_time, page_no, row_no)
|
||||
|
||||
return max(records, key=key)
|
||||
|
||||
|
||||
def build_rows(
|
||||
selected: dict[str, StlCandidate],
|
||||
all_candidates: dict[str, list[StlCandidate]],
|
||||
processed_root: Path,
|
||||
processed_files: dict[str, list[dict[str, Any]]],
|
||||
list_records: dict[str, list[dict[str, Any]]],
|
||||
) -> tuple[list[dict[str, Any]], list[dict[str, Any]], int]:
|
||||
asset_rows: list[dict[str, Any]] = []
|
||||
stl_rows: list[dict[str, Any]] = []
|
||||
stl_file_count = 0
|
||||
for ct_number in sorted(set(selected) | set(list_records)):
|
||||
candidate = selected.get(ct_number)
|
||||
records = list_records.get(ct_number, [])
|
||||
chosen_record = select_list_record(records) if records else None
|
||||
info = (chosen_record or {}).get("记录信息") or {}
|
||||
files = processed_files.get(ct_number, [])
|
||||
processed_dir = processed_root / ct_number
|
||||
asset_rows.append(
|
||||
{
|
||||
"ct_number": ct_number,
|
||||
"list_present": bool(records),
|
||||
"stl_present": candidate is not None,
|
||||
"patient_name": normalize_text(info.get("姓名")),
|
||||
"patient_sex": normalize_text(info.get("性别")),
|
||||
"patient_age": normalize_text(info.get("年龄")),
|
||||
"patient_id_masked": normalize_text(info.get("患者号")),
|
||||
"exam_date": normalize_text(info.get("检查日期")),
|
||||
"task_created_at": normalize_text(info.get("任务创建时间")),
|
||||
"exam_description": normalize_text(info.get("检查描述")),
|
||||
"exam_device": normalize_text(info.get("检查设备")),
|
||||
"algorithm_model": normalize_text(info.get("算法模型")),
|
||||
"upp_status": normalize_text(info.get("状态")),
|
||||
"list_record_count": len(records),
|
||||
"selected_list_record": json_dump(chosen_record) if chosen_record else "",
|
||||
"list_records": json_dump(records),
|
||||
"selected_source_case_dir": path_text(candidate.source_case_dir) if candidate else "",
|
||||
"selected_source_stl_dir": path_text(candidate.source_stl_dir) if candidate else "",
|
||||
"processed_stl_dir": path_text(processed_dir) if candidate else "",
|
||||
"stl_case_name": candidate.source_case_dir.name if candidate else "",
|
||||
"stl_sequence_no": candidate.sequence_no if candidate and candidate.sequence_no is not None else "",
|
||||
"stl_file_count": candidate.file_count if candidate else 0,
|
||||
"stl_total_bytes": candidate.total_bytes if candidate else 0,
|
||||
"stl_files": json_dump(files),
|
||||
"stl_candidates": json_dump([item.summary() for item in sorted(all_candidates.get(ct_number, []), key=lambda item: item.score(), reverse=True)]),
|
||||
}
|
||||
)
|
||||
if files:
|
||||
stl_file_count += len(files)
|
||||
stl_rows.append(
|
||||
{
|
||||
"ct_number": ct_number,
|
||||
"file_count": len(files),
|
||||
"total_bytes": sum(int(file_info.get("size_bytes") or 0) for file_info in files),
|
||||
"segment_names": json_dump([file_info["segment_name"] for file_info in files]),
|
||||
"segment_families": json_dump([file_info["family"] for file_info in files]),
|
||||
"segment_categories": json_dump([file_info["category"] for file_info in files]),
|
||||
"file_names": json_dump([file_info["file_name"] for file_info in files]),
|
||||
"source_file_paths": json_dump([file_info["source_file_path"] for file_info in files]),
|
||||
"processed_file_paths": json_dump([file_info["processed_file_path"] for file_info in files]),
|
||||
"files": json_dump(files),
|
||||
}
|
||||
)
|
||||
return asset_rows, stl_rows, stl_file_count
|
||||
|
||||
|
||||
def write_csv(rows: list[dict[str, Any]], fields: list[str], suffix: str) -> Path:
|
||||
temp_file = tempfile.NamedTemporaryFile("w", encoding="utf-8", newline="", suffix=suffix, delete=False)
|
||||
with temp_file:
|
||||
writer = csv.DictWriter(temp_file, fieldnames=fields)
|
||||
writer.writeheader()
|
||||
for row in rows:
|
||||
writer.writerow({field: row.get(field, "") for field in fields})
|
||||
return Path(temp_file.name)
|
||||
|
||||
|
||||
def run_psql(args: argparse.Namespace, asset_csv: Path, stl_csv: Path) -> None:
|
||||
sql = f"""
|
||||
\\set ON_ERROR_STOP on
|
||||
\\i {sql_quote_path(Path(args.schema))}
|
||||
CREATE TEMP TABLE stg_upp_exam_assets (
|
||||
ct_number text,
|
||||
list_present boolean,
|
||||
stl_present boolean,
|
||||
patient_name text,
|
||||
patient_sex text,
|
||||
patient_age text,
|
||||
patient_id_masked text,
|
||||
exam_date timestamptz,
|
||||
task_created_at timestamptz,
|
||||
exam_description text,
|
||||
exam_device text,
|
||||
algorithm_model text,
|
||||
upp_status text,
|
||||
list_record_count integer,
|
||||
selected_list_record text,
|
||||
list_records text,
|
||||
selected_source_case_dir text,
|
||||
selected_source_stl_dir text,
|
||||
processed_stl_dir text,
|
||||
stl_case_name text,
|
||||
stl_sequence_no integer,
|
||||
stl_file_count integer,
|
||||
stl_total_bytes bigint,
|
||||
stl_files text,
|
||||
stl_candidates text
|
||||
);
|
||||
CREATE TEMP TABLE stg_upp_stl_files (
|
||||
ct_number text,
|
||||
file_count integer,
|
||||
total_bytes bigint,
|
||||
segment_names text,
|
||||
segment_families text,
|
||||
segment_categories text,
|
||||
file_names text,
|
||||
source_file_paths text,
|
||||
processed_file_paths text,
|
||||
files text
|
||||
);
|
||||
\\copy stg_upp_exam_assets({",".join(ASSET_FIELDS)}) FROM {sql_quote_path(asset_csv)} WITH (FORMAT csv, HEADER true, NULL '')
|
||||
\\copy stg_upp_stl_files({",".join(STL_FIELDS)}) FROM {sql_quote_path(stl_csv)} WITH (FORMAT csv, HEADER true, NULL '')
|
||||
INSERT INTO upp_exam_assets (
|
||||
ct_number, list_present, stl_present, patient_name, patient_sex, patient_age, patient_id_masked,
|
||||
exam_date, task_created_at, exam_description, exam_device, algorithm_model, upp_status,
|
||||
list_record_count, selected_list_record, list_records, selected_source_case_dir, selected_source_stl_dir,
|
||||
processed_stl_dir, stl_case_name, stl_sequence_no, stl_file_count, stl_total_bytes, stl_files, stl_candidates, updated_at
|
||||
)
|
||||
SELECT
|
||||
ct_number, list_present, stl_present, patient_name, patient_sex, patient_age, patient_id_masked,
|
||||
exam_date, task_created_at, exam_description, exam_device, algorithm_model, upp_status,
|
||||
COALESCE(list_record_count, 0),
|
||||
NULLIF(selected_list_record, '')::jsonb,
|
||||
COALESCE(NULLIF(list_records, ''), '[]')::jsonb,
|
||||
selected_source_case_dir, selected_source_stl_dir, processed_stl_dir, stl_case_name, stl_sequence_no,
|
||||
COALESCE(stl_file_count, 0), COALESCE(stl_total_bytes, 0),
|
||||
COALESCE(NULLIF(stl_files, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(stl_candidates, ''), '[]')::jsonb,
|
||||
now()
|
||||
FROM stg_upp_exam_assets
|
||||
ON CONFLICT (ct_number) DO UPDATE SET
|
||||
list_present = EXCLUDED.list_present,
|
||||
stl_present = EXCLUDED.stl_present,
|
||||
patient_name = EXCLUDED.patient_name,
|
||||
patient_sex = EXCLUDED.patient_sex,
|
||||
patient_age = EXCLUDED.patient_age,
|
||||
patient_id_masked = EXCLUDED.patient_id_masked,
|
||||
exam_date = EXCLUDED.exam_date,
|
||||
task_created_at = EXCLUDED.task_created_at,
|
||||
exam_description = EXCLUDED.exam_description,
|
||||
exam_device = EXCLUDED.exam_device,
|
||||
algorithm_model = EXCLUDED.algorithm_model,
|
||||
upp_status = EXCLUDED.upp_status,
|
||||
list_record_count = EXCLUDED.list_record_count,
|
||||
selected_list_record = EXCLUDED.selected_list_record,
|
||||
list_records = EXCLUDED.list_records,
|
||||
selected_source_case_dir = EXCLUDED.selected_source_case_dir,
|
||||
selected_source_stl_dir = EXCLUDED.selected_source_stl_dir,
|
||||
processed_stl_dir = EXCLUDED.processed_stl_dir,
|
||||
stl_case_name = EXCLUDED.stl_case_name,
|
||||
stl_sequence_no = EXCLUDED.stl_sequence_no,
|
||||
stl_file_count = EXCLUDED.stl_file_count,
|
||||
stl_total_bytes = EXCLUDED.stl_total_bytes,
|
||||
stl_files = EXCLUDED.stl_files,
|
||||
stl_candidates = EXCLUDED.stl_candidates,
|
||||
updated_at = now();
|
||||
DELETE FROM upp_stl_files WHERE ct_number IN (SELECT ct_number FROM stg_upp_exam_assets);
|
||||
INSERT INTO upp_stl_files (
|
||||
ct_number, file_count, total_bytes, segment_names, segment_families, segment_categories, file_names, source_file_paths, processed_file_paths, files, updated_at
|
||||
)
|
||||
SELECT
|
||||
ct_number,
|
||||
COALESCE(file_count, 0),
|
||||
COALESCE(total_bytes, 0),
|
||||
COALESCE(NULLIF(segment_names, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(segment_families, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(segment_categories, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(file_names, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(source_file_paths, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(processed_file_paths, ''), '[]')::jsonb,
|
||||
COALESCE(NULLIF(files, ''), '[]')::jsonb,
|
||||
now()
|
||||
FROM stg_upp_stl_files;
|
||||
"""
|
||||
env = os.environ.copy()
|
||||
password = args.password or env.get("PGPASSWORD")
|
||||
if password:
|
||||
env["PGPASSWORD"] = password
|
||||
command = ["psql"]
|
||||
if args.host:
|
||||
command.extend(["-h", args.host])
|
||||
if args.port:
|
||||
command.extend(["-p", str(args.port)])
|
||||
if args.user:
|
||||
command.extend(["-U", args.user])
|
||||
if args.dbname:
|
||||
command.extend(["-d", args.dbname])
|
||||
command.extend(["-v", "ON_ERROR_STOP=1"])
|
||||
completed = subprocess.run(command, input=sql, text=True, env=env, capture_output=True)
|
||||
print(completed.stdout, end="")
|
||||
if completed.returncode != 0:
|
||||
print(completed.stderr, end="")
|
||||
completed.check_returncode()
|
||||
|
||||
|
||||
def write_report(path: Path, report: dict[str, Any]) -> None:
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--stl-root", default=str(DEFAULT_STL_ROOT), help="待处理STL数据根目录")
|
||||
parser.add_argument("--processed-root", default=str(DEFAULT_PROCESSED_ROOT), help="规范化后的STL输出目录")
|
||||
parser.add_argument("--list-json", default=str(DEFAULT_LIST_JSON), help="UPP列表合并结构化JSON")
|
||||
parser.add_argument("--schema", default=str(DEFAULT_SCHEMA), help="PostgreSQL建表SQL")
|
||||
parser.add_argument("--report", default=str(DEFAULT_REPORT), help="同步报告JSON")
|
||||
parser.add_argument("--link-mode", choices=["hardlink", "copy", "symlink"], default="hardlink", help="输出STL文件方式")
|
||||
parser.add_argument("--no-refresh-files", action="store_true", help="不清空已处理CT目录中的旧文件")
|
||||
parser.add_argument("--dry-run", action="store_true", help="只整理文件和生成报告,不写数据库")
|
||||
parser.add_argument("--host", default=os.getenv("PGHOST", ""))
|
||||
parser.add_argument("--port", default=os.getenv("PGPORT", ""))
|
||||
parser.add_argument("--dbname", default=os.getenv("PGDATABASE", ""))
|
||||
parser.add_argument("--user", default=os.getenv("PGUSER", ""))
|
||||
parser.add_argument("--password", default=os.getenv("PGPASSWORD", ""), help="数据库密码;也可用PGPASSWORD环境变量")
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
stl_root = Path(args.stl_root)
|
||||
processed_root = Path(args.processed_root)
|
||||
all_candidates, no_ct_dirs = scan_stl_candidates(stl_root)
|
||||
selected = choose_candidates(all_candidates)
|
||||
processed_files, link_methods = materialize_selected_stl(
|
||||
selected=selected,
|
||||
processed_root=processed_root,
|
||||
link_mode=args.link_mode,
|
||||
refresh_files=not args.no_refresh_files,
|
||||
)
|
||||
list_records, invalid_list_records = load_list_records(Path(args.list_json))
|
||||
asset_rows, stl_rows, stl_file_count = build_rows(
|
||||
selected=selected,
|
||||
all_candidates=all_candidates,
|
||||
processed_root=processed_root,
|
||||
processed_files=processed_files,
|
||||
list_records=list_records,
|
||||
)
|
||||
duplicate_ct_numbers = sorted(ct_number for ct_number, items in all_candidates.items() if len(items) > 1)
|
||||
report = {
|
||||
"stl_candidate_dirs": sum(len(items) for items in all_candidates.values()),
|
||||
"stl_unique_ct": len(selected),
|
||||
"stl_no_ct_dirs": len(no_ct_dirs),
|
||||
"stl_duplicate_ct": len(duplicate_ct_numbers),
|
||||
"list_unique_valid_ct": len(list_records),
|
||||
"list_invalid_ct_records": len(invalid_list_records),
|
||||
"asset_rows": len(asset_rows),
|
||||
"stl_table_rows": len(stl_rows),
|
||||
"stl_file_rows": stl_file_count,
|
||||
"matched_list_and_stl_ct": len(set(selected) & set(list_records)),
|
||||
"list_without_stl_ct": len(set(list_records) - set(selected)),
|
||||
"stl_without_list_ct": len(set(selected) - set(list_records)),
|
||||
"duplicate_ct_examples": {
|
||||
ct_number: [item.summary() for item in sorted(all_candidates[ct_number], key=lambda item: item.score(), reverse=True)]
|
||||
for ct_number in duplicate_ct_numbers[:20]
|
||||
},
|
||||
"no_ct_dir_examples": no_ct_dirs[:20],
|
||||
"invalid_list_ct_examples": invalid_list_records[:20],
|
||||
"link_methods": dict(sorted(link_methods.items())[:20]),
|
||||
"processed_root": path_text(processed_root),
|
||||
"selection_rule": "同一CT号优先选择STL文件数更多;文件数相同选择目录/病例名中的较大序号",
|
||||
"dry_run": args.dry_run,
|
||||
}
|
||||
write_report(Path(args.report), report)
|
||||
asset_csv = write_csv(asset_rows, ASSET_FIELDS, "_upp_exam_assets.csv")
|
||||
stl_csv = write_csv(stl_rows, STL_FIELDS, "_upp_stl_files.csv")
|
||||
try:
|
||||
if not args.dry_run:
|
||||
run_psql(args, asset_csv, stl_csv)
|
||||
finally:
|
||||
asset_csv.unlink(missing_ok=True)
|
||||
stl_csv.unlink(missing_ok=True)
|
||||
print(json.dumps({k: report[k] for k in [
|
||||
"stl_candidate_dirs",
|
||||
"stl_unique_ct",
|
||||
"stl_no_ct_dirs",
|
||||
"stl_duplicate_ct",
|
||||
"list_unique_valid_ct",
|
||||
"asset_rows",
|
||||
"stl_table_rows",
|
||||
"stl_file_rows",
|
||||
"matched_list_and_stl_ct",
|
||||
"list_without_stl_ct",
|
||||
"stl_without_list_ct",
|
||||
]}, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
157
UPP_数据库构建/03_统计STL名称.py
Executable file
157
UPP_数据库构建/03_统计STL名称.py
Executable file
@@ -0,0 +1,157 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""统计已处理STL名称,并按可解释的医学/结构类别归类。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
from collections import Counter, defaultdict
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
BASE_DIR = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_PROCESSED_ROOT = BASE_DIR / "UPP_STL处理" / "已处理STL数据"
|
||||
DEFAULT_JSON = BASE_DIR / "UPP_数据库构建" / "UPP_STL名称统计.json"
|
||||
DEFAULT_CSV = BASE_DIR / "UPP_数据库构建" / "UPP_STL名称统计.csv"
|
||||
DEFAULT_FAMILY_CSV = BASE_DIR / "UPP_数据库构建" / "UPP_STL_family统计.csv"
|
||||
DEFAULT_ORDERED_CSV = BASE_DIR / "UPP_数据库构建" / "UPP_STL文件family顺序明细.csv"
|
||||
|
||||
|
||||
def classify(segment_name: str) -> tuple[str, str]:
|
||||
if segment_name in {"liver", "liver_left", "liver_right"}:
|
||||
return "肝脏主体", segment_name
|
||||
if re.fullmatch(r"liver_segment_S[1-8]", segment_name):
|
||||
return "肝段", segment_name
|
||||
if segment_name in {"liver_artery", "liver_vein", "portal_vein", "bile_duct"}:
|
||||
return "血管胆管", segment_name
|
||||
if segment_name in {"pancreas", "spleen", "cholecyst"}:
|
||||
return "腹部脏器", segment_name
|
||||
if segment_name in {"skin", "rib", "vertebrae", "sternum", "hipbone", "sacrum"}:
|
||||
return "体表骨骼", segment_name
|
||||
if re.fullmatch(r"liver_tumor_\d+", segment_name):
|
||||
return "肝脏肿瘤", "liver_tumor_*"
|
||||
if re.fullmatch(r"liver_cyst_\d+", segment_name):
|
||||
return "肝囊肿", "liver_cyst_*"
|
||||
if re.fullmatch(r"liver_hemangioma_\d+", segment_name):
|
||||
return "肝血管瘤", "liver_hemangioma_*"
|
||||
if re.fullmatch(r"pancreas_tumor_\d+", segment_name):
|
||||
return "胰腺肿瘤", "pancreas_tumor_*"
|
||||
if re.fullmatch(r"Segment_\d+", segment_name):
|
||||
return "未命名分割", "Segment_*"
|
||||
return "其他", segment_name
|
||||
|
||||
|
||||
def collect(processed_root: Path) -> dict[str, Any]:
|
||||
segment_counter: Counter[str] = Counter()
|
||||
category_counter: Counter[str] = Counter()
|
||||
family_counter: Counter[str] = Counter()
|
||||
family_ct: dict[str, set[str]] = defaultdict(set)
|
||||
category_ct: dict[str, set[str]] = defaultdict(set)
|
||||
name_ct: dict[str, set[str]] = defaultdict(set)
|
||||
family_members: dict[str, set[str]] = defaultdict(set)
|
||||
ordered_files: list[dict[str, Any]] = []
|
||||
ct_dirs = [item for item in processed_root.iterdir() if item.is_dir()]
|
||||
|
||||
for ct_dir in sorted(ct_dirs):
|
||||
ct_number = ct_dir.name
|
||||
for order_no, stl_file in enumerate(sorted(ct_dir.glob("*.stl")), start=1):
|
||||
segment_name = stl_file.stem
|
||||
category, family = classify(segment_name)
|
||||
segment_counter[segment_name] += 1
|
||||
category_counter[category] += 1
|
||||
family_counter[family] += 1
|
||||
name_ct[segment_name].add(ct_number)
|
||||
family_ct[family].add(ct_number)
|
||||
category_ct[category].add(ct_number)
|
||||
family_members[family].add(segment_name)
|
||||
ordered_files.append(
|
||||
{
|
||||
"ct_number": ct_number,
|
||||
"order_no": order_no,
|
||||
"segment_name": segment_name,
|
||||
"family": family,
|
||||
"category": category,
|
||||
"file_name": stl_file.name,
|
||||
"file_path": str(stl_file),
|
||||
}
|
||||
)
|
||||
|
||||
by_name = [
|
||||
{
|
||||
"segment_name": name,
|
||||
"category": classify(name)[0],
|
||||
"family": classify(name)[1],
|
||||
"file_count": count,
|
||||
"ct_count": len(name_ct[name]),
|
||||
}
|
||||
for name, count in segment_counter.most_common()
|
||||
]
|
||||
by_category = [
|
||||
{
|
||||
"category": category,
|
||||
"file_count": count,
|
||||
"ct_count": len(category_ct[category]),
|
||||
}
|
||||
for category, count in category_counter.most_common()
|
||||
]
|
||||
by_family = [
|
||||
{
|
||||
"family": family,
|
||||
"category": classify(family.replace("*", "1"))[0] if "*" in family else classify(family)[0],
|
||||
"file_count": count,
|
||||
"ct_count": len(family_ct[family]),
|
||||
"segment_name_count": len(family_members[family]),
|
||||
"segment_names": "|".join(sorted(family_members[family])),
|
||||
}
|
||||
for family, count in family_counter.most_common()
|
||||
]
|
||||
return {
|
||||
"processed_root": str(processed_root.relative_to(BASE_DIR)),
|
||||
"ct_count": len(ct_dirs),
|
||||
"stl_file_count": sum(segment_counter.values()),
|
||||
"unique_segment_name_count": len(segment_counter),
|
||||
"by_category": by_category,
|
||||
"by_family": by_family,
|
||||
"by_name": by_name,
|
||||
"ordered_files": ordered_files,
|
||||
}
|
||||
|
||||
|
||||
def write_csv(path: Path, rows: list[dict[str, Any]], fieldnames: list[str]) -> None:
|
||||
with path.open("w", encoding="utf-8", newline="") as file:
|
||||
writer = csv.DictWriter(file, fieldnames=fieldnames)
|
||||
writer.writeheader()
|
||||
writer.writerows(rows)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
result = collect(DEFAULT_PROCESSED_ROOT)
|
||||
ordered_files = result.pop("ordered_files")
|
||||
DEFAULT_JSON.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
write_csv(DEFAULT_CSV, result["by_name"], ["segment_name", "category", "family", "file_count", "ct_count"])
|
||||
write_csv(
|
||||
DEFAULT_FAMILY_CSV,
|
||||
result["by_family"],
|
||||
["family", "category", "file_count", "ct_count", "segment_name_count", "segment_names"],
|
||||
)
|
||||
write_csv(
|
||||
DEFAULT_ORDERED_CSV,
|
||||
ordered_files,
|
||||
["ct_number", "order_no", "segment_name", "family", "category", "file_name", "file_path"],
|
||||
)
|
||||
print(json.dumps({
|
||||
"ct_count": result["ct_count"],
|
||||
"stl_file_count": result["stl_file_count"],
|
||||
"unique_segment_name_count": result["unique_segment_name_count"],
|
||||
"json": str(DEFAULT_JSON),
|
||||
"csv": str(DEFAULT_CSV),
|
||||
"family_csv": str(DEFAULT_FAMILY_CSV),
|
||||
"ordered_csv": str(DEFAULT_ORDERED_CSV),
|
||||
}, ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
45
UPP_数据库构建/README.md
Normal file
45
UPP_数据库构建/README.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# UPP STL资产索引
|
||||
|
||||
本目录用于把 `UPP_STL处理/待处理STL数据` 和 `UPP列表处理` 的合并 list 建成 CT 号唯一索引。
|
||||
|
||||
处理规则:
|
||||
|
||||
- STL 候选目录必须能从路径中提取 `CT号`,格式为 `CT...` 或 `DCT...`。
|
||||
- 同一 CT 号有多个 STL 目录时,优先选择 STL 文件数更多的目录。
|
||||
- 文件数相同,则选择病例名或 `*-STL` 目录中序号更大的目录。
|
||||
- 规范化输出到 `UPP_STL处理/已处理STL数据/<CT号>/`。
|
||||
- 数据库中 `upp_exam_assets.ct_number` 是主键,可用 list 的检查号查询对应 STL 路径;未来 CT 数据也可以继续挂在同一行。
|
||||
|
||||
运行:
|
||||
|
||||
```bash
|
||||
cd /home/wkmgc/Desktop/PACS数据处理
|
||||
PGHOST='<host>' PGPORT='<port>' PGDATABASE='<database>' PGUSER='<user>' PGPASSWORD='<password>' \
|
||||
python3 UPP_数据库构建/02_同步UPP_STL资产.py
|
||||
```
|
||||
|
||||
主要表:
|
||||
|
||||
- `upp_exam_assets`:CT 号唯一资产索引,含 list 元数据、最终选中 STL 目录、候选目录、STL 文件清单。
|
||||
- `upp_stl_files`:最终选中 STL 的 CT 级聚合表,每个 CT 号唯一一行,文件名、分割名、family、分类、路径和完整文件清单用 JSONB 存储。
|
||||
|
||||
STL 名称统计:
|
||||
|
||||
```bash
|
||||
python3 UPP_数据库构建/03_统计STL名称.py
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
- `UPP_数据库构建/UPP_STL名称统计.json`
|
||||
- `UPP_数据库构建/UPP_STL名称统计.csv`
|
||||
- `UPP_数据库构建/UPP_STL_family统计.csv`
|
||||
- `UPP_数据库构建/UPP_STL文件family顺序明细.csv`,含逐 CT 文件路径,默认不提交
|
||||
|
||||
常用查询:
|
||||
|
||||
```sql
|
||||
SELECT ct_number, patient_name, processed_stl_dir, stl_file_count
|
||||
FROM upp_exam_assets
|
||||
WHERE ct_number = '<CT号>';
|
||||
```
|
||||
30
UPP_数据库构建/UPP_STL_family统计.csv
Normal file
30
UPP_数据库构建/UPP_STL_family统计.csv
Normal file
@@ -0,0 +1,30 @@
|
||||
family,category,file_count,ct_count,segment_name_count,segment_names
|
||||
liver_cyst_*,肝囊肿,945,272,37,liver_cyst_1|liver_cyst_10|liver_cyst_11|liver_cyst_12|liver_cyst_13|liver_cyst_14|liver_cyst_15|liver_cyst_16|liver_cyst_17|liver_cyst_18|liver_cyst_19|liver_cyst_2|liver_cyst_20|liver_cyst_21|liver_cyst_22|liver_cyst_23|liver_cyst_24|liver_cyst_25|liver_cyst_26|liver_cyst_27|liver_cyst_28|liver_cyst_29|liver_cyst_3|liver_cyst_30|liver_cyst_31|liver_cyst_32|liver_cyst_33|liver_cyst_34|liver_cyst_35|liver_cyst_36|liver_cyst_37|liver_cyst_4|liver_cyst_5|liver_cyst_6|liver_cyst_7|liver_cyst_8|liver_cyst_9
|
||||
liver_tumor_*,肝脏肿瘤,839,576,12,liver_tumor_1|liver_tumor_10|liver_tumor_11|liver_tumor_12|liver_tumor_2|liver_tumor_3|liver_tumor_4|liver_tumor_5|liver_tumor_6|liver_tumor_7|liver_tumor_8|liver_tumor_9
|
||||
liver,肝脏主体,725,725,1,liver
|
||||
liver_left,肝脏主体,725,725,1,liver_left
|
||||
liver_right,肝脏主体,725,725,1,liver_right
|
||||
liver_segment_S1,肝段,725,725,1,liver_segment_S1
|
||||
liver_segment_S2,肝段,725,725,1,liver_segment_S2
|
||||
liver_segment_S3,肝段,725,725,1,liver_segment_S3
|
||||
liver_segment_S4,肝段,725,725,1,liver_segment_S4
|
||||
liver_segment_S5,肝段,725,725,1,liver_segment_S5
|
||||
liver_segment_S6,肝段,725,725,1,liver_segment_S6
|
||||
liver_segment_S7,肝段,725,725,1,liver_segment_S7
|
||||
liver_segment_S8,肝段,725,725,1,liver_segment_S8
|
||||
skin,体表骨骼,725,725,1,skin
|
||||
liver_artery,血管胆管,724,724,1,liver_artery
|
||||
portal_vein,血管胆管,724,724,1,portal_vein
|
||||
rib,体表骨骼,724,724,1,rib
|
||||
vertebrae,体表骨骼,724,724,1,vertebrae
|
||||
liver_vein,血管胆管,723,723,1,liver_vein
|
||||
pancreas,腹部脏器,723,723,1,pancreas
|
||||
bile_duct,血管胆管,720,720,1,bile_duct
|
||||
spleen,腹部脏器,717,717,1,spleen
|
||||
sternum,体表骨骼,684,684,1,sternum
|
||||
cholecyst,腹部脏器,565,565,1,cholecyst
|
||||
hipbone,体表骨骼,455,455,1,hipbone
|
||||
sacrum,体表骨骼,445,445,1,sacrum
|
||||
liver_hemangioma_*,肝血管瘤,74,43,5,liver_hemangioma_1|liver_hemangioma_2|liver_hemangioma_3|liver_hemangioma_4|liver_hemangioma_5
|
||||
pancreas_tumor_*,胰腺肿瘤,12,12,1,pancreas_tumor_1
|
||||
Segment_*,未命名分割,9,5,4,Segment_0|Segment_1|Segment_2|Segment_3
|
||||
|
84
UPP_数据库构建/UPP_STL名称统计.csv
Normal file
84
UPP_数据库构建/UPP_STL名称统计.csv
Normal file
@@ -0,0 +1,84 @@
|
||||
segment_name,category,family,file_count,ct_count
|
||||
liver,肝脏主体,liver,725,725
|
||||
liver_left,肝脏主体,liver_left,725,725
|
||||
liver_right,肝脏主体,liver_right,725,725
|
||||
liver_segment_S1,肝段,liver_segment_S1,725,725
|
||||
liver_segment_S2,肝段,liver_segment_S2,725,725
|
||||
liver_segment_S3,肝段,liver_segment_S3,725,725
|
||||
liver_segment_S4,肝段,liver_segment_S4,725,725
|
||||
liver_segment_S5,肝段,liver_segment_S5,725,725
|
||||
liver_segment_S6,肝段,liver_segment_S6,725,725
|
||||
liver_segment_S7,肝段,liver_segment_S7,725,725
|
||||
liver_segment_S8,肝段,liver_segment_S8,725,725
|
||||
skin,体表骨骼,skin,725,725
|
||||
liver_artery,血管胆管,liver_artery,724,724
|
||||
portal_vein,血管胆管,portal_vein,724,724
|
||||
rib,体表骨骼,rib,724,724
|
||||
vertebrae,体表骨骼,vertebrae,724,724
|
||||
liver_vein,血管胆管,liver_vein,723,723
|
||||
pancreas,腹部脏器,pancreas,723,723
|
||||
bile_duct,血管胆管,bile_duct,720,720
|
||||
spleen,腹部脏器,spleen,717,717
|
||||
sternum,体表骨骼,sternum,684,684
|
||||
liver_tumor_1,肝脏肿瘤,liver_tumor_*,576,576
|
||||
cholecyst,腹部脏器,cholecyst,565,565
|
||||
hipbone,体表骨骼,hipbone,455,455
|
||||
sacrum,体表骨骼,sacrum,445,445
|
||||
liver_cyst_1,肝囊肿,liver_cyst_*,272,272
|
||||
liver_cyst_2,肝囊肿,liver_cyst_*,154,154
|
||||
liver_tumor_2,肝脏肿瘤,liver_tumor_*,143,143
|
||||
liver_cyst_3,肝囊肿,liver_cyst_*,102,102
|
||||
liver_cyst_4,肝囊肿,liver_cyst_*,70,70
|
||||
liver_cyst_5,肝囊肿,liver_cyst_*,59,59
|
||||
liver_tumor_3,肝脏肿瘤,liver_tumor_*,50,50
|
||||
liver_cyst_6,肝囊肿,liver_cyst_*,49,49
|
||||
liver_hemangioma_1,肝血管瘤,liver_hemangioma_*,43,43
|
||||
liver_cyst_7,肝囊肿,liver_cyst_*,36,36
|
||||
liver_cyst_8,肝囊肿,liver_cyst_*,28,28
|
||||
liver_tumor_4,肝脏肿瘤,liver_tumor_*,25,25
|
||||
liver_cyst_9,肝囊肿,liver_cyst_*,23,23
|
||||
liver_cyst_10,肝囊肿,liver_cyst_*,20,20
|
||||
liver_hemangioma_2,肝血管瘤,liver_hemangioma_*,16,16
|
||||
liver_cyst_11,肝囊肿,liver_cyst_*,15,15
|
||||
liver_cyst_12,肝囊肿,liver_cyst_*,12,12
|
||||
liver_cyst_13,肝囊肿,liver_cyst_*,12,12
|
||||
pancreas_tumor_1,胰腺肿瘤,pancreas_tumor_*,12,12
|
||||
liver_tumor_5,肝脏肿瘤,liver_tumor_*,12,12
|
||||
liver_cyst_14,肝囊肿,liver_cyst_*,11,11
|
||||
liver_tumor_6,肝脏肿瘤,liver_tumor_*,10,10
|
||||
liver_cyst_15,肝囊肿,liver_cyst_*,9,9
|
||||
liver_hemangioma_3,肝血管瘤,liver_hemangioma_*,9,9
|
||||
liver_tumor_7,肝脏肿瘤,liver_tumor_*,9,9
|
||||
liver_cyst_16,肝囊肿,liver_cyst_*,8,8
|
||||
liver_cyst_17,肝囊肿,liver_cyst_*,7,7
|
||||
liver_cyst_18,肝囊肿,liver_cyst_*,6,6
|
||||
liver_cyst_19,肝囊肿,liver_cyst_*,6,6
|
||||
liver_cyst_20,肝囊肿,liver_cyst_*,6,6
|
||||
liver_cyst_21,肝囊肿,liver_cyst_*,5,5
|
||||
liver_tumor_8,肝脏肿瘤,liver_tumor_*,5,5
|
||||
liver_hemangioma_4,肝血管瘤,liver_hemangioma_*,4,4
|
||||
liver_cyst_22,肝囊肿,liver_cyst_*,4,4
|
||||
liver_cyst_23,肝囊肿,liver_cyst_*,4,4
|
||||
liver_cyst_24,肝囊肿,liver_cyst_*,4,4
|
||||
liver_cyst_25,肝囊肿,liver_cyst_*,4,4
|
||||
Segment_0,未命名分割,Segment_*,4,4
|
||||
liver_tumor_9,肝脏肿瘤,liver_tumor_*,4,4
|
||||
liver_cyst_26,肝囊肿,liver_cyst_*,3,3
|
||||
liver_cyst_27,肝囊肿,liver_cyst_*,3,3
|
||||
liver_cyst_28,肝囊肿,liver_cyst_*,3,3
|
||||
liver_tumor_10,肝脏肿瘤,liver_tumor_*,3,3
|
||||
liver_hemangioma_5,肝血管瘤,liver_hemangioma_*,2,2
|
||||
liver_cyst_29,肝囊肿,liver_cyst_*,2,2
|
||||
Segment_1,未命名分割,Segment_*,2,2
|
||||
Segment_2,未命名分割,Segment_*,2,2
|
||||
liver_cyst_30,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_31,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_32,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_33,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_34,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_35,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_36,肝囊肿,liver_cyst_*,1,1
|
||||
liver_cyst_37,肝囊肿,liver_cyst_*,1,1
|
||||
Segment_3,未命名分割,Segment_*,1,1
|
||||
liver_tumor_11,肝脏肿瘤,liver_tumor_*,1,1
|
||||
liver_tumor_12,肝脏肿瘤,liver_tumor_*,1,1
|
||||
|
875
UPP_数据库构建/UPP_STL名称统计.json
Normal file
875
UPP_数据库构建/UPP_STL名称统计.json
Normal file
@@ -0,0 +1,875 @@
|
||||
{
|
||||
"processed_root": "UPP_STL处理/已处理STL数据",
|
||||
"ct_count": 726,
|
||||
"stl_file_count": 18507,
|
||||
"unique_segment_name_count": 83,
|
||||
"by_category": [
|
||||
{
|
||||
"category": "肝段",
|
||||
"file_count": 5800,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"category": "体表骨骼",
|
||||
"file_count": 3757,
|
||||
"ct_count": 726
|
||||
},
|
||||
{
|
||||
"category": "血管胆管",
|
||||
"file_count": 2891,
|
||||
"ct_count": 724
|
||||
},
|
||||
{
|
||||
"category": "肝脏主体",
|
||||
"file_count": 2175,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"category": "腹部脏器",
|
||||
"file_count": 2005,
|
||||
"ct_count": 724
|
||||
},
|
||||
{
|
||||
"category": "肝囊肿",
|
||||
"file_count": 945,
|
||||
"ct_count": 272
|
||||
},
|
||||
{
|
||||
"category": "肝脏肿瘤",
|
||||
"file_count": 839,
|
||||
"ct_count": 576
|
||||
},
|
||||
{
|
||||
"category": "肝血管瘤",
|
||||
"file_count": 74,
|
||||
"ct_count": 43
|
||||
},
|
||||
{
|
||||
"category": "胰腺肿瘤",
|
||||
"file_count": 12,
|
||||
"ct_count": 12
|
||||
},
|
||||
{
|
||||
"category": "未命名分割",
|
||||
"file_count": 9,
|
||||
"ct_count": 5
|
||||
}
|
||||
],
|
||||
"by_family": [
|
||||
{
|
||||
"family": "liver_cyst_*",
|
||||
"category": "肝囊肿",
|
||||
"file_count": 945,
|
||||
"ct_count": 272,
|
||||
"segment_name_count": 37,
|
||||
"segment_names": "liver_cyst_1|liver_cyst_10|liver_cyst_11|liver_cyst_12|liver_cyst_13|liver_cyst_14|liver_cyst_15|liver_cyst_16|liver_cyst_17|liver_cyst_18|liver_cyst_19|liver_cyst_2|liver_cyst_20|liver_cyst_21|liver_cyst_22|liver_cyst_23|liver_cyst_24|liver_cyst_25|liver_cyst_26|liver_cyst_27|liver_cyst_28|liver_cyst_29|liver_cyst_3|liver_cyst_30|liver_cyst_31|liver_cyst_32|liver_cyst_33|liver_cyst_34|liver_cyst_35|liver_cyst_36|liver_cyst_37|liver_cyst_4|liver_cyst_5|liver_cyst_6|liver_cyst_7|liver_cyst_8|liver_cyst_9"
|
||||
},
|
||||
{
|
||||
"family": "liver_tumor_*",
|
||||
"category": "肝脏肿瘤",
|
||||
"file_count": 839,
|
||||
"ct_count": 576,
|
||||
"segment_name_count": 12,
|
||||
"segment_names": "liver_tumor_1|liver_tumor_10|liver_tumor_11|liver_tumor_12|liver_tumor_2|liver_tumor_3|liver_tumor_4|liver_tumor_5|liver_tumor_6|liver_tumor_7|liver_tumor_8|liver_tumor_9"
|
||||
},
|
||||
{
|
||||
"family": "liver",
|
||||
"category": "肝脏主体",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver"
|
||||
},
|
||||
{
|
||||
"family": "liver_left",
|
||||
"category": "肝脏主体",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_left"
|
||||
},
|
||||
{
|
||||
"family": "liver_right",
|
||||
"category": "肝脏主体",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_right"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S1",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S1"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S2",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S2"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S3",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S3"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S4",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S4"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S5",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S5"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S6",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S6"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S7",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S7"
|
||||
},
|
||||
{
|
||||
"family": "liver_segment_S8",
|
||||
"category": "肝段",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_segment_S8"
|
||||
},
|
||||
{
|
||||
"family": "skin",
|
||||
"category": "体表骨骼",
|
||||
"file_count": 725,
|
||||
"ct_count": 725,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "skin"
|
||||
},
|
||||
{
|
||||
"family": "liver_artery",
|
||||
"category": "血管胆管",
|
||||
"file_count": 724,
|
||||
"ct_count": 724,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_artery"
|
||||
},
|
||||
{
|
||||
"family": "portal_vein",
|
||||
"category": "血管胆管",
|
||||
"file_count": 724,
|
||||
"ct_count": 724,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "portal_vein"
|
||||
},
|
||||
{
|
||||
"family": "rib",
|
||||
"category": "体表骨骼",
|
||||
"file_count": 724,
|
||||
"ct_count": 724,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "rib"
|
||||
},
|
||||
{
|
||||
"family": "vertebrae",
|
||||
"category": "体表骨骼",
|
||||
"file_count": 724,
|
||||
"ct_count": 724,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "vertebrae"
|
||||
},
|
||||
{
|
||||
"family": "liver_vein",
|
||||
"category": "血管胆管",
|
||||
"file_count": 723,
|
||||
"ct_count": 723,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "liver_vein"
|
||||
},
|
||||
{
|
||||
"family": "pancreas",
|
||||
"category": "腹部脏器",
|
||||
"file_count": 723,
|
||||
"ct_count": 723,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "pancreas"
|
||||
},
|
||||
{
|
||||
"family": "bile_duct",
|
||||
"category": "血管胆管",
|
||||
"file_count": 720,
|
||||
"ct_count": 720,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "bile_duct"
|
||||
},
|
||||
{
|
||||
"family": "spleen",
|
||||
"category": "腹部脏器",
|
||||
"file_count": 717,
|
||||
"ct_count": 717,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "spleen"
|
||||
},
|
||||
{
|
||||
"family": "sternum",
|
||||
"category": "体表骨骼",
|
||||
"file_count": 684,
|
||||
"ct_count": 684,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "sternum"
|
||||
},
|
||||
{
|
||||
"family": "cholecyst",
|
||||
"category": "腹部脏器",
|
||||
"file_count": 565,
|
||||
"ct_count": 565,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "cholecyst"
|
||||
},
|
||||
{
|
||||
"family": "hipbone",
|
||||
"category": "体表骨骼",
|
||||
"file_count": 455,
|
||||
"ct_count": 455,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "hipbone"
|
||||
},
|
||||
{
|
||||
"family": "sacrum",
|
||||
"category": "体表骨骼",
|
||||
"file_count": 445,
|
||||
"ct_count": 445,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "sacrum"
|
||||
},
|
||||
{
|
||||
"family": "liver_hemangioma_*",
|
||||
"category": "肝血管瘤",
|
||||
"file_count": 74,
|
||||
"ct_count": 43,
|
||||
"segment_name_count": 5,
|
||||
"segment_names": "liver_hemangioma_1|liver_hemangioma_2|liver_hemangioma_3|liver_hemangioma_4|liver_hemangioma_5"
|
||||
},
|
||||
{
|
||||
"family": "pancreas_tumor_*",
|
||||
"category": "胰腺肿瘤",
|
||||
"file_count": 12,
|
||||
"ct_count": 12,
|
||||
"segment_name_count": 1,
|
||||
"segment_names": "pancreas_tumor_1"
|
||||
},
|
||||
{
|
||||
"family": "Segment_*",
|
||||
"category": "未命名分割",
|
||||
"file_count": 9,
|
||||
"ct_count": 5,
|
||||
"segment_name_count": 4,
|
||||
"segment_names": "Segment_0|Segment_1|Segment_2|Segment_3"
|
||||
}
|
||||
],
|
||||
"by_name": [
|
||||
{
|
||||
"segment_name": "liver",
|
||||
"category": "肝脏主体",
|
||||
"family": "liver",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_left",
|
||||
"category": "肝脏主体",
|
||||
"family": "liver_left",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_right",
|
||||
"category": "肝脏主体",
|
||||
"family": "liver_right",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S1",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S1",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S2",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S2",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S3",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S3",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S4",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S4",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S5",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S5",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S6",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S6",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S7",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S7",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_segment_S8",
|
||||
"category": "肝段",
|
||||
"family": "liver_segment_S8",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "skin",
|
||||
"category": "体表骨骼",
|
||||
"family": "skin",
|
||||
"file_count": 725,
|
||||
"ct_count": 725
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_artery",
|
||||
"category": "血管胆管",
|
||||
"family": "liver_artery",
|
||||
"file_count": 724,
|
||||
"ct_count": 724
|
||||
},
|
||||
{
|
||||
"segment_name": "portal_vein",
|
||||
"category": "血管胆管",
|
||||
"family": "portal_vein",
|
||||
"file_count": 724,
|
||||
"ct_count": 724
|
||||
},
|
||||
{
|
||||
"segment_name": "rib",
|
||||
"category": "体表骨骼",
|
||||
"family": "rib",
|
||||
"file_count": 724,
|
||||
"ct_count": 724
|
||||
},
|
||||
{
|
||||
"segment_name": "vertebrae",
|
||||
"category": "体表骨骼",
|
||||
"family": "vertebrae",
|
||||
"file_count": 724,
|
||||
"ct_count": 724
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_vein",
|
||||
"category": "血管胆管",
|
||||
"family": "liver_vein",
|
||||
"file_count": 723,
|
||||
"ct_count": 723
|
||||
},
|
||||
{
|
||||
"segment_name": "pancreas",
|
||||
"category": "腹部脏器",
|
||||
"family": "pancreas",
|
||||
"file_count": 723,
|
||||
"ct_count": 723
|
||||
},
|
||||
{
|
||||
"segment_name": "bile_duct",
|
||||
"category": "血管胆管",
|
||||
"family": "bile_duct",
|
||||
"file_count": 720,
|
||||
"ct_count": 720
|
||||
},
|
||||
{
|
||||
"segment_name": "spleen",
|
||||
"category": "腹部脏器",
|
||||
"family": "spleen",
|
||||
"file_count": 717,
|
||||
"ct_count": 717
|
||||
},
|
||||
{
|
||||
"segment_name": "sternum",
|
||||
"category": "体表骨骼",
|
||||
"family": "sternum",
|
||||
"file_count": 684,
|
||||
"ct_count": 684
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_1",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 576,
|
||||
"ct_count": 576
|
||||
},
|
||||
{
|
||||
"segment_name": "cholecyst",
|
||||
"category": "腹部脏器",
|
||||
"family": "cholecyst",
|
||||
"file_count": 565,
|
||||
"ct_count": 565
|
||||
},
|
||||
{
|
||||
"segment_name": "hipbone",
|
||||
"category": "体表骨骼",
|
||||
"family": "hipbone",
|
||||
"file_count": 455,
|
||||
"ct_count": 455
|
||||
},
|
||||
{
|
||||
"segment_name": "sacrum",
|
||||
"category": "体表骨骼",
|
||||
"family": "sacrum",
|
||||
"file_count": 445,
|
||||
"ct_count": 445
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_1",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 272,
|
||||
"ct_count": 272
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_2",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 154,
|
||||
"ct_count": 154
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_2",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 143,
|
||||
"ct_count": 143
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_3",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 102,
|
||||
"ct_count": 102
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_4",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 70,
|
||||
"ct_count": 70
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_5",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 59,
|
||||
"ct_count": 59
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_3",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 50,
|
||||
"ct_count": 50
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_6",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 49,
|
||||
"ct_count": 49
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_hemangioma_1",
|
||||
"category": "肝血管瘤",
|
||||
"family": "liver_hemangioma_*",
|
||||
"file_count": 43,
|
||||
"ct_count": 43
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_7",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 36,
|
||||
"ct_count": 36
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_8",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 28,
|
||||
"ct_count": 28
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_4",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 25,
|
||||
"ct_count": 25
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_9",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 23,
|
||||
"ct_count": 23
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_10",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 20,
|
||||
"ct_count": 20
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_hemangioma_2",
|
||||
"category": "肝血管瘤",
|
||||
"family": "liver_hemangioma_*",
|
||||
"file_count": 16,
|
||||
"ct_count": 16
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_11",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 15,
|
||||
"ct_count": 15
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_12",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 12,
|
||||
"ct_count": 12
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_13",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 12,
|
||||
"ct_count": 12
|
||||
},
|
||||
{
|
||||
"segment_name": "pancreas_tumor_1",
|
||||
"category": "胰腺肿瘤",
|
||||
"family": "pancreas_tumor_*",
|
||||
"file_count": 12,
|
||||
"ct_count": 12
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_5",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 12,
|
||||
"ct_count": 12
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_14",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 11,
|
||||
"ct_count": 11
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_6",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 10,
|
||||
"ct_count": 10
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_15",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 9,
|
||||
"ct_count": 9
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_hemangioma_3",
|
||||
"category": "肝血管瘤",
|
||||
"family": "liver_hemangioma_*",
|
||||
"file_count": 9,
|
||||
"ct_count": 9
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_7",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 9,
|
||||
"ct_count": 9
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_16",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 8,
|
||||
"ct_count": 8
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_17",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 7,
|
||||
"ct_count": 7
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_18",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 6,
|
||||
"ct_count": 6
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_19",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 6,
|
||||
"ct_count": 6
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_20",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 6,
|
||||
"ct_count": 6
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_21",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 5,
|
||||
"ct_count": 5
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_8",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 5,
|
||||
"ct_count": 5
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_hemangioma_4",
|
||||
"category": "肝血管瘤",
|
||||
"family": "liver_hemangioma_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_22",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_23",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_24",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_25",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "Segment_0",
|
||||
"category": "未命名分割",
|
||||
"family": "Segment_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_9",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 4,
|
||||
"ct_count": 4
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_26",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 3,
|
||||
"ct_count": 3
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_27",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 3,
|
||||
"ct_count": 3
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_28",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 3,
|
||||
"ct_count": 3
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_10",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 3,
|
||||
"ct_count": 3
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_hemangioma_5",
|
||||
"category": "肝血管瘤",
|
||||
"family": "liver_hemangioma_*",
|
||||
"file_count": 2,
|
||||
"ct_count": 2
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_29",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 2,
|
||||
"ct_count": 2
|
||||
},
|
||||
{
|
||||
"segment_name": "Segment_1",
|
||||
"category": "未命名分割",
|
||||
"family": "Segment_*",
|
||||
"file_count": 2,
|
||||
"ct_count": 2
|
||||
},
|
||||
{
|
||||
"segment_name": "Segment_2",
|
||||
"category": "未命名分割",
|
||||
"family": "Segment_*",
|
||||
"file_count": 2,
|
||||
"ct_count": 2
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_30",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_31",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_32",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_33",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_34",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_35",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_36",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_cyst_37",
|
||||
"category": "肝囊肿",
|
||||
"family": "liver_cyst_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "Segment_3",
|
||||
"category": "未命名分割",
|
||||
"family": "Segment_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_11",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
},
|
||||
{
|
||||
"segment_name": "liver_tumor_12",
|
||||
"category": "肝脏肿瘤",
|
||||
"family": "liver_tumor_*",
|
||||
"file_count": 1,
|
||||
"ct_count": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user