Example #1
0
def _runtask((tid, srcs, trgs, fun, args, kwargs, desc)):
    r"""
    Execute a single task.
    """
    # initialize logger
    logger = Logger.getInstance()
    # check required source files
    srcs_check = _check_files(srcs)
    if not numpy.all(srcs_check):
        raise TaskExecutionError('Task {} ({}): Required source file(s) missing: "{}"'.format(tid, desc, numpy.asarray(srcs)[~srcs_check]))
    # check target files
    trgs_check = _check_files(trgs)
    if numpy.all(trgs_check):
        logger.warning('Task {} ({}): All target files already existent; skipping task'.format(tid, desc))
        return
    elif numpy.any(trgs_check):
        raise TaskExecutionError('Task {} ({}): Some target file(s) already exist: "{}".'.format(tid, desc, numpy.asarray(trgs)[trgs_check]))
    # execute task
    try:
        fun(*args, **kwargs)
    except Exception as e:
        # remove target files (if partially created)
        for trg in trgs:
            try:
                if os.path.isfile(trg):
                    os.remove(trg)
            except Exception as e:
                pass 
        raise TaskExecutionError, TaskExecutionError('Task {} ({}): Execution failed. Partial results removed. Reason signaled: {}'.format(tid, desc, e)), sys.exc_info()[2]
    # check target files
    trgs_check = _check_files(trgs)
    if not numpy.all(srcs_check):
        raise TaskExecutionError('Task {} ({}): Execution failed to create some target files: "{}".'.format(tid, desc, numpy.asarray(trgs)[~trgs_check]))        
Example #2
0
def stripskull(directory, inset, stripsequence = False):
    r"""
    Compute a brain mask for MR brain images.
    
    Parameters
    ----------
    directory : string
        Where to place the results.
    inset : FileSet
        The input file set.
    stripsequence : False or string
        The sequence to use for computing the brain mask. If none supplied, the order
        in `~skullstripping.SEQUENCE_PREFERENCES` is respected.
        
    Returns
    -------
    resultset : FileSet
        A FilSet centered on ``directory`` and representing the binary brain masks.
    stripsequence : string
        The sequence employed for the skull-stripping.
    """
    logger = Logger.getInstance()
    
    # decide on strip-sequence
    if not stripsequence:
        for sequence in SEQUENCE_PREFERENCES:
            if sequence in inset.identifiers:
                stripsequence = sequence
                break
        if not stripsequence:
            stripsequence = inset.identifiers[0]
            logger.warning('None of the preferred sequences for skull-stripping "{}" available. Falling back to "{}"'.format(SEQUENCE_PREFERENCES, stripsequence))
    elif not stripsequence in inset.identifiers:
        raise ValueError('The chosen skull-strip sequence "{}" is not available in the input image set.'.format(stripsequence))

    # prepare the task machine
    tm = TaskMachine(multiprocessing=True)
        
    # prepare output
    resultset = FileSet(directory, inset.cases, False, ['{}.{}'.format(cid, PREFERRED_FILE_SUFFIX) for cid in inset.cases], 'cases', False)

    # prepare and register skull-stripping tasks
    for case in inset.cases:
        src = inset.getfile(case=case, identifier=stripsequence)
        dest = resultset.getfile(case=case)
        rfile = dest.replace('.{}'.format(PREFERRED_FILE_SUFFIX),  '_mask.{}'.format(PREFERRED_FILE_SUFFIX)) 
        tm.register([src], [dest], brainmask, [src, dest, rfile], dict(), 'skull-strip')
        
    # run
    tm.run()        
            
    return resultset, stripsequence
Example #3
0
 def __init__(self, multiprocessing = False, nprocesses = None) :
     r"""
     A class to which tasks can be registered and then executed, either sequential or in parallel.
     
     Parameters
     ----------
     multiprocessing : bool
         Enable/disable multiprocessing.
     nprocesses : int or None
         The number of processes to spawn. If ``None``, the number corresponds to the processor count.
     """
     self.logger = Logger.getInstance()
     self.tasks = []
     self.multiprocessing = multiprocessing
     self.nprocesses = nprocesses
