{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nfrom glob import glob\nimport pandas as pd\nfrom tqdm import tqdm\nimport pydicom\nfrom pydicom.dataelem import DataElement_from_raw\nfrom pydicom.datadict import keyword_for_tag\nfrom concurrent.futures import ThreadPoolExecutor\n\npd.set_option(\"display.max_columns\", 83)\nDIR_DATA = \"/kaggle/input/competitions/rsna-knee-abnormality-detection\"\n#--------------------------------------------------------------------------------\ndef get_list_element_dicom_series(dir_series):\n    list_path_dcm = glob(f\"{dir_series}/*.dcm\")\n    ds = pydicom.dcmread(list_path_dcm[0], stop_before_pixels=True, force=True)\n    list_elements = list(ds.elements())\n    list_elements.append({\"NumberOfSlices\":len(list_path_dcm)})\n    return list_elements\n#--------------------------------------------------------------------------------\ndef parse_element(elem):\n    if type(elem) == dict:\n        return 'NumberOfSlices', elem[\"NumberOfSlices\"]\n        \n    # 1. Convert RawDataElement to full DataElement if needed\n    if isinstance(elem, pydicom.dataelem.RawDataElement):\n        try:\n            elem = DataElement_from_raw(elem)\n        except Exception:\n            tag_hex = f\"({elem.tag.group:04X},{elem.tag.element:04X})\"\n            return tag_hex, elem.value\n\n    # 2. Skip PixelData and nested Sequences\n    if getattr(elem, \"keyword\", \"\") == \"PixelData\" or getattr(elem, \"VR\", \"\") == \"SQ\":\n        return None, None\n\n    # 3. Get keyword name or fallback to hex string\n    col_name = elem.keyword or keyword_for_tag(elem.tag) or f\"({elem.tag.group:04X},{elem.tag.element:04X})\"\n\n    # 4. Clean up DICOM value types for DataFrame compatibility\n    val = elem.value\n    if isinstance(val, pydicom.multival.MultiValue):\n        val = list(val)\n    elif isinstance(val, (pydicom.uid.UID, pydicom.valuerep.PersonName, pydicom.valuerep.DSfloat, pydicom.valuerep.IS)):\n        val = str(val)\n\n    return col_name, val\n#--------------------------------------------------------------------------------","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train_series  = pd.read_csv(f\"{DIR_DATA}/train_series.csv\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"list_available_study = os.listdir(f\"{DIR_DATA}/train_series\")\ndf_train_series_subset = df_train_series.loc[df_train_series['StudyInstanceUID'].isin(list_available_study)]\nlist_dir_series = df_train_series_subset.apply(lambda x: DIR_DATA + '/train_series/' + x['StudyInstanceUID'] + '/' + x['SeriesInstanceUID'], axis=1).to_list()\n\nwith ThreadPoolExecutor(max_workers=8) as pool:\n        list_series_element = list(tqdm(pool.map(get_list_element_dicom_series, list_dir_series), total=len(list_dir_series), desc=\"Extracting DICOM tags\", ncols=100, unit=\"series\"))\n\nrows = []\nfor series_elements in list_series_element:\n    series_dict = {}\n    for elem in series_elements:\n        col_name, val = parse_element(elem)\n        if col_name is not None:\n            series_dict[col_name] = val\n    rows.append(series_dict)\n\ndf_dicom = pd.DataFrame(rows)\ndf_dicom.to_csv(\"df_dicom.csv\", index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 1. Unique Identifiers & Hierarchy\n* **`StudyInstanceUID`**: Globally unique ID for the entire imaging exam/session (a study contains multiple series).\n* **`SeriesInstanceUID`**: Globally unique ID for a specific acquisition/sequence (a series contains multiple slices).\n* **`SOPInstanceUID`**: Globally unique ID for an individual slice / DICOM file.\n* **`SOPClassUID`**: Defines the DICOM storage type (e.g., standard *MR Image Storage*).\n* **`FrameOfReferenceUID`**: Unique ID defining a shared 3D physical coordinate system (series sharing this ID can be spatially co-registered).\n* **`PatientID`**: De-identified or original patient identifier.\n","metadata":{}},{"cell_type":"code","source":"columns_set_1 = ['StudyInstanceUID', 'SeriesInstanceUID', 'SOPInstanceUID', 'SOPClassUID', 'FrameOfReferenceUID', 'PatientID']\ndf_dicom[columns_set_1].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in columns_set_1:\n    print(f\"{col}: {df_dicom.loc[:, col].nunique()}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2. Series & Study Descriptions\n* **`Modality`**: Type of imaging equipment (`MR` for Magnetic Resonance).\n* **`SeriesDescription`**: Clinical name given to the scan (e.g., `\"SAG T2 TSE\"`, `\"AXIAL T1\"`). Crucial for filtering sequences.\n* **`BodyPartExamined`**: Anatomical region scanned (e.g., `KNEE`, `BRAIN`, `SPINE`).\n* **`PatientSex`**: Biological sex of the patient (`M`, `F`, or `O`).\n* **`Laterality`**: Left (`L`) vs Right (`R`) indicator for paired anatomical structures (e.g., knee, breast).\n* **`SeriesNumber` / `AcquisitionNumber` / `InstanceNumber`**: Numeric order tags assigned by the scanner for series, raw acquisitions, and individual slices.\n","metadata":{}},{"cell_type":"code","source":"columns_set_2 = ['Modality', 'SeriesDescription', 'BodyPartExamined', 'PatientSex', 'Laterality', 'SeriesNumber', 'AcquisitionNumber', 'InstanceNumber']\ndf_dicom[columns_set_2].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in columns_set_2:\n    print(df_dicom.loc[:, col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3. Scanner & Hardware Details\n* **`Manufacturer`**: Scanner vendor (e.g., `SIEMENS`, `GE MEDICAL SYSTEMS`, `PHILIPS`).\n* **`ManufacturerModelName`**: Exact scanner model (e.g., `MAGNETOM Skyra`, `Signa HDxt`).\n* **`SoftwareVersions`**: Scanner operating software build version.\n* **`MagneticFieldStrength`**: Main magnetic field power in Tesla (typically `1.5` or `3.0`).\n* **`ReceiveCoilName` / `ReceiveCoilType`**: Hardware antenna used to detect the RF signal (e.g., knee coil, 8-channel array).\n* **`TransmitCoilName` / `TransmitCoilType`**: Hardware coil used to transmit RF excitation pulses.\n","metadata":{}},{"cell_type":"code","source":"columns_set_3 = ['Manufacturer', 'ManufacturerModelName', 'SoftwareVersions', 'MagneticFieldStrength', 'ReceiveCoilName', 'ReceiveCoilType', 'TransmitCoilName', 'TransmitCoilType']\ndf_dicom[columns_set_3].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in columns_set_3:\n    print(df_dicom.loc[:, col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4. 3D Spatial Geometry & Slicing\n* **`ImagePositionPatient`**: 3D $(x, y, z)$ coordinates in millimeters of the upper-left corner of the slice. Used for exact 3D sorting.\n* **`ImageOrientationPatient`**: 6 direction cosines specifying the physical orientation of rows and columns (distinguishes Axial, Sagittal, Coronal, and Oblique).\n* **`SliceThickness`**: Thickness of the physical imaging slice along the slice-select axis (in mm).\n* **`SpacingBetweenSlices`**: Center-to-center distance between adjacent slices (in mm).\n* **`SliceLocation`**: 1D relative position along the slice axis (often vendor-dependent; fallback for sorting).\n* **`PatientPosition`**: Position of the patient on the table (e.g., `HFS` = Head First Supine, `FFS` = Feet First Supine).\n* **`StackID` / `InStackPositionNumber`**: Identifies a group of slices (stack) and the slice index within that stack for multi-stack scans.\n* **`NumberOfSlices`**: Number of slices of a series. \n","metadata":{}},{"cell_type":"code","source":"columns_set_4 = ['ImagePositionPatient', 'ImageOrientationPatient', 'SliceThickness', 'SpacingBetweenSlices', 'SliceLocation', 'PatientPosition', 'StackID', 'InStackPositionNumber', 'NumberOfSlices']\ndf_dicom[columns_set_4].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in ['SliceThickness', 'PatientPosition', 'StackID', 'InStackPositionNumber', 'NumberOfSlices']:\n    print(df_dicom.loc[:, col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 5. Resolution & 2D Image Geometry\n* **`Rows` / `Columns`**: Height and width of the 2D image matrix in pixels (e.g., $320 \\times 320$, $512 \\times 512$).\n* **`PixelSpacing`**: Physical spacing $[dy, dx]$ in millimeters between adjacent pixel centers.\n* **`AcquisitionMatrix`**: Raw k-space data acquisition grid size before zero-padding or reconstruction.\n* **`InPlanePhaseEncodingDirection`**: Direction of phase encoding across the image (`ROW` or `COL`); key for tracking motion artifacts.\n* **`PercentSampling` / `PercentPhaseFieldOfView`**: Fractions of k-space sampled, reflecting non-square FOV acquisitions to save scan time.","metadata":{}},{"cell_type":"code","source":"columns_set_5 = ['Rows', 'Columns', 'PixelSpacing', 'AcquisitionMatrix', 'InPlanePhaseEncodingDirection', 'PercentSampling', 'PercentPhaseFieldOfView']\ndf_dicom[columns_set_5].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in ['Rows', 'Columns', 'InPlanePhaseEncodingDirection']:\n    print(df_dicom.loc[:, col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 6. MRI Pulse Sequence Parameters (Image Contrast)\n* **`ScanningSequence`**: Fundamental pulse sequence family (e.g., `SE` = Spin Echo, `GRE` = Gradient Echo, `IR` = Inversion Recovery).\n* **`SequenceVariant`**: Enhancements applied (e.g., `SK` = Segmented k-space, `SP` = Spoiled, `MP` = Magnetization Prepared).\n* **`ScanOptions`**: Specific scan features active (e.g., `FS` = Fat Suppression, `PFP` = Partial Fourier).\n* **`SequenceName` / `PulseSequenceName`**: Scanner-internal proprietary sequence name (e.g., `*tse2d1_15`).\n* **`MRAcquisitionType`**: Acquisition mode: `2D` (slice-by-slice) vs `3D` (volumetric).\n* **`RepetitionTime` (TR)**: Time in ms between consecutive excitation RF pulses (major factor in T1 vs T2 weighting).\n* **`EchoTime` (TE)**: Time in ms between excitation and signal readout (longer TE = more T2 weighting).\n* **`InversionTime` (TI)**: Time in ms between an initial $180^\\circ$ inversion pulse and excitation (used in STIR, FLAIR).\n* **`FlipAngle` / `VariableFlipAngleFlag`**: RF nutation angle in degrees; and flag indicating whether flip angle varied across echoes.\n* **`EchoTrainLength` / `RFEchoTrainLength`**: Number of refocusing echoes per TR in Fast/Turbo Spin Echo (TSE/FSE).\n* **`NumberOfAverages`**: Number of signal acquisitions averaged together to improve signal-to-noise ratio (SNR).\n* **`ImagingFrequency`**: Larmor precession frequency of hydrogen protons in MHz (e.g., ~63.8 MHz at 1.5T, ~127.7 MHz at 3.0T).\n* **`PixelBandwidth`**: Readout receiver bandwidth per pixel in Hz/pixel.\n* **`ContrastBolusAgent`**: Name or composition of any injected contrast medium (e.g., Gadolinium), if administered.\n","metadata":{}},{"cell_type":"code","source":"columns_set_6 = ['ScanningSequence', 'SequenceVariant', 'ScanOptions', 'SequenceName', 'PulseSequenceName', 'MRAcquisitionType', 'RepetitionTime', 'EchoTime', 'InversionTime',\n                 'FlipAngle', 'VariableFlipAngleFlag', 'EchoTrainLength', 'RFEchoTrainLength', 'NumberOfAverages', 'ImagingFrequency', 'PixelBandwidth', 'ContrastBolusAgent']\ndf_dicom[columns_set_6].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in ['ScanningSequence', 'SequenceVariant', 'ScanOptions', 'SequenceName', 'PulseSequenceName', 'MRAcquisitionType']:\n    print(df_dicom.loc[:, col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 7. Pixel Value Encoding & Display\n* **`BitsAllocated`**: Total bit depth allocated for storing each pixel (usually `16`).\n* **`BitsStored`**: Number of significant bits actually used (e.g., `12` or `16`).\n* **`HighBit`**: Highest bit position used (typically `BitsStored - 1`, e.g., `11` or `15`).\n* **`PixelRepresentation`**: Integer representation (`0` = unsigned integer, `1` = signed 2's complement).\n* **`SamplesPerPixel`**: Color channels per pixel (`1` for standard grayscale MRI).\n* **`PhotometricInterpretation`**: Pixel color interpretation (almost always `MONOCHROME2`, where higher values = brighter pixels).\n* **`PlanarConfiguration`**: How color channels are ordered in memory (irrelevant for grayscale).\n* **`WindowCenter` / `WindowWidth`**: Default contrast/brightness (window level and width) intended for viewer displays.\n* **`RescaleIntercept` / `RescaleSlope` / `RescaleType`**: Linear transformation parameters to map stored raw pixel integers $P$ to physical units: $U = P \\times \\text{Slope} + \\text{Intercept}$.","metadata":{}},{"cell_type":"code","source":"columns_set_7 = ['BitsAllocated', 'BitsStored', 'HighBit', 'PixelRepresentation', 'SamplesPerPixel', 'PhotometricInterpretation', 'PlanarConfiguration', 'WindowCenter', 'WindowWidth',\n                 'RescaleIntercept', 'RescaleSlope', 'RescaleType']\ndf_dicom[columns_set_7].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in ['BitsAllocated', 'BitsStored', 'HighBit', 'PixelRepresentation', 'SamplesPerPixel', 'PhotometricInterpretation', 'PlanarConfiguration', 'RescaleType']:\n    print(df_dicom[col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 8. Diffusion & Advanced MRI Tags\n* **`DiffusionBValue`**: Diffusion weighting factor ($b$-value, in $\\text{s/mm}^2$, e.g., $0, 50, 800, 1000$).\n* **`DiffusionGradientOrientation`**: 3D direction vector $[x, y, z]$ of the applied diffusion-sensitizing gradient.\n* **`DiffusionDirectionality`**: Category of diffusion encoding (e.g., `DIRECTIONAL`, `ISOTROPIC`, `NONE`).\n* **`ParallelAcquisition` / `ParallelAcquisitionTechnique`**: Indicates acceleration techniques like SENSE or GRAPPA.\n* **`ParallelReductionFactorInPlane` / `ParallelReductionFactorOutOfPlane`**: Acceleration factor (speedup ratio) along in-plane and 3D slice directions.\n* **`PartialFourier` / `PartialFourierDirection`**: Indicates if half-Fourier reconstruction was used to save scan time.\n* **`NumberOfPhaseEncodingSteps` / `MRAcquisitionPhaseEncodingStepsInPlane` / `MRAcquisitionPhaseEncodingStepsOutOfPlane`**: Exact number of phase encoding steps sampled along each dimension.\n","metadata":{}},{"cell_type":"code","source":"columns_set_8 = ['DiffusionBValue', 'DiffusionGradientOrientation', 'DiffusionDirectionality', 'ParallelAcquisition', 'ParallelAcquisitionTechnique', 'ParallelReductionFactorInPlane', \n                 'ParallelReductionFactorOutOfPlane', 'PartialFourier', 'PartialFourierDirection', 'NumberOfPhaseEncodingSteps', 'MRAcquisitionPhaseEncodingStepsInPlane', \n                 'MRAcquisitionPhaseEncodingStepsOutOfPlane']\ndf_dicom[columns_set_8].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in columns_set_8:\n    print(df_dicom[col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 9. Multi-frame & Dynamic Imaging\n* **`ImageType`**: DICOM descriptor list (e.g., `['ORIGINAL', 'PRIMARY', 'M', 'SE']`, indicating reconstructed vs raw, magnitude vs phase).\n* **`NumberOfFrames`**: Number of frames packed into a single multi-frame DICOM file (usually 1 for single-slice files).\n* **`NumberOfTemporalPositions` / `TemporalPositionIndex`**: Total dynamic time points and the current time-point index in dynamic/cine acquisitions (e.g., cardiac or perfusion).\n* **`EchoNumbers`**: Identifies which echo number generated the image in multi-echo sequences.","metadata":{}},{"cell_type":"code","source":"columns_set_9 = ['ImageType', 'NumberOfFrames', 'NumberOfTemporalPositions', 'TemporalPositionIndex', 'EchoNumbers']\ndf_dicom[columns_set_9].head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in columns_set_9:\n    print(df_dicom[col].value_counts(dropna=False))\n    print(\"=\"*50)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_columns = []\nfor i in range(1, 10):\n    all_columns.extend(globals()[f\"columns_set_{i}\"])\n\nassert df_dicom.columns.isin(all_columns).all()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}