예제 #1
0
def write_results(results, cfg):
    # Write out the data
    write_scored_orientations(results, cfg)

    # grab working directory from config
    wdir = cfg.working_dir

    if not os.path.exists(cfg.analysis_dir):
        os.makedirs(cfg.analysis_dir)
    qbar_filename = os.path.join(
        wdir,
        'accepted_orientations_' + cfg.analysis_id + '.dat'
    )
    np.savetxt(qbar_filename, results['qbar'].T,
               fmt='%.18e', delimiter='\t')

    # ??? do we want to do this by default?
    gw = instrument.GrainDataWriter(
        os.path.join(cfg.analysis_dir, 'grains.out')
    )
    for gid, q in enumerate(results['qbar'].T):
        phi = 2*np.arccos(q[0])
        n = xfcapi.unitRowVector(q[1:])
        grain_params = np.hstack([phi*n, const.zeros_3, const.identity_6x1])
        gw.dump_grain(gid, 1., 0., grain_params)
    gw.close()
예제 #2
0
    def view_fit_grains_results(self):
        if self.fit_grains_results is None:
            QMessageBox.information(
                self.parent, "No Grains Fond", "No grains were found")
            return

        for result in self.fit_grains_results:
            print(result)

        # Build grains table
        num_grains = len(self.fit_grains_results)
        shape = (num_grains, 21)
        grains_table = np.empty(shape)
        gw = instrument.GrainDataWriter(array=grains_table)
        for result in self.fit_grains_results:
            gw.dump_grain(*result)
        gw.close()

        # Use the material to compute stress from strain
        material = HexrdConfig().active_material

        # Display results dialog
        dialog = FitGrainsResultsDialog(grains_table, material, self.parent)
        dialog.ui.resize(1200, 800)
        self.fit_grains_results_dialog = dialog
        dialog.show()
예제 #3
0
    def view_fit_grains_results(self):
        for result in self.fit_grains_results:
            print(result)

        # Build grains table
        num_grains = len(self.fit_grains_results)
        shape = (num_grains, 21)
        grains_table = np.empty(shape)
        gw = instrument.GrainDataWriter(array=grains_table)
        for result in self.fit_grains_results:
            gw.dump_grain(*result)
        gw.close()

        # Display grains table in popup dialog
        dialog = QDialog(self.parent)
        dialog.setWindowTitle('Fit Grains Results')

        model = FitGrainsResultsModel(grains_table, dialog)
        view = QTableView(dialog)
        view.setModel(model)
        view.verticalHeader().hide()
        view.resizeColumnToContents(0)

        layout = QVBoxLayout(dialog)
        layout.addWidget(view)
        dialog.setLayout(layout)
        dialog.resize(960, 320)
        dialog.exec_()
예제 #4
0
파일: utils.py 프로젝트: HEXRD/hexrdgui
def generate_grains_table(qbar):
    num_grains = qbar.shape[1]
    grains_table = np.empty((num_grains, 21))
    gw = instrument.GrainDataWriter(array=grains_table)
    for i, q in enumerate(qbar.T):
        phi = 2 * np.arccos(q[0])
        n = xfcapi.unitRowVector(q[1:])
        grain_params = np.hstack(
            [phi * n, constants.zeros_3, constants.identity_6x1])
        gw.dump_grain(i, 1, 0, grain_params)
    gw.close()
    return grains_table
