Example #1
0
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
Example #2
0
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
Example #3
0
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)
Example #4
0
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
Example #5
0
    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
Example #6
0
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
Example #7
0
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
Example #8
0
    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
Example #9
0
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())
Example #10
0
    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()
Example #11
0
    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")
Example #12
0
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}")