import numpy as np import nibabel as nib import os import cv2 import csv import nibabel as nib import pydicom from pydicom.dataset import FileDataset import numpy as np import os import datetime import zipfile def slices2nifti(ims, fn_out, spacing): """Save 2D slices to 3D NIfTI file considering the spacing.""" if len(ims) < 300: # cv2.merge does not support too many channels V = cv2.merge(ims) else: V = np.empty((ims[0].shape[0], ims[0].shape[1], len(ims))) for i in range(len(ims)): V[:, :, i] = ims[i] # The transformation matrix suitable for 3D slicer and ITK-SNAP T = np.array([[0, -spacing[1], 0, 0], [-spacing[0], 0, 0, 0], [0, 0, -spacing[2], 0], [0, 0, 0, 1]]) img = nib.Nifti1Image(V, T) path_out = os.path.join(dir_out, fn_out) nib.save(img, path_out) return path_out def load_slices(dir, slice_idxs): """Load slices from 16-bit PNG files and return images with their filenames.""" slice_idxs = np.array(slice_idxs) if not np.all(slice_idxs[1:] - slice_idxs[:-1] == 1): print(f"⚠️ Slice indices are not consecutive") ims = [] filenames = [] for slice_idx in slice_idxs: fn = f'{slice_idx:03d}.png' path = os.path.join(dir_in, dir, fn) im = cv2.imread(path, -1) # -1 to preserve 16-bit depth assert im is not None, f'Error reading {path}' im_corrected = (im.astype(np.int32) - 32768).astype(np.int16) ims.append(im_corrected) filenames.append(fn.split('.')[0]) return ims, filenames def read_DL_info(): """Read spacings and image indices in DeepLesion.""" spacings = [] idxs = [] with open(info_fn, 'r') as csvfile: # Use 'r' mode for reading text files reader = csv.reader(csvfile) rownum = 0 for row in reader: if rownum == 0: header = row rownum += 1 else: idxs.append([int(d) for d in row[1:4]]) spacings.append([float(d) for d in row[12].split(',')]) idxs = np.array(idxs) spacings = np.array(spacings) return idxs, spacings def nii_to_dicom(nii_path, output_folder, filenames): nii_img = nib.load(nii_path) data = nii_img.get_fdata() affine = nii_img.affine num_slices = data.shape[2] assert len(filenames) == num_slices, "Length of filename list must match number of slices." nii_base = os.path.splitext(os.path.basename(nii_path))[0] nii_base = nii_base.split('.')[0] dicom_subfolder = os.path.join(output_folder, nii_base) os.makedirs(dicom_subfolder, exist_ok=True) for i in range(num_slices): filename = os.path.join(dicom_subfolder, filenames[i])+'.dcm' # Use provided filename file_meta = pydicom.Dataset() file_meta.MediaStorageSOPClassUID = pydicom.uid.SecondaryCaptureImageStorage file_meta.MediaStorageSOPInstanceUID = pydicom.uid.generate_uid() file_meta.ImplementationClassUID = "1.2.3.4.5.6.7.8.9.0" file_meta.TransferSyntaxUID = pydicom.uid.ExplicitVRLittleEndian ds = FileDataset(filename, {}, file_meta=file_meta, preamble=b"\0" * 128) dt = datetime.datetime.now() ds.PatientName = "Test^Patient" ds.PatientID = "123456" ds.Modality = "MR" ds.StudyInstanceUID = pydicom.uid.generate_uid() ds.SeriesInstanceUID = pydicom.uid.generate_uid() ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID ds.SOPClassUID = file_meta.MediaStorageSOPClassUID ds.StudyDate = dt.strftime('%Y%m%d') ds.StudyTime = dt.strftime('%H%M%S') ds.Rows, ds.Columns = data.shape[:2] ds.InstanceNumber = i + 1 ds.ImagePositionPatient = [float(affine[0,3]), float(affine[1,3]), float(affine[2,3] + i)] ds.ImageOrientationPatient = [1,0,0,0,1,0] ds.PixelSpacing = [1.0, 1.0] ds.SliceThickness = 1.0 ds.SamplesPerPixel = 1 ds.PhotometricInterpretation = "MONOCHROME2" ds.BitsAllocated = 16 ds.BitsStored = 16 ds.HighBit = 15 ds.PixelRepresentation = 1 pixel_array = data[:, :, i].astype(np.uint16) ds.PixelData = pixel_array.tobytes() ds.is_little_endian = True ds.is_implicit_VR = False ds.save_as(filename) def extract_and_collect_main_folders(zip_root_dir): extracted_paths = [] for zip_name in sorted(os.listdir(zip_root_dir)): zip_path = os.path.join(zip_root_dir, zip_name) # فقط فایل‌های .zip واقعی if zip_name.lower().endswith('.zip') and os.path.isfile(zip_path): try: extract_folder = os.path.join(zip_root_dir, zip_name.replace('.zip', '')) os.makedirs(extract_folder, exist_ok=True) with zipfile.ZipFile(zip_path, 'r') as zip_ref: zip_ref.extractall(extract_folder) os.remove(zip_path) # حذف پوشه‌های مخفی extracted_subfolders = [ os.path.join(extract_folder, name) for name in os.listdir(extract_folder) if os.path.isdir(os.path.join(extract_folder, name)) and not name.startswith('.') ] if not extracted_subfolders: extracted_paths.append(extract_folder) else: extracted_paths.extend(extracted_subfolders) except zipfile.BadZipFile: print(f"⚠️ Skipping bad zip file: {zip_name}") continue return extracted_paths # Main zip_root_dir ='' folders = extract_and_collect_main_folders(zip_root_dir) def find_image_folders(directory): image_folders = [] # for root, dirs, files in os.walk(directory): for dir_name in dirs: if dir_name.startswith("Images_png_"): image_folders.append(os.path.join(root, dir_name)) return image_folders folder_path = '' result = find_image_folders(folder_path) print(result) dir_out = '' out_fmt = '%s.nii.gz' # format of the nifti file name to output info_fn ='' # file name of the information file idxs, spacings = read_DL_info() for folder in result : dir_in = folder + '/Images_png' if not os.path.exists(dir_out): os.mkdir(dir_out) img_dirs = os.listdir(dir_in) img_dirs.sort() for dir1 in img_dirs: #Find the image info according to the folder's name idxs1 = np.array([int(d) for d in dir1.split('_')]) i1 = np.where(np.all(idxs == idxs1, axis=1))[0] spacings1 = spacings[i1[0]] fns = os.listdir(os.path.join(dir_in, dir1)) slices = [int(d[:-4]) for d in fns if d.endswith('.png')] slices.sort() groups = [slices] for group in groups: # Group contains slices indices of a sub-volume ims,names = load_slices(dir1, group) fn_out = out_fmt % (dir1) path_out = slices2nifti(ims, fn_out, spacings1) nii_to_dicom(path_out, "",names)