Example #4
0
 def test_SaveLoad(self):
     """
     The bases essence of this test is to check if any one image format in any one
     dimension can be saved and read, as this is the only base requirement for using
     medpy.
     
     Additionally checks the basic expected behaviour of the load and save
     functionality.
     
     Since this usually does not make much sense, this implementation allows also to
     set a switch (verboose) which causes the test to print a comprehensive overview
     over which image formats with how many dimensions and which pixel data types
     can be read and written.
     """
     ####
     # VERBOOSE SETTINGS
     # The following are three variables that can be used to print some nicely
     # formatted additional output. When one of them is set to True, this unittest
     # should be run stand-alone.
     ####
     # Print a list of supported image types, dimensions and pixel data types
     supported = True
     # Print a list of image types that were tested but are not supported
     notsupported = False
     # Print a list of image type, dimensions and pixel data types configurations,
     # that seem to work but failed the consistency tests. These should be handled
     # with special care, as they might be the source of errors.
     inconsistent = True
     
     ####
     # OTHER SETTINGS
     ####
     # debug settings
     logger = Logger.getInstance()
     #logger.setLevel(logging.DEBUG)
     
     # run test either for most important formats or for all
     #__suffixes = self.__important # (choice 1)
     __suffixes = self.__pydicom + self.__nifti + self.__itk + self.__itk_more # (choice 2)
     
     
     # dimensions and dtypes to check
     __suffixes = list(set(__suffixes))
     __ndims = [1, 2, 3, 4, 5]
     __dtypes = [scipy.bool_,
                 scipy.int8, scipy.int16, scipy.int32, scipy.int64,
                 scipy.uint8, scipy.uint16, scipy.uint32, scipy.uint64,
                 scipy.float32, scipy.float64,
                 scipy.complex64, scipy.complex128]
     
     # prepare struct to save settings that passed the test
     valid_types = dict.fromkeys(__suffixes)
     for k1 in valid_types:
         valid_types[k1] = dict.fromkeys(__ndims)
         for k2 in valid_types[k1]:
             valid_types[k1][k2] = []
     
     # prepare struct to save settings that did not
     unsupported_type = dict.fromkeys(__suffixes)
     for k1 in unsupported_type:
         unsupported_type[k1] = dict.fromkeys(__ndims)
         for k2 in unsupported_type[k1]:
             unsupported_type[k1][k2] = dict.fromkeys(__dtypes)        
     
     # prepare struct to save settings that did not pass the data integrity test
     invalid_types = dict.fromkeys(__suffixes)
     for k1 in invalid_types:
         invalid_types[k1] = dict.fromkeys(__ndims)
         for k2 in invalid_types[k1]:
             invalid_types[k1][k2] = dict.fromkeys(__dtypes)
     
     # create artifical images, save them, load them again and compare them
     path = tempfile.mkdtemp()
     try:
         for ndim in __ndims:
             logger.debug('Testing for dimension {}...'.format(ndim))
             arr_base = scipy.random.randint(0, 10, range(10, ndim + 10))
             for dtype in __dtypes:
                 arr_save = arr_base.astype(dtype)
                 for suffix in __suffixes:
                     # do not run test, if in avoid array
                     if ndim in self.__avoid and suffix in self.__avoid[ndim]:
                         unsupported_type[suffix][ndim][dtype] = "Test skipped, as combination in the tests __avoid array."
                         continue
                     
                     image = '{}/img{}'.format(path, suffix)
                     try:
                         # attempt to save the image
                         save(arr_save, image)
                         self.assertTrue(os.path.exists(image), 'Image of type {} with shape={}/dtype={} has been saved without exception, but the file does not exist.'.format(suffix, arr_save.shape, dtype))
                                
                         # attempt to load the image
                         arr_load, header = load(image)
                         self.assertTrue(header, 'Image of type {} with shape={}/dtype={} has been loaded without exception, but no header has been supplied (got: {})'.format(suffix, arr_save.shape, dtype, header))
                         
                         # check for data consistency
                         msg = self.__diff(arr_save, arr_load)
                         if msg:
                             invalid_types[suffix][ndim][dtype] = msg
                         #elif list == type(valid_types[suffix][ndim]):
                         else:
                             valid_types[suffix][ndim].append(dtype)
                         
                         # remove image
                         if os.path.exists(image): os.remove(image)
                     except Exception as e: # clean up
                         unsupported_type[suffix][ndim][dtype] = e.message
                         if os.path.exists(image): os.remove(image)
     except Exception:
         if not os.listdir(path): os.rmdir(path)
         else: logger.debug('Could not delete temporary directory {}. Is not empty.'.format(path))
         raise
     
     if supported:
         print '\nsave() and load() support (at least) the following image configurations:'
         print 'type\tndim\tdtypes'
         for suffix in valid_types:
             for ndim, dtypes in valid_types[suffix].iteritems():
                 if list == type(dtypes) and not 0 == len(dtypes):
                     print '{}\t{}D\t{}'.format(suffix, ndim, map(lambda x: str(x).split('.')[-1][:-2], dtypes))
     if notsupported:
         print '\nthe following configurations are not supported:'
         print 'type\tndim\tdtype\t\terror'
         for suffix in unsupported_type:
             for ndim in unsupported_type[suffix]:
                 for dtype, msg in unsupported_type[suffix][ndim].iteritems():
                     if msg:
                         print '{}\t{}D\t{}\t\t{}'.format(suffix, ndim, str(dtype).split('.')[-1][:-2], msg)
         
     if inconsistent:
         print '\nthe following configurations show inconsistent saving and loading behaviour:'
         print 'type\tndim\tdtype\t\terror'
         for suffix in invalid_types:
             for ndim in invalid_types[suffix]:
                 for dtype, msg in invalid_types[suffix][ndim].iteritems():
                     if msg:
                         print '{}\t{}D\t{}\t\t{}'.format(suffix, ndim, str(dtype).split('.')[-1][:-2], msg)
