def __init__(self, plane_data, instr, crystal_params=None, sample_rmat=None, min_energy=5., max_energy=35., tth_width=None, eta_width=None, eta_period=np.r_[-180., 180.]): self.plane_data = plane_data self._instrument = instr if crystal_params is None: self._crystal_params = np.hstack( [constants.zeros_3, constants.zeros_3, constants.identity_6x1]) else: self.crystal_params = crystal_params self._min_energy = min_energy self._max_energy = max_energy if sample_rmat is None: self._sample_rmat = constants.identity_3x3 else: self.sample_rmat = sample_rmat self.tth_width = tth_width self.eta_width = eta_width eta_period = np.asarray(eta_period, float).flatten() assert len(eta_period) == 2, "eta period must be a 2-element sequence" if xfcapi.angularDifference( eta_period[0], eta_period[1], units='degrees') > 1e-4: raise RuntimeError("period specification is not 360 degrees") self._eta_period = eta_period
def matchOmegas(xyo_det, hkls_idx, chi, rMat_c, bMat, wavelength, vInv=vInv_ref, beamVec=bVec_ref, etaVec=eta_ref, omePeriod=None): """ For a given list of (x, y, ome) points, outputs the index into the results from oscillAnglesOfHKLs, including the calculated omega values. """ # get omegas for rMat_s calculation if omePeriod is not None: meas_omes = xfcapi.mapAngle(xyo_det[:, 2], omePeriod) else: meas_omes = xyo_det[:, 2] oangs0, oangs1 = xfcapi.oscillAnglesOfHKLs(hkls_idx.T, chi, rMat_c, bMat, wavelength, vInv=vInv, beamVec=beamVec, etaVec=etaVec) if np.any(np.isnan(oangs0)): # debugging # TODO: remove this import pdb pdb.set_trace() nanIdx = np.where(np.isnan(oangs0[:, 0]))[0] errorString = "Infeasible parameters for hkls:\n" for i in range(len(nanIdx)): errorString += "%d %d %d\n" % tuple(hkls_idx[:, nanIdx[i]]) raise RuntimeError(errorString) else: # CAPI version gives vstacked angles... must be (2, nhkls) calc_omes = np.vstack([oangs0[:, 2], oangs1[:, 2]]) if omePeriod is not None: calc_omes = np.vstack([ xfcapi.mapAngle(oangs0[:, 2], omePeriod), xfcapi.mapAngle(oangs1[:, 2], omePeriod) ]) # do angular difference diff_omes = xfcapi.angularDifference(np.tile(meas_omes, (2, 1)), calc_omes) match_omes = np.argsort(diff_omes, axis=0) == 0 calc_omes = calc_omes.T.flatten()[match_omes.T.flatten()] return match_omes, calc_omes
def __init__(self, plane_data, instr, tvec=np.zeros(3), eta_steps=360, eta_period=np.r_[-180., 180.]): self._plane_data = plane_data self._instrument = instr tvec = np.asarray(tvec, float).flatten() assert len(tvec) == 3, "tvec input must have exactly 3 elements" self._tvec = tvec self._eta_steps = eta_steps eta_period = np.asarray(eta_period, float).flatten() assert len(eta_period) == 2, "eta period must be a 2-element sequence" if xfcapi.angularDifference(eta_period[0], eta_period[1], units='degrees') > 1e-4: raise RuntimeError("period specification is not 360 degrees") self._eta_period = eta_period
def eta_period(self, x): x = np.asarray(x, float).flatten() assert len(x) == 2, "eta period must be a 2-element sequence" if xfcapi.angularDifference(x[0], x[1], units='degrees') > 1e-4: raise RuntimeError("period specification is not 360 degrees") self._eta_period = x
def generate_ring_points(self, tths, etas, panel, display_mode): delta_eta_nom = np.degrees(np.median(np.diff(etas))) ring_pts = [] skipped_tth = [] for i, tth in enumerate(tths): # construct ideal angular coords ang_crds = np.vstack([np.tile(tth, len(etas)), etas]).T # !!! must apply offset xys_full = panel.angles_to_cart(ang_crds, tvec_c=self.tvec) # skip if ring not on panel if len(xys_full) == 0: skipped_tth.append(i) continue # clip to detector panel xys, on_panel = panel.clip_to_panel(xys_full, buffer_edges=False) if display_mode == ViewType.polar: # !!! apply offset correction ang_crds, _ = panel.cart_to_angles(xys, tvec_c=self.instrument.tvec) if len(ang_crds) == 0: skipped_tth.append(i) continue # Swap columns, convert to degrees ang_crds[:, [0, 1]] = np.degrees(ang_crds[:, [1, 0]]) # fix eta period ang_crds[:, 0] = xfcapi.mapAngle(ang_crds[:, 0], self.eta_period, units='degrees') # sort points for monotonic eta eidx = np.argsort(ang_crds[:, 0]) ang_crds = ang_crds[eidx, :] # branch cut delta_eta_est = np.median(np.diff(ang_crds[:, 0])) cut_on_panel = bool( xfcapi.angularDifference(np.min(ang_crds[:, 0]), np.max(ang_crds[:, 0]), units='degrees') < 2 * delta_eta_est) if cut_on_panel and len(ang_crds) > 2: split_idx = np.argmax( np.abs(np.diff(ang_crds[:, 0]) - delta_eta_est)) + 1 ang_crds = np.vstack([ ang_crds[:split_idx, :], nans_row, ang_crds[split_idx:, :] ]) # append to list with nan padding ring_pts.append(np.vstack([ang_crds, nans_row])) elif display_mode in [ViewType.raw, ViewType.cartesian]: if display_mode == ViewType.raw: # !!! distortion if panel.distortion is not None: xys = panel.distortion.apply_inverse(xys) # Convert to pixel coordinates # ??? keep in pixels? xys = panel.cartToPixel(xys) diff_tol = np.radians(self.delta_eta) + 1e-4 ring_breaks = np.where( np.abs(np.diff(etas[on_panel])) > diff_tol)[0] + 1 n_segments = len(ring_breaks) + 1 if n_segments == 1: ring_pts.append(np.vstack([xys, nans_row])) else: src_len = sum(on_panel) dst_len = src_len + len(ring_breaks) nxys = np.nan * np.ones((dst_len, 2)) ii = 0 for i in range(n_segments - 1): jj = ring_breaks[i] nxys[ii + i:jj + i, :] = xys[ii:jj, :] ii = jj i = n_segments - 1 nxys[ii + i:, :] = xys[ii:, :] ring_pts.append(np.vstack([nxys, nans_row])) return ring_pts, skipped_tth
def fit_grain_FF_reduced(grain_id): """ Perform non-linear least-square fit for the specified grain. Parameters ---------- grain_id : int The grain id. Returns ------- grain_id : int The grain id. completeness : float The ratio of predicted to measured (observed) Bragg reflections. chisq: float Figure of merit describing the sum of squared residuals for each Bragg reflection in the form (x, y, omega) normalized by the total number of degrees of freedom. grain_params : array_like The optimized grain parameters [<orientation [3]>, <centroid [3]> <inverse stretch [6]>]. Notes ----- input parameters are [plane_data, instrument, imgser_dict, tth_tol, eta_tol, ome_tol, npdiv, threshold] """ grains_table = paramMP['grains_table'] plane_data = paramMP['plane_data'] instrument = paramMP['instrument'] imgser_dict = paramMP['imgser_dict'] tth_tol = paramMP['tth_tol'] eta_tol = paramMP['eta_tol'] ome_tol = paramMP['ome_tol'] npdiv = paramMP['npdiv'] refit = paramMP['refit'] threshold = paramMP['threshold'] eta_ranges = paramMP['eta_ranges'] ome_period = paramMP['ome_period'] analysis_dirname = paramMP['analysis_dirname'] prefix = paramMP['spots_filename'] spots_filename = None if prefix is None else prefix % grain_id grain = grains_table[grain_id] grain_params = grain[3:15] for tols in zip(tth_tol, eta_tol, ome_tol): complvec, results = instrument.pull_spots(plane_data, grain_params, imgser_dict, tth_tol=tols[0], eta_tol=tols[1], ome_tol=tols[2], npdiv=npdiv, threshold=threshold, eta_ranges=eta_ranges, ome_period=ome_period, dirname=analysis_dirname, filename=spots_filename, save_spot_list=False, quiet=True, check_only=False, interp='nearest') # ======= DETERMINE VALID REFLECTIONS ======= culled_results = dict.fromkeys(results) num_refl_tot = 0 num_refl_valid = 0 for det_key in culled_results: panel = instrument.detectors[det_key] ''' grab panel results: peak_id hkl_id hkl sum_int max_int, pred_angs, meas_angs, meas_xy ''' presults = results[det_key] nrefl = len(presults) # make data arrays refl_ids = np.empty(nrefl) max_int = np.empty(nrefl) for i, spot_data in enumerate(presults): refl_ids[i] = spot_data[0] max_int[i] = spot_data[4] valid_refl_ids = refl_ids >= 0 # find unsaturated spots on this panel unsat_spots = np.ones(len(valid_refl_ids), dtype=bool) if panel.saturation_level is not None: unsat_spots[valid_refl_ids] = \ max_int[valid_refl_ids] < panel.saturation_level idx = np.logical_and(valid_refl_ids, unsat_spots) # if an overlap table has been written, load it and use it overlaps = np.zeros_like(idx, dtype=bool) try: ot = np.load( os.path.join(analysis_dirname, os.path.join(det_key, 'overlap_table.npz'))) for key in ot.keys(): for this_table in ot[key]: these_overlaps = np.where(this_table[:, 0] == grain_id)[0] if len(these_overlaps) > 0: mark_these = np.array(this_table[these_overlaps, 1], dtype=int) otidx = [ np.where(refl_ids == mt)[0] for mt in mark_these ] overlaps[otidx] = True idx = np.logical_and(idx, ~overlaps) # logger.info("found overlap table for '%s'", det_key) except (IOError, IndexError): # logger.info("no overlap table found for '%s'", det_key) pass # attach to proper dict entry # FIXME: want to avoid looping again here culled_results[det_key] = [presults[i] for i in np.where(idx)[0]] num_refl_tot += len(valid_refl_ids) num_refl_valid += sum(valid_refl_ids) pass # now we have culled data # CAVEAT: completeness from pullspots only; incl saturated and overlaps # <JVB 2015-12-15> completeness = num_refl_valid / float(num_refl_tot) # ======= DO LEASTSQ FIT ======= if num_refl_valid <= 12: # not enough reflections to fit... exit return grain_id, completeness, np.inf, grain_params else: grain_params = fitGrain(grain_params, instrument, culled_results, plane_data.latVecOps['B'], plane_data.wavelength) # get chisq # TODO: do this while evaluating fit??? chisq = objFuncFitGrain(grain_params[gFlag_ref], grain_params, gFlag_ref, instrument, culled_results, plane_data.latVecOps['B'], plane_data.wavelength, ome_period, simOnly=False, return_value_flag=2) pass # end conditional on fit pass # end tolerance looping if refit is not None: # first get calculated x, y, ome from previous solution # NOTE: this result is a dict xyo_det_fit_dict = objFuncFitGrain(grain_params[gFlag_ref], grain_params, gFlag_ref, instrument, culled_results, plane_data.latVecOps['B'], plane_data.wavelength, ome_period, simOnly=True, return_value_flag=2) # make dict to contain new culled results culled_results_r = dict.fromkeys(culled_results) num_refl_valid = 0 for det_key in culled_results_r: presults = culled_results[det_key] if not presults: culled_results_r[det_key] = [] continue ims = imgser_dict[det_key] ome_step = sum(np.r_[-1, 1] * ims.metadata['omega'][0, :]) xyo_det = np.atleast_2d( np.vstack([np.r_[x[7], x[6][-1]] for x in presults])) xyo_det_fit = xyo_det_fit_dict[det_key] xpix_tol = refit[0] * panel.pixel_size_col ypix_tol = refit[0] * panel.pixel_size_row fome_tol = refit[1] * ome_step # define difference vectors for spot fits x_diff = abs(xyo_det[:, 0] - xyo_det_fit['calc_xy'][:, 0]) y_diff = abs(xyo_det[:, 1] - xyo_det_fit['calc_xy'][:, 1]) ome_diff = np.degrees( xfcapi.angularDifference(xyo_det[:, 2], xyo_det_fit['calc_omes'])) # filter out reflections with centroids more than # a pixel and delta omega away from predicted value idx_new = np.logical_and( x_diff <= xpix_tol, np.logical_and(y_diff <= ypix_tol, ome_diff <= fome_tol)) # attach to proper dict entry culled_results_r[det_key] = [ presults[i] for i in np.where(idx_new)[0] ] num_refl_valid += sum(idx_new) pass # only execute fit if left with enough reflections if num_refl_valid > 12: grain_params = fitGrain(grain_params, instrument, culled_results_r, plane_data.latVecOps['B'], plane_data.wavelength) # get chisq # TODO: do this while evaluating fit??? chisq = objFuncFitGrain(grain_params[gFlag_ref], grain_params, gFlag_ref, instrument, culled_results_r, plane_data.latVecOps['B'], plane_data.wavelength, ome_period, simOnly=False, return_value_flag=2) pass pass # close refit conditional return grain_id, completeness, chisq, grain_params
def objFuncFitGrain(gFit, gFull, gFlag, instrument, reflections_dict, bMat, wavelength, omePeriod, simOnly=False, return_value_flag=return_value_flag): """ Calculate residual between measured and simulated ff-HEDM G-vectors. gFull[0] = expMap_c[0] gFull[1] = expMap_c[1] gFull[2] = expMap_c[2] gFull[3] = tVec_c[0] gFull[4] = tVec_c[1] gFull[5] = tVec_c[2] gFull[6] = vInv_MV[0] gFull[7] = vInv_MV[1] gFull[8] = vInv_MV[2] gFull[9] = vInv_MV[3] gFull[10] = vInv_MV[4] gFull[11] = vInv_MV[5] OLD CALL objFuncFitGrain(gFit, gFull, gFlag, detectorParams, xyo_det, hkls_idx, bMat, wavelength, bVec, eVec, dFunc, dParams, omePeriod, simOnly=False, return_value_flag=return_value_flag) Parameters ---------- gFit : TYPE DESCRIPTION. gFull : TYPE DESCRIPTION. gFlag : TYPE DESCRIPTION. instrument : TYPE DESCRIPTION. reflections_dict : TYPE DESCRIPTION. bMat : TYPE DESCRIPTION. wavelength : TYPE DESCRIPTION. omePeriod : TYPE DESCRIPTION. simOnly : TYPE, optional DESCRIPTION. The default is False. return_value_flag : TYPE, optional DESCRIPTION. The default is return_value_flag. Raises ------ RuntimeError DESCRIPTION. Returns ------- retval : TYPE DESCRIPTION. """ bVec = instrument.beam_vector eVec = instrument.eta_vector # fill out parameters gFull[gFlag] = gFit # map parameters to functional arrays rMat_c = xfcapi.makeRotMatOfExpMap(gFull[:3]) tVec_c = gFull[3:6].reshape(3, 1) vInv_s = gFull[6:] vMat_s = mutil.vecMVToSymm(vInv_s) # NOTE: Inverse of V from F = V * R # loop over instrument panels # CAVEAT: keeping track of key ordering in the "detectors" attribute of # instrument here because I am not sure if instatiating them using # dict.fromkeys() preserves the same order if using iteration... # <JVB 2017-10-31> calc_omes_dict = dict.fromkeys(instrument.detectors, []) calc_xy_dict = dict.fromkeys(instrument.detectors) meas_xyo_all = [] det_keys_ordered = [] for det_key, panel in instrument.detectors.items(): det_keys_ordered.append(det_key) rMat_d, tVec_d, chi, tVec_s = extract_detector_transformation( instrument.detector_parameters[det_key]) results = reflections_dict[det_key] if len(results) == 0: continue """ extract data from results list fields: refl_id, gvec_id, hkl, sum_int, max_int, pred_ang, meas_ang, meas_xy or array from spots tables: 0:5 ID PID H K L 5:7 sum(int) max(int) 7:10 pred tth pred eta pred ome 10:13 meas tth meas eta meas ome 13:15 pred X pred Y 15:17 meas X meas Y """ if isinstance(results, list): # WARNING: hkls and derived vectors below must be columnwise; # strictly necessary??? change affected APIs instead? # <JVB 2017-03-26> hkls = np.atleast_2d(np.vstack([x[2] for x in results])).T meas_xyo = np.atleast_2d( np.vstack([np.r_[x[7], x[6][-1]] for x in results])) elif isinstance(results, np.ndarray): hkls = np.atleast_2d(results[:, 2:5]).T meas_xyo = np.atleast_2d(results[:, [15, 16, 12]]) # distortion handling if panel.distortion is not None: meas_omes = meas_xyo[:, 2] xy_unwarped = panel.distortion.apply(meas_xyo[:, :2]) meas_xyo = np.vstack([xy_unwarped.T, meas_omes]).T pass # append to meas_omes meas_xyo_all.append(meas_xyo) # G-vectors: # 1. calculate full g-vector components in CRYSTAL frame from B # 2. rotate into SAMPLE frame and apply stretch # 3. rotate back into CRYSTAL frame and normalize to unit magnitude # IDEA: make a function for this sequence of operations with option for # choosing ouput frame (i.e. CRYSTAL vs SAMPLE vs LAB) gVec_c = np.dot(bMat, hkls) gVec_s = np.dot(vMat_s, np.dot(rMat_c, gVec_c)) gHat_c = mutil.unitVector(np.dot(rMat_c.T, gVec_s)) # !!!: check that this operates on UNWARPED xy match_omes, calc_omes = matchOmegas(meas_xyo, hkls, chi, rMat_c, bMat, wavelength, vInv=vInv_s, beamVec=bVec, etaVec=eVec, omePeriod=omePeriod) # append to omes dict calc_omes_dict[det_key] = calc_omes # TODO: try Numba implementations rMat_s = xfcapi.makeOscillRotMatArray(chi, calc_omes) calc_xy = xfcapi.gvecToDetectorXYArray(gHat_c.T, rMat_d, rMat_s, rMat_c, tVec_d, tVec_s, tVec_c, beamVec=bVec) # append to xy dict calc_xy_dict[det_key] = calc_xy pass # stack results to concatenated arrays calc_omes_all = np.hstack([calc_omes_dict[k] for k in det_keys_ordered]) tmp = [] for k in det_keys_ordered: if calc_xy_dict[k] is not None: tmp.append(calc_xy_dict[k]) calc_xy_all = np.vstack(tmp) meas_xyo_all = np.vstack(meas_xyo_all) npts = len(meas_xyo_all) if np.any(np.isnan(calc_xy)): raise RuntimeError("infeasible pFull: may want to scale" + "back finite difference step size") # return values if simOnly: # return simulated values if return_value_flag in [None, 1]: retval = np.hstack([calc_xy_all, calc_omes_all.reshape(npts, 1)]) else: rd = dict.fromkeys(det_keys_ordered) for det_key in det_keys_ordered: rd[det_key] = { 'calc_xy': calc_xy_dict[det_key], 'calc_omes': calc_omes_dict[det_key] } retval = rd else: # return residual vector # IDEA: try angles instead of xys? diff_vecs_xy = calc_xy_all - meas_xyo_all[:, :2] diff_ome = xfcapi.angularDifference(calc_omes_all, meas_xyo_all[:, 2]) retval = np.hstack([diff_vecs_xy, diff_ome.reshape(npts, 1)]).flatten() if return_value_flag == 1: # return scalar sum of squared residuals retval = sum(abs(retval)) elif return_value_flag == 2: # return DOF-normalized chisq # TODO: check this calculation denom = 3 * npts - len(gFit) - 1. if denom != 0: nu_fac = 1. / denom else: nu_fac = 1. retval = nu_fac * sum(retval**2) return retval