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