From f9c883b5efecc04474b0883319ae02e705ebd4f6 Mon Sep 17 00:00:00 2001 From: Steffen Hauf <haufs@max-exfl059.desy.de> Date: Tue, 13 Nov 2018 16:51:16 +0100 Subject: [PATCH] PEP8 compliance --- cal_tools/cal_tools/agipdlib.py | 1385 +++++++++++++++++-------------- 1 file changed, 784 insertions(+), 601 deletions(-) diff --git a/cal_tools/cal_tools/agipdlib.py b/cal_tools/cal_tools/agipdlib.py index 2aeef583d..bb17e4393 100644 --- a/cal_tools/cal_tools/agipdlib.py +++ b/cal_tools/cal_tools/agipdlib.py @@ -5,11 +5,11 @@ import h5py import numpy as np from scipy.signal import cwt, ricker from sklearn.mixture import GaussianMixture -from sklearn.preprocessing import StandardScaler +from sklearn.preprocessing import StandardScaler from cal_tools.enums import BadPixels from cal_tools.tools import get_constant_from_db -from iCalibrationDB import ConstantMetaData, Constants, Conditions, Detectors, Versions +from iCalibrationDB import Constants, Conditions, Detectors class SnowResolution(Enum): @@ -17,7 +17,7 @@ class SnowResolution(Enum): """ NONE = "none" INTERPOLATE = "interpolate" - + class AgipdCorrections: """ @@ -29,12 +29,14 @@ class AgipdCorrections: infile = h5py.File(filename, "r", driver="core") outfile = h5py.File(filename_out, "w") - agp_corr = AgipdCorrections(infile, outfile, max_cells, channel, max_pulses, + agp_corr = AgipdCorrections(infile, outfile, max_cells, channel, + max_pulses, bins_gain_vs_signal, bins_signal_low_range, bins_signal_high_range) try: - agp_corr.initialize(offset, rel_gain, rel_gain_offset, mask, noise, flatfield) + agp_corr.initialize(offset, rel_gain, rel_gain_offset, mask, + noise, flatfield) except IOError: return @@ -43,16 +45,18 @@ class AgipdCorrections: hists, edges = agp_corr.get_histograms() - """ + """ + def __init__(self, infile, outfile, max_cells, channel, max_pulses, - bins_gain_vs_signal, bins_signal_low_range, bins_signal_high_range, + bins_gain_vs_signal, bins_signal_low_range, + bins_signal_high_range, bins_dig_gain_vs_signal, raw_fmt_version=2, chunk_size=512, h5_data_path="INSTRUMENT/SPB_DET_AGIPD1M-1/DET/{}CH0:xtdf/", h5_index_path="INDEX/SPB_DET_AGIPD1M-1/DET/{}CH0:xtdf/", do_rel_gain=True, chunk_size_idim=512): """ Initialize an AgipdCorrections Class - + :param infile: to be corrected h5py input file :param outfile: writeable h5py output file :param max_cell: maximum number of memory cells to handle, e.g. if @@ -60,14 +64,19 @@ class AgipdCorrections: :param channel: module/channel to correct :param max_pulses: maximum pulse id to consider for preview histograms :param bins_gain_vs_signal: number of bins for gain vs signal histogram - :param bins_signal_low_range: number of bins for the low signal range histogram - :param bins_signal_high_range: number of bins for the high signal range histogram + :param bins_signal_low_range: number of bins for the low signal + range histogram + :param bins_signal_high_range: number of bins for the high signal + range histogram :param raw_fmt_version: raw file format version to use :param chunk_size: images per chunk to compute upon - :param h5_data_path: path in HDF5 file which is prefixed to the image/data section - :param h5_index_path: path in HDF5 file which is prefixed to the index section + :param h5_data_path: path in HDF5 file which is prefixed to the + image/data section + :param h5_index_path: path in HDF5 file which is prefixed to the + index section :param do_rel_gain: do relative gain corrections - :param chunk_size_idim: chunking size on image dimension when writing data out + :param chunk_size_idim: chunking size on image dimension when + writing data out """ self.agipd_base = h5_data_path.format(channel) self.idx_base = h5_index_path.format(channel) @@ -98,161 +107,193 @@ class AgipdCorrections: self.melt_snow = SnowResolution.NONE self.match_asics = True self.chunk_size_idim = chunk_size_idim - + self.dohigh = 0 + def get_iteration_range(self): - """Returns a range expression over which to iterate in chunks + """Returns a range expression over which to iterate in chunks """ - return np.array_split(self.firange, self.firange.size//self.chunksize) - - def initialize(self, offset, rel_gain, mask, noise, thresholds, base_offset, swap_axis=True): + return np.array_split(self.firange, + self.firange.size // self.chunksize) + + def initialize(self, offset, rel_gain, mask, noise, thresholds, + base_offset, swap_axis=True): """ Initialize the calibration constants and the output data file. - - Any data that is not touched by the corrections is copied into the output - file at this point. Also data validity tests are performed. This functions - must be called before `correct_lpd` is executed. - + + Any data that is not touched by the corrections is copied into the + output file at this point. Also data validity tests are performed. + This functions must be called before `correct_lpd` is executed. + :param offset: offset map to use for corrections :param rel_gain: relative gain map to use for corrections :param mask: bad pixel mask to use for corrections :param noise: noise map to use for corrections :param thresholds: thresholds for gain determination - :param base_offset: base offsets from PC and FF data: should be a tuple: - PC offset for high gain, PC offset for medium gain, FF offset for high gain - :param swap_axis: set to True if using data from the calibration database. - - Note that this function may be called twice, when initializing partially from DB and file. + :param base_offset: base offsets from PC and FF data: should be a + tuple: + PC offset for high gain, PC offset for medium gain, FF offset + for high gain + :param swap_axis: set to True if using data from the calibration + database. + + Note that this function may be called twice, when initializing + partially from DB and file. Constants not to be set by the initial call should be set to `None`. """ - - # swap the axes such that memory cell dimension is first - the usual way of operation, - # and the one expected by the rest of this class. + + # swap the axes such that memory cell dimension is first - the usual + # way of operation, and the one expected by the rest of this class. if swap_axis: if offset is not None: - offset = np.ascontiguousarray(np.moveaxis(np.moveaxis(offset, 2, 0), 2, 1)) + mvd = np.moveaxis(np.moveaxis(offset, 2, 0), 2, 1) + offset = np.ascontiguousarray(mvd) if rel_gain is not None: - rel_gain = np.ascontiguousarray(np.moveaxis(np.moveaxis(rel_gain, 2, 0), 2, 1)) + mvd = np.moveaxis(np.moveaxis(rel_gain, 2, 0), 2, 1) + rel_gain = np.ascontiguousarray(mvd) if mask is not None: - mask = np.ascontiguousarray(np.moveaxis(np.moveaxis(mask, 2, 0), 2, 1)) + mvd = np.moveaxis(np.moveaxis(mask, 2, 0), 2, 1) + mask = np.ascontiguousarray(mvd) if noise is not None: - noise = np.ascontiguousarray(np.moveaxis(np.moveaxis(noise, 2, 0), 2, 1)) + mvd = np.moveaxis(np.moveaxis(noise, 2, 0), 2, 1) + noise = np.ascontiguousarray(mvd) if base_offset is not None: for i in range(len(base_offset)): if base_offset[i] is not None: - base_offset[i] = np.ascontiguousarray(np.moveaxis(np.moveaxis(base_offset[i], 2, 0), 2, 1)) + mvd = np.moveaxis(base_offset[i], 2, 0) + mvd = np.moveaxis(mvd, 2, 1) + base_offset[i] = np.ascontiguousarray(mvd) if thresholds is not None: - thresholds = np.ascontiguousarray(np.moveaxis(np.moveaxis(thresholds, 2, 0), 2, 1)) - + mvd = np.moveaxis(np.moveaxis(thresholds, 2, 0), 2, 1) + thresholds = np.ascontiguousarray(mvd) + if base_offset is not None: self.base_offset = base_offset if offset is not None: self.offset = offset - + if rel_gain is not None: - self.rel_gain = 1./rel_gain - - # if a mask already exists we OR the mask to maintain what we already have + self.rel_gain = 1. / rel_gain + + # if a mask already exists we OR the mask to maintain what we + # already have if mask is not None: if not hasattr(self, "mask"): self.mask = mask else: - self.mask |= mask[:self.mask.shape[0],...] - + self.mask |= mask[:self.mask.shape[0], ...] + if noise is not None: self.noise = noise - + if thresholds is not None: self.thresholds = thresholds - + if self.base_offset is not None and self.offset is not None: # calculate a delta offset from base offsets and normal offsets dohigh = np.zeros(self.offset.shape[:-1], np.float32) for i in range(8): for j in range(2): - asic_m = np.nanmedian(self.offset[:, i*64:(i+1)*64, j*64:(j+1)*64, 0], axis=(1,2)) - global_m = np.nanmedian(self.offset[:, ..., 0], axis=(1,2)) - dohigh[:, i*64:(i+1)*64, j*64:(j+1)*64] = (asic_m-global_m)[:,None,None] + asico = self.offset[:, i * 64:(i + 1) * 64, + j * 64:(j + 1) * 64, 0] + asic_m = np.nanmedian(asico, axis=(1, 2)) + global_m = np.nanmedian(self.offset[:, ..., 0], + axis=(1, 2)) + doa = (asic_m - global_m)[:, None, None] + dohigh[:, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] = doa + self.dohigh = dohigh - - dopc = self.base_offset[1]-self.base_offset[0] - dooff = self.offset[...,1] - self.offset[...,0] + + dopc = self.base_offset[1] - self.base_offset[0] + dooff = self.offset[..., 1] - self.offset[..., 0] doff = dopc - dooff for i in range(8): for j in range(2): - med = np.nanmedian(doff[:, i*64:(i+1)*64, j*64:(j+1)*64], axis=(1,2)) - doff[:, i*64:(i+1)*64, j*64:(j+1)*64] = med[:, None, None] - self.offset[...,1] += doff - + ado = doff[:, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] + med = np.nanmedian(ado, axis=(1, 2))[:, None, None] + doff[:, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] = med + + self.offset[..., 1] += doff + # this is the first call, usually from the database # we also generate file structures here, as the second call is optional if not self.initialized: - self.median_noise = np.nanmedian(self.noise[0,...]) - self.max_cells = np.min([self.offset.shape[0], self.mask.shape[0], self.max_cells]) - self.gen_valid_range() + self.median_noise = np.nanmedian(self.noise[0, ...]) + cops = [self.offset.shape[0], self.mask.shape[0], self.max_cells] + self.max_cells = np.min(cops) + self.gen_valid_range() self.copy_and_sanitize_non_cal_data() self.create_output_datasets() self.initialized = True - print("Offet medians are {}".format(np.nanmedian(self.offset, axis=(0,1,2)))) + print("Offet medians are {}".format( + np.nanmedian(self.offset, axis=(0, 1, 2)))) if hasattr(self, "rel_gain"): - print("Gain medians are {}".format(np.nanmedian(self.rel_gain, axis=(0,1,2)))) - print("Threshold medians are {}".format(np.nanmedian(self.thresholds, axis=(0,1,2)))) + print("Gain medians are {}".format( + np.nanmedian(self.rel_gain, axis=(0, 1, 2)))) + print("Threshold medians are {}".format( + np.nanmedian(self.thresholds, axis=(0, 1, 2)))) else: print("After reading from file: ") - print("Offet medians are {}".format(np.nanmedian(self.offset, axis=(0,1,2)))) - print("Gain medians are {}".format(np.nanmedian(self.rel_gain, axis=(0,1,2)))) - print("Threshold medians are {}".format(np.nanmedian(self.thresholds, axis=(0,1,2)))) - + print("Offet medians are {}".format( + np.nanmedian(self.offset, axis=(0, 1, 2)))) + print("Gain medians are {}".format( + np.nanmedian(self.rel_gain, axis=(0, 1, 2)))) + print("Threshold medians are {}".format( + np.nanmedian(self.thresholds, axis=(0, 1, 2)))) + def split_gain(self, d): """ Split gain information off AGIPD Data """ - return d[:,0,...], d[:,1,...] - + return d[:, 0, ...], d[:, 1, ...] + def baseline_correct_via_noise(self, d, noise, g): - """ Correct baseline shifts by evaluating the position of the noise peak - + """ Correct baseline shifts by evaluating position of the noise peak + :param: d: the data to correct, should be a single image :param noise: the mean noise for the cell id of `d` :param g: gain array matching `d` array - - Correction is done by identifying the left-most (significant) peak in the - histogram of `d` and shifting it to be centered on 0. This is done via - a continous wavelet transform (CWT), using a Ricker wavelet. - - Only high gain data is evaulated, and data larger than 50 ADU, equivalent of - rougly a 9 keV photon is ignored. - - Two passes are executed: the first shift is accurate to 10 ADU and will catch - baseline shifts smaller then -2000 ADU. CWT is evaluated for peaks of widths - one, three and five time the noise. The baseline is then shifted - by the position of the summed CWT maximum. - + + Correction is done by identifying the left-most (significant) peak + in the histogram of `d` and shifting it to be centered on 0. + This is done via a continous wavelet transform (CWT), using a Ricker + wavelet. + + Only high gain data is evaulated, and data larger than 50 ADU, + equivalent of roughly a 9 keV photon is ignored. + + Two passes are executed: the first shift is accurate to 10 ADU and + will catch baseline shifts smaller then -2000 ADU. CWT is evaluated + for peaks of widths one, three and five time the noise. + The baseline is then shifted by the position of the summed CWT maximum. + In a second pass histogram is evaluated within a range of +- 5*noise of the initial shift value, for peak of width noise. - + Baseline shifts larger than the maximum threshold or positive shifts larger 10 are discarded, and the original data is return, otherwise the shift corrected data in returned. - + """ - # we work on a copy to be able to filter values by setting them to np.nan + # we work on a copy to be able to filter values by setting them to + # np.nan dd = copy.copy(d) dd[g != 0] = np.nan # only high gain data dd[dd > 50] = np.nan # only noise data h, e = np.histogram(dd, bins=210, range=(-2000, 100)) - c = (e[1:]+e[:-1])/2 + c = (e[1:] + e[:-1]) / 2 try: - cwtmatr = cwt(h, ricker, [noise, 3.*noise, 5.*noise]) + cwtmatr = cwt(h, ricker, [noise, 3. * noise, 5. * noise]) except: return d cwtall = np.sum(cwtmatr, axis=0) marg = np.argmax(cwtall) pc = c[marg] - high_res_range = (dd > pc - 5*noise) & (dd < pc + 5*noise) + high_res_range = (dd > pc - 5 * noise) & (dd < pc + 5 * noise) dd[~high_res_range] = np.nan - h, e = np.histogram(dd, bins=100, range=(pc - 5*noise, pc + 5*noise)) - c = (e[1:]+e[:-1])/2 + h, e = np.histogram(dd, bins=100, + range=(pc - 5 * noise, pc + 5 * noise)) + c = (e[1:] + e[:-1]) / 2 try: cwtmatr = cwt(h, ricker, [noise, ]) except: @@ -262,89 +303,92 @@ class AgipdCorrections: # too large shift to be easily decernable via noise if pc > 10 or pc < -self.baseline_corr_noise_threshold: return d - return d-pc + return d - pc def correct_baseline_via_hist(self, d, pcm, g): - """ Correct baseline shifts by matching edges of high and medium gain histogram - + """ Correct baseline shifts by matching edges of high and medium + gain histogram + :param d: single image to correct :param pcm: relative gain slope for medium gain of each pixel in `d` :param g: gain values for `d` - - As a preparation the median value of medium gain pixels is shifted in + + As a preparation the median value of medium gain pixels is shifted in increments of 50 ADU as long as it is negative. - + Correction is then performed by evaluating histograms for high gain - and medium gain pixels in `d`. The right-most significant bin of the high - gain histogram is then shifted such that it coincides with the left-most - significant bin of the medium gain histogram. Significant here means that - bin counts are at least 10% of the average bin count of histogram for all - bins larger 10 counts. This initial evaluation uses histograms in range - between -10000 and +10000 ADU with a resultion of 100 ADU per bin. The - shift required to match both histogram borders is an initial estimate for the - baseline shift. - + and medium gain pixels in `d`. The right-most significant bin of the + high gain histogram is then shifted such that it coincides with the + left-most significant bin of the medium gain histogram. Significant + here means that bin counts are at least 10% of the average bin count + of histogram for all bins larger 10 counts. This initial evaluation + uses histograms in range between -10000 and +10000 ADU with a + resultion of 100 ADU per bin. The shift required to match both + histogram borders is an initial estimate for the baseline shift. + Finally, the mean bin count of the five outermost bins of the high and - medium gain histograms is compared. The baseline is shifted by increments - of 1 ADU until they are within 20% of each other. - - From this point on additional shifts are performed while the deviation stays - within 30% of each other. The final shift is then evaluated as the point of minimal - deviation of these values. - - A maximum iteration count of 200 is imposed on the procedure. If no convergence - is reached within this limit, the original data is returned, otherwise the - baseline corrected data is returned. + medium gain histograms is compared. The baseline is shifted by + increments of 1 ADU until they are within 20% of each other. + + From this point on additional shifts are performed while the + deviation stays within 30% of each other. The final shift is then + evaluated as the point of minimal deviation of these values. + + A maximum iteration count of 200 is imposed on the procedure. If no + convergence is reached within this limit, the original data is + returned, otherwise the baseline corrected data is returned. """ dd = copy.copy(d) # shift while the medium gain produces negative values on average pc = 0 it = 0 max_it = 100 - while np.nanmedian(dd[g==1]-pc) < 0: + while np.nanmedian(dd[g == 1] - pc) < 0: pc -= 50 if it > max_it: return d it += 1 - def min_hist_distance(pc, bins=100, ran=(-10000, 10000), dec=20, minbin=10): - opc = pc - hh, e = np.histogram(dd[g==0]-pc, bins=bins, range=ran) - hm, e = np.histogram((dd[g==1]-pc)*pcm[g==1], bins=bins, range=ran) + def min_hist_distance(pc, bins=100, ran=(-10000, 10000), dec=20, + minbin=10): + hh, e = np.histogram(dd[g == 0] - pc, bins=bins, range=ran) + hm, e = np.histogram((dd[g == 1] - pc) * pcm[g == 1], bins=bins, + range=ran) # right most significant value of high gain histogram hhm = np.nanmean(hh[hh > minbin]) - rmh = hh.size-np.argmax((hh > 0.1*hhm)[::-1]) + rmh = hh.size - np.argmax((hh > 0.1 * hhm)[::-1]) # left most significant value of high gain histogram hmm = np.nanmean(hm[hm > minbin]) - lmm = np.argmax((hm > 0.1*hmm)) - med_pcm = np.nanmedian(pcm[g==1]) - eh = e[rmh] + lmm = np.argmax((hm > 0.1 * hmm)) + med_pcm = np.nanmedian(pcm[g == 1]) + eh = e[rmh] el = e[lmm] - pc += (el - eh)/(med_pcm -1) + pc += (el - eh) / (med_pcm - 1) return pc # set a pc one halfstep higher than initial guesstimate pc += 50 pc = min_hist_distance(pc) - + # now start rolling back until we have similar signal levels def comp_signal_level(pc, minbin=1): - hh, e = np.histogram(dd[g==0]-pc, bins=100, range=(5000, 7000)) - hm, e = np.histogram((dd[g==1]-pc)*pcm[g==1], bins=100, range=(5000, 7000)) + hh, e = np.histogram(dd[g == 0] - pc, bins=100, range=(5000, 7000)) + hm, e = np.histogram((dd[g == 1] - pc) * pcm[g == 1], bins=100, + range=(5000, 7000)) # right most significant value of high gain histogram hhm = np.nanmean(hh[hh > minbin]) - rmh = hh.size-np.argmax((hh > 0.1*hhm)[::-1]) + rmh = hh.size - np.argmax((hh > 0.1 * hhm)[::-1]) # left most significant value of high gain histogram hmm = np.nanmean(hm[hm > minbin]) - lmm = np.argmax((hm > 0.1*hmm)) + lmm = np.argmax((hm > 0.1 * hmm)) reg = 5 - ret = np.abs(np.sum(hh[rmh-reg:rmh])-np.sum(hm[lmm:lmm+reg])) - ret /= np.sum(hh[rmh-reg:rmh]) + ret = np.abs(np.sum(hh[rmh - reg:rmh]) - np.sum(hm[lmm:lmm + reg])) + ret /= np.sum(hh[rmh - reg:rmh]) return ret it = 0 @@ -365,7 +409,7 @@ class AgipdCorrections: slevs = [] slev = comp_signal_level(pc) slevs.append(slev) - while slev < 0.3: + while slev < 0.3: pc += 1 slev = comp_signal_level(pc) slevs.append(slev) @@ -374,61 +418,64 @@ class AgipdCorrections: pc = pcs[np.argmin(slevs)] - return d-pc - + return d - pc + def correct_baseline_via_hist_asic(self, d, g): """ Correct diagonal falloff on ASICs by matching corner histograms - + :param d: single image data :param g: gain values for `d` - - Corrections are performed for the top and bottom row of ASICs seperately. - - In easch row, starting from the beam-hole closes ASIC, the histogram of a - square region of size 8x8 pixels is evaluted for its maximum bin count - location (only medium gain values), and compared to a histogram produced - from a neighbouring region on the next ASIC. Double sized pixels are avoided. - - The reasoning is that the histograms should not dramatically differ for - continously distributed photon events. Each ASIC is then shifted such that - histograms much and the corrected data is returned. - + + Corrections are performed for the top and bottom row of ASICs + seperately. + + In easch row, starting from the beam-hole closes ASIC, the histogram + of a square region of size 8x8 pixels is evaluted for its maximum + bin count location (only medium gain values), and compared to a + histogram produced from a neighbouring region on the next ASIC. + Double sized pixels are avoided. + + The reasoning is that the histograms should not dramatically differ + for continuously distributed photon events. Each ASIC is then shifted + such that histograms much and the corrected data is returned. + Warning: this feature is very experimental! """ - + rsize = 8 - def hist_ends(dm, bins=5000, ran=(-5000, 5000), minbin=4/(16/rsize)): - hm, e = np.histogram(dm, bins=bins, range=ran) + def hist_ends(dm, bins=5000, ran=(-5000, 5000), + minbin=4 / (16 / rsize)): + hm, e = np.histogram(dm, bins=bins, range=ran) # left most significant value of medium gain histogram - #hmm = np.nanmean(hm[hm > minbin]) + # hmm = np.nanmean(hm[hm > minbin]) lmm = np.argmax((hm > minbin)) - return e[lmm], np.sum(hm) - + return e[lmm], np.sum(hm) for i in range(1, 8): # upper asics - casic = np.concatenate((d[i*64-rsize:i*64-1,64:64+rsize].flatten(), - d[i*64+1:i*64+rsize,64-rsize:64].flatten())) - - casic_g = np.concatenate((g[i*64-rsize:i*64-1,64:64+rsize].flatten(), - g[i*64+1:i*64+rsize,64-rsize:64].flatten())) + casic = np.concatenate( + (d[i * 64 - rsize:i * 64 - 1, 64:64 + rsize].flatten(), + d[i * 64 + 1:i * 64 + rsize, 64 - rsize:64].flatten())) + casic_g = np.concatenate( + (g[i * 64 - rsize:i * 64 - 1, 64:64 + rsize].flatten(), + g[i * 64 + 1:i * 64 + rsize, 64 - rsize:64].flatten())) idxm = casic_g == 1 cme, cms = hist_ends(casic[idxm]) - asic = d[i*64+1:i*64+rsize,64:64+rsize].flatten() - asic_g = g[i*64+1:i*64+rsize,64:64+rsize].flatten() + asic = d[i * 64 + 1:i * 64 + rsize, 64:64 + rsize].flatten() + asic_g = g[i * 64 + 1:i * 64 + rsize, 64:64 + rsize].flatten() idxm = asic_g == 1 - me, ms = hist_ends( asic[idxm]) + me, ms = hist_ends(asic[idxm]) - if cms > rsize*rsize/10 and ms > rsize*rsize/10: + if cms > rsize * rsize / 10 and ms > rsize * rsize / 10: pc = cme - me else: pc = 0 @@ -436,28 +483,32 @@ class AgipdCorrections: if np.abs(pc) > 500: # somthing when wrong pc = 0 - d[i*64:(i+1)*64,64:] += pc - + d[i * 64:(i + 1) * 64, 64:] += pc for i in range(0, 7): # lower asics - casic = np.concatenate((d[(i+1)*64-rsize:(i+1)*64-1,64:64+rsize].flatten(), - d[(i+1)*64+1:(i+1)*64+rsize,64-rsize:64].flatten())) - - casic_g = np.concatenate((g[(i+1)*64-rsize:(i+1)*64-1,64:64+rsize].flatten(), - g[(i+1)*64+1:(i+1)*64+rsize,64-rsize:64].flatten())) + casic = np.concatenate((d[(i + 1) * 64 - rsize:(i + 1) * 64 - 1, + 64:64 + rsize].flatten(), + d[(i + 1) * 64 + 1:(i + 1) * 64 + rsize, + 64 - rsize:64].flatten())) + casic_g = np.concatenate((g[(i + 1) * 64 - rsize:(i + 1) * 64 - 1, + 64:64 + rsize].flatten(), + g[(i + 1) * 64 + 1:(i + 1) * 64 + rsize, + 64 - rsize:64].flatten())) idxm = casic_g == 1 cme, cms = hist_ends(casic[idxm]) - asic = d[(i+1)*64-rsize:(i+1)*64-1,64-rsize:64].flatten() - asic_g = g[(i+1)*64-rsize:(i+1)*64-1,64-rsize:64].flatten() + asic = d[(i + 1) * 64 - rsize:(i + 1) * 64 - 1, + 64 - rsize:64].flatten() + asic_g = g[(i + 1) * 64 - rsize:(i + 1) * 64 - 1, + 64 - rsize:64].flatten() idxm = asic_g == 1 - me, ms = hist_ends( asic[idxm]) + me, ms = hist_ends(asic[idxm]) - if cms > rsize*rsize/10 and ms > rsize*rsize/10: + if cms > rsize * rsize / 10 and ms > rsize * rsize / 10: pc = cme - me else: pc = 0 @@ -465,84 +516,97 @@ class AgipdCorrections: if np.abs(pc) > 500: # somthing went wrong pc = 0 - d[i*64:(i+1)*64,:64] += pc + d[i * 64:(i + 1) * 64, :64] += pc return d - + def match_asic_borders(self, d, chunk=8): """ Match border pixels of the two ASIC rows to the same median value - + :param d: single image data - :param chunk: number of pixels on each ASIC boundary to generate mean values for - - Each ASIC has 64//chunk mean value calculated along its two border pixel rows. The - deviaiton of chunk pairs is then evaluated and the upper/lower ASICs are shifted - by the mean deviation for the right/left hemisphere of the detector. - + :param chunk: number of pixels on each ASIC boundary to generate + mean values for + + Each ASIC has 64//chunk mean value calculated along its two border + pixel rows. The deviation of chunk pairs is then evaluated and the + upper/lower ASICs are shifted by the mean deviation for the + right/left hemisphere of the detector. + The corrected data is returned. - """ + """ for i in range(8): - these_asics = d[:, i*64:(i+1)*64,:] + these_asics = d[:, i * 64:(i + 1) * 64, :] diffs = np.zeros((d.shape[0], 8)) - for k in range(64//chunk): - - lowerasic = np.nanmedian(these_asics[:,k*chunk:(k+1)*chunk,62:64], axis=(1,2)) - upperasic = np.nanmedian(these_asics[:,k*chunk:(k+1)*chunk,64:66], axis=(1,2)) - diffs[:, k] = lowerasic-upperasic if self.channel < 8 else upperasic-lowerasic + for k in range(64 // chunk): + ldat = these_asics[:, k * chunk:(k + 1) * chunk, 62:64] + lowerasic = np.nanmedian(ldat, axis=(1, 2)) + udat = these_asics[:, k * chunk:(k + 1) * chunk, 64:66] + upperasic = np.nanmedian(udat, axis=(1, 2)) + low_up = lowerasic - upperasic + up_low = upperasic - lowerasic + diff = low_up if self.channel < 8 else up_low + diffs[:, k] = diff + + mn = np.nanmean(diffs, axis=1)[:, None, None] / 2 if self.channel < 8: - d[:, i*64:(i+1)*64,64:] += np.nanmean(diffs, axis=1)[:,None,None]/2 - d[:, i*64:(i+1)*64,:64] -= np.nanmean(diffs, axis=1)[:,None,None]/2 + d[:, i * 64:(i + 1) * 64, 64:] += mn + d[:, i * 64:(i + 1) * 64, :64] -= mn else: - d[:, i*64:(i+1)*64,:64] += np.nanmean(diffs, axis=1)[:,None,None]/2 - d[:, i*64:(i+1)*64,64:] -= np.nanmean(diffs, axis=1)[:,None,None]/2 + d[:, i * 64:(i + 1) * 64, :64] += mn + d[:, i * 64:(i + 1) * 64, 64:] -= mn return d - + def melt_snowy_pixels(self, raw, im, gain, rgain, resolution=None): """ Identify (and optionally interpolate) 'snowy' pixels - - :param raw: raw data + + :param raw: raw data :param im: offet and relativegain corrected data: :param gain: gain values for `raw` - :param rgain: raw gain data, scaled by the threshold for high-to-medium gain - switching - :param resolution: resolution strategy, should of enum-type `SnowResolution` - - Snowy pixels are pixels which are already identified as pixels in the medium - gain stage by their gain values, but which have transitional image values between - the largest high gain value and the smalles medium gain value. As these value - may be encountered again in the context of medium gain, they are ambigigous and - cannot readily be identified by simple thresholding alone. - - It is attempted to identify these snowy pixels by using a Gaussian mixture - clustering algorithm acting on multispectral imput. Positions in the cluster are - identified as follows: - + :param rgain: raw gain data, scaled by the threshold for + high-to-medium gain switching + :param resolution: resolution strategy, should of enum-type + `SnowResolution` + + Snowy pixels are pixels which are already identified as pixels in + the medium gain stage by their gain values, but which have + transitional image values between the largest high gain value and + the smalles medium gain value. As these value may be encountered again + in the context of medium gain, they are ambigigous and cannot + readily be identified by simple thresholding alone. + + It is attempted to identify these snowy pixels by using a Gaussian + mixture clustering algorithm acting on multispectral imput. + Positions in the cluster are identified as follows: + x: abs(p_r-p_bg)*p_rg**2 y: p_r - - where p_r is a given pixel raw value, p_bg is the mean value of the 8 direct - neighbor pixels, and p_rg is the raw gain value of the pixel. - - Note that these cluster params are purely emprically derived, and do not have - connection to ASIC design etc. - - Only pixels in medium gain are evaluated, and evaluation is image-wise, but - subdivided further into ASICs. - - The so-created graph [[x_0, x_1, ..., x_n], [y_0, y_1, ..., y_n]] is scaled - using a `sklearn.StandardScaler` and input into a two-component - `GaussianMixture` clustering algorithm. This results in a set of labels, - identifying pixels as likely snowy, or not. However, for a given image we - do not know which label is which from the output. Hence, labels are - differentiated under the assumption that snowy pixel occur be out-of-context, - i.e. their surround pixels more likely at lower signal values, than if the - pixel is in a region were gain switching led to a large value. - - Depending on the resolution strategy so-identified pixels are either set - to `np.nan` or to the interpolated value of the direct (high-gain) neighbors. - - The corrected data is returned alonside an updated gain mask and bad pixel + + where p_r is a given pixel raw value, p_bg is the mean value of the + 8 direct neighbor pixels, and p_rg is the raw gain value of the pixel. + + Note that these cluster params are purely emprically derived, + and do not have connection to ASIC design etc. + + Only pixels in medium gain are evaluated, and evaluation is + image-wise, but subdivided further into ASICs. + + The so-created graph [[x_0, x_1, ..., x_n], [y_0, y_1, ..., y_n]] is + scaled using a `sklearn.StandardScaler` and input into a two-component + `GaussianMixture` clustering algorithm. This results in a set of + labels, identifying pixels as likely snowy, or not. However, + for a given image we do not know which label is which from the + output. Hence, labels are differentiated under the assumption that + snowy pixel occur be out-of-context, i.e. their surround pixels more + likely at lower signal values, than if the pixel is in a region were + gain switching led to a large value. + + Depending on the resolution strategy so-identified pixels are either + set to `np.nan` or to the interpolated value of the direct (high-gain) + neighbors. + + The corrected data is returned alonside an updated gain mask and bad + pixel mask. """ snow_mask = np.zeros(im.shape, np.uint32) @@ -550,22 +614,26 @@ class AgipdCorrections: for i in range(8): for j in range(2): - fidx = gain[k, i*64:(i+1)*64,j*64:(j+1)*64] == 1 - fidx_h = gain[k, i*64:(i+1)*64,j*64:(j+1)*64] == 0 - - # need at least two pixels in medium gain to be able to cluster in two groups + fidx = gain[k, i * 64:(i + 1) * 64, + j * 64:(j + 1) * 64] == 1 + fidx_h = gain[k, i * 64:(i + 1) * 64, + j * 64:(j + 1) * 64] == 0 + + # need at least two pixels in medium gain to be able to + # cluster in two groups if np.count_nonzero(fidx) < 2: continue # data for a given asic - asic = raw[k, i*64:(i+1)*64,j*64:(j+1)*64] - asic_g = gain[k, i*64:(i+1)*64,j*64:(j+1)*64] - asic_r = rgain[k, i*64:(i+1)*64,j*64:(j+1)*64] - + asic = raw[k, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] + asic_g = gain[k, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] + asic_r = rgain[k, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] + asic_h = copy.copy(asic) asic_h[~fidx_h] = np.nan - - # calculate the background of by averaging the 8 direct neighbours of each pixel + + # calculate the background of by averaging the 8 direct + # neighbours of each pixel rl = np.roll(asic, 1, axis=0) rr = np.roll(asic, -1, axis=0) ru = np.roll(asic, 1, axis=1) @@ -574,9 +642,10 @@ class AgipdCorrections: rld = np.roll(rl, -1, axis=1) rru = np.roll(rr, 1, axis=1) rrd = np.roll(rr, -1, axis=1) - bg = (rl + rr + ru + rd + rlu + rld + rru + rrd)/8 + bg = (rl + rr + ru + rd + rlu + rld + rru + rrd) / 8 - # calculate the background of by averaging the 8 direct neighbours of each pixel + # calculate the background of by averaging the 8 direct + # neighbours of each pixel # here only for high gain pixels rl = np.roll(asic_h, 1, axis=0) rr = np.roll(asic_h, -1, axis=0) @@ -586,10 +655,13 @@ class AgipdCorrections: rld = np.roll(asic_h, -1, axis=1) rru = np.roll(asic_h, 1, axis=1) rrd = np.roll(asic_h, -1, axis=1) - bg_h = np.nanmean(np.array([rl, rr, ru, rd, rlu, rld, rru, rrd]), axis=0) + bg_h = np.nanmean( + np.array([rl, rr, ru, rd, rlu, rld, rru, rrd]), axis=0) # construct a graph - graph = np.array([np.abs(asic[fidx]-bg[fidx])*asic_r[fidx]**2, asic[fidx]]).T + graph = np.array( + [np.abs(asic[fidx] - bg[fidx]) * asic_r[fidx] ** 2, + asic[fidx]]).T # scale it graph = StandardScaler().fit_transform(graph) # perform clustering @@ -598,14 +670,15 @@ class AgipdCorrections: # and get labels labels = spc.predict(graph) - asic = im[k, i*64:(i+1)*64,j*64:(j+1)*64] - asic_msk = snow_mask[k, i*64:(i+1)*64,j*64:(j+1)*64] + asic = im[k, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] + asic_msk = snow_mask[k, i * 64:(i + 1) * 64, + j * 64:(j + 1) * 64] mim = asic[fidx] asicb = bg_h mimb = asicb[fidx] mimg = asic_g[fidx] mmsk = asic_msk[fidx] - + # identify which labels are which mn1 = np.nanmean(bg[fidx][labels == 0]) mn2 = np.nanmean(bg[fidx][labels == 1]) @@ -613,13 +686,14 @@ class AgipdCorrections: if mn1 > mn2: implabel = 0 - # if we've misidentified, then we will have to many snowy pixels + # if we've misidentified, then we will have to many + # snowy pixels # happens if the ASIC is generally on a high signal level - if np.count_nonzero([labels == implabel]) > 64*64/3: + if np.count_nonzero([labels == implabel]) > 64 * 64 / 3: continue - + # or a large portion of misidenfied label covers the ASIC - if np.count_nonzero([labels != implabel]) > 0.8*64*64: + if np.count_nonzero([labels != implabel]) > 0.8 * 64 * 64: continue # set to NaN if requested @@ -630,150 +704,171 @@ class AgipdCorrections: mim[labels == implabel] = mimb[labels == implabel] mimg[labels == implabel] = 0 # identify these pixels in a bad pixel mask - mmsk[labels == implabel] = BadPixels.TRANSITION_REGION.value - + mmsk[ + labels == implabel] = BadPixels.TRANSITION_REGION.value + # now set back to data asic[fidx] = mim asic_g[fidx] = mimg asic_msk[fidx] = mmsk - im[k, i*64:(i+1)*64,j*64:(j+1)*64] = asic - gain[k, i*64:(i+1)*64,j*64:(j+1)*64] = asic_g - snow_mask[k, i*64:(i+1)*64,j*64:(j+1)*64] = asic_msk + im[k, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] = asic + gain[k, i * 64:(i + 1) * 64, j * 64:(j + 1) * 64] = asic_g + snow_mask[k, i * 64:(i + 1) * 64, + j * 64:(j + 1) * 64] = asic_msk return im, gain, snow_mask def correct_agipd(self, irange): """ Correct Raw AGIOPD data for offset and relative gain effects :param irange: list of image indices to work on, should be contiguous - + Will raise an RuntimeError if `initialize()` has not been called first. """ if not self.initialized: raise RuntimeError("Must call initialize() first!") - + agipd_base = self.agipd_base cidx = self.cidx - im = np.array(self.infile[agipd_base+"image/data"][irange,...]) - trainId = np.squeeze(self.infile[agipd_base+"/image/trainId"][irange, ...]) - pulseId = np.squeeze(self.infile[agipd_base+"image/pulseId"][irange, ...]) - status = np.squeeze(self.infile[agipd_base+"/image/status"][irange, ...]) - cellid = np.squeeze(np.array(self.infile[agipd_base+"/image/cellId"][irange, ...])) - length = np.squeeze(np.array(self.infile[agipd_base+"/image/length"][irange, ...])) + im = np.array(self.infile[agipd_base + "image/data"][irange, ...]) + trainId = np.squeeze( + self.infile[agipd_base + "/image/trainId"][irange, ...]) + pulseId = np.squeeze( + self.infile[agipd_base + "image/pulseId"][irange, ...]) + status = np.squeeze( + self.infile[agipd_base + "/image/status"][irange, ...]) + cellid = np.squeeze( + np.array(self.infile[agipd_base + "/image/cellId"][irange, ...])) + length = np.squeeze( + np.array(self.infile[agipd_base + "/image/length"][irange, ...])) # split off image and gain into two separate arrays im, ga = self.split_gain(im) # we will work on float data from now on im = im.astype(np.float32) - + # some correction require us to maintain a copy of the raw data raw = copy.copy(im) - + # this far end of the image index range we are working on - nidx = int(cidx+irange.size) - + nidx = int(cidx + irange.size) + # first evaluate the gain into 0, 1, 2 --> high, medium, low gain = np.zeros(ga.shape, np.uint8) gain[...] = 0 - t0 = self.thresholds[...,0] - t1 = self.thresholds[...,1] + t0 = self.thresholds[..., 0] + t1 = self.thresholds[..., 1] # exceeding first threshold means data is medium or low gain gain[ga > t0[cellid, ...]] = 1 # exceeding also second threshold means data is low gain gain[ga > t1[cellid, ...]] = 2 - # check if any data has zero standard deviation, and mark this in the bad pixel maks + # check if any data has zero standard deviation, and mark this in + # the bad pixel maks # this can be done on otherwise not corrected data. if self.sig_zero_mask is None: - self.sig_zero_mask = np.zeros((self.max_cells, im.shape[1], im.shape[2]), np.uint32) + self.sig_zero_mask = np.zeros( + (self.max_cells, im.shape[1], im.shape[2]), np.uint32) for c in range(self.max_cells): std = np.nanstd(im[cellid == c, ...], axis=0) - self.sig_zero_mask[c, std==0] = BadPixels.DATA_STD_IS_ZERO.value - + self.sig_zero_mask[ + c, std == 0] = BadPixels.DATA_STD_IS_ZERO.value + # for feedback we produced histograms for the first chunk if cidx == 0: - H, xe, ye = np.histogram2d(im.flatten(), ga.flatten(), - bins=self.bins_gain_vs_signal, - range=[[4000, 8192], [4000, 8192]]) + H, xe, ye = np.histogram2d(im.flatten(), ga.flatten(), + bins=self.bins_gain_vs_signal, + range=[[4000, 8192], [4000, 8192]]) self.hists_gain_vs_signal += H self.signal_edges = (xe, ye) - H, xe, ye = np.histogram2d(im.flatten(), gain.flatten(), - bins=self.bins_dig_gain_vs_signal, - range=[[4000, 8192], [0, 4]]) + H, xe, ye = np.histogram2d(im.flatten(), gain.flatten(), + bins=self.bins_dig_gain_vs_signal, + range=[[4000, 8192], [0, 4]]) self.hists_dig_gain_vs_signal += H self.dig_signal_edges = (xe, ye) # now get the correct constants depending on cell id - rc = self.rel_gain[cellid,...] - offsetb = self.offset[cellid,...] - tmask = self.mask[cellid,...] + rc = self.rel_gain[cellid, ...] + offsetb = self.offset[cellid, ...] + tmask = self.mask[cellid, ...] # choose constants according to gain setting - off = np.choose(gain, (offsetb[...,0], offsetb[...,1], offsetb[...,2])) - rel_cor = np.choose(gain, (rc[...,0], rc[...,1], rc[...,2])) - + off = np.choose(gain, + (offsetb[..., 0], offsetb[..., 1], offsetb[..., 2])) + rel_cor = np.choose(gain, (rc[..., 0], rc[..., 1], rc[..., 2])) + # also choose the correct bad pixel mask - msk = np.choose(gain, (tmask[...,0], tmask[...,1], tmask[...,2])) + msk = np.choose(gain, (tmask[..., 0], tmask[..., 1], tmask[..., 2])) # scale raw gain for use in the identifying snowy pixels rgain = None - if self.melt_snow is not False: - rgain = ga/t0[cellid, ...] - + if self.melt_snow is not False: + rgain = ga / t0[cellid, ...] + # subtract offset im -= off - - # before doing relative gain correction we need to evaluate any baseline shifts + + # before doing relative gain correction we need to evaluate any + # baseline shifts # as they are effectively and additional offset in the data if self.baseline_corr_using_noise or self.baseline_corr_using_hmatch: - + # do this image wise, as the shift is per image for i in range(im.shape[0]): - - # first correction requested may be to evaluate shift via noise peak + + # first correction requested may be to evaluate shift via + # noise peak if self.baseline_corr_using_noise: - dd = self.baseline_correct_via_noise(im[i,...], - np.nanmean(self.noise[cellid[i],...,0]), - gain[i,...]) + mn_noise = np.nanmean(self.noise[cellid[i], ..., 0]) + dd = self.baseline_correct_via_noise(im[i, ...], + mn_noise, + gain[i, ...]) # if not we continue with initial data else: - dd = im[i,...] - - # if we have enough pixels in medium or low gain and correction via hist - # matching is requested to this now - if np.count_nonzero(gain[i,...]>0) > 1000 and self.baseline_corr_using_hmatch: - dd2 = self.correct_baseline_via_hist(im[i,...], - rel_cor[i,...], - gain[i,...]) - im[i,...] = np.maximum(dd, dd2) - + dd = im[i, ...] + + # if we have enough pixels in medium or low gain and + # correction via hist matching is requested to this now + gcrit = np.count_nonzero(gain[i, ...] > 0) > 1000 + if gcrit and self.baseline_corr_using_hmatch: + dd2 = self.correct_baseline_via_hist(im[i, ...], + rel_cor[i, ...], + gain[i, ...]) + im[i, ...] = np.maximum(dd, dd2) + # finally correct diagonal effects if requested if self.correct_asic_diag: - im[i,...] = self.correct_baseline_via_hist_asic(im[i,...], gain[i,...]) - # if there is not enough medium or low gain data to do an evaluation, do nothing + ii = im[i, ...] + gg = gain[i, ...] + adim = self.correct_baseline_via_hist_asic(ii, gg) + im[i, ...] = adim + # if there is not enough medium or low gain data to do an + # evaluation, do nothing else: - im[i,...] = dd + im[i, ...] = dd # now we can correct for relative gain if requested if self.do_rel_gain: - im *= rel_cor - + im *= rel_cor + # try to identify snowy pixels at this point if self.melt_snow is not False: + ms = self.melt_snow im, gain, snowmask = self.melt_snowy_pixels(raw, im, - gain, - rgain, - resolution=self.melt_snow) - - # finally, with all corrections performed we can match ASIC borders if needed + gain, + rgain, + resolution=ms) + + # finally, with all corrections performed we can match ASIC borders + # if needed if self.match_asics: im = self.match_asic_borders(im) # create a bad pixel mask for the data # we add any non-finite values to the mask - bidx = ~np.isfinite(im) + bidx = ~np.isfinite(im) im[bidx] = 0 msk[bidx] |= BadPixels.VALUE_IS_NAN.value @@ -782,7 +877,8 @@ class AgipdCorrections: im[bidx] = 0 msk[bidx] |= BadPixels.VALUE_OUT_OF_RANGE.value - # include pixels with zero standard deviation in the dataset into the mask + # include pixels with zero standard deviation in the dataset into + # the mask msk |= self.sig_zero_mask[cellid, ...] if self.melt_snow is not False: msk |= snowmask @@ -791,56 +887,66 @@ class AgipdCorrections: if cidx == 0: copim = copy.copy(im) copim[copim < self.median_noise] = np.nan - H, xe, ye = np.histogram2d(np.nanmean(copim, axis=(1,2)), pulseId, - bins=(self.bins_signal_low_range, self.max_pulses), - range=[[-50, 1000],[0, self.max_pulses+1]]) + bins = (self.bins_signal_low_range, self.max_pulses) + rnge = [[-50, 1000], [0, self.max_pulses + 1]] + H, xe, ye = np.histogram2d(np.nanmean(copim, axis=(1, 2)), + pulseId, + bins=bins, + range=rnge) self.hists_signal_low += H self.low_edges = (xe, ye) - H, xe, ye = np.histogram2d(np.nanmean(copim, axis=(1,2)), pulseId, - bins=(self.bins_signal_high_range, self.max_pulses), - range=[[0, 200000],[0, self.max_pulses+1]]) + bins = (self.bins_signal_high_range, self.max_pulses) + rnge = [[0, 200000], [0, self.max_pulses + 1]] + H, xe, ye = np.histogram2d(np.nanmean(copim, axis=(1, 2)), + pulseId, + bins=bins, + range=rnge) self.hists_signal_high += H self.high_edges = (xe, ye) # now write out the data - self.ddset[cidx:nidx,...] = im - self.gdset[cidx:nidx,...] = gain - self.mdset[cidx:nidx,...] = msk + self.ddset[cidx:nidx, ...] = im + self.gdset[cidx:nidx, ...] = gain + self.mdset[cidx:nidx, ...] = msk - self.outfile[agipd_base+"image/cellId"][cidx:nidx] = cellid - self.outfile[agipd_base+"image/trainId"][cidx:nidx] = trainId - self.outfile[agipd_base+"image/pulseId"][cidx:nidx] = pulseId - self.outfile[agipd_base+"image/status"][cidx:nidx] = status - self.outfile[agipd_base+"image/length"][cidx:nidx] = length - self.cidx = nidx + self.outfile[agipd_base + "image/cellId"][cidx:nidx] = cellid + self.outfile[agipd_base + "image/trainId"][cidx:nidx] = trainId + self.outfile[agipd_base + "image/pulseId"][cidx:nidx] = pulseId + self.outfile[agipd_base + "image/status"][cidx:nidx] = status + self.outfile[agipd_base + "image/length"][cidx:nidx] = length + self.cidx = nidx def get_valid_image_idx(self): """ Return the indices of valid data """ agipd_base = self.idx_base if self.index_v == 2: - count = np.squeeze(self.infile[agipd_base+"image/count"]) - first = np.squeeze(self.infile[agipd_base+"image/first"]) + count = np.squeeze(self.infile[agipd_base + "image/count"]) + first = np.squeeze(self.infile[agipd_base + "image/first"]) if np.count_nonzero(count != 0) == 0: raise IOError("File has no valid counts") valid = count != 0 idxtrains = np.squeeze(self.infile["/INDEX/trainId"]) medianTrain = np.nanmedian(idxtrains) - valid &= (idxtrains > medianTrain - 1e4) & (idxtrains < medianTrain + 1e4) + lowok = (idxtrains > medianTrain - 1e4) + highok = (idxtrains < medianTrain + 1e4) + valid &= lowok & highok - last_index = int(first[valid][-1]+count[valid][-1]) + last_index = int(first[valid][-1] + count[valid][-1]) first_index = int(first[valid][0]) elif self.index_v == 1: - status = np.squeeze(self.infile[agipd_base+"image/status"]) + status = np.squeeze(self.infile[agipd_base + "image/status"]) if np.count_nonzero(status != 0) == 0: - raise IOError("File {} has no valid counts".format(infile)) - last = np.squeeze(self.infile[agipd_base+"image/last"]) + raise IOError("File {} has no valid counts".format( + self.infile)) + last = np.squeeze(self.infile[agipd_base + "image/last"]) valid = status != 0 last_index = int(last[status != 0][-1]) first_index = int(last[status != 0][0]) else: - raise AttributeError("Not a known raw format version: {}".format(self.index_v)) + raise AttributeError( + "Not a known raw format version: {}".format(self.index_v)) self.valid = valid self.first_index = first_index @@ -854,20 +960,26 @@ class AgipdCorrections: last_index = self.last_index max_cells = self.max_cells agipd_base = self.agipd_base - allcells = np.squeeze(np.array(self.infile[agipd_base+"image/cellId"][first_index:last_index, ...])) - single_image = np.array(np.array(self.infile[agipd_base+"image/data"][first_index, ...])) + allcells = self.infile[agipd_base + "image/cellId"] + allcells = np.squeeze(allcells[first_index:last_index, ...]) + single_image = self.infile[agipd_base + "image/data"][first_index, ...] + single_image = np.array(single_image) + can_calibrate = allcells < max_cells if np.count_nonzero(can_calibrate) == 0: return allcells = allcells[can_calibrate] firange = np.arange(first_index, last_index) firange = firange[can_calibrate] - self.oshape = (firange.size, single_image.shape[1], single_image.shape[2]) + self.oshape = (firange.size, + single_image.shape[1], + single_image.shape[2]) self.firange = firange self.single_image = single_image def copy_and_sanitize_non_cal_data(self): - """ Copy and sanitize data in `infile` that is not touched by `correctAGIPD` + """ Copy and sanitize data in `infile` that is not touched by + `correctAGIPD` """ agipd_base = self.agipd_base idx_base = self.idx_base @@ -876,18 +988,20 @@ class AgipdCorrections: valid = self.valid idxtrains = self.idxtrains firange = self.firange - alltrains = np.squeeze(self.infile[agipd_base+"image/trainId"][first_index:last_index,...]) - + alltrains = self.infile[agipd_base + "image/trainId"] + alltrains = np.squeeze(alltrains[first_index:last_index, ...]) + # these are touched in the correct function, do not copy them here - dont_copy = ["data", "cellId", "trainId", "pulseId", "status", "length"] - dont_copy = [agipd_base+"image/{}".format(do) - for do in dont_copy] - + dont_copy = ["data", "cellId", "trainId", "pulseId", "status", + "length"] + dont_copy = [agipd_base + "image/{}".format(do) + for do in dont_copy] + # don't copy these as we may need to adjust if we filter trains - dont_copy += [idx_base+"{}/first".format(do) - for do in ["image",]] - dont_copy += [idx_base+"{}/count".format(do) - for do in ["image",]] + dont_copy += [idx_base + "{}/first".format(do) + for do in ["image", ]] + dont_copy += [idx_base + "{}/count".format(do) + for do in ["image", ]] # a visitor to copy everything else def visitor(k, item): @@ -901,19 +1015,23 @@ class AgipdCorrections: self.infile.copy(k, self.outfile[group]) self.infile.visititems(visitor) - + # sanitize indices for do in ["image", ]: - existing = np.squeeze(self.infile[idx_base+"{}/first".format(do)]) - updated = existing-np.min(existing[valid]) - self.outfile[idx_base+"{}/first".format(do)] = updated - - existing = np.squeeze(self.infile[idx_base+"{}/count".format(do)]) - new_counts, _ = np.histogram(alltrains[firange], bins=np.count_nonzero(valid)+1, - range=(np.min(idxtrains[valid]), np.max(idxtrains[valid]+1))) + existing = np.squeeze( + self.infile[idx_base + "{}/first".format(do)]) + updated = existing - np.min(existing[valid]) + self.outfile[idx_base + "{}/first".format(do)] = updated + + existing = np.squeeze( + self.infile[idx_base + "{}/count".format(do)]) + new_counts, _ = np.histogram(alltrains[firange], + bins=np.count_nonzero(valid) + 1, + range=(np.min(idxtrains[valid]), + np.max(idxtrains[valid] + 1))) updated = existing updated[valid] = new_counts[:-1] - self.outfile[idx_base+"{}/count".format(do)] = updated + self.outfile[idx_base + "{}/count".format(do)] = updated def create_output_datasets(self): """ Initialize output data sets @@ -922,279 +1040,335 @@ class AgipdCorrections: chunksize = self.chunk_size_idim firange = self.firange oshape = self.oshape - self.ddset = self.outfile.create_dataset(agipd_base+"image/data", - oshape, chunks=(chunksize, oshape[1], oshape[2]), dtype=np.float32) - self.gdset = self.outfile.create_dataset(agipd_base+"image/gain", - oshape, chunks=(chunksize, oshape[1], oshape[2]), dtype=np.uint8, - compression="gzip", compression_opts=1, shuffle=True) - self.mdset = self.outfile.create_dataset(agipd_base+"image/mask", - oshape, chunks=(chunksize, oshape[1], oshape[2]), dtype=np.uint32, - compression="gzip", compression_opts=1, shuffle=True) - - self.outfile[agipd_base+"image/cellId"] = np.zeros(firange.size, np.uint16) - self.outfile[agipd_base+"image/trainId"] = np.zeros(firange.size, np.uint64) - self.outfile[agipd_base+"image/pulseId"] = np.zeros(firange.size, np.uint64) - self.outfile[agipd_base+"image/status"] = np.zeros(firange.size, np.uint16) - self.outfile[agipd_base+"image/length"] = np.zeros(firange.size, np.uint32) + chunks = (chunksize, oshape[1], oshape[2]) + self.ddset = self.outfile.create_dataset(agipd_base + "image/data", + oshape, chunks=chunks, + dtype=np.float32) + self.gdset = self.outfile.create_dataset(agipd_base + "image/gain", + oshape, chunks=chunks, + dtype=np.uint8, + compression="gzip", + compression_opts=1, + shuffle=True) + self.mdset = self.outfile.create_dataset(agipd_base + "image/mask", + oshape, chunks=chunks, + dtype=np.uint32, + compression="gzip", + compression_opts=1, + shuffle=True) + + fsz = firange.size + self.outfile[agipd_base + "image/cellId"] = np.zeros(fsz, np.uint16) + self.outfile[agipd_base + "image/trainId"] = np.zeros(fsz, np.uint64) + self.outfile[agipd_base + "image/pulseId"] = np.zeros(fsz, np.uint64) + self.outfile[agipd_base + "image/status"] = np.zeros(fsz, np.uint16) + self.outfile[agipd_base + "image/length"] = np.zeros(fsz, np.uint32) self.outfile.flush() - + def get_histograms(self): """ Return preview histograms computed from the first chunk """ - return ((self.hists_signal_low, self.hists_signal_high, self.hists_gain_vs_signal, self.hists_dig_gain_vs_signal), - (self.low_edges, self.high_edges, self.signal_edges, self.dig_signal_edges)) - + return ((self.hists_signal_low, self.hists_signal_high, + self.hists_gain_vs_signal, self.hists_dig_gain_vs_signal), + (self.low_edges, self.high_edges, self.signal_edges, + self.dig_signal_edges)) + def initialize_from_db(self, dbparms, qm, only_dark=False): """ Initialize calibration constants from the calibration database - - :param dbparms: a tuple containing relevant database parameters, can be either: - * cal_db_interface, creation_time, max_cells_db, bias_voltage, photon_energy - in which case the db timeout is set to 300 seconds, the the cells to query - dark image derived constants from the database is set to the global value - - * cal_db_interface, creation_time, max_cells_db, bias_voltage, photon_energy, max_cells_db_dark - here the number of memory cells to query dark derived data differs from the global value, the - the db timeout is set to 300 seconds - - * cal_db_interface, creation_time, max_cells_db, bias_voltage, photon_energy, - max_cells_db_dark, timeout_cal_db - addiitonally a timeout is given - - :param qm: quadrant and module of the constants to load in Q1M1 notation - :param only_dark: load only dark image derived constants. This implies that a `calfile` is - used to load the remaining constants. Useful to reduce DB traffic and interactions - for non-frequently changing constants, i.e. such which are not usually updated during - a beamtime. - - The `cal_db_interface` parameter in the `dbparms` tuple may be in one of the following notations: - * tcp://host:port to directly identify the host and port to connect to - * tcp://host:port_low#port_high to specify a port range from which a random port will be picked. - + + :param dbparms: a tuple containing relevant database parameters, + can be either: + * cal_db_interface, creation_time, max_cells_db, bias_voltage, + photon_energy in which case the db timeout is set to 300 seconds, + the cells to query dark image derived constants from the + database is set to the global value + + * cal_db_interface, creation_time, max_cells_db, bias_voltage, + photon_energy, max_cells_db_dark here the number of memory + cells to query dark derived data differs from the global + value, the the db timeout is set to 300 seconds + + * cal_db_interface, creation_time, max_cells_db, bias_voltage, + photon_energy, max_cells_db_dark, timeout_cal_db + additionally a timeout is given + + :param qm: quadrant and module of the constants to load in Q1M1 + notation + :param only_dark: load only dark image derived constants. This + implies that a `calfile` is used to load the remaining + constants. Useful to reduce DB traffic and interactions + for non-frequently changing constants, i.e. such which are + not usually updated during a beamtime. + + The `cal_db_interface` parameter in the `dbparms` tuple may be in + one of the following notations: + * tcp://host:port to directly identify the host and port to + connect to + * tcp://host:port_low#port_high to specify a port range from + which a random port will be picked. + The latter notation allows for load-balancing. - - This routine loads the following constants as given in `iCalibrationDB`: - + + This routine loads the following constants as given in + `iCalibrationDB`: + Dark Image Derived ------------------ - + * Constants.AGIPD.Offset * Constants.AGIPD.Noise * Constants.AGIPD.BadPixelsDark * Constants.AGIPD.ThresholdsDark - + Pulse Capacitor Derived ----------------------- - - * Constants.AGIPD.SlopesPC - older data, whith the parameter dimension first is correctly handled - + + * Constants.AGIPD.SlopesPC - older data, whith the parameter + dimension first is correctly handled + Flat-Field Derived - + * Constants.AGIPD.SlopesFF - - For CI derived gain, a mean multiplication factor of 4.48 compared to medium gain is used, as no - reliable CI data for all memory cells exists for all memory cells of the current AGIPD instances. - - Relative gain is derived both from pulse capacitor as well as flat field data: - - * from the pulse capacitor data we get the relative slopes of a given pixels memory cells - with respect to all memory cells of that pixel: - + + For CI derived gain, a mean multiplication factor of 4.48 compared + to medium gain is used, as no reliable CI data for all memory cells + exists for all memory cells of the current AGIPD instances. + + Relative gain is derived both from pulse capacitor as well as flat + field data: + + * from the pulse capacitor data we get the relative slopes of a + given pixels memory cells with respect to all memory cells of that + pixel: + rpch = m_h / median(m_h) - + where m_h is the high gain slope m of each memory cell of the pixel. - - * we also derive the factor between high and medium gain in a similar way and scale it to be relative - to the pixels memory cells: - + + * we also derive the factor between high and medium gain in a + similar way and scale it to be relative to the pixels memory cells: + fpc = m_m/m_h rfpc = fpc/ median(fpc) - - where m_m is the medium gain slope of all memory cells of a given pixel and m_h is the high gain - slope as above - - * finally, we get the relative X-ray gain of all memory cells for a given pixel from flat field - data: - + + where m_m is the medium gain slope of all memory cells of a given + pixel and m_h is the high gain slope as above + + * finally, we get the relative X-ray gain of all memory cells for a + given pixel from flat field data: + ff = median(m_ff) ff /= median(ff) - - where m_ff is the flat field derived (high gain) slope of all memory cells of a given pixel. - The pixel values are then scaled to the complete module. Note that the first 32 memory cells - are known to exhibit differing behaviour and are skipped if possible. - - With this data the relative gain for the three gain stages evaluates to: - + + where m_ff is the flat field derived (high gain) slope of all + memory cells of a given pixel. The pixel values are then scaled to + the complete module. Note that the first 32 memory cells are known + to exhibit differing behaviour and are skipped if possible. + + With this data the relative gain for the three gain stages evaluates + to: + high gain = ff * rpch medium gain = ff * rfpc low gain = medium gain / 4.48 - + """ if len(dbparms) == 5: - cal_db_interface, creation_time, max_cells_db, bias_voltage, photon_energy = dbparms + (cal_db_interface, creation_time, max_cells_db, + bias_voltage, photon_energy) = dbparms max_cells_db_dark = max_cells_db timeout_cal_db = 300000 - + elif len(dbparms) == 6: (cal_db_interface, creation_time, max_cells_db, bias_voltage, photon_energy, max_cells_db_dark) = dbparms - + if max_cells_db_dark is None: max_cells_db_dark = max_cells_db timeout_cal_db = 300000 else: (cal_db_interface, creation_time, max_cells_db, - bias_voltage, photon_energy, max_cells_db_dark, timeout_cal_db) = dbparms - + bias_voltage, photon_energy, max_cells_db_dark, + timeout_cal_db) = dbparms + if max_cells_db_dark is None: max_cells_db_dark = max_cells_db - + if "#" in cal_db_interface: prot, serv, ran = cal_db_interface.split(":") r1, r2 = ran.split("#") - cal_db_interface = ":".join([prot, serv, str(np.random.randint(int(r1), int(r2)))]) - + cal_db_interface = ":".join( + [prot, serv, str(np.random.randint(int(r1), int(r2)))]) + offset = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.Offset(), - Conditions.Dark.AGIPD(memory_cells=max_cells_db_dark, - bias_voltage=bias_voltage), - np.zeros((128,512,max_cells_db,3)), + Conditions.Dark.AGIPD( + memory_cells=max_cells_db_dark, + bias_voltage=bias_voltage), + np.zeros((128, 512, max_cells_db, 3)), cal_db_interface, creation_time=creation_time, timeout=timeout_cal_db) - + noise = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.Noise(), - Conditions.Dark.AGIPD(memory_cells=max_cells_db_dark, - bias_voltage=bias_voltage), - np.zeros((128,512,max_cells_db,3)), + Conditions.Dark.AGIPD( + memory_cells=max_cells_db_dark, + bias_voltage=bias_voltage), + np.zeros((128, 512, max_cells_db, 3)), cal_db_interface, creation_time=creation_time, timeout=timeout_cal_db) - + bpixels = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.BadPixelsDark(), - Conditions.Dark.AGIPD(memory_cells=max_cells_db_dark, - bias_voltage=bias_voltage), - np.zeros((128,512,max_cells_db,3)), + Conditions.Dark.AGIPD( + memory_cells=max_cells_db_dark, + bias_voltage=bias_voltage), + np.zeros((128, 512, max_cells_db, 3)), cal_db_interface, creation_time=creation_time, - timeout=timeout_cal_db).astype(np.uint32) - + timeout=timeout_cal_db).astype( + np.uint32) + thresholds = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.ThresholdsDark(), - Conditions.Dark.AGIPD(memory_cells=max_cells_db_dark, - bias_voltage=bias_voltage), - np.ones((128,512,max_cells_db,2)), + Conditions.Dark.AGIPD( + memory_cells=max_cells_db_dark, + bias_voltage=bias_voltage), + np.ones((128, 512, max_cells_db, 2)), cal_db_interface, creation_time=creation_time, timeout=timeout_cal_db) - + # we have all we need for dark image derived constants # initialize and return if only_dark: self.initialize(offset, None, bpixels, noise, thresholds, None) - return + return - # load also non-dark constants from the database + # load also non-dark constants from the database slopesPC = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.SlopesPC(), - Conditions.Dark.AGIPD(memory_cells=max_cells_db, - bias_voltage=bias_voltage), - np.ones((128,512,max_cells_db,10)), + Conditions.Dark.AGIPD( + memory_cells=max_cells_db, + bias_voltage=bias_voltage), + np.ones((128, 512, max_cells_db, 10)), cal_db_interface, creation_time=creation_time, - timeout=timeout_cal_db).astype(np.float32) - + timeout=timeout_cal_db) + slopesPC = slopesPC.astype(np.float32) + # this will handle some historical data in a slighly different format - if slopesPC.shape[0] == 10: # constant dimension injected first + if slopesPC.shape[0] == 10: # constant dimension injected first slopesPC = np.moveaxis(slopesPC, 0, 3) slopesPC = np.moveaxis(slopesPC, 0, 2) - - slopesFF = np.squeeze(get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), - Constants.AGIPD.SlopesFF(), - Conditions.Illuminated.AGIPD(max_cells_db, bias_voltage, photon_energy, - pixels_x=512, pixels_y=128, - beam_energy=None), - np.ones((128,512,max_cells_db,2)), - cal_db_interface, - creation_time=creation_time, - timeout=timeout_cal_db)) - + + slopesFF = np.squeeze( + get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), + Constants.AGIPD.SlopesFF(), + Conditions.Illuminated.AGIPD(max_cells_db, + bias_voltage, + photon_energy, + pixels_x=512, + pixels_y=128, + beam_energy=None), # noqa + np.ones((128, 512, max_cells_db, 2)), + cal_db_interface, + creation_time=creation_time, + timeout=timeout_cal_db)) + # calculate relative gain from the constants # we default to a value of one, so basically a unity transform rel_gain = np.ones((128, 512, self.max_cells, 3), np.float32) - + # high gain slope from pulse capacitor data - pc_high_m = slopesPC[...,:self.max_cells,0] - + pc_high_m = slopesPC[..., :self.max_cells, 0] + # factor of high vs. medium gain for each pixel as derived from PC data - fac_high_med = slopesPC[...,:self.max_cells,3] / slopesPC[...,:self.max_cells,0] - - # if the flat field data contains a field for X-ray offset we can make used of it + fac_high_med = slopesPC[..., :self.max_cells, 3] / pc_high_m + + # if the flat field data contains a field for X-ray offset we can + # make used of it xrayOffset = 0 if len(slopesFF.shape) == 4: - xrayOffset = slopesFF[...,:self.max_cells,1] - slopesFF = slopesFF[...,0] - - # first 32 cells are known to behave differently so if we can avoid them + xrayOffset = slopesFF[..., :self.max_cells, 1] + slopesFF = slopesFF[..., 0] + + # first 32 cells are known to behave differently so if we can avoid + # them # when calculating the mean X-ray derived gain slope for each pixel if slopesFF.shape[2] > 32: - xraygain = np.nanmedian(slopesFF[...,32:min(96,self.max_cells)], axis=2) + xraygain = np.nanmedian(slopesFF[..., 32:min(96, self.max_cells)], + axis=2) else: - xraygain = np.nanmedian(slopesFF[...,:min(96,self.max_cells)], axis=2) - - # relative X-ray gain is then each pixels gain, by the median gain of all pixels + xraygain = np.nanmedian(slopesFF[..., :min(96, self.max_cells)], + axis=2) + + # relative X-ray gain is then each pixels gain, by the median gain + # of all pixels xraygain /= np.nanmedian(xraygain) - - # scale the relative PC gain of each memory cell in a pixel to the median value of all cells - pcrel = pc_high_m/np.nanmedian(pc_high_m, axis=2)[...,None] - - # and get high gain relative gain by multiplying with x-ray derived gain - rel_gain[..., 0] = pcrel*xraygain[...,None] - + + # scale the relative PC gain of each memory cell in a pixel to the + # median value of all cells + pcrel = pc_high_m / np.nanmedian(pc_high_m, axis=2)[..., None] + + # and get high gain relative gain by multiplying with x-ray derived + # gain + rel_gain[..., 0] = pcrel * xraygain[..., None] + # same scaling for high-to medium gain factor - mfac = fac_high_med/np.nanmedian(fac_high_med, axis=2)[...,None]*np.nanmedian(fac_high_med) - + medfhm = np.nanmedian(fac_high_med, axis=2)[..., None] + mfac = fac_high_med / medfhm * np.nanmedian(fac_high_med) + # and scale with X-ray derived gain - rel_gain[..., 1] = xraygain[...,None] * mfac - + rel_gain[..., 1] = xraygain[..., None] * mfac + # finally low gain with a constant factor rel_gain[..., 2] = rel_gain[..., 1] * 0.223 - + # for base offsets we pass a tuple containing the relevant information - base_offset = [slopesPC[...,:self.max_cells,1], slopesPC[...,:self.max_cells,4], xrayOffset] - + base_offset = [slopesPC[..., :self.max_cells, 1], + slopesPC[..., :self.max_cells, 4], xrayOffset] + # add additonal bad pixel information bppc = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.BadPixelsPC(), - Conditions.Dark.AGIPD(memory_cells=max_cells_db, - bias_voltage=bias_voltage), - np.zeros((max_cells_db, 128,512)), + Conditions.Dark.AGIPD( + memory_cells=max_cells_db, + bias_voltage=bias_voltage), + np.zeros((max_cells_db, 128, 512)), cal_db_interface, creation_time=creation_time, timeout=timeout_cal_db) - bpixels |= np.moveaxis(bppc.astype(np.uint32), 0, 2)[...,:bpixels.shape[2], None] - + bppc = np.moveaxis(bppc.astype(np.uint32), 0, 2) + bpixels |= bppc[..., :bpixels.shape[2], None] + bpff = get_constant_from_db(getattr(Detectors.AGIPD1M1, qm), Constants.AGIPD.BadPixelsFF(), - Conditions.Illuminated.AGIPD(max_cells_db, bias_voltage, photon_energy, - pixels_x=512, pixels_y=128, - beam_energy=None), - np.zeros((128,512,max_cells_db)), + Conditions.Illuminated.AGIPD(max_cells_db, + bias_voltage, + photon_energy, + pixels_x=512, + pixels_y=128, + beam_energy=None), # noqa + np.zeros((128, 512, max_cells_db)), cal_db_interface, creation_time=creation_time, timeout=timeout_cal_db) - - bpixels |= bpff.astype(np.uint32)[...,:bpixels.shape[2], None] - self.initialize(offset, rel_gain, bpixels, noise, thresholds, base_offset) - + bpixels |= bpff.astype(np.uint32)[..., :bpixels.shape[2], None] + + self.initialize(offset, rel_gain, bpixels, noise, thresholds, + base_offset) + def initialize_from_file(self, filename, qm, with_dark=True): """ Initialize calibration constants from a calibration file - - :param filename: path to a file containing the calibration constants. It is + + :param filename: path to a file containing the calibration + constants. It is expected to have the following structure: - + /{qm}/BadPixelsFF/data /{qm}/BadPixelsPC/data /{qm}/Offset/data @@ -1203,97 +1377,106 @@ class AgipdCorrections: /{qm}/BadPixelsDark/data /{qm}/SlopesFF/data /{qm}/SlopesPC/data - + where qm is the `qm` parameter. - - :param qm: quadrant and module of the constants to load in Q1M1 notation - :param with_dark: also load dark image derived constants from the file. This will overwrite - any constants previously loaded from the calibration database. - - For CI derived gain, a mean multiplication factor of 4.48 compared to medium gain is used, as no - reliable CI data for all memory cells exists for all memory cells of the current AGIPD instances. - - Relative gain is derived both from pulse capacitor as well as flat field data: - - * from the pulse capacitor data we get the relative slopes of a given pixels memory cells - with respect to all memory cells of that pixel: - + + :param qm: quadrant and module of the constants to load in Q1M1 + notation + :param with_dark: also load dark image derived constants from the + file. This will overwrite any constants previously loaded from + the calibration database. + + For CI derived gain, a mean multiplication factor of 4.48 compared + to medium gain is used, as no reliable CI data for all memory cells + exists for all memory cells of the current AGIPD instances. + + Relative gain is derived both from pulse capacitor as well as flat + field data: + + * from the pulse capacitor data we get the relative slopes of a given + pixels memory cells with respect to all memory cells of that pixel: + rpch = m_h / median(m_h) - + where m_h is the high gain slope m of each memory cell of the pixel. - - * we also derive the factor between high and medium gain in a similar way and scale it to be relative - to the pixels memory cells: - + + * we also derive the factor between high and medium gain in a similar + way and scale it to be relative to the pixels memory cells: + fpc = m_m/m_h rfpc = fpc/ median(fpc) - - where m_m is the medium gain slope of all memory cells of a given pixel and m_h is the high gain - slope as above - - * finally, we get the relative X-ray gain of all memory cells for a given pixel from flat field - data: - + + where m_m is the medium gain slope of all memory cells of a given + pixel and m_h is the high gain slope as above + + * finally, we get the relative X-ray gain of all memory cells for a + given pixel from flat field data: + ff = median(m_ff) ff /= median(ff) - - where m_ff is the flat field derived (high gain) slope of all memory cells of a given pixel. - The pixel values are then scaled to the complete module. Note that the first 32 memory cells - are known to exhibit differing behaviour and are skipped if possible. - - With this data the relative gain for the three gain stages evaluates to: - + + where m_ff is the flat field derived (high gain) slope of all memory + cells of a given pixel. The pixel values are then scaled to the + complete module. Note that the first 32 memory cells are known to + exhibit differing behaviour and are skipped if possible. + + With this data the relative gain for the three gain stages evaluates + to: + high gain = ff * rpch medium gain = ff * rfpc low gain = medium gain / 4.48 - + """ offsets = None - rel_gains = None bpixels = None noises = None thresholds = None with h5py.File(filename, "r") as calfile: - + bpixels = calfile["{}/{}/data".format(qm, "BadPixelsFF")][()] - bpixels |= np.moveaxis(calfile["{}/{}/data".format(qm, "BadPixelsPC")][()], 0, 2) - bpixels = bpixels[...,None] # without gain dimension up to here + bpixels |= np.moveaxis( + calfile["{}/{}/data".format(qm, "BadPixelsPC")][()], 0, 2) + bpixels = bpixels[..., None] # without gain dimension up to here if with_dark: offsets = calfile["{}/{}/data".format(qm, "Offset")][()] noises = calfile["{}/{}/data".format(qm, "Noise")][()] - thresholds = calfile["{}/{}/data".format(qm, "ThresholdsDark")][()] - bpixels |= calfile["{}/{}/data".format(qm, "BadPixelsDark")][()] - + thresholds = calfile["{}/{}/data".format(qm, "ThresholdsDark")][()] # noqa + bpixels |= calfile["{}/{}/data".format(qm, "BadPixelsDark")][ + ()] + slopesFF = calfile["{}/{}/data".format(qm, "SlopesFF")][()] slopesPC = calfile["{}/{}/data".format(qm, "SlopesPC")][()] - if slopesPC.shape[0] == 10: # constant dimension injected first + if slopesPC.shape[0] == 10: # constant dimension injected first slopesPC = np.moveaxis(slopesPC, 0, 3) slopesPC = np.moveaxis(slopesPC, 0, 2) - + rel_gain = np.ones((128, 512, self.max_cells, 3), np.float32) - pc_high_m = slopesPC[...,:self.max_cells,0] - pc_med_m = slopesPC[...,:self.max_cells,3] - fac_high_med = slopesPC[...,:self.max_cells,3] / slopesPC[...,:self.max_cells,0] - + pc_high_m = slopesPC[..., :self.max_cells, 0] + pc_med_m = slopesPC[..., :self.max_cells, 3] + fac_high_med = pc_med_m / pc_high_m + xrayOffset = 0 if len(slopesFF.shape) == 4: - xrayOffset = slopesFF[...,:self.max_cells,1] - slopesFF = slopesFF[...,0] + xrayOffset = slopesFF[..., :self.max_cells, 1] + slopesFF = slopesFF[..., 0] if slopesFF.shape[2] > 32: - xraygain = np.nanmedian(slopesFF[...,32:], axis=2) + xraygain = np.nanmedian(slopesFF[..., 32:], axis=2) else: - xraygain = np.nanmedian(slopesFF[...,:min(96,self.max_cells)], axis=2) - + xraygain = np.nanmedian( + slopesFF[..., :min(96, self.max_cells)], axis=2) + xraygain /= np.nanmedian(xraygain) - pcrel = pc_high_m/np.nanmedian(pc_high_m, axis=2)[...,None] - - rel_gain[..., 0] = pcrel*xraygain[...,None] - mfac = fac_high_med/np.nanmedian(fac_high_med, axis=2)[...,None]*np.nanmedian(fac_high_med) - rel_gain[..., 1] = xraygain[...,None] * mfac + pcrel = pc_high_m / np.nanmedian(pc_high_m, axis=2)[..., None] + + rel_gain[..., 0] = pcrel * xraygain[..., None] + mfac = fac_high_med / np.nanmedian(fac_high_med, axis=2)[ + ..., None] * np.nanmedian(fac_high_med) + rel_gain[..., 1] = xraygain[..., None] * mfac rel_gain[..., 2] = rel_gain[..., 1] * 0.223 - - base_offset = [slopesPC[...,:self.max_cells,1], slopesPC[...,:self.max_cells,4], xrayOffset] - - self.initialize(offsets, rel_gain, bpixels, noises, thresholds, base_offset) - \ No newline at end of file + base_offset = [slopesPC[..., :self.max_cells, 1], + slopesPC[..., :self.max_cells, 4], xrayOffset] + + self.initialize(offsets, rel_gain, bpixels, noises, thresholds, + base_offset) -- GitLab