def invert_elastix_transform_parameters(fixed: Path, tform_file: Path, param: Path, outdir: Path, threads: str):
    """
    Invert the transform and get a new transform file
    """
    if not common.test_installation('elastix'):
        raise OSError('elastix not installed')

    cmd = ['elastix',
           '-t0', tform_file,
           '-p', param,
           '-f', fixed,
           '-m', fixed,
           '-out', outdir,
           '-threads', threads   # 11/09/18. This was set to 1. Can iversions take advantage of multithreading?
           ]


    try:
        subprocess.check_output(cmd)
    except (Exception, subprocess.CalledProcessError) as e:
        msg = f'Inverting transform file failed. cmd: {cmd}\n{str(e)}:'
        logging.error(msg)
        logging.exception(msg)
        return False
    return True
Exemple #2
0
    def _invert(self, volume, tform, outdir, threads=None):
        """
        Using the iverted elastix transform paramter file, invert a volume with transformix

        Parameters
        ----------
        vol: str
            path to volume to invert
        tform: str
            path to elastix transform parameter file
        outdir: str
            path to save transformix output
        rename_output: str
            rename the transformed volume to this
        threads: str/None
            number of threads for transformix to use. if None, use all available cpus
        Returns
        -------
        str/bool
            path to new img if succesful else False
        """
        common.test_installation('transformix')
        lm_basename = os.path.splitext(os.path.basename(volume))[0]
        new_img_path = join(outdir, lm_basename + FILE_FORMAT)

        cmd = [
            'transformix',
            '-in', volume,
            '-tp', tform,
            '-out', outdir
        ]

        if threads:
            cmd.extend(['-threads', str(threads)])
        try:
            subprocess.check_output(cmd)
        except Exception as e:
            logging.exception('transformix failed inverting volume: {} Is transformix installed?. Error: {}'.format(volume, e))
            raise
        try:
            old_img = os.path.join(outdir, TRANSFORMIX_OUT)
            os.rename(old_img, new_img_path)
        except OSError:

            return old_img
        else:
            return new_img_path
    def __init__(self, config_path: Path, invertable, outdir, threads=None, noclobber=False):
        """
        Inverts a series of volumes. A yaml config file specifies the order of inverted transform parameters
        to use. This config file should be in the root of the directory containing these inverted tform dirs.

        Also need to input a directory containing volumes/label maps etc to invert. These need to be in directories
        named with the same name as the corresponding inverted tform file directories

        Parameters
        ----------
        config_path
            path to yaml config containing the oder of the inverted directories to use. The directories containing
            propagation tfransfrom files should be in the same diretory
        threads: str/ None
            number of threas to use. If None, use all available threads
        invertable: str
            path to object to invert (raw image, mask, label map etc)
        outdir
            where to store inverted volumes
        invertable: str
            dir or path. If dir, invert all objects within the subdirectories.
                If path to object (eg. labelmap) invert that instead
        noclobber: bool
            if True do not overwrite already inverted labels

        """

        self.noclobber = noclobber

        common.test_installation('transformix')

        self.config = cfg_load(config_path)

        self.invertables = invertable
        self.config_dir = config_path.parent  # The dir containing the inverted elx param files

        self.threads = threads
        self.out_dir = outdir
        common.mkdir_if_not_exists(self.out_dir)

        self.elx_param_prefix = ELX_PARAM_PREFIX
        self.PROPAGATION_TFORM_NAME = None  # Set in subclasses
        self.last_invert_dir = None # I thik this is used as a way to find volumes to do organ vol calculation on