예제 #5
0
    def run_fit_grains(self, config):
        self.fit_grains_results = None
        min_samples, mean_rpg = create_clustering_parameters(
            config, self.ome_maps)

        # Add fit_grains config
        dialog_config = HexrdConfig().indexing_config['fit_grains']
        config.set('fitgrains:npdiv', dialog_config['npdiv'])
        config.set('fitgrains:refit', dialog_config['refit'])
        config.set('fitgrains:threshold', dialog_config['threshold'])
        config.set('fitgrains:tth_max', dialog_config['tth_max'])
        config.set('fitgrains:tolerance:tth', dialog_config['tth_tolerances'])
        config.set('fitgrains:tolerance:eta', dialog_config['eta_tolerances'])
        config.set('fitgrains:tolerance:omega',
                   dialog_config['omega_tolerances'])

        kwargs = {
            'compl': self.completeness,
            'qfib': self.qfib,
            'qsym': config.material.plane_data.getQSym(),
            'cfg': config,
            'min_samples': min_samples,
            'compl_thresh': config.find_orientations.clustering.completeness,
            'radius': config.find_orientations.clustering.radius
        }
        self.update_progress_text('Running clustering')
        qbar, cl = run_cluster(**kwargs)

        # Generate grains table
        num_grains = qbar.shape[1]
        if num_grains == 0:
            print('Fit Grains Complete - no grains were found')
            return

        shape = (num_grains, 21)
        grains_table = np.empty(shape)
        gw = instrument.GrainDataWriter(array=grains_table)
        for gid, q in enumerate(qbar.T):
            phi = 2 * np.arccos(q[0])
            n = xfcapi.unitRowVector(q[1:])
            grain_params = np.hstack(
                [phi * n, const.zeros_3, const.identity_6x1])
            gw.dump_grain(gid, 1., 0., grain_params)
        gw.close()

        self.update_progress_text(
            f'Found {num_grains} grains. Running fit optimization.')

        self.fit_grains_results = fit_grains(config,
                                             grains_table,
                                             write_spots_files=False)
        print('Fit Grains Complete')
예제 #6
0
def write_results(results, cfg):
    # Write out the data
    np.savez_compressed('_'.join(['scored_orientations', cfg.analysis_id]),
                        **results['scored_orientations'])

    if not os.path.exists(cfg.analysis_dir):
        os.makedirs(cfg.analysis_dir)
    qbar_filename = 'accepted_orientations_' + cfg.analysis_id + '.dat'
    np.savetxt(qbar_filename, results['qbar'].T, fmt='%.18e', delimiter='\t')

    gw = instrument.GrainDataWriter(
        os.path.join(cfg.analysis_dir, 'grains.out'))
    for gid, q in enumerate(results['qbar'].T):
        phi = 2 * np.arccos(q[0])
        n = xfcapi.unitRowVector(q[1:])
        grain_params = np.hstack([phi * n, const.zeros_3, const.identity_6x1])
        gw.dump_grain(gid, 1., 0., grain_params)
    gw.close()
예제 #7
0
파일: fit_grains.py 프로젝트: johnkit/hexrd
def write_results(fit_results, cfg, grains_filename='grains.out'):
    instr = cfg.instrument.hedm

    # make output directories
    if not os.path.exists(cfg.analysis_dir):
        os.mkdir(cfg.analysis_dir)
        for det_key in instr.detectors:
            os.mkdir(os.path.join(cfg.analysis_dir, det_key))
    else:
        # make sure panel dirs exist under analysis dir
        for det_key in instr.detectors:
            if not os.path.exists(os.path.join(cfg.analysis_dir, det_key)):
                os.mkdir(os.path.join(cfg.analysis_dir, det_key))

    gw = instrument.GrainDataWriter(
        os.path.join(cfg.analysis_dir, grains_filename))
    for fit_result in fit_results:
        gw.dump_grain(*fit_result)
    gw.close()
예제 #8
0
    def run_fit_grains(self, config):
        min_samples, mean_rpg = create_clustering_parameters(
            config, self.ome_maps)

        kwargs = {
            'compl': self.completeness,
            'qfib': self.qfib,
            'qsym': config.material.plane_data.getQSym(),
            'cfg': config,
            'min_samples': min_samples,
            'compl_thresh': config.find_orientations.clustering.completeness,
            'radius': config.find_orientations.clustering.radius
        }
        self.update_progress_text('Running clustering')
        qbar, cl = run_cluster(**kwargs)

        # Generate grains table
        num_grains = qbar.shape[1]
        if num_grains == 0:
            QMessageBox.warning(self.parent, 'No Grains',
                                'Clustering found no grains')
            return

        shape = (num_grains, 21)
        grains_table = np.empty(shape)
        gw = instrument.GrainDataWriter(array=grains_table)
        for gid, q in enumerate(qbar.T):
            phi = 2 * np.arccos(q[0])
            n = xfcapi.unitRowVector(q[1:])
            grain_params = np.hstack(
                [phi * n, const.zeros_3, const.identity_6x1])
            gw.dump_grain(gid, 1., 0., grain_params)
        gw.close()

        self.update_progress_text(
            f'Found {num_grains} grains. Running fit optimization.')
        self.fit_grains_results = fit_grains(config,
                                             grains_table,
                                             write_spots_files=False)
        print('Fit Grains Complete')
