def read_multiple(self, path: str) -> List[Image]:
        """
        Allows to read in multiple images at once in case they are mixed within a single directory.
        """

        self._logger.info("Reading dicom images/series from: " + path)

        file_paths_by_series_uid = self._build_file_paths_by_series_uid_map(
            path)

        self._logger.info("Reading in {} images with series UIDs: {}".format(
            len(file_paths_by_series_uid),
            ", ".join([uid for uid in file_paths_by_series_uid.keys()])))

        images: List[Image] = list()
        for uid, file_path_list in file_paths_by_series_uid.items():

            self._logger.debug("Reading in series {}".format(uid))

            # read in all slices
            slices = [
                pydicom.dcmread(file_path) for file_path in file_path_list
            ]

            # filter non-image slices
            # todo: validate criterion
            slices = [
                slice for slice in slices if "ImagePositionPatient" in slice
            ]
            if len(slices) == 0:
                continue

            # load image volume and meta-data
            volume = self._build_volume(slices)
            image_meta_data, slice_meta_data_by_index = self._get_meta_data(
                slices)

            image = Image(image_data=volume)
            image.get_meta_data().update(
                {DICOM_META_DATA_KEY: image_meta_data})

            # attach slice-specific meta-data
            for index, slice_meta_data in slice_meta_data_by_index.items():

                image.get_or_add_slice(index).get_meta_data().update(
                    {DICOM_META_DATA_KEY: slice_meta_data})

            # deduce image information from dicom meta-data
            image.set_voxel_size(self._get_voxel_size(image))
            image.set_voxel_spacing(self._get_voxel_spacing(image))

            images.append(image)

        return images
Exemplo n.º 2
0
    def _build_manifest(self, image: Image, segment_slugs: dict) -> dict:

        manifest = {
            "image": {
                "precision_bytes":
                image.get_image_data().dtype.itemsize,
                "size":
                list(image.get_image_data().shape
                     ),  # the image volume byte sequence does not contain this
                "voxel_size":
                list(image.get_voxel_size())
                if image.get_voxel_size() else None,
                "voxel_spacing":
                list(image.get_voxel_spacing())
                if image.get_voxel_spacing() else None,
            },
            "meta_data":
            image.get_meta_data(),
            "slices": [
                self._build_image_slice_manifest(image_slice)
                for image_slice in image.get_slices()
            ],
            "segments": [
                self._build_image_segment_manifest(
                    image_segment,
                    segment_slugs[image_segment.get_identifier()])
                for image_segment in image.get_segments()
            ],
        }

        # make sure the result will actually be readable
        self._validate_manifest(manifest)

        return manifest
    def _get_pixel_spacing(self,
                           image: Image) -> Optional[Tuple[float, float]]:

        try:
            # "Pixel Spacing" (0028,0030)
            pixel_spacing = image.get_meta_data()[DICOM_META_DATA_KEY][str(
                0x00280030)]
        except KeyError:
            return None

        assert isinstance(pixel_spacing, list)
        assert len(pixel_spacing) == 2
        assert all(isinstance(value, float) for value in pixel_spacing)

        return (pixel_spacing[0], pixel_spacing[1])
    def _get_voxel_size(self, image: Image) -> Optional[Vector3]:
        """Deduces the voxel size based on slice thickness and pixel spacing.

        Relies on the assumption of non-overlap and non-sparseness in the xy-plane.
        """

        pixel_spacing = self._get_pixel_spacing(image)

        if pixel_spacing is None:
            return None

        try:
            slice_thickness = image.get_meta_data()["Slice Thickness"]
            assert isinstance(slice_thickness, float)
            assert slice_thickness > 0
        except KeyError:
            return None

        return (
            slice_thickness,
            pixel_spacing[1],
            pixel_spacing[0],
        )
    def _get_voxel_spacing(self, image: Image) -> Optional[Vector3]:
        """Deduces the voxel spacing based on slice increment and pixel spacing."""

        pixel_spacing = self._get_pixel_spacing(image)

        if pixel_spacing is None:
            return None

        explicit_slice_increment: Optional[float] = None

        # Read explicit value from "Spacing Between Slices" (0018,0088)
        try:
            explicit_slice_increment = image.get_meta_data(
            )[DICOM_META_DATA_KEY][str(0x00180088)]
            assert isinstance(explicit_slice_increment, float)
            explicit_slice_increment = abs(explicit_slice_increment)
        except KeyError:
            pass

        implicit_slice_increment = self._calculate_z_spacing(image)

        if explicit_slice_increment is None and implicit_slice_increment is None:
            # Cannot do anything
            return None

        elif explicit_slice_increment is not None and implicit_slice_increment is not None:
            # Assert consistency
            assert explicit_slice_increment == implicit_slice_increment,\
                "Derived slice increment differs from defined value"

        slice_increment = explicit_slice_increment if explicit_slice_increment is not None else implicit_slice_increment

        return (
            slice_increment,
            pixel_spacing[1],
            pixel_spacing[0],
        )