CoolFace
Modelpublic

DRDMsig/Data_Engineering

sourceHugging Facemitupdated 5mo agoView on Hugging Face
0likes
dataclean_OASIS_1_CS_Sectional.py359 linesDownload Raw Back to OAISIS_clean
1#coding:utf-8
2'''
3write by ygq
4create on 2025-09-01
5
6OASIS(Open Access Series of Imaging Studies) 是一个旨在向科研界免费提供脑部MRI数据的项目。本横断面(Cross-Sectional)数据集是其第一个版本,发布于2007年。
7OASIS-1 是横断面的,意味着它无法捕捉个体随时间的动态变化。对于研究疾病进展,后续的 OASIS-2 和 OASIS-3(纵向数据集)是更好的选择。
8
91. 目录与文件命名规则
10    根目录下按受试者会话ID建立文件夹。
11    受试者ID格式:OAS1_xxxx (例如 OAS1_0012)
12    会话ID格式:OAS1_xxxx_MRy (例如 OAS1_0012_MR1,y代表第几次访问成像)
13    OAS1_xxxx_MRy/
14    │
15    ├── OAS1_xxxx_MRy.xml     # 包含采集细节和解剖指标的XML元数据文件
16    ├── OAS1_xxxx_MRy.txt     # 与XML内容相同的文本格式文件(便于查看)
17    ├── RAW/                  # 存储原始扫描图像(DICOM或Analyze格式)
18    ├── PROCESSED/            # 预处理后的图像
19    │   ├── SUBJ_111/         # 原始空间下的平均配准图像(各向同性1mm³)
20    │   └── T88_111/          # 图谱配准空间下的图像
21    │       ├── t4_files/     # 存储配准变换矩阵文件
22    │       └── ...           # 配准后的图像文件
23    └── FSL_SEG/              # 基于图谱配准图像生成的脑组织分割结果(灰质2/白质3/脑脊液1)
24
25
26所有图像均以 Analyze 7.5格式 存储,包含:
27    一个图像文件(.img)
28    一个头文件(.hdr)
29    使用 16位大端序(big-endian) 存储
30
31    OAS1_xxxx_MRy_mpr-z_anon	单次原始扫描	256x256x128	1x1x1.25 mm	矢状位
32    OAS1_xxxx_MRy_mpr_ni_anon_sbj_111	多次扫描平均配准图像	256x256x160	1x1x1 mm	矢状位
33    OAS1_xxxx_MRy_mpr_ni_anon_111_t88_gfc	增益场校正后的图谱配准图像	176x208x176	1x1x1 mm	横断位
34    OAS1_xxxx_MRy_mpr_ni_anon_111_t88_masked_gfc	去除非脑组织的掩模图像	176x208x176	1x1x1 mm	横断位
35    OAS1_xxxx_MRy_mpr_ni_anon_111_t88_masked_gfc_fseg	脑组织分割图像(灰/白/CSF)	176x208x176	1x1x1 mm	横断位
36
37    1. 人口统计学信息
38        性别(M/F)
39        用手习惯(Hand)(均为右利手)
40        年龄(Age)
41        教育程度(Educ)(1-5级)
42        社会经济地位(SES)
43
44    2. 临床评估
45        MMSE(简易精神状态检查)
46        CDR(临床痴呆评级:0=正常,0.5=非常轻微,1=轻度,2=中度)
47
48    3. 衍生解剖指标
49        eTIV:估计颅内容积
50        ASF:图谱缩放因子
51        nWBV:标准化全脑体积
52
53
54 OASIS Cross-Sectional 数据集经过 FreeSurfer 处理后的版本。这通常被称为 OASIS Cross-Sectional FreeSurfer Processed 数据集
55 经过 FreeSurfer 处理后,每个受试者的数据都会存储在一个独立的目录中,其结构遵循 FreeSurfer 的标准输出格式。
56    ├── sub-OASIS10001/         # 受试者1的FreeSurfer输出目录
57    │   ├── mri/                # 体积数据(Volume-based data)
58    │   │   ├── orig.mgz        # 原始图像(转换为FreeSurfer格式)
59    │   │   ├── nu.mgz          # 强度归一化后的图像
60    │   │   ├── T1.mgz          # 用于分割的图像
61    │   │   ├── aseg.mgz        # 自动亚结构分割(皮质下分割)
62    │   │   ├── aparc+aseg.mgz  # 皮层+皮质下融合分割
63    │   │   ├── brain.mgz       # 去除非脑组织后的图像
64    │   │   ├── brainmask.mgz   # 大脑掩模
65    │   │   └── ... (其他文件)
66    │   ├── surf/               # 表面数据(Surface-based data)
67    │   │   ├── lh.pial         # 左半球软脑膜表面
68    │   │   ├── lh.white        # 左半球白质表面
69    │   │   ├── rh.pial         # 右半球软脑膜表面
70    │   │   ├── rh.white        # 右半球白质表面
71    │   │   ├── lh.thickness    # 左半球皮层厚度图
72    │   │   └── ... (其他文件)
73    │   ├── stats/              # 统计结果(文本文件)
74    │   │   ├── aseg.stats      # 皮质下结构体积统计
75    │   │   ├── lh.aparc.stats  # 左半球皮层脑区厚度/面积统计
76    │   │   └── rh.aparc.stats  # 右半球皮层脑区厚度/面积统计
77    │   └── label/              # 标签文件
78    │       └── ...
79'''
80import os
81import glob
82import pandas as pd
83import SimpleITK as sitk
84import argparse
85import json
86from tqdm import tqdm
87from util import meta_data
88import util
89import numpy as np
90# from bert_helper import *
91
92import shutil
93
94import warnings
95warnings.filterwarnings("ignore")
96meta_id_name='ID'
97##性别(M/F),用手习惯(Hand)(均为右利手),年龄(Age),教育程度(Educ)(1-5级),社会经济地位(SES),MMSE(简易精神状态检查),CDR(临床痴呆评级:0=正常,0.5=非常轻微,1=轻度,2=中度),eTIV:估计颅内容积,ASF:图谱缩放因子,nWBV:标准化全脑体积
98META_COLUMN=['ID', 'M/F', 'Hand', 'Age', 'Educ', 'SES', 'MMSE', 'CDR', 'eTIV','nWBV', 'ASF', 'Delay']
99
100TASK_VALUE="segmentation"
101CLAMP_RANGE_CT = [-300,300]
102CLAMP_RANGE_MRI = None # MRI images threshold placeholder TBC...
103TARGET_VOXEL_SPACING=None
104
105##参考MSD的sub_modality描述信息
106SUB_MODALITY=["FLAIR","T1w","t1gd","T2w"]
107##文件名对应的排序顺序
108SERIES_ORDER=["flair","t1","t1ce","t2"]
109
110LABEL_DICT={
111    "0":"backgroud",
112    "1":"cerebrospinal fluid",#CSF
113    "2":"gray matter",#GM
114    "3":"white matter"#WM
115}
116# def find_metadata_files(path):
117#     # for Cancer Image Archive (TCIA) dataset
118#     search_pattern = os.path.join(path, '**', 'metadata.csv')
119#     return glob.glob(search_pattern, recursive=True)
120
121def find_metadata_files(path):
122    # for Cancer Image Archive (TCIA) dataset
123    search_pattern = os.path.join(path, '*.csv')
124    return glob.glob(search_pattern, recursive=True)
125##added by yanguoqing on 20250527
126def find_image_dirs(path):
127    return os.listdir(path)
128
129##modify by yanguoqing on 20250527
130def load_dicom_images(folder_path):
131    reader = sitk.ImageSeriesReader()
132    dicom_names = reader.GetGDCMSeriesFileNames(folder_path)
133    reader.SetFileNames(dicom_names)
134    image = reader.Execute()
135    return dicom_names,image
136
137##added by yanguoqing on 20250527
138def load_dicom_tag(imgs):
139    reader = sitk.ImageFileReader()
140    # dicom_names = reader.GetGDCMSeriesFileNames(folder_path)
141    reader.SetFileName(imgs)
142    reader.ReadImageInformation()  # 仅读取元信息,不加载像素数据
143    # metadata_keys = reader.GetMetaDataKeys()
144    tag=reader.Execute()
145    return tag
146
147def load_nrrd(fp):
148    return sitk.ReadImage(fp)
149
150##modify by yanguoqing on 20250805
151def load_brtas_images(series_files):
152    '''
153    每个病例包含四种不同序列的 3D MRI 扫描(均已进行预处理,如配准、重采样到 1mm³ 各向同性、颅骨剥离)
154    将多个分开的模态合并,构建第四个维度的数组,分别按照FLAIR,T1,T1CE,T2顺序存放
155    '''
156    reader = sitk.ImageSeriesReader()
157    reader.SetFileNames(series_files)
158    image = reader.Execute()
159    return image
160
161def save_nifti(image, output_path, folder_path):
162    # Set metadata in the NIfTI file's header
163    output_dirpath = os.path.dirname(output_path)
164    if not os.path.exists(output_dirpath):
165        print(f"Creating directory {output_dirpath}")
166        os.makedirs(output_dirpath)
167    # Set metadata in the NIfTI file's header
168    image.SetMetaData("FolderPath", folder_path)
169    sitk.WriteImage(image, output_path)
170
171##modify by yanguoqing on 20250527
172def convert_windows_to_linux_path(windows_path):
173    # Replace backslashes with forward slashes and remove the drive letter
174    # Some meta files have windows paths, but the data is stored on a linux server
175    linux_path = windows_path.replace('\\', '/')
176    if ':' in linux_path:
177        linux_path = linux_path.split(':', 1)[1]
178    return linux_path
179
180def main(target_path, output_dir):
181    
182    pid_dirs=find_image_dirs(target_path)
183    failed_files = []
184    if not os.path.isdir(output_dir):
185        os.makedirs(output_dir)
186    json_output_path = os.path.join(output_dir, 'nifti_mappings.json')
187    failed_files_path = os.path.join(output_dir, 'failed_files.json')
188    meta = meta_data()
189    
190    # Initialize the JSON file
191    if not os.path.exists(json_output_path):
192        with open(json_output_path, 'w') as json_file:
193            json.dump({}, json_file)
194    ##方便处理解析信息,转成csv文件
195    meta_file=os.path.join(os.path.dirname(os.path.realpath(__file__)),'oasis_cross-sectional-5708aa0a98d82080.csv')
196    meta_file_ori=os.path.join(target_path,'oasis_cross-sectional-5708aa0a98d82080.xlsx')
197    if os.path.isfile(meta_file):
198        mf_flag=True
199        df_meta=pd.read_csv(meta_file,sep=',')
200    else:
201        mf_flag=False
202
203
204    if pid_dirs:
205        for pid_dir in tqdm(pid_dirs, desc="Processing pid dirs"):
206            if not os.path.isdir(os.path.join(target_path,pid_dir)):
207                continue
208            
209            ##遍历所有目录下的病例数据
210            image_dirs=find_image_dirs(os.path.join(target_path,pid_dir))
211            
212            for data_dir in tqdm(image_dirs, desc="Processing images files"):
213                ##data_dir即id
214                full_path=os.path.join(target_path,pid_dir,data_dir)
215                
216                modality="MRI"
217                study='OASIS_1'##Dataset_name
218                CIA_other_info = {'metadata_file':''}
219                CIA_other_info['split'] = "train"
220                CIA_other_info['metadata_file']=meta_file_ori
221                data_info_row=df_meta[df_meta[meta_id_name]==data_dir]
222                
223                if data_info_row.shape[0]>0:
224                    data_info_row=data_info_row.reset_index()
225                    #print(data_info_row[meta_id_name])
226                    for keyname in META_COLUMN[1:]:
227                        CIA_other_info[keyname]=str(data_info_row[keyname][0])
228                    
229                    CIA_other_info['Image_id']=data_dir
230                  
231
232                else:
233                    meta_image_id=data_dir
234                    for keyname in META_COLUMN[1:]:
235                        CIA_other_info[keyname]=''
236                
237                
238
239                try:
240                    ##读取去骨保留脑组织的img
241                    #\PROCESSED\MPRAGE\T88_111\OAS1_0001_MR1_mpr_n4_anon_111_t88_masked_gfc.img
242                    full_file=glob.glob("%s/PROCESSED/MPRAGE/T88_111/%s_*_anon_111_t88_masked_gfc.img"%(full_path,data_dir))[0]
243                    # full_file=os.path.join(full_path,"PROCESSED/MPRAGE/T88_111","%s_mpr_n4_anon_111_t88_masked_gfc.img"%data_dir)
244                    
245                    if os.path.isfile(full_file):
246                        ##存在有效的MRI影像数据进行后续处理
247                        sitk_img_original=util.load_nifti(full_file)
248                    else:
249                        print("病例数据%s为空"%data_dir)
250                        continue
251                    
252                    
253                    original_spacing = list(sitk_img_original.GetSpacing())
254                    original_size = list(sitk_img_original.GetSize())
255
256                    
257
258                    meta.add_keyvalue('Spacing_mm',min(original_spacing))
259                    meta.add_keyvalue('OriImg_path',full_file)
260                    meta.add_keyvalue('Size',original_size)  # 这里用处理后的size -- YH Jachin
261                    meta.add_keyvalue('Modality',modality)
262                    meta.add_keyvalue('Dataset_name',study)
263                    meta.add_keyvalue('ROI','head')
264
265                    meta.add_keyvalue('Label_Dict',LABEL_DICT)
266
267                    output_image_file = os.path.join(output_dir,data_dir, f"{data_dir}.nii.gz")
268                    # output_path=convert_windows_to_linux_path(output_path)
269                    ##
270                    save_nifti(sitk_img_original, output_image_file, full_path)
271                    print(f"Saved NIfTI file to {output_image_file}")
272                    ##Label processing
273
274                    label_path_dict={}
275                    #OAS1_0001_MR1_mpr_n4_anon_111_t88_masked_gfc_fseg.img
276                    full_label_file=glob.glob("%s/FSL_SEG/%s_*_anon_111_t88_masked_gfc_fseg.img"%(full_path,data_dir))[0]
277                    
278
279                    process_label_path=os.path.join(output_dir,data_dir,'segmentation')
280
281                    processed_lbl_full_path=os.path.join(process_label_path, f"{data_dir}.nii.gz")
282
283                    if not os.path.isdir(process_label_path):
284                        os.makedirs(process_label_path,exist_ok=True)
285
286                    if not os.path.isfile(full_label_file):
287                        label_flag=False
288                    else:
289                        sitk_lbl_original = util.load_nifti(full_label_file)
290                        util.save_nifti(sitk_lbl_original, processed_lbl_full_path, os.path.dirname(full_label_file)) # Save original
291                        print(f"Saved Segemention NIfTI file to {processed_lbl_full_path}")
292
293                        label_path_dict['head'] = processed_lbl_full_path
294                        label_flag=True
295                    
296                    if label_flag:
297                        meta.add_keyvalue('Task',TASK_VALUE)
298                        meta.add_keyvalue('Label_path',{TASK_VALUE:label_path_dict})
299
300
301               
302                #     try:
303                #         assert sitk_img_processed.GetSize() == sitk_lbl_processed.GetSize()
304                #     except Exception as e:
305                #         failed_files.append(full_path_label)
306                #         continue
307                    print(sitk_img_original.GetSize(),sitk_lbl_original.GetSize())
308
309                except Exception as e:
310                    print(e)
311                    failed_files.append(data_dir)
312                    print(f"Failed to load BRATS images from {data_dir}")
313                    continue
314
315                
316                
317                meta.add_extra_keyvalue('Metadata',CIA_other_info)
318
319
320                # Write the mapping to the JSON file on the fly
321                with open(json_output_path, 'r+') as json_file:
322                    existing_mappings = json.load(json_file)
323                    existing_mappings[output_image_file] = meta.get_meta_data()
324                    json_file.seek(0)
325                    # print(existing_mappings)
326                    json.dump(existing_mappings, json_file, indent=4)
327                    json_file.truncate()
328    # else:
329    #     print("No metadata.csv files found.")
330    
331    with open(failed_files_path, "w") as json_file:
332        json.dump(failed_files, json_file)
333        
334    print(f"The list has been written to {failed_files_path}")
335    print(f"Saved NIfTI mappings to {json_output_path}")
336
337if __name__ == "__main__":
338    parser = argparse.ArgumentParser(description="Process DICOM files and save as NIfTI.")
339    parser.add_argument("--target_path", type=str, help="Path to the target directory containing metadata files.", default="/home/data/Github/data/data_gen_def/DATASETS/OASIS/OASIS_1/oasis_cs_sectional/")
340    parser.add_argument("--output_dir", type=str, help="Directory to save the NIfTI files.", default="/home/data/Github/data/data_gen_def/DATASETS_processed/OASIS/OASIS_1/CS_SECTIONAL")
341    args = parser.parse_args()
342    print(args.target_path, args.output_dir)
343    main(args.target_path, args.output_dir)
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359