예제 #9
0
파일: run.py 프로젝트: HEXRD/hexrdgui
def create_fit_grains_results_dialog(fit_grains_results, parent=None):
    # Build grains table
    num_grains = len(fit_grains_results)
    shape = (num_grains, 21)
    grains_table = np.empty(shape)
    gw = instrument.GrainDataWriter(array=grains_table)
    for result in fit_grains_results:
        gw.dump_grain(*result)
    gw.close()

    # Use the material to compute stress from strain
    indexing_config = HexrdConfig().indexing_config
    name = indexing_config.get('_selected_material')
    if name not in HexrdConfig().materials:
        name = HexrdConfig().active_material_name
    material = HexrdConfig().material(name)

    # Create the dialog
    dialog = FitGrainsResultsDialog(grains_table, material, parent)
    dialog.ui.resize(1200, 800)

    return dialog
예제 #10
0
ncpus = cfg.multiprocessing

if threshold is None:
    threshold = cfg.fit_grains.threshold

# =============================================================================
# %% FITTING
# =============================================================================

# make sure grains.out is there...
if not os.path.exists(grains_filename) or clobber_grains:
    try:
        qbar = np.loadtxt('accepted_orientations_' + analysis_id + '.dat',
                          ndmin=2).T

        gw = instrument.GrainDataWriter(grains_filename)
        grain_params_list = []
        for i_g, q in enumerate(qbar.T):
            phi = 2 * np.arccos(q[0])
            n = xfcapi.unitRowVector(q[1:])
            grain_params = np.hstack(
                [phi * n, cnst.zeros_3, cnst.identity_6x1])
            gw.dump_grain(int(i_g), 1., 0., grain_params)
            grain_params_list.append(grain_params)
        gw.close()
    except (IOError):
        if os.path.exists(cfg.fit_grains.estimate):
            grains_filename = cfg.fit_grains.estimate
        else:
            raise (
                RuntimeError,
예제 #11
0
def find_orientations(cfg, hkls=None, clean=False, profile=False, nsim=100):
    print('ready to run find_orientations')
    # %%
    # =============================================================================
    # SEARCH SPACE GENERATION
    # =============================================================================

    hedm = cfg.instrument.hedm
    plane_data = cfg.material.plane_data

    ncpus = cfg.multiprocessing

    # for indexing
    active_hkls = cfg.find_orientations.orientation_maps.active_hkls
    fiber_ndiv = cfg.find_orientations.seed_search.fiber_ndiv
    fiber_seeds = cfg.find_orientations.seed_search.hkl_seeds
    on_map_threshold = cfg.find_orientations.threshold

    # for clustering
    cl_radius = cfg.find_orientations.clustering.radius
    min_compl = cfg.find_orientations.clustering.completeness
    compl_thresh = cfg.find_orientations.clustering.completeness

    eta_ome = get_eta_ome(cfg, clean=clean)

    print("INFO:\tgenerating search quaternion list using %d processes" %
          ncpus)
    start = timeit.default_timer()
    qfib = generate_orientation_fibers(eta_ome,
                                       hedm.chi,
                                       on_map_threshold,
                                       fiber_seeds,
                                       fiber_ndiv,
                                       ncpus=ncpus)
    print("INFO:\t\t...took %f seconds" % (timeit.default_timer() - start))
    print("INFO: will test %d quaternions using %d processes" %
          (qfib.shape[1], ncpus))

    # %%
    # =============================================================================
    # ORIENTATION SCORING
    # =============================================================================

    scoredq_filename = 'scored_orientations_' + analysis_id(cfg) + '.npz'

    print("INFO:\tusing map search with paintGrid on %d processes" % ncpus)
    start = timeit.default_timer()

    completeness = indexer.paintGrid(
        qfib,
        eta_ome,
        etaRange=np.radians(cfg.find_orientations.eta.range),
        omeTol=np.radians(cfg.find_orientations.omega.tolerance),
        etaTol=np.radians(cfg.find_orientations.eta.tolerance),
        omePeriod=np.radians(cfg.find_orientations.omega.period),
        threshold=on_map_threshold,
        doMultiProc=ncpus > 1,
        nCPUs=ncpus)
    print("INFO:\t\t...took %f seconds" % (timeit.default_timer() - start))
    completeness = np.array(completeness)

    # export scored orientations
    np.savez_compressed(scoredq_filename,
                        quaternions=qfib,
                        completeness=completeness)
    print("INFO:\tsaved scored orientations to file: '%s'" %
          (scoredq_filename))

    # %%
    # =============================================================================
    # CLUSTERING AND GRAINS OUTPUT
    # =============================================================================

    if not os.path.exists(cfg.analysis_dir):
        os.makedirs(cfg.analysis_dir)
    qbar_filename = 'accepted_orientations_' + analysis_id(cfg) + '.dat'

    print("INFO:\trunning clustering using '%s'" %
          cfg.find_orientations.clustering.algorithm)
    start = timeit.default_timer()

    # Simulate N random grains to get neighborhood size
    print("INFO:\trunning %d simulations to determine neighborhood size" %
          nsim)
    seed_hkl_ids = [
        plane_data.hklDataList[active_hkls[i]]['hklID'] for i in fiber_seeds
    ]

    # need ome_ranges from imageseries
    # CAVEAT: assumes that all imageseries have same omega ranges!!!
    oims = OmegaImageSeries(cfg.image_series.itervalues().next())
    ome_ranges = [(np.radians([i['ostart'], i['ostop']]))
                  for i in oims.omegawedges.wedges]

    if seed_hkl_ids is not None:
        rand_q = mutil.unitVector(np.random.randn(4, nsim))
        rand_e = np.tile(2.*np.arccos(rand_q[0, :]), (3, 1)) \
          * mutil.unitVector(rand_q[1:, :])
        refl_per_grain = np.zeros(nsim)
        num_seed_refls = np.zeros(nsim)
        grain_param_list = np.vstack([
            rand_e,
            np.zeros((3, nsim)),
            np.tile(cnst.identity_6x1, (nsim, 1)).T
        ]).T
        sim_results = hedm.simulate_rotation_series(
            plane_data,
            grain_param_list,
            eta_ranges=np.radians(cfg.find_orientations.eta.range),
            ome_ranges=ome_ranges,
            ome_period=np.radians(cfg.find_orientations.omega.period))

        refl_per_grain = np.zeros(nsim)
        seed_refl_per_grain = np.zeros(nsim)
        for sim_result in sim_results.itervalues():
            for i, refl_ids in enumerate(sim_result[0]):
                refl_per_grain[i] += len(refl_ids)
                seed_refl_per_grain[i] += np.sum(
                    [sum(refl_ids == hkl_id) for hkl_id in seed_hkl_ids])

        min_samples = max(
            int(
                np.floor(0.5 * cfg.find_orientations.clustering.completeness *
                         min(seed_refl_per_grain))), 2)
        mean_rpg = int(np.round(np.average(refl_per_grain)))
    else:
        min_samples = 1
        mean_rpg = 1

    print("INFO:\tmean reflections per grain: %d" % mean_rpg)
    print("INFO:\tneighborhood size: %d" % min_samples)

    qbar, cl = run_cluster(completeness,
                           qfib,
                           plane_data.getQSym(),
                           cfg,
                           min_samples=min_samples,
                           compl_thresh=compl_thresh,
                           radius=cl_radius)

    print("INFO:\t\t...took %f seconds" % (timeit.default_timer() - start))
    print("INFO:\tfound %d grains; saved to file: '%s'" %
          (qbar.shape[1], qbar_filename))

    np.savetxt(qbar_filename, qbar.T, fmt='%.18e', delimiter='\t')

    gw = instrument.GrainDataWriter(
        os.path.join(cfg.analysis_dir, 'grains.out'))
    grain_params_list = []
    for gid, q in enumerate(qbar.T):
        phi = 2 * np.arccos(q[0])
        n = xfcapi.unitRowVector(q[1:])
        grain_params = np.hstack([phi * n, cnst.zeros_3, cnst.identity_6x1])
        gw.dump_grain(gid, 1., 0., grain_params)
        grain_params_list.append(grain_params)
    gw.close()
예제 #12
0
eta_ranges = np.radians(cfg.find_orientations.eta.range)
ome_period = np.radians(cfg.find_orientations.omega.period)

ncpus = cfg.multiprocessing

# =============================================================================
# %% FITTING
# =============================================================================

grains_filename = os.path.join(cfg.analysis_dir, 'grains.out')

# make sure grains.out is there...
if not os.path.exists(grains_filename) or clobber_grains:
    qbar = np.loadtxt('accepted_orientations_' + analysis_id + '.dat').T

    gw = instrument.GrainDataWriter(grains_filename)
    grain_params_list = []
    for i_g, q in enumerate(qbar.T):
        phi = 2*np.arccos(q[0])
        n = xfcapi.unitRowVector(q[1:])
        grain_params = np.hstack([phi*n, cnst.zeros_3, cnst.identity_6x1])
        gw.dump_grain(int(i_g), 1., 0., grain_params)
        grain_params_list.append(grain_params)
    gw.close()
    pass

grains_table = np.loadtxt(grains_filename, ndmin=2)
spots_filename = "spots_%05d.out"
params = dict(
        grains_table=grains_table,
        plane_data=plane_data,
예제 #13
0
파일: fit_grains.py 프로젝트: johnkit/hexrd
def execute(args, parser):
    # load the configuration settings
    cfgs = config.open(args.yml)

    # configure logging to the console:
    log_level = logging.DEBUG if args.debug else logging.INFO
    if args.quiet:
        log_level = logging.ERROR
    logger = logging.getLogger('hexrd')
    logger.setLevel(log_level)
    ch = logging.StreamHandler()
    ch.setLevel(logging.CRITICAL if args.quiet else log_level)
    cf = logging.Formatter('%(asctime)s - %(message)s', '%y-%m-%d %H:%M:%S')
    ch.setFormatter(cf)
    logger.addHandler(ch)

    # if find-orientations has not already been run, do so:
    quats_f = os.path.join(
        cfgs[0].working_dir,
        'accepted_orientations_%s.dat' % cfgs[0].analysis_id)
    if os.path.exists(quats_f):
        try:
            qbar = np.loadtxt(quats_f).T
        except (IOError):
            raise (RuntimeError,
                   "error loading indexing results '%s'" % quats_f)
    else:
        logger.info("Missing %s, running find-orientations", quats_f)
        logger.removeHandler(ch)
        from hexrd.findorientations import find_orientations
        results = find_orientations(cfgs[0])
        qbar = results['qbar']
        logger.addHandler(ch)

    logger.info('=== begin fit-grains ===')

    clobber = args.force or args.clean
    for cfg in cfgs:
        # prepare the analysis directory
        if os.path.exists(cfg.analysis_dir) and not clobber:
            logger.error(
                'Analysis "%s" at %s already exists.'
                ' Change yml file or specify "force"', cfg.analysis_name,
                cfg.analysis_dir)
            sys.exit()

        # make output directories
        instr = cfg.instrument.hedm
        if not os.path.exists(cfg.analysis_dir):
            os.makedirs(cfg.analysis_dir)
            for det_key in instr.detectors:
                os.mkdir(os.path.join(cfg.analysis_dir, det_key))
        else:
            # make sure panel dirs exist under analysis dir
            for det_key in instr.detectors:
                if not os.path.exists(os.path.join(cfg.analysis_dir, det_key)):
                    os.mkdir(os.path.join(cfg.analysis_dir, det_key))

        logger.info('*** begin analysis "%s" ***', cfg.analysis_name)

        # configure logging to file for this particular analysis
        logfile = os.path.join(cfg.working_dir, cfg.analysis_name,
                               'fit-grains.log')
        fh = logging.FileHandler(logfile, mode='w')
        fh.setLevel(log_level)
        ff = logging.Formatter('%(asctime)s - %(name)s - %(message)s',
                               '%m-%d %H:%M:%S')
        fh.setFormatter(ff)
        logger.info("logging to %s", logfile)
        logger.addHandler(fh)

        if args.profile:
            import cProfile as profile
            import pstats
            from io import StringIO

            pr = profile.Profile()
            pr.enable()

        grains_filename = os.path.join(cfg.analysis_dir, 'grains.out')

        # some conditions for arg handling
        existing_analysis = os.path.exists(grains_filename)
        new_with_estimate = not existing_analysis \
            and cfg.fit_grains.estimate is not None
        new_without_estimate = not existing_analysis \
            and cfg.fit_grains.estimate is None
        force_with_estimate = args.force \
            and cfg.fit_grains.estimate is not None
        force_without_estimate = args.force and cfg.fit_grains.estimate is None

        # handle args
        if args.clean or force_without_estimate or new_without_estimate:
            # need accepted orientations from indexing in this case
            if args.clean:
                logger.info(
                    "'clean' specified; ignoring estimate and using default")
            elif force_without_estimate:
                logger.info(
                    "'force' option specified, but no initial estimate; " +
                    "using default")
            try:
                gw = instrument.GrainDataWriter(grains_filename)
                for i_g, q in enumerate(qbar.T):
                    phi = 2 * np.arccos(q[0])
                    n = xfcapi.unitRowVector(q[1:])
                    grain_params = np.hstack(
                        [phi * n, cnst.zeros_3, cnst.identity_6x1])
                    gw.dump_grain(int(i_g), 1., 0., grain_params)
                gw.close()
            except (IOError):
                raise (RuntimeError, "indexing results '%s' not found!" %
                       'accepted_orientations_' + cfg.analysis_id + '.dat')
        elif force_with_estimate or new_with_estimate:
            grains_filename = cfg.fit_grains.estimate
        elif existing_analysis and not (clean or force):
            raise (RuntimeError, "fit results '%s' exist, " % grains_filename +
                   "but --clean or --force options not specified")

        grains_table = np.loadtxt(grains_filename, ndmin=2)

        # process the data
        gid_list = None
        if args.grains is not None:
            gid_list = [int(i) for i in args.grains.split(',')]
            pass

        cfg.fit_grains.qbar = qbar
        fit_results = fit_grains(
            cfg,
            grains_table,
            show_progress=not args.quiet,
            ids_to_refine=gid_list,
        )

        if args.profile:
            pr.disable()
            s = StringIO.StringIO()
            ps = pstats.Stats(pr, stream=s).sort_stats('cumulative')
            ps.print_stats(50)
            logger.info('%s', s.getvalue())

        # stop logging for this particular analysis
        fh.flush()
        fh.close()
        logger.removeHandler(fh)

        logger.info('*** end analysis "%s" ***', cfg.analysis_name)

        write_results(fit_results, cfg, grains_filename)

    logger.info('=== end fit-grains ===')
    # stop logging to the console
    ch.flush()
    ch.close()
    logger.removeHandler(ch)
예제 #14
0
def fit_grains(cfg,
               force=False, clean=False,
               show_progress=False, ids_to_refine=None):
    """
    Performs optimization of grain parameters.

    operates on a single HEDM config block
    """
    grains_filename = os.path.join(
        cfg.analysis_dir, 'grains.out'
    )

    # grab imageseries dict
    imsd = cfg.image_series

    # grab instrument
    instr = cfg.instrument.hedm

    # process plane data
    plane_data = cfg.material.plane_data
    tth_max = cfg.fit_grains.tth_max
    if isinstance(tth_max, bool):
        if tth_max:
            max_tth = instrument.max_tth(instr)
            plane_data.tThMax = max_tth
            logger.info("\tsetting the maximum 2theta to instrument"
                        + " maximum: %.2f degrees",
                        np.degrees(max_tth))
        else:
            logger.info("\tnot adjusting exclusions in planeData")
    else:
        # a value for tth max has been specified
        plane_data.exclusions = None
        plane_data.tThMax = np.radians(tth_max)
        logger.info("\tsetting the maximum 2theta to %.2f degrees",
                    tth_max)

    # make output directories
    if not os.path.exists(cfg.analysis_dir):
        os.mkdir(cfg.analysis_dir)
        for det_key in instr.detectors:
            os.mkdir(os.path.join(cfg.analysis_dir, det_key))
    else:
        # make sure panel dirs exist under analysis dir
        for det_key in instr.detectors:
            if not os.path.exists(os.path.join(cfg.analysis_dir, det_key)):
                os.mkdir(os.path.join(cfg.analysis_dir, det_key))

    # grab eta ranges and ome_period
    eta_ranges = np.radians(cfg.find_orientations.eta.range)

    # handle omega period
    # !!! we assume all detector ims have the same ome ranges, so any will do!
    oims = next(iter(imsd.values()))
    ome_period = np.radians(oims.omega[0, 0] + np.r_[0., 360.])

    # number of processes
    ncpus = cfg.multiprocessing

    # threshold for fitting
    threshold = cfg.fit_grains.threshold

    # some conditions for arg handling
    existing_analysis = os.path.exists(grains_filename)
    new_with_estimate = not existing_analysis \
        and cfg.fit_grains.estimate is not None
    new_without_estimate = not existing_analysis \
        and cfg.fit_grains.estimate is None
    force_with_estimate = force and cfg.fit_grains.estimate is not None
    force_without_estimate = force and cfg.fit_grains.estimate is None

    # handle args
    if clean or force_without_estimate or new_without_estimate:
        # need accepted orientations from indexing in this case
        if clean:
            logger.info(
                "'clean' specified; ignoring estimate and using default"
            )
        elif force_without_estimate:
            logger.info(
                "'force' option specified, but no initial estimate; "
                + "using default"
            )
        try:
            qbar = np.loadtxt(
                'accepted_orientations_' + cfg.analysis_id + '.dat',
                ndmin=2).T

            gw = instrument.GrainDataWriter(grains_filename)
            for i_g, q in enumerate(qbar.T):
                phi = 2*np.arccos(q[0])
                n = xfcapi.unitRowVector(q[1:])
                grain_params = np.hstack(
                    [phi*n, cnst.zeros_3, cnst.identity_6x1]
                )
                gw.dump_grain(int(i_g), 1., 0., grain_params)
            gw.close()
        except(IOError):
            raise(RuntimeError,
                  "indexing results '%s' not found!"
                  % 'accepted_orientations_' + cfg.analysis_id + '.dat')
    elif force_with_estimate or new_with_estimate:
        grains_filename = cfg.fit_grains.estimate
    elif existing_analysis and not (clean or force):
        raise(RuntimeError,
              "fit results '%s' exist, " % grains_filename
              + "but --clean or --force options not specified")

    # load grains table
    grains_table = np.loadtxt(grains_filename, ndmin=2)
    if ids_to_refine is not None:
        grains_table = np.atleast_2d(grains_table[ids_to_refine, :])
    spots_filename = "spots_%05d.out"
    params = dict(
            grains_table=grains_table,
            plane_data=plane_data,
            instrument=instr,
            imgser_dict=imsd,
            tth_tol=cfg.fit_grains.tolerance.tth,
            eta_tol=cfg.fit_grains.tolerance.eta,
            ome_tol=cfg.fit_grains.tolerance.omega,
            npdiv=cfg.fit_grains.npdiv,
            refit=cfg.fit_grains.refit,
            threshold=threshold,
            eta_ranges=eta_ranges,
            ome_period=ome_period,
            analysis_dirname=cfg.analysis_dir,
            spots_filename=spots_filename)

    # =====================================================================
    # EXECUTE MP FIT
    # =====================================================================

    # DO FIT!
    if len(grains_table) == 1 or ncpus == 1:
        logger.info("\tstarting serial fit")
        start = timeit.default_timer()
        fit_grain_FF_init(params)
        fit_results = list(
            map(fit_grain_FF_reduced,
                np.array(grains_table[:, 0], dtype=int))
        )
        fit_grain_FF_cleanup()
        elapsed = timeit.default_timer() - start
    else:
        nproc = min(ncpus, len(grains_table))
        chunksize = max(1, len(grains_table)//ncpus)
        logger.info("\tstarting fit on %d processes with chunksize %d",
                    nproc, chunksize)
        start = timeit.default_timer()
        pool = multiprocessing.Pool(
            nproc,
            fit_grain_FF_init,
            (params, )
        )
        fit_results = pool.map(
            fit_grain_FF_reduced,
            np.array(grains_table[:, 0], dtype=int),
            chunksize=chunksize
        )
        pool.close()
        pool.join()
        elapsed = timeit.default_timer() - start
    logger.info("fitting took %f seconds", elapsed)

    # =====================================================================
    # WRITE OUTPUT
    # =====================================================================

    gw = instrument.GrainDataWriter(
        os.path.join(cfg.analysis_dir, 'grains.out')
    )
    for fit_result in fit_results:
        gw.dump_grain(*fit_result)
        pass
    gw.close()