Spaces:
Sleeping
Sleeping
| import cv2 | |
| import glob | |
| import pydicom | |
| import numpy as np | |
| import pandas as pd | |
| from collections import Counter | |
| class dotdict(dict): | |
| """ | |
| A dictionary that allows accessing its elements as attributes. | |
| """ | |
| __setattr__ = dict.__setitem__ | |
| __delattr__ = dict.__delitem__ | |
| def __getattr__(self, name): | |
| """ | |
| Get an attribute from the dictionary. | |
| Args: | |
| name (str): The name of the attribute to get. | |
| Returns: | |
| The value of the attribute. | |
| Raises: | |
| AttributeError: If the attribute does not exist. | |
| """ | |
| try: | |
| return self[name] | |
| except KeyError: | |
| raise AttributeError(name) | |
| def read_series_metadata( | |
| study_id, | |
| series_id, | |
| series_description, | |
| data_path="../input/train_images/", | |
| return_imgs=True, | |
| ): | |
| """ | |
| Reads the metadata and images from a DICOM series. | |
| Args: | |
| study_id (int): The ID of the study. | |
| series_id (int): The ID of the series. | |
| series_description (str): The description of the series. | |
| data_path (str, optional): The path to the input data. Defaults to "../input/train_images/". | |
| return_imgs (bool, optional): Whether to return the images. Defaults to True. | |
| Returns: | |
| tuple: A tuple containing the DataFrame with metadata and the list of images. | |
| """ | |
| dicom_dir = data_path + f"{study_id}/{series_id}" | |
| # read dicom file | |
| dicom_file = sorted( | |
| glob.glob(f"{dicom_dir}/*.dcm"), key=lambda x: int(x.split("/")[-1][:-4]) | |
| ) | |
| instance_number = [int(f.split("/")[-1].split(".")[0]) for f in dicom_file] | |
| dicom = [pydicom.dcmread(f, stop_before_pixels=False) for f in dicom_file] | |
| dicom_df, images = [], {} | |
| for i, d in zip(instance_number, dicom): # d__.dict__ | |
| images[i] = d.pixel_array if return_imgs else None | |
| try: | |
| dicom_df.append( | |
| dotdict( | |
| study_id=study_id, | |
| series_id=series_id, | |
| series_description=series_description, | |
| instance_number=i, | |
| ImagePositionPatient=[float(v) for v in d.ImagePositionPatient], | |
| ImageOrientationPatient=[ | |
| float(v) for v in d.ImageOrientationPatient | |
| ], | |
| PixelSpacing=[float(v) for v in d.PixelSpacing], | |
| SpacingBetweenSlices=float(d.SpacingBetweenSlices), | |
| SliceThickness=float(d.SliceThickness), | |
| grouping=0, | |
| ) | |
| ) | |
| except Exception: # Missing fields | |
| dicom_df.append( | |
| dotdict( | |
| study_id=study_id, | |
| series_id=series_id, | |
| series_description=series_description, | |
| instance_number=i, | |
| ImagePositionPatient=[float(v) for v in d.ImagePositionPatient], | |
| ImageOrientationPatient=-1, | |
| PixelSpacing=-1, | |
| SpacingBetweenSlices=-1, | |
| SliceThickness=-1, | |
| grouping=0, | |
| ) | |
| ) | |
| dicom_df = pd.DataFrame(dicom_df) | |
| # Sort slices | |
| # More robust orientation check based on ImageOrientationPatient if description fails | |
| if "sagittal" in series_description.lower(): | |
| dicom_df["order"] = dicom_df["ImagePositionPatient"].apply(lambda x: x[0]) | |
| elif "axial" in series_description.lower(): | |
| dicom_df["order"] = dicom_df["ImagePositionPatient"].apply(lambda x: x[2]) | |
| else: | |
| # Fallback using ImageOrientationPatient | |
| # Assuming ImageOrientationPatient is [r_x, r_y, r_z, c_x, c_y, c_z] | |
| # Cross product gives the normal vector (slice direction) | |
| orientations = dicom_df["ImageOrientationPatient"].values | |
| orders = [] | |
| for i, pos in enumerate(dicom_df["ImagePositionPatient"].values): | |
| if type(orientations[i]) is list and len(orientations[i]) == 6: | |
| r = np.array(orientations[i][:3]) | |
| c = np.array(orientations[i][3:]) | |
| normal = np.cross(r, c) | |
| # the position along the normal vector | |
| orders.append(np.dot(normal, pos)) | |
| else: | |
| orders.append(pos[0]) # naive fallback | |
| dicom_df["order"] = orders | |
| df = dicom_df.sort_values("order", ignore_index=True) | |
| df["group"] = 0 | |
| df.loc[:, "z"] = np.arange(len(df)) | |
| images = [images[k] for k in df["instance_number"]] | |
| return df.reset_index(drop=True), images | |
| def process_2(study, series, orient, data_path="", on_gpu=False): | |
| """ | |
| Processes the DICOM series and returns the images and metadata. | |
| Args: | |
| study (int): The ID of the study. | |
| series (int): The ID of the series. | |
| orient (str): The orientation of the series. | |
| data_path (str, optional): The path to the input data directory. Defaults to "". | |
| on_gpu (bool, optional): Whether to process on GPU. Defaults to False. | |
| Returns: | |
| tuple: A tuple containing the processed images and the DataFrame with metadata. | |
| """ | |
| df, imgs = read_series_metadata( | |
| study, | |
| series, | |
| orient, | |
| data_path=data_path, | |
| ) | |
| try: | |
| imgs = np.array(imgs) | |
| except Exception: | |
| shapes = Counter([img.shape for img in imgs]) | |
| shape = shapes.most_common()[0][0] | |
| # print("Different shapes:", shapes, f"resize to {shape} - {orient}") | |
| imgs = np.array( | |
| [cv2.resize(img, shape) if img.shape != shape else img for img in imgs] | |
| ) | |
| return imgs, df | |
| def process_and_save( | |
| study, | |
| series, | |
| orient, | |
| data_path, | |
| save_folder="", | |
| save_meta=False, | |
| save_middle_frame=False, | |
| ): | |
| """ | |
| Processes the DICOM series and saves the images and metadata. | |
| Args: | |
| study (int): The ID of the study. | |
| series (int): The ID of the series. | |
| orient (str): The orientation of the series. | |
| data_path (str): The path to the input data directory. | |
| save_folder (str, optional): Path to the saving folder. Defaults to "". | |
| save_meta (bool, optional): Whether to save the metadata. Defaults to False. | |
| save_middle_frame (bool, optional): Whether to save the middle frame. Defaults to False. | |
| Returns: | |
| dict: A dictionary containing the study ID, series ID, and the list of frame numbers. | |
| """ | |
| imgs, df_series = process_2(int(study), int(series), orient, data_path=data_path) | |
| if save_folder: | |
| np.save(save_folder + f"npy/{study}_{series}.npy", imgs) | |
| if save_meta: | |
| df_series.to_csv(save_folder + f"csv/{study}_{series}.csv", index=False) | |
| if save_middle_frame: | |
| img = imgs[len(imgs) // 2] | |
| img = np.clip( | |
| img, np.percentile(img.flatten(), 0), np.percentile(img.flatten(), 98) | |
| ) | |
| max_, min_ = img.max(), img.min() | |
| if max_ != min_: | |
| img = (img - min_) / (max_ - min_) | |
| else: | |
| img = img - min_ | |
| img = (img * 255).astype(np.uint8) | |
| cv2.imwrite(save_folder + f"mid/{study}_{series}.png", img) | |
| return { | |
| "study_id": study, | |
| "series_id": series, | |
| "frames": df_series.instance_number.values.tolist(), | |
| } | |