Ejemplo n.º 1
0
    def __init__(self, image: nib.Nifti1Image, imageMetadata: dict,
                 datasetDescription: dict = None):
        """
        Initializes a BIDS Incremental object with provided image and metadata.

        Args:
            image: NIfTI image as an NiBabel NiftiImage or PyBids BIDSImageFile
            imageMetadata: Metadata for image, which must include all variables
                in BidsIncremental.REQUIRED_IMAGE_METADATA.
            datasetDescription: Top-level dataset metadata for the BIDS dataset
                to be placed in a dataset_description.json. Defaults to None and
                a default description is used.

        Raises:
            MissingMetadataError: If any required metadata is missing.
            TypeError: If the image is not an Nibabel Nifti1Image or
                Nifti2Image.

        Examples:
            >>> import nibabel as nib
            >>> imageMetadata = {'subject': '01', 'task': 'test',
                                 'suffix': 'bold', 'datatype': 'func',
                                 'RepetitionTime': 1.5}
            >>> image = nib.load('/tmp/testfile.nii')
            >>> datasetDescription = {'Name': 'Example Dataset',
                                   'BIDSVersion': '1.5.1',
                                   'Authors': 'The RT-Cloud Authors'}
            >>> incremental = BidsIncremental(image, imageMetadata,
                datasetDescription)
            >>> print(incremental)
            "Image shape: (64, 64, 27, 1); Metadata Key Count: 6; BIDS-I
            Version: 1"
        """
        # TODO(spolcyn): Enable a BIDS incremental to store an index that
        # specifies where the image should be inserted into the archive. This
        # would extend capabilities beyond just appending.

        """ Do basic input validation """
        # IMAGE
        validTypes = [nib.Nifti1Image, nib.Nifti2Image, BIDSImageFile]
        if image is None or type(image) not in validTypes:
            raise TypeError("Image must be one of " +
                            str([typ.__name__ for typ in validTypes]) +
                            f"(got {type(image)})")
        if type(image) is BIDSImageFile:
            image = image.get_image()

        # DATASET DESCRIPTION
        if datasetDescription is not None:
            missingFields = [field for field in DATASET_DESC_REQ_FIELDS
                             if datasetDescription.get(field, None) is None]
            if missingFields:
                raise MissingMetadataError(
                    f"Dataset description needs: {str(missingFields)}")

        """ Process, validate, and store image metadata """
        imageMetadata = self._preprocessMetadata(imageMetadata)
        self._exceptIfMissingMetadata(imageMetadata)
        self._imgMetadata = self._postprocessMetadata(imageMetadata)

        """ Store dataset description"""
        if datasetDescription is None:
            self.datasetDescription = deepcopy(DEFAULT_DATASET_DESC)
        else:
            self.datasetDescription = deepcopy(datasetDescription)

        """ Validate and store image """
        # Remove singleton dimensions past the 3rd dimension
        # Note: this function does not remove trailing 1's if the image is 3-D,
        # (i.e., 160x160x1 image will retain that shape), so a later check is
        # needed to ensure that the 3rd dimension is > 1
        image = nib.funcs.squeeze_image(image)

        # BIDS-I is currently used for BOLD data, and according to the BIDS
        # Standard, BOLD data must be in 4-D NIfTI files. Thus, upgrade 3-D to
        # 4-D images with singleton final dimension, if necessary.
        imageShape = image.shape
        if len(imageShape) < 3:
            raise ValueError("Image must have at least 3 dimensions")
        elif len(imageShape) == 3:
            if imageShape[2] <= 1:
                raise ValueError("Image's 3rd (and any higher) dimensions are "
                                 " <= 1, which means it is a 2D image; images "
                                 "must have at least 3 dimensions")

            newData = np.expand_dims(getNiftiData(image), -1)
            image = image.__class__(newData, image.affine, image.header)
            correct3DHeaderTo4D(image, self._imgMetadata['RepetitionTime'])

        assert len(image.shape) == 4

        self.image = image

        # Configure README
        self.readme = DEFAULT_README

        # Configure events file
        self.events = pd.DataFrame(columns=DEFAULT_EVENTS_HEADERS)
        self.events = correctEventsFileDatatypes(self.events)

        # BIDS-I version for serialization
        self.version = 1