diff --git a/src/toolbox_scs/routines/boz.py b/src/toolbox_scs/routines/boz.py index d3bf0a729258032c1d476e1d7adcaf2e3f08a34e..7e0b862f8d235a4a1e5b7c146a31ae6a167db0c4 100644 --- a/src/toolbox_scs/routines/boz.py +++ b/src/toolbox_scs/routines/boz.py @@ -874,6 +874,8 @@ def _plane_flat_field(p, roi, params): def compute_flat_field_correction(rois, params, plot=False): if params.ff_type == 'plane': return compute_plane_flat_field_correction(rois, params, plot) + elif params.ff_type == 'polyline': + return compute_polyline_flat_field_correction(rois, params, plot) else: raise ValueError(f'Uknown flat field type {params.ff_type}') @@ -920,6 +922,54 @@ def compute_plane_flat_field_correction(rois, params, plot=False): return flat_field +def compute_polyline_flat_field_correction(rois, params, plot=False): + """Compute the 1D polyline field correction on beam rois. + + Inputs + ------ + rois: dictionnary of beam rois['n', '0', 'p'] + params: parameters + plot: boolean, True by default, diagnostic plot + + Returns + ------- + numpy 2D array of the flat-field correction evaluated over one DSSC ladder + (2 sensors) + """ + flat_field = np.ones((128, 512)) + + z = params.get_flat_field() + coeffs = np.logspace(-(z.shape[0]-1), 0, z.shape[0]) + poly = np.poly1d(z*coeffs) + n = rois['n'] + p = rois['p'] + wn = n['xh']-n['xl'] + wp = p['xh']-p['xl'] + assert wn == wp, (\ + f"For polyline flat field normalization, both 'n' and 'p' ROIs " + f"must have the same width {wn} and {wp}px" + ) + x = np.arange(wn) + norm = poly(x) + + n_int = flat_field[n['yl']:n['yh'], n['xl']:n['xh']] + flat_field[n['yl']:n['yh'], n['xl']:n['xh']] = \ + norm*n_int + + p_int = flat_field[p['yl']:p['yh'], p['xl']:p['xh']] + flat_field[p['yl']:p['yh'], p['xl']:p['xh']] = \ + norm*p_int # not the mirror + + if plot: + f, ax = plt.subplots(1, 1, figsize=(6, 2)) + img = ax.pcolormesh( + np.flipud(flat_field[:, :256]), cmap='Greys_r') + f.colorbar(img, ax=[ax], label='amplitude') + ax.set_xlabel('px') + ax.set_ylabel('px') + ax.set_aspect('equal') + + return flat_field def inspect_flat_field_domain(avg, rois, prod_th, ratio_th, vmin=None, vmax=None): """Extract beams roi from average image and compute the ratio. @@ -1056,6 +1106,87 @@ def inspect_plane_fitting(avg, rois, domain=None, vmin=None, vmax=None): return fig +def inspect_ff_fitting_sk(avg, rois, ff, domain=None, vmin=None, vmax=None): + """Extract beams roi from average image and compute the ratio. + + Inputs + ------ + avg: module average image with no saturated shots for the flat-field + determination + rois: dictionnary of rois + ff: 2D array, flat field normalization + domain: list of domain mask for the -1st and +1st order + vmin: imshow vmin level, default None will use 5 percentile value + vmax: imshow vmax level, default None will use 99.8 percentile value + + Returns + ------- + fig: matplotlib figure plotted + """ + if vmin is None: + vmin = np.percentile(avg, 5) + if vmax is None: + vmax = np.percentile(avg, 99.8) + + refn = avg[rois['n']['yl']:rois['n']['yh'], + rois['n']['xl']:rois['n']['xh']] + refp = avg[rois['p']['yl']:rois['p']['yh'], + rois['p']['xl']:rois['p']['xh']] + mid = avg[rois['0']['yl']:rois['0']['yh'], + rois['0']['xl']:rois['0']['xh']] + mref = 0.5*(refn + refp) + + ffn = ff[rois['n']['yl']:rois['n']['yh'], + rois['n']['xl']:rois['n']['xh']] + ffp = ff[rois['p']['yl']:rois['p']['yh'], + rois['p']['xl']:rois['p']['xh']] + ffmid = ff[rois['0']['yl']:rois['0']['yh'], + rois['0']['xl']:rois['0']['xh']] + norm = (ffn+ffp) + + fig, axs = plt.subplots(3, 3, sharex=True, sharey=True, + figsize=(8, 4)) + im = axs[0, 0].imshow(mref) + axs[0, 0].set_title('(n+p)/2') + fig.colorbar(im, ax=axs[0, 0]) + + im = axs[1, 0].imshow(mid) + axs[1, 0].set_title('0') + fig.colorbar(im, ax=axs[1, 0]) + + im = axs[2, 0].imshow(mid/mref-1, cmap='RdBu_r', vmin=-1, vmax=1) + axs[2, 0].set_title('02/(n+p) - 1') + fig.colorbar(im, ax=axs[2, 0]) + + im = axs[0, 1].imshow(norm) + axs[0, 1].set_title('n+p norm') + fig.colorbar(im, ax=axs[0, 1]) + + im = axs[1, 1].imshow(ffmid) + axs[1, 1].set_title('0 norm') + fig.colorbar(im, ax=axs[1, 1]) + + im = axs[2, 1].imshow(ffmid/norm-1, cmap='RdBu_r', vmin=-1, vmax=1) + axs[2, 1].set_title('0/(n+p)norm-1') + fig.colorbar(im, ax=axs[2, 1]) + + + im = axs[0, 2].imshow(mref/norm) + axs[0, 2].set_title('(n+p)/norm') + fig.colorbar(im, ax=axs[0, 2]) + + im = axs[1, 2].imshow(mid) + axs[1, 2].set_title('0') + fig.colorbar(im, ax=axs[1, 2]) + + im = axs[2, 2].imshow(mid/(mref/norm)-1, cmap='RdBu_r', vmin=-1, vmax=1) + axs[2, 2].set_title('0/(n+p) norm') + fig.colorbar(im, ax=axs[2, 2]) + + # fig.suptitle(f'{proposalNB}-run{runNB}-dark{darkrunNB} sat={sat_level}') + + return fig + def plane_fitting_domain(avg, rois, prod_th, ratio_th): """Extract beams roi, compute their ratio and the domain. @@ -1216,8 +1347,47 @@ def ff_refine_crit(p, alpha, params, arr_dark, arr, tid, rois, return bad + 1e3*(alpha*err_sigma + (1-alpha)*err_mean) +def ff_refine_crit_sk(p, alpha, params, arr_dark, arr, tid, rois, + mask, sat_level=511): + """Criteria for the ff_refine_fit, combining 'n' and 'p' as reference. + + Inputs + ------ + p: ff plane + params: parameters + arr_dark: dark data + arr: data + tid: train id of arr data + rois: ['n', '0', 'p', 'sat'] rois + mask: mask fo good pixels + sat_level: integer, default 511, at which level pixel begin to saturate + + Returns + ------- + sum of standard deviation on binned 0th order intensity + """ + params.set_flat_field(p, params.ff_type) + ff = compute_flat_field_correction(rois, params) + if np.any(ff < 0.0): + bad = 1e6 + else: + bad = 0.0 + + data = process(None, arr_dark, arr, tid, rois, mask, ff, + sat_level, params._using_gpu) + + # drop saturated shots + d = data.where(data['sat_sat'] == False, drop=True) + + r = xas(d, 40, Iokey='np_mean', Itkey='0', nrjkey='0', fluorescence=True) + + err_sigma = np.nansum(r['sigmaA']) + err_mean = (1.0 - np.nanmean(r['muA']))**2 + + return bad + 1e3*(alpha*err_sigma + (1-alpha)*err_mean) + -def ff_refine_fit(params): +def ff_refine_fit(params, crit=ff_refine_crit): """Refine the flat-field fit by minimizing data spread. Inputs @@ -1258,11 +1428,11 @@ def ff_refine_fit(params): fit_callback.counter += 1 temp = list(fixed_p) - Jalpha = ff_refine_crit(x, *temp) + Jalpha = crit(x, *temp) temp[0] = 0 - J0 = ff_refine_crit(x, *temp) + J0 = crit(x, *temp) temp[0] = 1 - J1 = ff_refine_crit(x, *temp) + J1 = crit(x, *temp) fit_callback.res.append([J0, Jalpha, J1]) print(f'{fit_callback.counter-1}: {time_delta} ' f'({J0}, {Jalpha}, {J1}), {x}') @@ -1270,7 +1440,7 @@ def ff_refine_fit(params): return False fit_callback(p0) - res = minimize(ff_refine_crit, p0, fixed_p, + res = minimize(crit, p0, fixed_p, options={'disp': True, 'maxiter': params.ff_max_iter}, callback=fit_callback) @@ -1400,8 +1570,6 @@ def nl_crit_sk(p, domain, alpha, arr_dark, arr, tid, rois, mask, flat_field, # drop saturated shots d = data.where(data['sat_sat'] == False, drop=True) - d['np_mean'] = 0.5*(d['n'] + d['p']) - v = snr(d['np_mean'].values.flatten(), d['0'].values.flatten(), methods=['weighted']) err = 1e8*v['weighted']['s']**2 @@ -1596,7 +1764,7 @@ def inspect_Fnl(Fnl): def inspect_correction(params, gain=None): - """Criteria for the non linear correction. + """Comparison plot of the different corrections. Inputs ------ @@ -1716,7 +1884,7 @@ def inspect_correction(params, gain=None): return f def inspect_correction_sk(params, ff, gain=None): - """Criteria for the non linear correction. + """Comparison plot of the different corrections, combining 'n' and 'p'. Inputs ------ @@ -2018,6 +2186,8 @@ def process_module(arr, tid, dark, rois, mask=None, sat_level=511, # compute dark corrected ROI values v = {} + r_ff = {} + ff = {} for n in rois.keys(): r[n] = r[n] - rd[n] @@ -2026,25 +2196,36 @@ def process_module(arr, tid, dark, rois, mask=None, sat_level=511, # TODO: flat-field should not be applied on intra darks # ff = flat_field[:, rois[n]['yl']:rois[n]['yh'], # rois[n]['xl']:rois[n]['xh']] - ff = flat_field[rois[n]['yl']:rois[n]['yh'], - rois[n]['xl']:rois[n]['xh']] - r[n] = r[n]/ff + ff[n] = flat_field[rois[n]['yl']:rois[n]['yh'], + rois[n]['xl']:rois[n]['xh']] + r_ff[n] = r[n]/ff[n] + else: + ff[n] = 1.0 + r_ff[n] = r[n] - v[n] = r[n].sum(axis=(2, 3)) + v[n] = r_ff[n].sum(axis=(2, 3)) + + # np_mean roi where we normalize the sum of flat_field + np_mean = 0.5*(r['n'] + r['p'])/(ff['n'] + ff['p']) + v['np_mean'] = np_mean.sum(axis=(2,3)) res = xr.Dataset() dims = ['trainId', 'pulseId'] r_coords = {'trainId': tid, 'pulseId': np.arange(0, narr.shape[1])} for n in rois.keys(): + res[n] = xr.DataArray(ensure_on_host(v[n]), coords=r_coords, dims=dims) res[n + '_sat'] = xr.DataArray(ensure_on_host(r_sat[n][:, :]), coords=r_coords, dims=dims) - res[n] = xr.DataArray(ensure_on_host(v[n]), coords=r_coords, dims=dims) + res['np_mean'] = xr.DataArray(ensure_on_host(v['np_mean']), + coords=r_coords, dims=dims) + res['np_mean_sat'] = res['n_sat'] + res['p_sat'] for n in rois.keys(): roi = rois[n] res[n + '_area'] = xr.DataArray(np.array([ (roi['yh'] - roi['yl'])*(roi['xh'] - roi['xl'])])) + res['np_mean_area'] = res['n_area'] + res['p_area'] return res