def test_convert_itk_to_internal(image: Path):
    def assert_img_properties(img: SimpleITK.Image, internal_image: Image):
        color_space = {
            1: Image.COLOR_SPACE_GRAY,
            3: Image.COLOR_SPACE_RGB,
            4: Image.COLOR_SPACE_RGBA,
        }

        assert internal_image.color_space == color_space.get(
            img.GetNumberOfComponentsPerPixel())
        if img.GetDimension() == 4:
            assert internal_image.timepoints == img.GetSize()[-1]
        else:
            assert internal_image.timepoints is None
        if img.GetDepth():
            assert internal_image.depth == img.GetDepth()
            assert internal_image.voxel_depth_mm == img.GetSpacing()[2]
        else:
            assert internal_image.depth is None
            assert internal_image.voxel_depth_mm is None

        assert internal_image.width == img.GetWidth()
        assert internal_image.height == img.GetHeight()
        assert internal_image.voxel_width_mm == approx(img.GetSpacing()[0])
        assert internal_image.voxel_height_mm == approx(img.GetSpacing()[1])
        assert internal_image.resolution_levels is None

    img_ref = load_sitk_image(image)
    internal_image = convert_itk_to_internal(img_ref)
    assert_img_properties(img_ref, internal_image[0])
    def convert_itk_file(headers: Mapping[str, Union[str, None]],
                         filename: Path) -> Tuple[Image, Sequence[ImageFile]]:
        try:
            simple_itk_image = load_sitk_image(filename.absolute())
            simple_itk_image: SimpleITK.Image
        except RuntimeError:
            raise ValueError("SimpleITK cannot open file")

        return convert_itk_to_internal(simple_itk_image, name=filename.name)
def image_builder_nifti(*, files: Set[Path], **_) -> ImageBuilderResult:
    """
    Constructs image objects from files in NifTI format (nii/nii.gz)

    Parameters
    ----------
    files
        Path to images that were uploaded during an upload session.

    Returns
    -------
    An `ImageBuilder` object consisting of:
     - a list of filenames for all files consumed by the image builder
     - a list of detected images
     - a list files associated with the detected images
     - path->error message map describing what is wrong with a given file
    """
    errors = {}
    new_images = set()
    new_image_files = set()
    consumed_files = set()
    for file in files:
        if not (file.name.endswith(".nii") or file.name.endswith(".nii.gz")):
            continue

        try:
            reader = SimpleITK.ImageFileReader()
            reader.SetImageIO("NiftiImageIO")
            reader.SetFileName(str(file.absolute()))
            img: SimpleITK.Image = reader.Execute()
        except RuntimeError:
            errors[file] = format_error("Not a valid NifTI image file")
            continue

        try:
            n_image, n_image_files = convert_itk_to_internal(
                img, name=file.name
            )
            new_images.add(n_image)
            new_image_files |= set(n_image_files)
            consumed_files.add(file)
        except ValueError as e:
            errors[file] = format_error(e)

    return ImageBuilderResult(
        consumed_files=consumed_files,
        file_errors=errors,
        new_images=new_images,
        new_image_files=new_image_files,
        new_folders=set(),
    )
Exemple #4
0
def image_builder_fallback(*, files: Set[Path], **_) -> ImageBuilderResult:
    """
    Constructs image objects by inspecting files in a directory.

    Parameters
    ----------
    path
        Path to a directory that contains all images that were uploaded during
        an upload session.

    Returns
    -------
    An `ImageBuilder` object consisting of:
     - a list of filenames for all files consumed by the image builder
     - a list of detected images
     - a list files associated with the detected images
     - path->error message map describing what is wrong with a given file
    """
    errors = {}
    new_images = set()
    new_image_files = set()
    consumed_files = set()
    for file in files:
        try:
            img = Image.open(file)

            if img.format.lower() not in ["jpeg", "png"]:
                raise ValidationError(
                    f"Unsupported image format: {img.format}"
                )

            img_array = np.array(img)
            is_vector = img.mode != "L"
            img = SimpleITK.GetImageFromArray(img_array, isVector=is_vector)
            n_image, n_image_files = convert_itk_to_internal(
                img, name=file.name, use_spacing=False
            )
            new_images.add(n_image)
            new_image_files |= set(n_image_files)
            consumed_files.add(file)
        except (IOError, ValidationError, DecompressionBombError):
            errors[file] = format_error("Not a valid image file")

    return ImageBuilderResult(
        consumed_files=consumed_files,
        file_errors=errors,
        new_images=new_images,
        new_image_files=new_image_files,
        new_folders=set(),
    )
def _process_dicom_file(*, dicom_ds, created_image_prefix):  # noqa: C901
    ref_file = pydicom.dcmread(str(dicom_ds.headers[0]["file"]))
    ref_origin = tuple(
        float(i) for i in getattr(ref_file, "ImagePositionPatient", (0, 0, 0))
    )
    dimensions = 4 if dicom_ds.n_time and dicom_ds.n_time > 1 else 3
    direction = np.eye(dimensions, dtype=np.float)
    direction = _extract_direction(dicom_ds, direction)
    pixel_dims = (
        dicom_ds.n_slices,
        int(ref_file.Rows),
        int(ref_file.Columns),
    )
    if dimensions == 4:
        pixel_dims = (dicom_ds.n_time,) + pixel_dims

    # Additional Meta data Contenttimes and Exposures
    content_times = []
    exposures = []

    origin = None
    origin_diff = np.array((0, 0, 0), dtype=float)
    n_diffs = 0
    for partial in dicom_ds.headers:
        ds = partial["data"]
        if "ImagePositionPatient" in ds:
            file_origin = np.array(ds.ImagePositionPatient, dtype=float)
            if origin is not None:
                diff = file_origin - origin
                origin_diff = origin_diff + diff
                n_diffs += 1
            origin = file_origin
    avg_origin_diff = tuple(origin_diff / n_diffs)
    try:
        z_i = avg_origin_diff[2]
    except IndexError:
        z_i = 1.0

    img = _create_itk_from_dcm(
        content_times=content_times,
        dicom_ds=dicom_ds,
        dimensions=dimensions,
        exposures=exposures,
        pixel_dims=pixel_dims,
        z_i=z_i,
    )

    if origin is None:
        origin = (0.0, 0.0, 0.0)
    sitk_origin = ref_origin if z_i >= 0.0 else tuple(origin)
    z_i = np.abs(z_i) if not np.isnan(z_i) else 1.0

    if "PixelSpacing" in ref_file:
        x_i, y_i = (float(x) for x in ref_file.PixelSpacing)
    else:
        x_i = y_i = 1.0

    sitk_spacing = (x_i, y_i, z_i)
    if dimensions == 4:
        sitk_spacing += (1.0,)
        sitk_origin += (0.0,)

    sitk_direction = tuple(direction.flatten())
    img.SetDirection(sitk_direction)
    img.SetSpacing(sitk_spacing)
    img.SetOrigin(sitk_origin)

    if dimensions == 4:
        # Set Additional Meta Data
        img.SetMetaData("ContentTimes", " ".join(content_times))
        img.SetMetaData("Exposures", " ".join(exposures))

    for f in OPTIONAL_METADATA_FIELDS:
        if getattr(ref_file, f, False):
            img.SetMetaData(f, str(getattr(ref_file, f)))

    # Convert the SimpleITK image to our internal representation
    return convert_itk_to_internal(
        img,
        name=f"{created_image_prefix}-{dicom_ds.headers[0]['data'].StudyInstanceUID}-{dicom_ds.index}",
    )