kyle0x54/medvision

View on GitHub
medvision/dicom/dr_io.py

Summary

Maintainability
A
1 hr
Test Coverage
from enum import Enum, unique
from pathlib import Path
from typing import Union
import pydicom
import medvision as mv


@unique
class DrReadMode(Enum):
    MONOCHROME1 = 1
    MONOCHROME2 = 2
    UNCHANGED = None


def _invert_if_needed(img, mode, mono):
    # Convert 'Photometric Interpretation' if needed
    mono = 1 if mono.upper().find('MONOCHROME1') != -1 else 2
    if mode.value != mono and mode.value is not DrReadMode.UNCHANGED.value:
        return img.max() - img + img.min(), True
    else:
        return img, False


def dcmread_dr(
    path: Union[str, Path],
    mode: DrReadMode = DrReadMode.MONOCHROME2,
    read_header: bool = False,
    itk_handler_enabled: bool = True
):
    """ Read 2D digital radiography image data from the DICOM file.

    Args:
        path (str or Path): path of the dicom file.
        mode ('DrReadMode'): read mode, refer to 'DrReadMode'.
        read_header (bool): whether to return the dicom header together
            with the image array.
        itk_handler_enabled (bool): whether to use SimpleITK to read the
            dicom if pydicom fails. N.B. SimpleITK reader may cause
            segmentation fault which cannot be caught in python code.
    Returns:
        (ndarray): dicom image array.
        (dict, optional): dicom metadata.

    Note:
        MONOCHROME1 indicates that the greyscale ranges from bright to dark
        with ascending pixel values, whereas MONOCHROME2 ranges from dark
        to bright with ascending pixel values.
    """
    img, ds = mv.dcmread(
        path,
        read_header=True,
        itk_handler_enabled=itk_handler_enabled
    )

    if img.ndim == 3:
        img = img[:, :, 0]

    # fetch monochrome value
    assert 'PhotometricInterpretation' in ds
    mono = ds.PhotometricInterpretation

    img, is_inverted = _invert_if_needed(img, mode, mono)

    if read_header:
        if 'WindowCenter' in ds and 'WindowWidth' in ds:
            if isinstance(ds.WindowCenter, pydicom.multival.MultiValue):
                ds.WindowCenter = ds.WindowCenter[0]
                ds.WindowWidth = ds.WindowWidth[0]
            ds.WindowCenter = float(ds.WindowCenter)
            ds.WindowWidth = float(ds.WindowWidth)
            if is_inverted:
                ds.WindowCenter = (
                    float(img.max()) + float(img.min()) - ds.WindowCenter
                )
        if is_inverted:
            ds.PhotometricInterpretation = mode.name
        return img, ds
    else:
        return img


def dcmread_dr_itk(
    path: Union[str, Path],
    mode: DrReadMode = DrReadMode.MONOCHROME2,
):
    """ See dcmread_dr() for more details, the only difference is this
        function use SimpleITK for dicom parsing.
    """
    img, metadata = mv.dcmread_itk(
        path,
        read_header=True,
    )

    if img.ndim == 3:
        img = img[:, :, 0]

    # fetch monochrome value
    monochrome_tag = '0028|0004'
    assert monochrome_tag in metadata
    mono = metadata[monochrome_tag].upper()
    img, is_inverted = _invert_if_needed(img, mode, mono)

    return img