Exemple #4
0
def reverse_registration(config: Union[str, LamaConfig]):
    common.test_installation('elastix')

    if isinstance(config, (Path, str)):
        config = LamaConfig(Path(config))

    # threads = str(config['threads'])

    inv_outdir = config.mkdir('inverted_transforms')

    # Set the fixed volume to be the rigidly-aligned volume from the forward registration
    paths = LamaSpecimenData(config.config_dir).setup()
    fixed_vols_dir = paths.reg_dirs[0]
    # Get the fixed vols
    fixed_vol_paths = common.get_images_ignore_elx_itermediates(fixed_vols_dir)

    # Get the fixed and moving images. They are flipped compared to the forward registration
    moving_vol = config['fixed_volume']

    # Do the forward registration for each image (usually just one image if using the jobrunner script)
    for fixed_vol in fixed_vol_paths:
        run_registration_schedule(config, fixed_vol, moving_vol, inv_outdir)
def batch_invert_transform_parameters(config: Union[Path, LamaConfig],
                                      clobber=True, new_log:bool=False):
    """
    Create new elastix TransformParameter files that can then be used by transformix to invert labelmaps, stats etc

    Parameters
    ----------
    config
        path to original reg pipeline config file

    clobber
        if True overwrite inverted parameters present

    new_log:
        Whether to create a new log file. If called from another module, logging may happen there
    """
    common.test_installation('elastix')

    if isinstance(config, (Path, str)):
        config = LamaConfig(config)

    threads = str(config['threads'])

    if new_log:
        common.init_logging(config / 'invert_transforms.log')

    reg_dirs = get_reg_dirs(config)

    # Get the image basenames from the first stage registration folder (usually rigid)
    # ignore images in non-relevent folder that may be present
    volume_names = [x.stem for x in common.get_file_paths(reg_dirs[0], ignore_folders=[RESOLUTION_IMGS_DIR, IMG_PYRAMID_DIR])]

    inv_outdir = config.mkdir('inverted_transforms')

    stages_to_invert = defaultdict(list)

    jobs: List[Dict] = []

    reg_stage_dir: Path

    for i, vol_id in enumerate(volume_names):

        for reg_stage_dir in reg_dirs:

            if not reg_stage_dir.is_dir():
                logging.error('cannot find {}'.format(reg_stage_dir))
                raise FileNotFoundError(f'Cannot find registration dir {reg_stage_dir}')

            inv_stage_dir = inv_outdir / reg_stage_dir.name

            specimen_stage_reg_dir = reg_stage_dir / vol_id
            specimen_stage_inversion_dir = inv_stage_dir / vol_id

            transform_file = common.getfile_startswith(specimen_stage_reg_dir, ELX_TRANSFORM_NAME)
            parameter_file = common.getfile_startswith(reg_stage_dir, ELX_PARAM_PREFIX)

            # Create the folder to put the specimen inversion parameter files in.
            inv_stage_dir.mkdir(exist_ok=True)

            # Add the stage to the inversion order config (in reverse order), if not already.
            if reg_stage_dir.name not in stages_to_invert['label_propagation_order']:
                stages_to_invert['label_propagation_order'].insert(0, reg_stage_dir.name)

            if clobber:
                common.mkdir_force(specimen_stage_inversion_dir)  # Overwrite any inversion file that exist for a single specimen

            # Each registration directory contains a metadata file, which contains the relative path to the fixed volume
            reg_metadata = cfg_load(specimen_stage_reg_dir / common.INDV_REG_METADATA)
            fixed_volume = (specimen_stage_reg_dir / reg_metadata['fixed_vol']).resolve()

            # Invert the Transform parameters with options for normal image inversion

            job = {
                'specimen_stage_inversion_dir': specimen_stage_inversion_dir,
                'parameter_file': abspath(parameter_file),
                'transform_file': transform_file,
                'fixed_volume': fixed_volume,
                'param_file_output_name': 'inversion_parameters.txt',
                'image_replacements': IMAGE_REPLACEMENTS,
                'label_replacements': LABEL_REPLACEMENTS,
                'image_transform_file': PROPAGATE_IMAGE_TRANSFORM,
                'label_transform_file': PROPAGATE_LABEL_TRANFORM,
                'clobber': clobber,
                'threads': threads
            }

            jobs.append(job)

    # By putting each inverison job (a single job per registration stage) we can speed things up a bit
    # If we can get multithreded inversion in elastix we can remove this python multithreading
    pool = Pool(8)
    try:
        pool.map(_invert_transform_parameters, jobs)

    except KeyboardInterrupt:
        print('terminating inversion')
        pool.terminate()
        pool.join()

    # TODO: Should we replace the need for this invert.yaml?
    reg_dir = Path(os.path.relpath(reg_stage_dir, inv_outdir))
    stages_to_invert['registration_directory'] = str(reg_dir)  # Doc why we need this
    # Create a yaml config file so that inversions can be run seperatley
    invert_config = config['inverted_transforms'] / PROPAGATE_CONFIG

    with open(invert_config, 'w') as yf:
        yf.write(yaml.dump(dict(stages_to_invert), default_flow_style=False))
