add DICOM metadata details modal
This commit is contained in:
104
web_backend.py
104
web_backend.py
@@ -17,6 +17,7 @@ from urllib.parse import parse_qs, quote, unquote, urlparse
|
||||
os.environ.setdefault("MPLCONFIGDIR", "/tmp/head_ct_morph_matplotlib")
|
||||
|
||||
import pydicom
|
||||
from pydicom.multival import MultiValue
|
||||
from PIL import Image
|
||||
|
||||
from generate_head_extension_video import generate_video
|
||||
@@ -187,6 +188,103 @@ def make_library_slice_preview(item_id, index):
|
||||
}
|
||||
|
||||
|
||||
def dicom_value(ds, name, fallback="-"):
|
||||
value = getattr(ds, name, fallback)
|
||||
if value in [None, ""]:
|
||||
return fallback
|
||||
if isinstance(value, (list, tuple, MultiValue)):
|
||||
return " / ".join(str(item) for item in value)
|
||||
return str(value)
|
||||
|
||||
|
||||
def dicom_date(value):
|
||||
value = str(value or "")
|
||||
if len(value) == 8 and value.isdigit():
|
||||
return f"{value[0:4]}-{value[4:6]}-{value[6:8]}"
|
||||
return value or "-"
|
||||
|
||||
|
||||
def dicom_time(value):
|
||||
value = str(value or "")
|
||||
if len(value) >= 6 and value[:6].isdigit():
|
||||
return f"{value[0:2]}:{value[2:4]}:{value[4:6]}"
|
||||
return value or "-"
|
||||
|
||||
|
||||
def make_library_info(item_id):
|
||||
item = find_library_item(item_id)
|
||||
if not item:
|
||||
raise RuntimeError("影像库中没有找到该数据。")
|
||||
|
||||
dicom_files = sorted_dicom_files(item["dicomPath"])
|
||||
if not dicom_files:
|
||||
raise RuntimeError("该影像数据没有可读取的 .dcm 文件。")
|
||||
|
||||
first = pydicom.dcmread(str(dicom_files[0]), stop_before_pixels=True, force=True)
|
||||
last = pydicom.dcmread(str(dicom_files[-1]), stop_before_pixels=True, force=True)
|
||||
pixel_spacing = dicom_value(first, "PixelSpacing")
|
||||
matrix = f"{dicom_value(first, 'Columns')} x {dicom_value(first, 'Rows')}"
|
||||
instance_range = f"{dicom_value(first, 'InstanceNumber')} - {dicom_value(last, 'InstanceNumber')}"
|
||||
|
||||
return {
|
||||
"id": item["id"],
|
||||
"patientId": item["patientId"],
|
||||
"fileCount": len(dicom_files),
|
||||
"groups": [
|
||||
{
|
||||
"title": "患者信息",
|
||||
"items": [
|
||||
{"label": "患者姓名", "value": dicom_value(first, "PatientName")},
|
||||
{"label": "患者 ID", "value": dicom_value(first, "PatientID")},
|
||||
{"label": "性别", "value": dicom_value(first, "PatientSex")},
|
||||
{"label": "年龄", "value": dicom_value(first, "PatientAge")},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "检查信息",
|
||||
"items": [
|
||||
{"label": "检查日期", "value": dicom_date(getattr(first, "StudyDate", ""))},
|
||||
{"label": "检查时间", "value": dicom_time(getattr(first, "StudyTime", ""))},
|
||||
{"label": "检查描述", "value": dicom_value(first, "StudyDescription")},
|
||||
{"label": "检查号", "value": dicom_value(first, "AccessionNumber")},
|
||||
{"label": "机构", "value": dicom_value(first, "InstitutionName")},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "序列信息",
|
||||
"items": [
|
||||
{"label": "模态", "value": dicom_value(first, "Modality")},
|
||||
{"label": "部位", "value": dicom_value(first, "BodyPartExamined")},
|
||||
{"label": "序列描述", "value": dicom_value(first, "SeriesDescription")},
|
||||
{"label": "序列号", "value": dicom_value(first, "SeriesNumber")},
|
||||
{"label": "制造商", "value": dicom_value(first, "Manufacturer")},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "图像参数",
|
||||
"items": [
|
||||
{"label": "切片数", "value": str(len(dicom_files))},
|
||||
{"label": "Instance 范围", "value": instance_range},
|
||||
{"label": "矩阵", "value": matrix},
|
||||
{"label": "像素间距", "value": pixel_spacing},
|
||||
{"label": "层厚", "value": dicom_value(first, "SliceThickness")},
|
||||
{"label": "层间距", "value": dicom_value(first, "SpacingBetweenSlices")},
|
||||
{"label": "卷积核", "value": dicom_value(first, "ConvolutionKernel")},
|
||||
],
|
||||
},
|
||||
{
|
||||
"title": "扫描参数",
|
||||
"items": [
|
||||
{"label": "KVP", "value": dicom_value(first, "KVP")},
|
||||
{"label": "管电流", "value": dicom_value(first, "XRayTubeCurrent")},
|
||||
{"label": "曝光时间", "value": dicom_value(first, "ExposureTime")},
|
||||
{"label": "重建直径", "value": dicom_value(first, "ReconstructionDiameter")},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def parse_multipart(headers, body):
|
||||
content_type = headers.get("content-type", "")
|
||||
message = BytesParser(policy=default).parsebytes(
|
||||
@@ -418,6 +516,12 @@ class Handler(BaseHTTPRequestHandler):
|
||||
self.send_json(make_library_slice_preview(item_id, index))
|
||||
return
|
||||
|
||||
if parsed.path == "/api/library/info":
|
||||
params = parse_qs(parsed.query)
|
||||
item_id = params.get("id", [""])[0]
|
||||
self.send_json(make_library_info(item_id))
|
||||
return
|
||||
|
||||
if parsed.path == "/api/job":
|
||||
params = parse_qs(parsed.query)
|
||||
job_id = params.get("id", [""])[0]
|
||||
|
||||
Reference in New Issue
Block a user