Example #5
0
    def test_SaveLoad(self):
        """
        The bases essence of this test is to check if any one image format in any one
        dimension can be saved and read, as this is the only base requirement for using
        medpy.
        
        Additionally checks the basic expected behaviour of the load and save
        functionality.
        
        Since this usually does not make much sense, this implementation allows also to
        set a switch (verboose) which causes the test to print a comprehensive overview
        over which image formats with how many dimensions and which pixel data types
        can be read and written.
        """
        ####
        # VERBOOSE SETTINGS
        # The following are three variables that can be used to print some nicely
        # formatted additional output. When one of them is set to True, this unittest
        # should be run stand-alone.
        ####
        # Print a list of supported image types, dimensions and pixel data types
        supported = True
        # Print a list of image types that were tested but are not supported
        notsupported = False
        # Print a list of image type, dimensions and pixel data types configurations,
        # that seem to work but failed the consistency tests. These should be handled
        # with special care, as they might be the source of errors.
        inconsistent = True

        ####
        # OTHER SETTINGS
        ####
        # debug settings
        logger = Logger.getInstance()
        #logger.setLevel(logging.DEBUG)

        # run test either for most important formats or for all
        #__suffixes = self.__important # (choice 1)
        __suffixes = self.__pydicom + self.__nifti + self.__itk + self.__itk_more  # (choice 2)

        # dimensions and dtypes to check
        __suffixes = list(set(__suffixes))
        __ndims = [1, 2, 3, 4, 5]
        __dtypes = [
            scipy.bool_, scipy.int8, scipy.int16, scipy.int32, scipy.int64,
            scipy.uint8, scipy.uint16, scipy.uint32, scipy.uint64,
            scipy.float32, scipy.float64, scipy.complex64, scipy.complex128
        ]

        # prepare struct to save settings that passed the test
        valid_types = dict.fromkeys(__suffixes)
        for k1 in valid_types:
            valid_types[k1] = dict.fromkeys(__ndims)
            for k2 in valid_types[k1]:
                valid_types[k1][k2] = []

        # prepare struct to save settings that did not
        unsupported_type = dict.fromkeys(__suffixes)
        for k1 in unsupported_type:
            unsupported_type[k1] = dict.fromkeys(__ndims)
            for k2 in unsupported_type[k1]:
                unsupported_type[k1][k2] = dict.fromkeys(__dtypes)

        # prepare struct to save settings that did not pass the data integrity test
        invalid_types = dict.fromkeys(__suffixes)
        for k1 in invalid_types:
            invalid_types[k1] = dict.fromkeys(__ndims)
            for k2 in invalid_types[k1]:
                invalid_types[k1][k2] = dict.fromkeys(__dtypes)

        # create artifical images, save them, load them again and compare them
        path = tempfile.mkdtemp()
        try:
            for ndim in __ndims:
                logger.debug('Testing for dimension {}...'.format(ndim))
                arr_base = scipy.random.randint(0, 10,
                                                list(range(10, ndim + 10)))
                for dtype in __dtypes:
                    arr_save = arr_base.astype(dtype)
                    for suffix in __suffixes:
                        # do not run test, if in avoid array
                        if ndim in self.__avoid and suffix in self.__avoid[
                                ndim]:
                            unsupported_type[suffix][ndim][
                                dtype] = "Test skipped, as combination in the tests __avoid array."
                            continue

                        image = '{}/img{}'.format(path, suffix)
                        try:
                            # attempt to save the image
                            save(arr_save, image)
                            self.assertTrue(
                                os.path.exists(image),
                                'Image of type {} with shape={}/dtype={} has been saved without exception, but the file does not exist.'
                                .format(suffix, arr_save.shape, dtype))

                            # attempt to load the image
                            arr_load, header = load(image)
                            self.assertTrue(
                                header,
                                'Image of type {} with shape={}/dtype={} has been loaded without exception, but no header has been supplied (got: {})'
                                .format(suffix, arr_save.shape, dtype, header))

                            # check for data consistency
                            msg = self.__diff(arr_save, arr_load)
                            if msg:
                                invalid_types[suffix][ndim][dtype] = msg
                            #elif list == type(valid_types[suffix][ndim]):
                            else:
                                valid_types[suffix][ndim].append(dtype)

                            # remove image
                            if os.path.exists(image): os.remove(image)
                        except Exception as e:  # clean up
                            unsupported_type[suffix][ndim][dtype] = e.message
                            if os.path.exists(image): os.remove(image)
        except Exception:
            if not os.listdir(path): os.rmdir(path)
            else:
                logger.debug(
                    'Could not delete temporary directory {}. Is not empty.'.
                    format(path))
            raise

        if supported:
            print(
                '\nsave() and load() support (at least) the following image configurations:'
            )
            print('type\tndim\tdtypes')
            for suffix in valid_types:
                for ndim, dtypes in list(valid_types[suffix].items()):
                    if list == type(dtypes) and not 0 == len(dtypes):
                        print(('{}\t{}D\t{}'.format(
                            suffix, ndim,
                            [str(x).split('.')[-1][:-2] for x in dtypes])))
        if notsupported:
            print('\nthe following configurations are not supported:')
            print('type\tndim\tdtype\t\terror')
            for suffix in unsupported_type:
                for ndim in unsupported_type[suffix]:
                    for dtype, msg in list(
                            unsupported_type[suffix][ndim].items()):
                        if msg:
                            print(('{}\t{}D\t{}\t\t{}'.format(
                                suffix, ndim,
                                str(dtype).split('.')[-1][:-2], msg)))

        if inconsistent:
            print(
                '\nthe following configurations show inconsistent saving and loading behaviour:'
            )
            print('type\tndim\tdtype\t\terror')
            for suffix in invalid_types:
                for ndim in invalid_types[suffix]:
                    for dtype, msg in list(
                            invalid_types[suffix][ndim].items()):
                        if msg:
                            print(('{}\t{}D\t{}\t\t{}'.format(
                                suffix, ndim,
                                str(dtype).split('.')[-1][:-2], msg)))
    def test_MetadataConsistency(self):
        """
        This test checks the ability of different image formats to consistently save
        meta-data information. Especially if a conversion between formats is required,
        that involves different 3rd party modules, this is not always guaranteed.
        
        The images are saved in one format, loaded and then saved in another format.
        Subsequently the differences in the meta-data is checked.
        
        Currently this test can only check:
        - voxel spacing
        - image offset
        
        Note that some other test are inherently performed by the
        loadsave.TestIOFacilities class:
        - data type
        - shape
        - content
        
        With the verboose switches, a comprehensive list of the results can be obtianed.
        """
        ####
        # VERBOOSE SETTINGS
        # The following are two variables that can be used to print some nicely
        # formatted additional output. When one of them is set to True, this unittest
        # should be run stand-alone.
        ####
        # Print a list of format to format conversion which preserve meta-data
        consistent = True
        # Print a list of format to format conversion which do not preserve meta-data
        inconsistent = False
        # Print a list of formats that failed conversion in general
        unsupported = False

        ####
        # OTHER SETTINGS
        ####
        # debug settings
        logger = Logger.getInstance()
        #logger.setLevel(logging.DEBUG)

        # run test either for most important formats or for all (see loadsave.TestIOFacilities)
        #__suffixes = self.__important # (choice 1)
        __suffixes = self.__important + self.__itk  # (choice 2)

        # dimensions and dtypes to check
        __suffixes = list(set(__suffixes))
        __ndims = [1, 2, 3, 4, 5]
        __dtypes = [
            scipy.bool_,
            scipy.int8,
            scipy.int16,
            scipy.int32,
            scipy.int64,
            scipy.uint8,
            scipy.uint16,
            scipy.uint32,
            scipy.uint64,
            scipy.float32,
            scipy.
            float64,  #scipy.float128, # last one removed, as not present on every machine
            scipy.complex64,
            scipy.complex128,
        ]  #scipy.complex256 ## removed, as not present on every machine

        # prepare struct to save settings that passed the test
        consistent_types = dict.fromkeys(__suffixes)
        for k0 in consistent_types:
            consistent_types[k0] = dict.fromkeys(__suffixes)
            for k1 in consistent_types[k0]:
                consistent_types[k0][k1] = dict.fromkeys(__ndims)
                for k2 in consistent_types[k0][k1]:
                    consistent_types[k0][k1][k2] = []

        # prepare struct to save settings that did not
        inconsistent_types = dict.fromkeys(__suffixes)
        for k0 in inconsistent_types:
            inconsistent_types[k0] = dict.fromkeys(__suffixes)
            for k1 in inconsistent_types[k0]:
                inconsistent_types[k0][k1] = dict.fromkeys(__ndims)
                for k2 in inconsistent_types[k0][k1]:
                    inconsistent_types[k0][k1][k2] = dict.fromkeys(__dtypes)

        # prepare struct to save settings that did not pass the data integrity test
        unsupported_types = dict.fromkeys(__suffixes)
        for k0 in consistent_types:
            unsupported_types[k0] = dict.fromkeys(__suffixes)
            for k1 in unsupported_types[k0]:
                unsupported_types[k0][k1] = dict.fromkeys(__ndims)
                for k2 in unsupported_types[k0][k1]:
                    unsupported_types[k0][k1][k2] = dict.fromkeys(__dtypes)

        # create artifical images, save them, load them again and compare them
        path = tempfile.mkdtemp()
        try:
            for ndim in __ndims:
                logger.debug('Testing for dimension {}...'.format(ndim))
                arr_base = scipy.random.randint(0, 10,
                                                list(range(10, ndim + 10)))
                for dtype in __dtypes:
                    arr_save = arr_base.astype(dtype)
                    for suffix_from in __suffixes:
                        # do not run test, if in avoid array
                        if ndim in self.__avoid and suffix_from in self.__avoid[
                                ndim]:
                            unsupported_types[suffix_from][suffix_from][ndim][
                                dtype] = "Test skipped, as combination in the tests __avoid array."
                            continue

                        # save array as file, load again to obtain header and set the meta-data
                        image_from = '{}/img{}'.format(path, suffix_from)
                        try:
                            save(arr_save, image_from, None, True)
                            if not os.path.exists(image_from):
                                raise Exception(
                                    'Image of type {} with shape={}/dtype={} has been saved without exception, but the file does not exist.'
                                    .format(suffix_from, arr_save.shape,
                                            dtype))
                        except Exception as e:
                            unsupported_types[suffix_from][suffix_from][
                                ndim][dtype] = e.message if hasattr(
                                    e, 'message') else str(e.args)
                            continue

                        try:
                            img_from, hdr_from = load(image_from)
                            img_from = img_from.astype(
                                dtype
                            )  # change dtype of loaded image again, as sometimes the type is higher (e.g. int64 instead of int32) after loading!
                        except Exception as e:
                            _message = e.message if hasattr(
                                e, 'message') else str(e.args)
                            unsupported_types[suffix_from][suffix_from][ndim][
                                dtype] = 'Saved reference image of type {} with shape={}/dtype={} could not be loaded. Reason: {}'.format(
                                    suffix_from, arr_save.shape, dtype,
                                    _message)
                            continue

                        header.set_pixel_spacing(hdr_from, [
                            scipy.random.rand() * scipy.random.randint(1, 10)
                            for _ in range(img_from.ndim)
                        ])
                        try:
                            header.set_pixel_spacing(hdr_from, [
                                scipy.random.rand() *
                                scipy.random.randint(1, 10)
                                for _ in range(img_from.ndim)
                            ])
                            header.set_offset(hdr_from, [
                                scipy.random.rand() *
                                scipy.random.randint(1, 10)
                                for _ in range(img_from.ndim)
                            ])
                        except Exception as e:
                            logger.error(
                                'Could not set the header meta-data for image of type {} with shape={}/dtype={}. This should not happen and hints to a bug in the code. Signaled reason is: {}'
                                .format(suffix_from, arr_save.shape, dtype, e))
                            unsupported_types[suffix_from][suffix_from][
                                ndim][dtype] = e.message if hasattr(
                                    e, 'message') else str(e.args)
                            continue

                        for suffix_to in __suffixes:
                            # do not run test, if in avoid array
                            if ndim in self.__avoid and suffix_to in self.__avoid[
                                    ndim]:
                                unsupported_types[suffix_from][suffix_to][ndim][
                                    dtype] = "Test skipped, as combination in the tests __avoid array."
                                continue

                            # for each other format, try format to format conversion an check if the meta-data is consistent
                            image_to = '{}/img_to{}'.format(path, suffix_to)
                            try:
                                save(img_from, image_to, hdr_from, True)
                                if not os.path.exists(image_to):
                                    raise Exception(
                                        'Image of type {} with shape={}/dtype={} has been saved without exception, but the file does not exist.'
                                        .format(suffix_to, arr_save.shape,
                                                dtype))
                            except Exception as e:
                                unsupported_types[suffix_from][suffix_from][
                                    ndim][dtype] = e.message if hasattr(
                                        e, 'message') else str(e.args)
                                continue

                            try:
                                _, hdr_to = load(image_to)
                            except Exception as e:
                                _message = e.message if hasattr(
                                    e, 'message') else str(e.args)
                                unsupported_types[suffix_from][suffix_to][ndim][
                                    dtype] = 'Saved testing image of type {} with shape={}/dtype={} could not be loaded. Reason: {}'.format(
                                        suffix_to, arr_save.shape, dtype,
                                        _message)
                                continue

                            msg = self.__diff(hdr_from, hdr_to)
                            if msg:
                                inconsistent_types[suffix_from][suffix_to][
                                    ndim][dtype] = msg
                            else:
                                consistent_types[suffix_from][suffix_to][
                                    ndim].append(dtype)

                            # remove testing image
                            if os.path.exists(image_to): os.remove(image_to)

                        # remove reference image
                        if os.path.exists(image_to): os.remove(image_to)

        except Exception:
            if not os.listdir(path): os.rmdir(path)
            else:
                logger.debug(
                    'Could not delete temporary directory {}. Is not empty.'.
                    format(path))
            raise

        if consistent:
            print(
                '\nthe following format conversions are meta-data consistent:')
            print('from\tto\tndim\tdtypes')
            for suffix_from in consistent_types:
                for suffix_to in consistent_types[suffix_from]:
                    for ndim, dtypes in list(
                            consistent_types[suffix_from][suffix_to].items()):
                        if list == type(dtypes) and not 0 == len(dtypes):
                            print(('{}\t{}\t{}D\t{}'.format(
                                suffix_from, suffix_to, ndim,
                                [str(x).split('.')[-1][:-2] for x in dtypes])))
        if inconsistent:
            print(
                '\nthe following form conversions are not meta-data consistent:'
            )
            print('from\tto\tndim\tdtype\t\terror')
            for suffix_from in inconsistent_types:
                for suffix_to in inconsistent_types[suffix_from]:
                    for ndim in inconsistent_types[suffix_from][suffix_to]:
                        for dtype, msg in list(inconsistent_types[suffix_from]
                                               [suffix_to][ndim].items()):
                            if msg:
                                print(('{}\t{}\t{}D\t{}\t\t{}'.format(
                                    suffix_from, suffix_to, ndim,
                                    str(dtype).split('.')[-1][:-2], msg)))

        if unsupported:
            print(
                '\nthe following form conversions could not be tested due to errors:'
            )
            print('from\tto\tndim\tdtype\t\terror')
            for suffix_from in unsupported_types:
                for suffix_to in unsupported_types[suffix_from]:
                    for ndim in unsupported_types[suffix_from][suffix_to]:
                        for dtype, msg in list(unsupported_types[suffix_from]
                                               [suffix_to][ndim].items()):
                            if msg:
                                print(('{}\t{}\t{}D\t{}\t\t{}'.format(
                                    suffix_from, suffix_to, ndim,
                                    str(dtype).split('.')[-1][:-2], msg)))