Exemple #6
0
def batch_invert_transform_parameters(config: Union[str, LamaConfig],
                                      clobber=True,
                                      new_log: bool = False):
    """
    Create new elastix TransformParameter files that can then be used by transformix to invert labelmaps, stats etc

    Parameters
    ----------
    config
        path to original reg pipeline config file

    clobber
        if True overwrite inverted parameters present

    new_log:
        Whether to create a new log file. If called from another module, logging may happen there
    """
    common.test_installation('elastix')

    if isinstance(config, Path):
        config = LamaConfig(config)

    threads = str(config['threads'])

    if new_log:
        common.init_logging(config / 'invert_transforms.log')

    reg_dirs = get_reg_dirs(config)

    # Get the image basenames from the first stage registration folder (usually rigid)
    # ignore images in non-relevent folder that may be present
    volume_names = [
        x.stem for x in common.get_file_paths(reg_dirs[0],
                                              ignore_folder=IGNORE_FOLDER)
    ]

    inv_outdir = config.mkdir('inverted_transforms')

    stages_to_invert = defaultdict(list)

    jobs: List[Dict] = []

    reg_stage_dir: Path

    for i, vol_id in enumerate(volume_names):

        label_replacements = {
            'FinalBSplineInterpolationOrder': '0',
            'FixedInternalImagePixelType': 'short',
            'MovingInternalImagePixelType': 'short',
            'ResultImagePixelType': 'unsigned char',
            'WriteTransformParametersEachResolution': 'false',
            'WriteResultImageAfterEachResolution': 'false'
        }

        image_replacements = {
            'FinalBSplineInterpolationOrder': '3',
            'FixedInternalImagePixelType': 'float',
            'MovingInternalImagePixelType': 'float',
            'ResultImagePixelType': 'float',
            'WriteTransformParametersEachResolution': 'false',
            'WriteResultImageAfterEachResolution': 'false'
        }

        for reg_stage_dir in reg_dirs:

            if not reg_stage_dir.is_dir():
                logging.error('cannot find {}'.format(reg_stage_dir))
                raise FileNotFoundError(
                    f'Cannot find registration dir {reg_stage_dir}')

            inv_stage_dir = inv_outdir / reg_stage_dir.name

            specimen_stage_reg_dir = reg_stage_dir / vol_id
            specimen_stage_inversion_dir = inv_stage_dir / vol_id

            transform_file = common.getfile_startswith(specimen_stage_reg_dir,
                                                       ELX_TRANSFORM_PREFIX)
            parameter_file = common.getfile_startswith(reg_stage_dir,
                                                       ELX_PARAM_PREFIX)

            # Create the folder to put the specimen inversion parameter files in.
            inv_stage_dir.mkdir(exist_ok=True)

            # Add the stage to the inversion order config (in reverse order), if not already.
            if reg_stage_dir.name not in stages_to_invert['inversion_order']:
                stages_to_invert['inversion_order'].insert(
                    0, reg_stage_dir.name)

            if clobber:
                common.mkdir_force(
                    specimen_stage_inversion_dir
                )  # Overwrite any inversion file that exist for a single specimen

            # Each registration directory contains a metadata file, which contains the relative path to the fixed volume
            reg_metadata = cfg_load(specimen_stage_reg_dir /
                                    common.INDV_REG_METADATA)
            fixed_volume = (specimen_stage_reg_dir /
                            reg_metadata['fixed_vol']).resolve()

            # Invert the Transform parameters with options for normal image inversion

            job = {
                'specimen_stage_inversion_dir': specimen_stage_inversion_dir,
                'parameter_file': abspath(parameter_file),
                'transform_file': transform_file,
                'fixed_volume': fixed_volume,
                'param_file_output_name': 'inversion_parameters.txt',
                'image_replacements': image_replacements,
                'label_replacements': label_replacements,
                'image_transform_file': IMAGE_INVERTED_TRANSFORM,
                'label_transform_file': LABEL_INVERTED_TRANFORM,
                'clobber': clobber,
                'threads': threads
            }

            jobs.append(job)

    # Run the inversion jobs. Currently using only one thread as it seems that elastix now uses multiple threads on the
    # Inversions

    logging.info('inverting with {} threads: '.format(threads))
    pool = Pool(
        1
    )  # 17/09/18 If we can get multithreded inversion in elastix 4.9 we can remove the python multithreading
    try:
        pool.map(_invert_transform_parameters, jobs)

    except KeyboardInterrupt:
        print('terminating inversion')
        pool.terminate()
        pool.join()

    # TODO: Should we replace the need for this invert.yaml?
    reg_dir = Path(os.path.relpath(reg_stage_dir, inv_outdir))
    stages_to_invert['registration_directory'] = str(
        reg_dir)  # Doc why we need this
    # Create a yaml config file so that inversions can be run seperatley
    invert_config = config['inverted_transforms'] / INVERT_CONFIG

    with open(invert_config, 'w') as yf:
        yf.write(yaml.dump(dict(stages_to_invert), default_flow_style=False))
