Python 的 for
循环是基于迭代器的,enumerate
会依次提供下一个索引和对应的元素。通过手动操作更新状态(如加入第 7 和第 8 张)并跳过循环中的部分逻辑后,索引会自动递增到未处理的项。
例子:拆分子序列
1 [-218.400, -202.000, -54.000]
2 [-218.400, -202.000, -54.625]
3 [-218.400, -202.000, -55.250]
4 [-218.400, -202.000, -55.875]
5 [-218.400, -202.000, -56.500]
6 [-218.400, -202.000, -54.000]
7 [-218.400, -202.000, -59.000]
8 [-218.400, -202.000, -64.000]
9 [-218.400, -202.000, -69.000]
import os
import pydicom
import shutil
from collections import defaultdict
import numpy as npdef load_dicom_images(dicom_directory):"""从指定目录加载 DICOM 图像,并提取必要的属性。"""dicom_files = [] # 空列表,用来存储从 DICOM 文件中提取的信息,每个元素是一个字典for root, dirs, files in os.walk(dicom_directory): # 遍历目录中的所有文件for file in files:try:dicom_path = os.path.join(root, file) # 构建完整文件路径dicom = pydicom.dcmread(dicom_path) # 读取 DICOM 文件dicom_files.append({"path": dicom_path, # 文件路径"instance_number": dicom.get("InstanceNumber", 0), # 实例编号"image_type": dicom.get("ImageType", []), # 图像类型"position": np.array(dicom.ImagePositionPatient,dtype=float) if "ImagePositionPatient" in dicom else None, # 图像位置"orientation": np.array(dicom.ImageOrientationPatient,dtype=float) if "ImageOrientationPatient" in dicom else None, # 图像方向})except Exception as e:# 如果读取失败,打印错误信息print(f"读取 {file} 失败: {e}")return dicom_filesdef classify_images(dicom_files):"""根据 ImageType 将图像分类为 Localizer 和 Axial。"""localizer_images = [] # 用于存储 Localizer 图像axial_images = [] # 用于存储 Axial 图像for dicom in dicom_files:image_type = dicom["image_type"] # 获取图像类型if "LOCALIZER" in image_type: # 如果类型中包含 LOCALIZERlocalizer_images.append(dicom)elif "AXIAL" in image_type or len(dicom_files) > 3: # 如果类型中包含 AXIAL 或文件总数超过 3axial_images.append(dicom)print(f"Localizer 图像数量: {len(localizer_images)}")print(f"Axial 图像数量: {len(axial_images)}")return localizer_images, axial_imagesdef split_into_subseries(dicom_files, output_directory):"""根据位置差异和方向,将图像划分为子序列,避免不必要的拆分。"""dicom_files.sort(key=lambda x: x["instance_number"]) # 按实例编号排序,确保图像顺序一致subseries = defaultdict(list) # 用于存储每个子序列current_subseries_index = 0 # 当前子序列的索引previous_position = None # 上一张图像的位置no_step = None # 记录正常步长for i, dicom in enumerate(dicom_files):current_position = dicom["position"] # 当前图像的位置if previous_position is None: # 如果是第一张图像previous_position = current_position # 初始化上一张图像的位置subseries[current_subseries_index].append(dicom) # 添加到当前子序列continue# 计算当前图像与上一张图像之间的步长step = np.linalg.norm(current_position - previous_position)if no_step is None: # 如果尚未初始化步长no_step = step # 初始化步长elif abs(step - no_step) > 0.01: # 判断是否为新子序列的条件# 创建一个新的子序列current_subseries_index += 1subseries[current_subseries_index].append(dicom)# 判断是否需要将接下来的两张图像归入当前子序列if i + 2 < len(dicom_files): # 确保至少有两张图像可以检查next_position = dicom_files[i + 1]["position"] # 下一张图像的位置next_next_position = dicom_files[i + 2]["position"] # 再下一张图像的位置# 计算步长step_1 = np.linalg.norm(next_position - current_position) # 当前到下一张的步长step_2 = np.linalg.norm(next_next_position - next_position) # 下一张到再下一张的步长# 比较步长差异if abs(step_1 - step_2) < 0.01: # 如果步长差异较小# 将两张图像都归入当前子序列subseries[current_subseries_index].append(dicom_files[i + 1])subseries[current_subseries_index].append(dicom_files[i + 2])# 更新位置变量以跳过这些图像current_position = next_next_position # 更新为再下一张的位置previous_position = current_position # 同步上一张位置no_step = step_2 # 更新步长为新的步长continue # 跳过本次循环,直接处理后续图像# 如果步长变化大,更新步长no_step = stepelse:# 如果步长在阈值范围内,将图像添加到当前子序列subseries[current_subseries_index].append(dicom)# 更新上一张图像的位置previous_position = current_positionprint(f"拆分成的子序列总数: {len(subseries)}")# 将每个子序列保存到对应的输出目录for index, images in subseries.items():subseries_path = os.path.join(output_directory, f"subseries_{index + 1}")os.makedirs(subseries_path, exist_ok=True) # 确保目录存在for dicom in images:dicom_basename = os.path.basename(dicom["path"]) # 获取文件名output_path = os.path.join(subseries_path, dicom_basename) # 构建输出路径shutil.copy(dicom["path"], output_path) # 复制文件到目标目录def main(dicom_directory, output_directory):# 加载 DICOM 图像dicom_files = load_dicom_images(dicom_directory)# 将图像分类为 Localizer 和 Axiallocalizer_images, axial_images = classify_images(dicom_files)# 分别处理 Localizer 和 Axial 图像的子序列创建print("正在处理 Localizer 图像...")if localizer_images:localizer_output = os.path.join(output_directory, "Localizer")os.makedirs(localizer_output, exist_ok=True) # 确保输出目录存在split_into_subseries(localizer_images, localizer_output)print("正在处理 Axial 图像...")if axial_images:axial_output = os.path.join(output_directory, "Axial")os.makedirs(axial_output, exist_ok=True) # 确保输出目录存在split_into_subseries(axial_images, axial_output)print(f"子序列已保存至 {output_directory}")# 示例用法
dicom_directory = 'D:/Users/LX9999/Desktop/CT-jiao-553/CT-JIAO SHAN XUE-20241118'
output_directory = 'D:/Users/LX9999/Desktop/CT-jiao-553/553-1-output'
main(dicom_directory, output_directory)
核心点:for
循环的机制
在 Python 中,for i, dicom in enumerate(dicom_files)
是基于一个迭代器工作的:
- 每次循环都会从
dicom_files
中获取下一个元素,同时更新索引i
。 continue
只跳过当前循环的剩余部分,并不会直接修改循环索引i
。
真正影响跳过逻辑的代码:
关键在 手动修改状态和 continue
结合迭代器的自动递增,具体看这一段代码:
if abs(step_1 - step_2) < 0.01:# 如果差异较小,将两张图像都加入当前序列subseries[current_subseries_index].append(dicom_files[i + 1])subseries[current_subseries_index].append(dicom_files[i + 2])# 更新位置变量current_position = dicom_files[i + 2]["position"]previous_position = current_positionno_step = step_2# 跳过后续两张图像的逻辑部分continue
拆解逻辑:跳过的关键点
-
手动操作的核心:
dicom_files[i + 1]
和dicom_files[i + 2]
- 在处理第 6 张图像时,代码手动读取了 第 7 张和第 8 张 的数据:
subseries[current_subseries_index].append(dicom_files[i + 1]) subseries[current_subseries_index].append(dicom_files[i + 2])
- 这意味着程序已经 主动处理 了第 7 张和第 8 张,因此在逻辑上跳过了它们。
- 在处理第 6 张图像时,代码手动读取了 第 7 张和第 8 张 的数据:
-
continue
的作用:continue
不会跳过两次迭代。- 它只是结束当前循环,直接进入下一次迭代。
- 当循环进入下一次迭代时,
enumerate
会根据其内部逻辑自动更新索引i
。
-
enumerate
的索引自增:- 在处理第 6 张图像时,
i = 5
。 - 下一次迭代时,
enumerate
的索引会自动更新为6
,即对应 第 7 张图像。
- 在处理第 6 张图像时,
-
为什么跳过了第 7 和第 8 张?
- 虽然
i = 6
时,程序会进入第 7 张图像,但此时程序已经提前 手动处理过第 7 张和第 8 张。 - 由于第 7 和第 8 张已经包含在当前序列,接下来的逻辑无需重复处理它们。
- 于是,循环继续处理 第 9 张图像(
i = 8
)。
- 虽然
跳过的效果是如何实现的?
跳过的 效果 并不是直接修改索引 i
,而是通过以下逻辑 间接跳过:
- 手动处理了第 7 和第 8 张:
- 在
if abs(step_1 - step_2) < 0.01
条件下,将第 7 张和第 8 张加入序列,已经明确它们的归属。
- 在
- 循环索引自然递增:
- 即便
enumerate
会尝试递增索引,程序的主逻辑中已经不需要处理第 7 和第 8 张。
- 即便
- 更新位置变量并
continue
:- 更新了
current_position
和previous_position
为第 8 张的位置:current_position = dicom_files[i + 2]["position"] previous_position = current_position
- 这些变量控制了后续逻辑,使得程序能够顺利进入第 9 张的处理。
- 更新了