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
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