def get_session_num(ident): """ For those times when you always want a numeric session (including for phantoms who are technically always session '1') """ if ident.session: try: num = int(ident.session) except ValueError: raise ParseException(f"ID {ident} has non-numeric session number") elif is_phantom(ident): num = 1 else: raise ParseException(f"ID {ident} is missing a session number") return num
def parse_filename(path): """ Parse a datman style file name. Args: path (:obj:`str`): A file name or full path to parse Raises: ParseException: If the file name does not match the datman convention. Returns: (tuple): A tuple containing: * ident (:obj:`DatmanIdentifier`): The parsed subject ID portion of the path. * tag (:obj:`str`): The scan tag that identifies the acquisition. * series (int): The series number. * description (:obj:`str`): The series description. Should be identical to the SeriesDescription field of the dicom headers (aside from some mangling to non-alphanumeric characters). """ fname = os.path.basename(path) match = FILENAME_PHA_PATTERN.match(fname) # check PHA first if not match: match = FILENAME_PATTERN.match(fname) if not match: raise ParseException() ident = DatmanIdentifier(match.group("id")) tag = match.group("tag") series = match.group("series") description = match.group("description") return ident, tag, series, description
def get_kcni_identifier(identifier, settings=None): """ Get a KCNIIdentifier from a valid string or an identifier. Args: identifier (:obj:`string`): A string matching a supported naming convention. settings (:obj:`dict`, optional): A settings dictionary matching the format described in :py:func:`parse`. Defaults to None. Raises: ParseException: When an ID that doesnt match any supported convention is given or when a valid ID can't be converted to KCNI format. Returns: KCNIIdentifier: An instance of a KCNI identifier with any field mappings applied. """ if isinstance(identifier, KCNIIdentifier): return identifier try: return KCNIIdentifier(identifier, settings) except ParseException: pass if isinstance(identifier, DatmanIdentifier): ident = identifier else: ident = DatmanIdentifier(identifier) if settings: # Flip settings from KCNI -> datman to datman -> KCNI to ensure the # KCNI convention is used in KCNIIdentifer.orig_id reverse = {} for entry in settings: if entry == "ID_TYPE" or not isinstance(settings[entry], dict): reverse[entry] = settings[entry] continue reverse[entry] = {val: key for key, val in settings[entry].items()} else: reverse = None study = get_field(ident._match_groups, "study", reverse) site = get_field(ident._match_groups, "site", reverse) if not is_phantom(ident): kcni = (f"{study}_{site}_{ident.subject.zfill(4)}_" f"{ident.timepoint}_SE{ident.session}_MR") return KCNIIdentifier(kcni, settings) try: pha_type = ident._match_groups.group("type") num = ident._match_groups.group("num") except IndexError: raise ParseException(f"Can't parse datman phantom {ident} to KCNI ID") subject = f"{pha_type}PHA_{num}" return KCNIIdentifier(f"{study}_{site}_{subject}_MR", settings)
def validate_subject_id(subject_id, config): """Ensures subject ID correctness based on configuration settings. This checks that a given ID: 1. Matches a supported naming convention 2. Matches a study tag that's defined in the configuration file for the current study 3. Matches a site that is defined for the study tag Args: subject_id (:obj:`str`): A subject ID to check. config (:obj:`datman.config.config`): A datman config instance that has been initialized to the study the subject ID should belong to. Raises: ParseException: When an ID is given that does not match any supported convention or that contains incorrect fields for the current study. Returns: :obj:`datman.scanid.Identifier`: A parsed datman identifier matching subject_id """ try: settings = config.get_key("ID_MAP") except datman.config.UndefinedSetting: settings = None new_subject_id = scanid.parse(subject_id, settings) valid_tags = config.get_study_tags() try: sites = valid_tags[new_subject_id.study] except KeyError: raise ParseException( f"Subject id {new_subject_id} has undefined " f"study code {new_subject_id.study}" ) if new_subject_id.site not in sites: raise ParseException( f"Subject id {new_subject_id} has undefined " f"site {new_subject_id.site} for study " f"{new_subject_id.study}" ) return new_subject_id
def match(self, identifier): if not isinstance(identifier, str): raise ParseException("Must be given a string to verify ID matches") match = self.scan_pattern.match(identifier) if not match: match = self.pha_pattern.match(identifier) return match
def parse_bids_filename(path): fname = os.path.basename(path) match = BIDS_SCAN_PATTERN.match(fname) if not match: raise ParseException(f"Invalid BIDS file name {path}") try: ident = BIDSFile( subject=match.group("subject"), session=match.group("session"), run=match.group("run"), suffix=match.group("suffix"), task=match.group("task"), acq=match.group("acq"), ce=match.group("ce"), dir=match.group("dir"), rec=match.group("rec"), echo=match.group("echo"), mod=match.group("mod"), ) except ParseException as e: raise ParseException(f"Invalid BIDS file name {path} - {e}") return ident
def get_project(session): config = datman.config.config() try: project = config.map_xnat_archive_to_project(session) except ParseException as e: raise ParseException( "Can't guess the XNAT Archive for {}. Reason - {}. Please provide " "an XNAT Archive name with the --project option".format( session, e)) except Exception as e: raise type(e)("Can't determine XNAT Archive for {}. Reason - {}" "".format(session, e)) return project
def __init__( self, subject, session, suffix, task=None, acq=None, ce=None, dir=None, rec=None, run=None, echo=None, mod=None, ): self.subject = subject self.session = session if not run: run = "1" self.run = run self.suffix = suffix if echo or task: if any([ce, dir, mod]): raise ParseException("Invalid entity found for task data") if ce or mod: if any([task, echo, dir]): raise ParseException("Invalid entity found for anat data") if dir: if any([ce, rec, mod, task, echo]): raise ParseException( "Invalid entity found for multiphase fmap") self.task = task self.acq = acq self.ce = ce self.dir = dir self.rec = rec self.echo = echo self.mod = mod
def rename_xnat_session(xnat, orig_name, new_name, project=None): """Rename a session on XNAT. Args: xnat (:obj:`datman.xnat.xnat`): A connection to the XNAT server. orig_name (:obj:`str`): The current session name on XNAT. new_name (:obj:`str`): The new name to apply to the session. project (:obj:`str`, optional): The XNAT project the session belongs to. If not given, it will be guessed based on orig_name. Defaults to None. Raises: XnatException: If problems internal to :obj:`datman.xnat.xnat.rename_session` occur. requests.HTTPError: If XNAT's API reports issues. Returns: bool: True if rename succeeded, False otherwise """ if not project: project = get_project(orig_name) try: ident = datman.scanid.parse(new_name) except ParseException: raise ParseException("New ID {} for experiment {} doesnt match a " "supported naming convention.".format( new_name, orig_name)) logger.info("Renaming {} to {} in project {}".format( orig_name, new_name, project)) orig_subject = xnat.find_subject(project, orig_name) try: xnat.rename_subject(project, orig_subject, ident.get_xnat_subject_id()) except HTTPError as e: if e.response.status_code == 500: # This happens on success sometimes (usually when experiment # is empty). Check if the rename succeeded. try: xnat.get_subject(project, ident.get_xnat_subject_id()) except XnatException: raise e xnat.rename_experiment(project, ident.get_xnat_subject_id(), orig_name, ident.get_xnat_experiment_id())
def __init__(self, identifier, settings=None): match = self.match(identifier) if not match: # work around for matching scanids when session not supplied match = self.scan_pattern.match(identifier + "_XX") if not match: raise ParseException(f"Invalid Datman ID {identifier}") self._match_groups = match self.orig_id = match.group("id") self.study = match.group("study") self.site = match.group("site") self.subject = match.group("subject") self.timepoint = match.group("timepoint") # Bug fix: spaces were being left after the session number leading to # broken file name self._session = match.group("session").strip()
def __init__(self, identifier, settings=None): match = self.match(identifier) if not match: raise ParseException(f"Invalid KCNI ID {identifier}") self._match_groups = match self.orig_id = match.group("id") self.study = get_field(match, "study", settings=settings) self.site = get_field(match, "site", settings=settings) self.subject = match.group("subject") try: self.pha_type = match.group("pha_type") except IndexError: # Not a phantom self.pha_type = None else: self.subject = f"PHA_{self.pha_type}{self.subject}" self.timepoint = match.group("timepoint") self.session = match.group("session")
def parse(identifier, settings=None): """ Parse a subject ID matching a supported naming convention. The 'settings' flag can be used to exclude any IDs that do not match the specified convention, or to translate certain ID fields to maintain consistency within a single naming convention. Accepted keys include: 'IdType': Restricts parsing to one naming convention (e.g. 'DATMAN' or 'KCNI') 'Study': Allows the 'study' field of an ID to be mapped from another convention's study code to a datman study code. 'Site': Allows the 'site' field of an ID to be translated from another convention's site code to a datman site code. .. note:: All 'settings' keys are case-sensitive. Using the settings from the below example will cause parse to reject any IDs that are not KCNI format, will translate any valid IDs containing 'DTI01' to the study code 'DTI', and will translate any valid IDs containing the site 'UTO' to the site 'UT1'. .. code-block:: python settings = { 'IdType': 'KCNI', 'Study': { 'DTI01': 'DTI' }, 'Site': { 'UTO': 'UT2' } } Args: identifier (:obj:`str`): A string that might be a valid subject ID. settings (:obj:`dict`, optional): A dictionary of settings to use when parsing the ID. Defaults to None. Raises: ParseException: If identifier does not match any supported naming convention. Returns: :obj:`Identifer`: An instance of a subclass of Identifier for the matched naming convention. """ if isinstance(identifier, Identifier): if not settings: return identifier # ID may need to be reparsed based on settings identifier = identifier.orig_id if settings and "IdType" in settings: id_type = settings["IdType"] else: id_type = "DETECT" if id_type in ("DATMAN", "DETECT"): try: return DatmanIdentifier(identifier) except ParseException: pass if id_type in ("KCNI", "DETECT"): try: return KCNIIdentifier(identifier, settings=settings) except ParseException: pass raise ParseException(f"Invalid ID - {identifier}")