Source code for bidshandler.bidstree

import os
import os.path as op
import xml.etree.ElementTree as ET

from .project import Project
from .subject import Subject
from .session import Session
from .scan import Scan
from .querymixin import QueryMixin
from .bidserrors import NoProjectError
from .utils import _copyfiles, _realize_paths, _prettyprint_xml


[docs]class BIDSTree(QueryMixin): """Highest BIDS archive level containing all the various projects Parameters ---------- fpath : str or path-like object File path containing all the project folders organised using the BIDS specification. initialize : bool, optional Whether to parse the folder and load any child structures. """
[docs] def __init__(self, fpath, initialize=True): super(BIDSTree, self).__init__() self.path = fpath self._projects = dict() self._queryable_types = ('project', 'subject', 'session', 'scan') if initialize: self._add_projects()
#region public methods
[docs] def add(self, other, copier=_copyfiles): """.. # noqa Add another Scan, Session, Subject, Project or BIDSTree to this object. Parameters ---------- other : Instance of :class:`bidshandler.Scan`, :class:`bidshandler.Session`, :class:`bidshandler.Subject`, :class:`bidshandler.Project` or :class:`bidshandler.BIDSTree` Object to be added to this BIDSTree. The added object must already exist in the same context as this object (except BIDSTree objects). copier : function A function to facilitate the copying of any applicable data. This function must have the call signature `function(src_files: list, dst_files: list)` Where src_files is the list of files to be moved and dst_files is the list of corresponding destinations. This will default to using utils._copyfiles which simply implements :py:func:`shutil.copy` and creates any directories that do not already exist. """ if isinstance(other, BIDSTree): # merge all child projects in for project in other.projects: self.add(project, copier) elif isinstance(other, Project): if other._id in self._projects: self.project(other._id).add(other, copier) else: new_project = Project._clone_into_bidstree(self, other) # copy over the description and readme: file_list = [other._description, other._readme] fl_left = _realize_paths(other, file_list) fl_right = _realize_paths(new_project, file_list) copier(fl_left, fl_right) new_project._description = other._description new_project._readme = other._readme new_project.add(other, copier) self._projects[other._id] = new_project elif isinstance(other, (Subject, Session, Scan)): # If the project the subject is a part of exists add the subject to # this project. if other.project._id in self._projects: self.project(other.project._id).add(other, copier) else: new_project = Project._clone_into_bidstree(self, other.project) # copy over the description and readme: file_list = [other.project._description, other.project._readme] fl_left = _realize_paths(other.project, file_list) fl_right = _realize_paths(new_project, file_list) copier(fl_left, fl_right) new_project._description = other.project._description new_project._readme = other.project._readme new_project.add(other, copier) self._projects[other.project._id] = new_project else: raise TypeError("Cannot add a {0} object to a BIDSTree".format( other.__name__))
[docs] def generate_map(self, output_file=None): """ Generate a map of the BIDS folder. Parameters ---------- output_file : str Path to write the file to. If not provided this will return the string representation. Returns ------- str String representation of xml structure. """ root = ET.Element('BIDSTree', attrib={'path': self.path}) for project in self.projects: root.append(project._generate_map()) if output_file is None: return _prettyprint_xml(ET.tostring(root, encoding='unicode')) dir_path = op.dirname(output_file) if dir_path != '': if not op.exists(dir_path): os.makedirs(op.dirname(output_file)) with open(output_file, 'w') as file: file.write(_prettyprint_xml(ET.tostring(root, encoding='unicode')))
[docs] def project(self, id_): """Return the Project corresponding to the provided id.""" try: return self._projects[id_] except KeyError: raise NoProjectError("Project {0} doesn't exist in this " "BIDS folder".format(id_))
#region private methods def _add_projects(self): """Add all the projects in the folder to the BIDS folder.""" projects = dict() for f in os.listdir(self.path): full_path = op.join(self.path, f) if op.isdir(full_path): projects[f] = Project(f, self) self._projects = projects #region properties @property def projects(self): """List of all Projects contained in the BIDS folder.""" return list(self._projects.values()) @property def scans(self): """List of all Scans contained in the BIDS folder.""" scan_list = [] for project in self.projects: scan_list.extend(project.scans) return scan_list @property def sessions(self): """List of all Sessions contained in the BIDS folder.""" session_list = [] for project in self.projects: session_list.extend(project.sessions) return session_list @property def subjects(self): """List of all Subjects contained in the BIDS folder.""" subject_list = [] for project in self.projects: subject_list.extend(project.subjects) return subject_list #region class methods
[docs] def __contains__(self, other): """.. # noqa Determine if the Subject contains a certain Scan, Session, Subject or Project. Parameters ---------- other : Instance of :class:`bidshandler.Scan`, :class:`bidshandler.Session`, :class:`bidshandler.Subject` or :class:`bidshandler.Project` Object to check whether it is contained in this BIDS folder. """ if isinstance(other, Project): return other._id in self.projects elif isinstance(other, (Subject, Session, Scan)): for project in self.projects: if other in project: return True return False raise TypeError("Can only determine if a Scan, Session or Subject is" "contained.")
[docs] def __iter__(self): """Iterable of the contained Project objects.""" return iter(self.projects)
[docs] def __getitem__(self, item): """ Return the child project with the corresponding name (if it exists). """ return self.project(item)
def __repr__(self): return '<BIDSTree, {0} project{1}, @ {2}>'.format( len(self.projects), ('s' if len(self.projects) > 1 else ''), self.path) def __str__(self): return "BIDS folder containing {0} projects".format(len(self.projects))