{"cells":[{"metadata":{},"cell_type":"markdown","source":"# Randomly shift the DICOM windows using albumentations\n\nWith this custom Albumentation, you can randomly shift the DICOM window (width and center) within the given min/max values. (Separately for each channel)"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"import albumentations as A\nimport cv2\nimport numpy as np\nimport pydicom\nimport random\n\nfrom albumentations.core.transforms_interface import ImageOnlyTransform\nfrom matplotlib import pyplot as plt\n\nIMAGE_DIR = '/kaggle/input/rsna-intracranial-hemorrhage-detection/stage_1_test_images/'","execution_count":null,"outputs":[]},{"metadata":{"_kg_hide-input":true,"trusted":true},"cell_type":"code","source":"def apply_window(image, center, width):\n    image = image.copy()\n\n    min_value = center - width // 2\n    max_value = center + width // 2\n\n    image[image < min_value] = min_value\n    image[image > max_value] = max_value\n\n    return image\n\n\ndef dicom_window_shift(img, windows, min_max_normalize=True):\n    image = np.zeros((img.shape[0], img.shape[1], 3))\n\n    if img.ndim == 2:\n        img = np.repeat(img[:, :, np.newaxis], 3, axis=2)\n\n    for i in range(3):\n        ch = apply_window(img[:, :, i], windows[i][0], windows[i][1])\n\n        if min_max_normalize:\n            image[:, :, i] = (ch - ch.min()) / (ch.max() - ch.min())\n        else:\n            image[:, :, i] = ch\n\n    return image\n\n\nclass DicomWindowShift(ImageOnlyTransform):\n    \"\"\"Randomly shift the DICOM window (per channel) between min and max values.\n    \n    Note: It won't work for preprocessed png or jpg images. Please use the dicom's HU values\n    (rescaled width slope/intercept!)\n    \n    Args:\n        window_width_mins (int, int, int): minimun window width per channel\n        window_width_maxs (int, int, int): maximum window width per channel\n        window_center_mins (int, int, int): minimum value for window center per channel\n        window_center_maxs (int, int, int): maximum value for window center per channel\n        min_max_normalize: (bool) Apply min-max normalization\n    Targets:\n        image\n    Image types:\n        uint8 (shape: HxW | HxWxC)\n    \"\"\"\n    def __init__(\n            self,\n            window_width_mins=(80, 200, 380),\n            window_width_maxs=(80, 200, 380),\n            window_center_mins=(40, 80, 40),\n            window_center_maxs=(40, 80, 40),\n            min_max_normalize=True,\n            always_apply=False,\n            p=0.5,\n    ):\n        super(DicomWindowShift, self).__init__(always_apply, p)\n        self.window_width_mins = window_width_mins\n        self.window_width_maxs = window_width_maxs\n        self.window_center_mins = window_center_mins\n        self.window_center_maxs = window_center_maxs\n        self.min_max_normalize = min_max_normalize\n\n        assert len(self.window_width_mins) == 3\n        assert len(self.window_width_maxs) == 3\n        assert len(self.window_center_mins) == 3\n        assert len(self.window_center_maxs) == 3\n\n    def apply(self, image, windows=(), min_max_normalize=True, **params):\n        return dicom_window_shift(image, windows, min_max_normalize)\n\n    def get_params_dependent_on_targets(self, params):\n        windows = []\n\n        for i in range(3):\n            window_width = random.randint(self.window_width_mins[i], self.window_width_maxs[i])\n            window_center = random.randint(self.window_center_mins[i], self.window_center_maxs[i])\n\n            windows.append([window_center, window_width])\n\n        return {\"windows\": windows, \"min_max_normalize\": self.min_max_normalize}\n\n    @property\n    def targets_as_params(self):\n        return [\"image\"]\n\n    def get_transform_init_args_names(self):\n        return \"window_width_mins\", \"window_width_maxs\", \"window_center_mins\", \"window_center_maxs\", \"min_max_normalize\"\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Load a sample image\nBelow you can see the 'brain' window of the source image."},{"metadata":{"trusted":true},"cell_type":"code","source":"sample_id = 'ID_687b495b6'\n\ndicom = pydicom.read_file(IMAGE_DIR + sample_id + '.dcm')\nimage = dicom.pixel_array\nimage = image * dicom.RescaleSlope + dicom.RescaleIntercept\n\nplt.imshow(apply_window(image, 40, 80), cmap='gray')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## DICOM window augmentation\n\nFor this demonstration, I chose a broad range for min/max values, as you can see, sometimes the result is out of the 'brain' window. I'll leave up to you to find the proper width/center min/max parameters for your model."},{"metadata":{"trusted":true},"cell_type":"code","source":"transform = DicomWindowShift(\n    # (brain_width_min, subdural_width_min, bones_width_min)\n    window_width_mins=(75, 190, 360),\n    \n    # (brain_width_max, subdural_width_min, bones_width_min)\n    window_width_maxs=(85, 210, 400),\n    \n    # (brain_center_min, subdural_center_min, bones_center_min)\n    window_center_mins=(15, 75, 35),\n    \n    # (brain_center_max, subdural_center_max, bones_center_max)\n    window_center_maxs=(85, 85, 45),\n\n    min_max_normalize=True,\n    p=1.0\n)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"For visibility purposes, I only show the 'brain' window on the images below."},{"metadata":{"trusted":true},"cell_type":"code","source":"f, ax = plt.subplots(2, 5, figsize=(16, 8))\nax = ax.flatten()\n\nfor i in range(10):\n    tr = transform(image=image)\n    ax[i].imshow(tr['image'][:,:,0], cmap='gray')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## Compose transforms\nYou can easily add this augmentation to your existing transformations."},{"metadata":{"trusted":true},"cell_type":"code","source":"transform = A.Compose([\n    A.Rotate(p=1.0),\n    DicomWindowShift(window_width_mins=(75, 190, 360),\n                     window_width_maxs=(85, 210, 400),\n                     window_center_mins=(15, 75, 35),\n                     window_center_maxs=(85, 85, 45),\n                     min_max_normalize=True,\n                     p=1.0)\n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"f, ax = plt.subplots(2, 5, figsize=(16, 8))\nax = ax.flatten()\n\nfor i in range(10):\n    tr = transform(image=image)\n    ax[i].imshow(tr['image'][:,:,0], cmap='gray')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**Thanks for reading. If you find it useful, please don't forget to vote.**"},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":1}