Example #7
0
    def test_MetadataConsistency(self):
        """
        This test checks the ability of different image formats to consistently save
        meta-data information. Especially if a conversion between formats is required,
        that involves different 3rd party modules, this is not always guaranteed.
        
        The images are saved in one format, loaded and then saved in another format.
        Subsequently the differences in the meta-data is checked.
        
        Currently this test can only check:
        - voxel spacing
        - image offset
        
        Note that some other test are inherently performed by the
        loadsave.TestIOFacilities class:
        - data type
        - shape
        - content
        
        With the verboose switches, a comprehensive list of the results can be obtianed.
        """
        ####
        # VERBOOSE SETTINGS
        # The following are two variables that can be used to print some nicely
        # formatted additional output. When one of them is set to True, this unittest
        # should be run stand-alone.
        ####
        # Print a list of format to format conversion which preserve meta-data
        consistent = True
        # Print a list of format to format conversion which do not preserve meta-data
        inconsistent = True
        # Print a list of formats that failed conversion in general
        unsupported = False
        
        ####
        # OTHER SETTINGS
        ####
        # debug settings
        logger = Logger.getInstance()
        #logger.setLevel(logging.DEBUG)
        
        # run test either for most important formats or for all (see loadsave.TestIOFacilities)
        #__suffixes = self.__important # (choice 1)
        __suffixes = self.__pydicom + self.__nifti + self.__itk + self.__itk_more # (choice 2)
        
        # dimensions and dtypes to check
        __suffixes = list(set(__suffixes))
        __ndims = [1, 2, 3, 4, 5]
        __dtypes = [scipy.bool_,
                    scipy.int8, scipy.int16, scipy.int32, scipy.int64,
                    scipy.uint8, scipy.uint16, scipy.uint32, scipy.uint64,
                    scipy.float32, scipy.float64, #scipy.float128, # last one removed, as not present on every machine
                    scipy.complex64, scipy.complex128, ] #scipy.complex256 ## removed, as not present on every machine
        
        # prepare struct to save settings that passed the test
        consistent_types = dict.fromkeys(__suffixes)
        for k0 in consistent_types:
            consistent_types[k0] = dict.fromkeys(__suffixes)
            for k1 in consistent_types[k0]:
                consistent_types[k0][k1] = dict.fromkeys(__ndims)
                for k2 in consistent_types[k0][k1]:
                    consistent_types[k0][k1][k2] = []
        
        # prepare struct to save settings that did not
        inconsistent_types = dict.fromkeys(__suffixes)
        for k0 in inconsistent_types:
            inconsistent_types[k0] = dict.fromkeys(__suffixes)
            for k1 in inconsistent_types[k0]:
                inconsistent_types[k0][k1] = dict.fromkeys(__ndims)
                for k2 in inconsistent_types[k0][k1]:
                    inconsistent_types[k0][k1][k2] = dict.fromkeys(__dtypes)
        
        # prepare struct to save settings that did not pass the data integrity test
        unsupported_types = dict.fromkeys(__suffixes)
        for k0 in consistent_types:
            unsupported_types[k0] = dict.fromkeys(__suffixes)
            for k1 in unsupported_types[k0]:
                unsupported_types[k0][k1] = dict.fromkeys(__ndims)
                for k2 in unsupported_types[k0][k1]:
                    unsupported_types[k0][k1][k2] = dict.fromkeys(__dtypes)
        
        # create artifical images, save them, load them again and compare them
        path = tempfile.mkdtemp()
        try:
            for ndim in __ndims:
                logger.debug('Testing for dimension {}...'.format(ndim))
                arr_base = scipy.random.randint(0, 10, range(10, ndim + 10))
                for dtype in __dtypes:
                    arr_save = arr_base.astype(dtype)
                    for suffix_from in __suffixes:
                        # do not run test, if in avoid array
                        if ndim in self.__avoid and suffix_from in self.__avoid[ndim]:
                            unsupported_types[suffix_from][suffix_from][ndim][dtype] = "Test skipped, as combination in the tests __avoid array."
                            continue
                        
                        # save array as file, load again to obtain header and set the meta-data
                        image_from = '{}/img{}'.format(path, suffix_from)
                        try:
                            save(arr_save, image_from, None, True)
                            if not os.path.exists(image_from):
                                raise Exception('Image of type {} with shape={}/dtype={} has been saved without exception, but the file does not exist.'.format(suffix_from, arr_save.shape, dtype))
                        except Exception as e:
                            unsupported_types[suffix_from][suffix_from][ndim][dtype] = e.message
                            continue
                        
                        try:
                            img_from, hdr_from = load(image_from)
                            img_from = img_from.astype(dtype) # change dtype of loaded image again, as sometimes the type is higher (e.g. int64 instead of int32) after loading!
                        except Exception as e:
                            unsupported_types[suffix_from][suffix_from][ndim][dtype] = 'Saved reference image of type {} with shape={}/dtype={} could not be loaded. Reason: {}'.format(suffix_from, arr_save.shape, dtype, e.message)
                            continue

                        header.set_pixel_spacing(hdr_from, [scipy.random.rand() * scipy.random.randint(1, 10) for _ in range(img_from.ndim)])
                        try:
                            header.set_pixel_spacing(hdr_from, [scipy.random.rand() * scipy.random.randint(1, 10) for _ in range(img_from.ndim)])
                            header.set_offset(hdr_from, [scipy.random.rand() * scipy.random.randint(1, 10) for _ in range(img_from.ndim)])
                        except Exception as e:
                            logger.error('Could not set the header meta-data for image of type {} with shape={}/dtype={}. This should not happen and hints to a bug in the code. Signaled reason is: {}'.format(suffix_from, arr_save.shape, dtype, e))
                            unsupported_types[suffix_from][suffix_from][ndim][dtype] = e.message             
                            continue

                        for suffix_to in __suffixes:
                            # do not run test, if in avoid array
                            if ndim in self.__avoid and suffix_to in self.__avoid[ndim]:
                                unsupported_types[suffix_from][suffix_to][ndim][dtype] = "Test skipped, as combination in the tests __avoid array."
                                continue
                            
                            # for each other format, try format to format conversion an check if the meta-data is consistent
                            image_to = '{}/img_to{}'.format(path, suffix_to)
                            try:
                                save(img_from, image_to, hdr_from, True)
                                if not os.path.exists(image_to):
                                    raise Exception('Image of type {} with shape={}/dtype={} has been saved without exception, but the file does not exist.'.format(suffix_to, arr_save.shape, dtype))
                            except Exception as e:
                                unsupported_types[suffix_from][suffix_from][ndim][dtype] = e.message
                                continue
                            
                            try:
                                _, hdr_to = load(image_to)
                            except Exception as e:
                                unsupported_types[suffix_from][suffix_to][ndim][dtype] = 'Saved testing image of type {} with shape={}/dtype={} could not be loaded. Reason: {}'.format(suffix_to, arr_save.shape, dtype, e.message)
                                continue
                            
                            msg = self.__diff(hdr_from, hdr_to)
                            if msg:
                                inconsistent_types[suffix_from][suffix_to][ndim][dtype] = msg
                            else:
                                consistent_types[suffix_from][suffix_to][ndim].append(dtype)
                                
                            # remove testing image
                            if os.path.exists(image_to): os.remove(image_to)
                        
                        # remove reference image
                        if os.path.exists(image_to): os.remove(image_to)
                        
        except Exception:
            if not os.listdir(path): os.rmdir(path)
            else: logger.debug('Could not delete temporary directory {}. Is not empty.'.format(path))
            raise
        
        if consistent:
            print '\nthe following format conversions are meta-data consistent:'
            print 'from\tto\tndim\tdtypes'
            for suffix_from in consistent_types:
                for suffix_to in consistent_types[suffix_from]:
                    for ndim, dtypes in consistent_types[suffix_from][suffix_to].iteritems():
                        if list == type(dtypes) and not 0 == len(dtypes):
                            print '{}\t{}\t{}D\t{}'.format(suffix_from, suffix_to, ndim, map(lambda x: str(x).split('.')[-1][:-2], dtypes))
        if inconsistent:
            print '\nthe following form conversions are not meta-data consistent:'
            print 'from\tto\tndim\tdtype\t\terror'
            for suffix_from in inconsistent_types:
                for suffix_to in inconsistent_types[suffix_from]:
                    for ndim in inconsistent_types[suffix_from][suffix_to]:
                        for dtype, msg in inconsistent_types[suffix_from][suffix_to][ndim].iteritems():
                            if msg:
                                print '{}\t{}\t{}D\t{}\t\t{}'.format(suffix_from, suffix_to, ndim, str(dtype).split('.')[-1][:-2], msg)
            
        if unsupported:
            print '\nthe following form conversions could not be tested due to errors:'
            print 'from\tto\tndim\tdtype\t\terror'
            for suffix_from in unsupported_types:
                for suffix_to in unsupported_types[suffix_from]:
                    for ndim in unsupported_types[suffix_from][suffix_to]:
                        for dtype, msg in unsupported_types[suffix_from][suffix_to][ndim].iteritems():
                            if msg:
                                print '{}\t{}\t{}D\t{}\t\t{}'.format(suffix_from, suffix_to, ndim, str(dtype).split('.')[-1][:-2], msg)