Ejemplo n.º 1
0
def testEquals(sample4DNifti1, sample3DNifti1, imageMetadata):
    # Test images with different headers
    assert BidsIncremental(sample4DNifti1, imageMetadata) != \
           BidsIncremental(sample3DNifti1, imageMetadata)

    # Test images with the same header, but different data
    newData = 2 * getNiftiData(sample4DNifti1)
    reversedNifti1 = nib.Nifti1Image(newData,
                                     sample4DNifti1.affine,
                                     header=sample4DNifti1.header)
    assert BidsIncremental(sample4DNifti1, imageMetadata) != \
        BidsIncremental(reversedNifti1, imageMetadata)

    # Test different image metadata
    modifiedImageMetadata = deepcopy(imageMetadata)
    modifiedImageMetadata["subject"] = "newSubject"
    assert BidsIncremental(sample4DNifti1, imageMetadata) != \
           BidsIncremental(sample4DNifti1, modifiedImageMetadata)

    # Test different dataset metadata
    datasetMeta1 = {"Name": "Dataset_1", "BIDSVersion": "1.0"}
    datasetMeta2 = {"Name": "Dataset_2", "BIDSVersion": "2.0"}
    assert BidsIncremental(sample4DNifti1, imageMetadata, datasetMeta1) != \
           BidsIncremental(sample4DNifti1, imageMetadata, datasetMeta2)

    # Test different readme
    incremental1 = BidsIncremental(sample4DNifti1, imageMetadata)
    incremental2 = BidsIncremental(sample4DNifti1, imageMetadata)
    readme1 = "README 1"
    readme2 = "README 2"

    incremental1.readme = readme1
    incremental2.readme = readme2
    assert incremental1 != incremental2

    # Test different events file
    incremental1 = BidsIncremental(sample4DNifti1, imageMetadata)
    incremental2 = BidsIncremental(sample4DNifti1, imageMetadata)

    events1 = {
        'onset': [1, 25, 50],
        'duration': [10, 10, 10],
        'response_time': [15, 36, 70]
    }
    events2 = {key: [v + 5 for v in events1[key]] for key in events1.keys()}

    incremental1.events = pd.DataFrame(data=events1)
    incremental2.events = pd.DataFrame(data=events2)
    assert incremental1 != incremental2
Ejemplo n.º 2
0
    def getBidsRun(self, **entities) -> BidsRun:
        """
        Get a BIDS Run from the archive.

        Args:
            entities: Entities defining a run in the archive.

        Returns:
            A BidsRun containing all the BidsIncrementals in the specified run.

        Raises:
            NoMatchError: If the entities don't match any runs in the archive.
            QueryError: If the entities match more than one run in the archive.

        Examples:
            >>> archive = BidsArchive('/tmp/dataset')
            >>> run = archive.getBidsRun(subject='01', session='02',
                                         task='testTask', run=1)
            >>> print(run.numIncrementals())
            53
        """
        images = self.getImages(**entities)
        if len(images) == 0:
            raise NoMatchError(f"Found no runs matching entities {entities}")
        if len(images) > 1:
            entities = [img.get_entities() for img in images]
            raise QueryError("Provided entities were not unique to one run; "
                             "try specifying more entities "
                             f" (got runs with these entities: {entities}")
        else:
            bidsImage = images[0]
            niftiImage = bidsImage.get_image()
            # TODO: Add inheritance processing for higher-level metadata JSON
            # files, in the style of the below events file inheritance
            metadata = self.getSidecarMetadata(bidsImage)
            metadata.pop('extension')  # only used in PyBids

            # This incremental will typically have a 4th (time) dimension > 1
            incremental = BidsIncremental(niftiImage, metadata)

            # Get dataset description, set
            incremental.datasetDescription = self.getDatasetDescription()

            # Get README, set
            with open(self.getReadme().path) as readmeFile:
                incremental.readme = readmeFile.read()

            # Get events file, set
            # Due to inheritance, must find and process all events files the
            # target image inherits from to create the final events file for
            # this run

            # Parse out the events files that the image file inherits from
            inheritedFiles = []
            searchEntities = bidsImage.get_entities()
            # only want to compare entities, not file type
            searchEntities.pop('extension', None)
            searchEntities.pop('suffix', None)

            allEventsFiles = self.getEvents()
            for eventFile in allEventsFiles:
                fileEntities = eventFile.get_entities()
                # only want to compare entities, not file type
                fileEntities.pop('extension', None)
                fileEntities.pop('suffix', None)
                if all(item in searchEntities.items()
                       for item in fileEntities.items()):
                    inheritedFiles.append(eventFile)

            # Sort the files by their position in the hierarchy.
            # Metric: Files with shorter path lengths are higher in the
            # inheritance hierarchy.
            inheritedFiles.sort(key=lambda eventsFile: len(eventsFile.path))

            # Merge every subsequent events file's DataFrame, in order of
            # inheritance (from top level to bottom level)
            # Using a dictionary representation of the DataFrame gives access to
            # the dict.update() method, which has exactly the desired
            # combination behavior for inheritance (replace conflicting values
            # with the new values, keep any non-conflicting values)
            def mergeEventsFiles(base: dict, eventsFile: BIDSDataFile):
                # Set DataFrame to be indexed by 'onset' column to ensure
                # dictionary update changes rows when onsets match
                dfToAdd = eventsFile.get_df()
                dfToAdd.set_index('onset', inplace=True, drop=False)
                base.update(dfToAdd.to_dict(orient='index'))
                return base

            eventsDFDict = functools.reduce(mergeEventsFiles, inheritedFiles,
                                            {})
            eventsDF = pd.DataFrame.from_dict(eventsDFDict, orient='index')
            # If there's no data in the DataFrame, create the default empty
            # events file DataFrame
            if eventsDF.empty:
                eventsDF = pd.DataFrame(columns=DEFAULT_EVENTS_HEADERS)

            # Ensure the events file order is the same as presentation/onset
            # order
            eventsDF.sort_values(by='onset', inplace=True, ignore_index=True)
            incremental.events = correctEventsFileDatatypes(eventsDF)

            run = BidsRun()
            # appendIncremental will take care of splitting the BidsIncremental
            # into its component 3-D images
            run.appendIncremental(incremental, validateAppend=False)
            return run