Exemple #7
0
def run(configfile: Path):
    """
        This is the main function Lama script for generating data from registering volumes
        It reads in the config file, creates directories, and initialises the registration process.

        Looks for paths to inputs relative the directory containing the config file

        Parameters
        ----------
        param config
            A toml config file
        """
    try:
        config = LamaConfig(configfile)
    except OSError as e:
        logging.error(f'Cannot open LAMA config file: {str(configfile)}\n{e}')
        raise
    except Exception as e:
        raise (LamaConfigError(e))

    config.mkdir('output_dir')
    qc_dir = config.mkdir('qc_dir')
    config.mkdir('average_folder')
    config.mkdir('root_reg_dir')

    # TODO find the histogram batch code
    # if not config['no_qc']:
    #     input_histogram_dir = config.mkdir('input_image_histograms')
    #     make_histograms(config['inputs'], input_histogram_dir)

    logpath = config.config_path.parent / LOG_FILE  # Make log in same directory as config file
    common.init_logging(logpath)

    if not common.test_installation('elastix'):
        raise OSError('Make sure elastix is installed')

    # Catch ctr-c signals so we can write that to logs
    # signal.signal(signal.SIGTERM, common.service_shutdown)
    signal.signal(signal.SIGINT, common.service_shutdown)

    mem_monitor = MonitorMemory(Path(config['output_dir']).absolute())

    # Disable QC output?
    no_qc: bool = config['no_qc']

    logging.info(common.git_log()
                 )  # If running from a git repo, log the branch and commit #

    logging.info("Registration started")

    final_registration_dir = run_registration_schedule(config)

    make_deformations_at_different_scales(config)

    create_glcms(config, final_registration_dir)

    if config['skip_transform_inversion']:
        logging.info('Skipping inversion of transforms')
    else:
        logging.info('inverting transforms')
        batch_invert_transform_parameters(config)

        logging.info('inverting volumes')
        invert_volumes(config)

        if config['label_map']:

            generate_organ_volumes(config)

    if not generate_staging_data(config):
        logging.warning('No staging data generated')

    # Write out the names of the registration dirs in the order they were run
    with open(config['root_reg_dir'] / REG_DIR_ORDER, 'w') as fh:
        for reg_stage in config['registration_stage_params']:
            fh.write(f'{reg_stage["stage_id"]}\n')

    if not no_qc:
        if config['skip_transform_inversion']:
            inverted_label_overlay_dir = None
        else:
            inverted_label_overlay_dir = config.mkdir(
                'inverted_label_overlay_dir')

        # registered_midslice_dir = config.mkdir('registered_midslice_dir')

        make_qc_images(config.config_dir, config['fixed_volume'], qc_dir)

    mem_monitor.stop()

    return True
