# NiBabel

### Access a cacophony of neuro-imaging file formats

# `batteryrunners`

Battery runner classes and Report classes

These classes / objects are for generic checking / fixing batteries

The `BatteryRunner` class will run a series of checks on a single object.

A check is a callable, of signature `func(obj, fix=False)` which returns a tuple `(obj, Report)` for `func(obj, False)` or `func(obj, True)`, where the obj may be a modified object, or a different object, if `fix==True`.

To run checks only, and return problem report objects:

    >>> from nibabel.batteryrunners import BatteryRunner, Report
    >>> def chk(obj, fix=False): # minimal check
    ...     return obj, Report()
    >>> btrun = BatteryRunner((chk,))
    >>> reports = btrun.check_only('a string')

To run checks and fixes, returning fixed object and problem report sequence, with possible fix messages:

    >>> fixed_obj, report_seq = btrun.check_fix('a string')

Reports are iterable things, where the elements in the iterations are `Problems`, with attributes `error`, `problem_level`, `problem_msg`, and possibly empty `fix_msg`. The `problem_level` is an integer, giving the level of problem, from 0 (no problem) to 50 (very bad problem). The levels follow the log levels from the logging module (e.g 40 equivalent to “error” level, 50 to “critical”). The `error` can be one of `None` if no error to suggest, or an Exception class that the user might consider raising for this situation. The `problem_msg` and `fix_msg` are human readable strings that should explain what happened.

## More about `checks`

Checks are callables returning objects and reports, like `chk` below, such that:

    obj, report = chk(obj, fix=False)
    obj, report = chk(obj, fix=True)

For example, for the Analyze header, we need to check the datatype:

    def chk_datatype(hdr, fix=True):
        rep = Report(hdr, HeaderDataError)
        code = int(hdr['datatype'])
        try:
            dtype = AnalyzeHeader._data_type_codes.dtype[code]
        except KeyError:
            rep.problem_level = 40
            rep.problem_msg = 'data code not recognized'
        else:
            if dtype.type is np.void:
                rep.problem_level = 40
                rep.problem_msg = 'data code not supported'
            else:
                return hdr, rep
        if fix:
            rep.fix_problem_msg = 'not attempting fix'
        return hdr, rep

or the bitpix:

    def chk_bitpix(hdr, fix=True):
        rep = Report(HeaderDataError)
        code = int(hdr['datatype'])
        try:
            dt = AnalyzeHeader._data_type_codes.dtype[code]
        except KeyError:
            rep.problem_level = 10
            rep.problem_msg = 'no valid datatype to fix bitpix'
            return hdr, rep
        bitpix = dt.itemsize * 8
        if bitpix == hdr['bitpix']:
            return hdr, rep
        rep.problem_level = 10
        rep.problem_msg = 'bitpix does not match datatype')
        if fix:
            hdr['bitpix'] = bitpix # inplace modification
            rep.fix_msg = 'setting bitpix to match datatype'
        return hdr, ret

or the pixdims:

    def chk_pixdims(hdr, fix=True):
        rep = Report(hdr, HeaderDataError)
        if not np.any(hdr['pixdim'][1:4] < 0):
            return hdr, rep
        rep.problem_level = 40
        rep.problem_msg = 'pixdim[1,2,3] should be positive'
        if fix:
            hdr['pixdim'][1:4] = np.abs(hdr['pixdim'][1:4])
            rep.fix_msg = 'setting to abs of pixdim values'
        return hdr, rep

`BatteryRunner`(checks) | Class to run set of checks
`Report`([error, problem_level, problem_msg, ...]) | Initialize report with values

## `BatteryRunner`

class nibabel.batteryrunners.BatteryRunner(checks)

  Bases: `object`

  Class to run set of checks

  Initialize instance from sequence of checks

  Parameters:
    checks sequence

      sequence of checks, where checks are callables matching signature `obj, rep = chk(obj, fix=False)`. Checks are run in the order they are passed.

  Examples

      >>> def chk(obj, fix=False): # minimal check
      ...     return obj, Report()
      >>> btrun = BatteryRunner((chk,))

  __init__(checks)

    Initialize instance from sequence of checks

    Parameters:
      checks sequence

        sequence of checks, where checks are callables matching signature `obj, rep = chk(obj, fix=False)`. Checks are run in the order they are passed.

    Examples

        >>> def chk(obj, fix=False): # minimal check
        ...     return obj, Report()
        >>> btrun = BatteryRunner((chk,))

  check_fix(obj)

    Run checks, with fixes, on obj returning obj, reports

    Parameters:
      obj anything

        object on which to run checks, fixes

    Returns:
      obj anything

        possibly modified or replaced obj, after fixes

      reports sequence

        sequence of reports on checks, fixes

  check_only(obj)

    Run checks on obj returning reports

    Parameters:
      obj anything

        object on which to run checks

    Returns:
      reports sequence

        sequence of report objects reporting on result of running checks (without fixes) on obj

## `Report`

class nibabel.batteryrunners.Report(error=<class 'Exception'>, problem_level=0, problem_msg='', fix_msg='')

  Bases: `object`

  Initialize report with values

  Parameters:
    error None or Exception

      Error to raise if raising error for this check. If None, no error can be raised for this check (it was probably normal).

    problem_level int

      level of problem. From 0 (no problem) to 50 (severe problem). If the report originates from a fix, then this is the level of the problem remaining after the fix. Default is 0

    problem_msg string

      String describing problem detected. Default is ‘’

    fix_msg string

      String describing any fix applied. Default is ‘’.

  Examples

      >>> rep = Report()
      >>> rep.problem_level
      0
      >>> rep = Report(TypeError, 10)
      >>> rep.problem_level
      10

  __init__(error=<class 'Exception'>, problem_level=0, problem_msg='', fix_msg='')

    Initialize report with values

    Parameters:
      error None or Exception

        Error to raise if raising error for this check. If None, no error can be raised for this check (it was probably normal).

      problem_level int

        level of problem. From 0 (no problem) to 50 (severe problem). If the report originates from a fix, then this is the level of the problem remaining after the fix. Default is 0

      problem_msg string

        String describing problem detected. Default is ‘’

      fix_msg string

        String describing any fix applied. Default is ‘’.

    Examples

        >>> rep = Report()
        >>> rep.problem_level
        0
        >>> rep = Report(TypeError, 10)
        >>> rep.problem_level
        10

  log_raise(logger, error_level=40)

    Log problem, raise error if problem >= error_level

    Parameters:
      logger log

        log object, implementing `log` method

      error_level int, optional

        If `self.problem_level` >= error_level, raise error

  property message

    formatted message string, including fix message if present

  write_raise(stream, error_level=40, log_level=30)

    Write report to stream

    Parameters:
      stream file-like

        implementing `write` method

      error_level int, optional

        level at which to raise error for problem detected in `self`

      log_level int, optional

        Such that if log_level is >= `self.problem_level` we write the report to stream, otherwise we write nothing.