Exemple #8
0
def run(configfile: Path):
    """
        This is the main function Lama script for generating data from registering volumes
        It reads in the config file, creates directories, and initialises the registration process.

        Looks for paths to inputs relative the directory containing the config file

        Parameters
        ----------
        param config
            A toml config file
        """
    try:
        config = LamaConfig(configfile)
    except OSError as e:
        logging.error(f'Cannot open LAMA config file: {str(configfile)}\n{e}')
        raise
    except Exception as e:
        raise (LamaConfigError(e))

    config.mkdir('output_dir')
    qc_dir = config.mkdir('qc_dir')
    config.mkdir('average_folder')
    config.mkdir('root_reg_dir')

    # TODO find the histogram batch code
    # if not config['no_qc']:
    #     input_histogram_dir = config.mkdir('input_image_histograms')
    #     make_histograms(config['inputs'], input_histogram_dir)

    logpath = config.config_path.parent / LOG_FILE  # Make log in same directory as config file
    common.init_logging(logpath)

    if not common.test_installation('elastix'):
        raise OSError('Make sure elastix is installed')

    # Catch ctr-c signals so we can write that to logs
    # signal.signal(signal.SIGTERM, common.service_shutdown)
    signal.signal(signal.SIGINT, common.service_shutdown)

    mem_monitor = MonitorMemory(Path(config['output_dir']).absolute())

    # Disable QC output?
    no_qc: bool = config['no_qc']

    logging.info(common.git_log()
                 )  # If running from a git repo, log the branch and commit #

    logging.info("Registration started")

    first_stage_only = config['skip_forward_registration']
    # If we only want the reverse label propagation we just need the initial rigid registration to act as the
    # Fixed image for the moving populaiton average
    final_registration_dir = run_registration_schedule(
        config, first_stage_only=first_stage_only)

    if not first_stage_only:
        neg_jac = make_deformations_at_different_scales(config)
        folding_report(neg_jac,
                       config['output_dir'],
                       config['label_info'],
                       outdir=config['output_dir'])

        create_glcms(config, final_registration_dir)

    # Write out the names of the registration dirs in the order they were run
    with open(config['root_reg_dir'] / REG_DIR_ORDER_CFG, 'w') as fh:
        for reg_stage in config['registration_stage_params']:
            fh.write(f'{reg_stage["stage_id"]}\n')
            if first_stage_only:
                break

    if config['skip_transform_inversion']:
        logging.info('Skipping inversion of transforms')
    else:
        logging.info('inverting transforms')

        if config['label_propagation'] == 'reverse_registration':
            reverse_registration(config)
        else:  # invert_transform method is the default
            batch_invert_transform_parameters(config)

        logging.info('propagating volumes')
        invert_volumes(config)

        # Now that labels have been inverted, should we delete the transorm files?
        if config['delete_inverted_transforms']:
            shutil.rmtree(config['output_dir'] / 'inverted_transforms')

        if config['label_map']:

            generate_organ_volumes(config)

            if config['seg_plugin_dir']:
                plugin_interface.secondary_segmentation(config)

    if not generate_staging_data(config):
        logging.warning('No staging data generated')

    if not no_qc:

        rev_reg = True if config[
            'label_propagation'] == 'reverse_registration' else False
        make_qc_images(config.config_dir,
                       config['fixed_volume'],
                       qc_dir,
                       mask=None,
                       reverse_reg_propagation=rev_reg)

    mem_monitor.stop()

    return True