Skip to content
Snippets Groups Projects
Commit 1df6b681 authored by Karim Ahmed's avatar Karim Ahmed
Browse files

Merge branch 'fix/operation_mode_fixed_gain_selected' into 'master'

[AGIPD][DARK] fix gain mode validation for FIXED_GAIN operation mode

See merge request detectors/pycalibration!662
parents f8321fe9 d112f81e
No related branches found
No related tags found
1 merge request!662[AGIPD][DARK] fix gain mode validation for FIXED_GAIN operation mode
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# AGIPD Characterize Dark Images # # AGIPD Characterize Dark Images #
Author: European XFEL Detector Group, Version: 2.0 Author: European XFEL Detector Group, Version: 2.0
The following code analyzes a set of dark images taken with the AGIPD detector to deduce detector offsets , noise, bad-pixel maps and thresholding. All four types of constants are evaluated per-pixel and per-memory cell. Data for the detector's three gain stages needs to be present, separated into separate runs. The following code analyzes a set of dark images taken with the AGIPD detector to deduce detector offsets , noise, bad-pixel maps and thresholding. All four types of constants are evaluated per-pixel and per-memory cell. Data for the detector's three gain stages needs to be present, separated into separate runs.
The evaluated calibration constants are stored locally and injected in the calibration data base. The evaluated calibration constants are stored locally and injected in the calibration data base.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
in_folder = "/gpfs/exfel/d/raw/CALLAB/202031/p900113" # path to input data, required in_folder = "/gpfs/exfel/d/raw/CALLAB/202031/p900113" # path to input data, required
out_folder = "" # path to output to, required out_folder = "" # path to output to, required
modules = [-1] # list of modules to evaluate, RANGE ALLOWED modules = [-1] # list of modules to evaluate, RANGE ALLOWED
run_high = 9985 # run number in which high gain data was recorded, required run_high = 9985 # run number in which high gain data was recorded, required
run_med = 9984 # run number in which medium gain data was recorded, required run_med = 9984 # run number in which medium gain data was recorded, required
run_low = 9983 # run number in which low gain data was recorded, required run_low = 9983 # run number in which low gain data was recorded, required
operation_mode = "ADAPTIVE_GAIN" # Detector operation mode, optional (defaults to "ADAPTIVE_GAIN") operation_mode = "ADAPTIVE_GAIN" # Detector operation mode, optional (defaults to "ADAPTIVE_GAIN")
karabo_id = "HED_DET_AGIPD500K2G" # karabo karabo_id karabo_id = "HED_DET_AGIPD500K2G" # karabo karabo_id
karabo_da = ['-1'] # a list of data aggregators names, Default [-1] for selecting all data aggregators karabo_da = ['-1'] # a list of data aggregators names, Default [-1] for selecting all data aggregators
receiver_template = "{}CH0" # inset for receiver devices receiver_template = "{}CH0" # inset for receiver devices
instrument_source_template = '{}/DET/{}:xtdf' # path in the HDF5 file to images instrument_source_template = '{}/DET/{}:xtdf' # path in the HDF5 file to images
ctrl_source_template = '{}/MDL/FPGA_COMP' # path to control information ctrl_source_template = '{}/MDL/FPGA_COMP' # path to control information
karabo_id_control = "HED_EXP_AGIPD500K2G" # karabo-id for control device ' karabo_id_control = "HED_EXP_AGIPD500K2G" # karabo-id for control device '
use_dir_creation_date = True # use dir creation date as data production reference date use_dir_creation_date = True # use dir creation date as data production reference date
cal_db_interface = "tcp://max-exfl016:8020" # the database interface to use cal_db_interface = "tcp://max-exfl016:8020" # the database interface to use
cal_db_timeout = 3000000 # timeout on caldb requests" cal_db_timeout = 3000000 # timeout on caldb requests"
local_output = True # output constants locally local_output = True # output constants locally
db_output = False # output constants to database db_output = False # output constants to database
mem_cells = 0 # number of memory cells used, set to 0 to automatically infer mem_cells = 0 # number of memory cells used, set to 0 to automatically infer
bias_voltage = 0 # bias voltage, set to 0 to use stored value in slow data. bias_voltage = 0 # bias voltage, set to 0 to use stored value in slow data.
gain_setting = -1 # the gain setting, use -1 to use value stored in slow data. gain_setting = -1 # the gain setting, use -1 to use value stored in slow data.
gain_mode = -1 # gain mode, use -1 to use value stored in slow data. gain_mode = -1 # gain mode, use -1 to use value stored in slow data.
integration_time = -1 # integration time, negative values for auto-detection. integration_time = -1 # integration time, negative values for auto-detection.
acq_rate = 0. # the detector acquisition rate, use 0 to try to auto-determine acq_rate = 0. # the detector acquisition rate, use 0 to try to auto-determine
interlaced = False # assume interlaced data format, for data prior to Dec. 2017 interlaced = False # assume interlaced data format, for data prior to Dec. 2017
thresholds_offset_sigma = 3. # offset sigma thresholds for offset deduced bad pixels thresholds_offset_sigma = 3. # offset sigma thresholds for offset deduced bad pixels
thresholds_offset_hard = [0, 0] # For setting the same threshold offset for the 3 gains. Left for backcompatability. Default [0, 0] to take the following parameters. thresholds_offset_hard = [0, 0] # For setting the same threshold offset for the 3 gains. Left for backcompatability. Default [0, 0] to take the following parameters.
thresholds_offset_hard_hg = [3000, 7000] # High-gain thresholds in absolute ADU terms for offset deduced bad pixels thresholds_offset_hard_hg = [3000, 7000] # High-gain thresholds in absolute ADU terms for offset deduced bad pixels
thresholds_offset_hard_mg = [6000, 10000] # Medium-gain thresholds in absolute ADU terms for offset deduced bad pixels thresholds_offset_hard_mg = [6000, 10000] # Medium-gain thresholds in absolute ADU terms for offset deduced bad pixels
thresholds_offset_hard_lg = [6000, 10000] # Low-gain thresholds in absolute ADU terms for offset deduced bad pixels thresholds_offset_hard_lg = [6000, 10000] # Low-gain thresholds in absolute ADU terms for offset deduced bad pixels
thresholds_offset_hard_hg_fixed = [3500, 6500] # Same as thresholds_offset_hard_hg, but for fixed gain operation thresholds_offset_hard_hg_fixed = [3500, 6500] # Same as thresholds_offset_hard_hg, but for fixed gain operation
thresholds_offset_hard_mg_fixed = [3500, 6500] # Same as thresholds_offset_hard_mg, but for fixed gain operation thresholds_offset_hard_mg_fixed = [3500, 6500] # Same as thresholds_offset_hard_mg, but for fixed gain operation
thresholds_offset_hard_lg_fixed = [3500, 6500] # Same as thresholds_offset_hard_lg, but for fixed gain operation thresholds_offset_hard_lg_fixed = [3500, 6500] # Same as thresholds_offset_hard_lg, but for fixed gain operation
thresholds_noise_sigma = 5. # noise sigma thresholds for offset deduced bad pixels thresholds_noise_sigma = 5. # noise sigma thresholds for offset deduced bad pixels
thresholds_noise_hard = [0, 0] # For setting the same threshold noise for the 3 gains. Left for backcompatability. Default [0, 0] to take the following parameters. thresholds_noise_hard = [0, 0] # For setting the same threshold noise for the 3 gains. Left for backcompatability. Default [0, 0] to take the following parameters.
thresholds_noise_hard_hg = [4, 20] # High-gain thresholds in absolute ADU terms for offset deduced bad pixels thresholds_noise_hard_hg = [4, 20] # High-gain thresholds in absolute ADU terms for offset deduced bad pixels
thresholds_noise_hard_mg = [4, 20] # Medium-gain thresholds in absolute ADU terms for offset deduced bad pixels thresholds_noise_hard_mg = [4, 20] # Medium-gain thresholds in absolute ADU terms for offset deduced bad pixels
thresholds_noise_hard_lg = [4, 20] # Low-gain thresholds in absolute ADU terms for offset deduced bad pixels thresholds_noise_hard_lg = [4, 20] # Low-gain thresholds in absolute ADU terms for offset deduced bad pixels
thresholds_gain_sigma = 5. # Gain separation sigma threshold thresholds_gain_sigma = 5. # Gain separation sigma threshold
max_trains = 550 # Maximum number of trains to use for processing dark. Set to 0 to process all available trains. 550 added for ~500GB nodes to temporarely avoid memory issues. max_trains = 550 # Maximum number of trains to use for processing dark. Set to 0 to process all available trains. 550 added for ~500GB nodes to temporarely avoid memory issues.
min_trains = 1 # Miniumum number of trains for processing dark. If run folder has less than minimum trains, processing is stopped. min_trains = 1 # Miniumum number of trains for processing dark. If run folder has less than minimum trains, processing is stopped.
high_res_badpix_3d = False # set this to True if you need high-resolution 3d bad pixel plots. ~7mins extra time for 64 memory cells high_res_badpix_3d = False # set this to True if you need high-resolution 3d bad pixel plots. ~7mins extra time for 64 memory cells
# This is used if modules is not specified: # This is used if modules is not specified:
def find_modules(in_folder, run_high, modules): def find_modules(in_folder, run_high, modules):
if (modules is not None) and modules != [-1]: if (modules is not None) and modules != [-1]:
return modules return modules
from pathlib import Path from pathlib import Path
import re import re
modules = set() modules = set()
for file in Path(in_folder, f'r{run_high:04d}').iterdir(): for file in Path(in_folder, f'r{run_high:04d}').iterdir():
m = re.search(r'-AGIPD(\d{2})-', file.name) m = re.search(r'-AGIPD(\d{2})-', file.name)
if m: if m:
modules.add(int(m.group(1))) modules.add(int(m.group(1)))
return sorted(modules) return sorted(modules)
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
import itertools import itertools
import multiprocessing import multiprocessing
import os import os
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
from pathlib import Path from pathlib import Path
from typing import List, Tuple from typing import List, Tuple
import dateutil.parser import dateutil.parser
import matplotlib import matplotlib
import numpy as np import numpy as np
import pasha as psh import pasha as psh
import tabulate import tabulate
import yaml import yaml
from IPython.display import Latex, Markdown, display from IPython.display import Latex, Markdown, display
from extra_data import RunDirectory from extra_data import RunDirectory
matplotlib.use('agg') matplotlib.use('agg')
import iCalibrationDB import iCalibrationDB
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
from cal_tools.agipdlib import AgipdCtrl from cal_tools.agipdlib import AgipdCtrl
from cal_tools.enums import AgipdGainMode, BadPixels from cal_tools.enums import AgipdGainMode, BadPixels
from cal_tools.plotting import ( from cal_tools.plotting import (
create_constant_overview, create_constant_overview,
plot_badpix_3d, plot_badpix_3d,
show_overview, show_overview,
show_processed_modules, show_processed_modules,
) )
from cal_tools.tools import ( from cal_tools.tools import (
get_dir_creation_date, get_dir_creation_date,
get_from_db, get_from_db,
get_pdu_from_db, get_pdu_from_db,
get_random_db_interface, get_random_db_interface,
get_report, get_report,
map_gain_stages, map_gain_stages,
module_index_to_qm, module_index_to_qm,
run_prop_seq_from_path, run_prop_seq_from_path,
save_const_to_h5, save_const_to_h5,
send_to_db, send_to_db,
) )
%matplotlib inline %matplotlib inline
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# insert control device if format string (does nothing otherwise) # insert control device if format string (does nothing otherwise)
ctrl_src = ctrl_source_template.format(karabo_id_control) ctrl_src = ctrl_source_template.format(karabo_id_control)
runs_dict = OrderedDict() runs_dict = OrderedDict()
run_numbers = [run_high, run_med, run_low]
for gain_idx, (run_name, run_number) in enumerate(zip( for gain_idx, (run_name, run_number) in enumerate(zip(["high", "med", "low"], run_numbers)):
["high", "med", "low"],
[run_high, run_med, run_low]
)):
runs_dict[run_name] = { runs_dict[run_name] = {
"number": run_number, "number": run_number,
"gain": gain_idx, "gain": gain_idx,
"dc": RunDirectory(f'{in_folder}/r{run_number:04d}/') "dc": RunDirectory(f'{in_folder}/r{run_number:04d}/')
} }
creation_time=None creation_time=None
if use_dir_creation_date: if use_dir_creation_date:
creation_time = get_dir_creation_date(in_folder, run_high) creation_time = get_dir_creation_date(in_folder, run_high)
print(f"Using {creation_time} as creation time of constant.") print(f"Using {creation_time} as creation time of constant.")
run, prop, seq = run_prop_seq_from_path(in_folder) run, prop, seq = run_prop_seq_from_path(in_folder)
# Read report path and create file location tuple to add with the injection # Read report path and create file location tuple to add with the injection
file_loc = f"proposal:{prop} runs:{run_low} {run_med} {run_high}" file_loc = f"proposal:{prop} runs:{run_low} {run_med} {run_high}"
report = get_report(out_folder) report = get_report(out_folder)
cal_db_interface = get_random_db_interface(cal_db_interface) cal_db_interface = get_random_db_interface(cal_db_interface)
print(f'Calibration database interface: {cal_db_interface}') print(f'Calibration database interface: {cal_db_interface}')
instrument = karabo_id.split("_")[0] instrument = karabo_id.split("_")[0]
if instrument == "SPB": if instrument == "SPB":
dinstance = "AGIPD1M1" dinstance = "AGIPD1M1"
nmods = 16 nmods = 16
elif instrument == "MID": elif instrument == "MID":
dinstance = "AGIPD1M2" dinstance = "AGIPD1M2"
nmods = 16 nmods = 16
elif instrument == "HED": elif instrument == "HED":
dinstance = "AGIPD500K" dinstance = "AGIPD500K"
nmods = 8 nmods = 8
instrument_src = instrument_source_template.format(karabo_id, receiver_template) instrument_src = instrument_source_template.format(karabo_id, receiver_template)
run_numbers = [run_high, run_med, run_low]
def create_karabo_da_list(modules): def create_karabo_da_list(modules):
return(["AGIPD{:02d}".format(i) for i in modules]) return(["AGIPD{:02d}".format(i) for i in modules])
if karabo_da[0] == '-1': if karabo_da[0] == '-1':
if modules[0] == -1: if modules[0] == -1:
modules = list(range(nmods)) modules = list(range(nmods))
karabo_da = create_karabo_da_list(modules) karabo_da = create_karabo_da_list(modules)
else: else:
modules = [int(x[-2:]) for x in karabo_da] modules = [int(x[-2:]) for x in karabo_da]
print(f"Detector in use is {karabo_id}") print(f"Detector in use is {karabo_id}")
print(f"Instrument {instrument}") print(f"Instrument {instrument}")
print(f"Detector instance {dinstance}") print(f"Detector instance {dinstance}")
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Create out_folder if it doesn't exist. # Create out_folder if it doesn't exist.
Path(out_folder).mkdir(parents=True, exist_ok=True) Path(out_folder).mkdir(parents=True, exist_ok=True)
file_sizes = [] file_sizes = []
for run_dict in runs_dict.values(): for run_dict in runs_dict.values():
missing_modules = [] # modules with no images within a run. missing_modules = [] # modules with no images within a run.
n_trains_list = [] # list of the number of trains for each module within a run. n_trains_list = [] # list of the number of trains for each module within a run.
# This is important in case of no slurm parallelization over modules is done. # This is important in case of no slurm parallelization over modules is done.
# (e.g. running notebook interactively) # (e.g. running notebook interactively)
for m in modules: for m in modules:
# validate that there are trains for the selected modules and run. # validate that there are trains for the selected modules and run.
dc = run_dict["dc"].select( dc = run_dict["dc"].select(
instrument_src.format(m), "*", require_all=True) instrument_src.format(m), "*", require_all=True)
n_trains = len(dc.train_ids) n_trains = len(dc.train_ids)
if n_trains == 0: if n_trains == 0:
print(f"WARNING: No images for module AGIPD{m:02d}, run {run_dict['number']}.") print(f"WARNING: No images for module AGIPD{m:02d}, run {run_dict['number']}.")
missing_modules.append(m) missing_modules.append(m)
# Raise a warning if the module has less trains than expected. # Raise a warning if the module has less trains than expected.
elif n_trains < min_trains: elif n_trains < min_trains:
print(f"WARNING: AGIPD{m:02d}, run {run_dict['number']} " print(f"WARNING: AGIPD{m:02d}, run {run_dict['number']} "
f"has trains less than minimum trains: {min_trains}.") f"has trains less than minimum trains: {min_trains}.")
else: else:
print(f"Processing {max_trains if max_trains < n_trains else n_trains} " print(f"Processing {max_trains if max_trains < n_trains else n_trains} "
f"for AGIPD{m:02d}, run {run_dict['number']} ") f"for AGIPD{m:02d}, run {run_dict['number']} ")
n_trains_list.append(n_trains) n_trains_list.append(n_trains)
file_sizes += [os.path.getsize(f.filename) / 1e9 for f in dc.files] file_sizes += [os.path.getsize(f.filename) / 1e9 for f in dc.files]
if max(n_trains_list) == 0: if max(n_trains_list) == 0:
raise ValueError(f"No images to process for run: {run_dict['number']}") raise ValueError(f"No images to process for run: {run_dict['number']}")
elif max(n_trains_list) < min_trains: elif max(n_trains_list) < min_trains:
raise ValueError(f"{run_dict['number']} has less than minimum trains: {min_trains}") raise ValueError(f"{run_dict['number']} has less than minimum trains: {min_trains}")
# Update modules and karabo_da lists based on available modules to processes. # Update modules and karabo_da lists based on available modules to processes.
modules = [m for m in modules if m not in missing_modules] modules = [m for m in modules if m not in missing_modules]
karabo_da = create_karabo_da_list(modules) karabo_da = create_karabo_da_list(modules)
print(f"Will process data in a total of {len(file_sizes)} files ({sum(file_sizes):.02f} GB).") print(f"Will process data in a total of {len(file_sizes)} files ({sum(file_sizes):.02f} GB).")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Read and validate the runs control data. ## Read and validate the runs control data.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
def read_run_conditions(runs_dict: dict): def read_run_conditions(runs_dict: dict):
agipd_cond = AgipdCtrl( agipd_cond = AgipdCtrl(
run_dc=runs_dict["dc"], run_dc=runs_dict["dc"],
image_src=instrument_src_mod, image_src=instrument_src_mod,
ctrl_src=ctrl_src, ctrl_src=ctrl_src,
) )
cond_dict["runs"].append(runs_dict["number"]) cond_dict["runs"].append(runs_dict["number"])
if acq_rate == 0: if acq_rate == 0:
cond_dict["acq_rate"].append(agipd_cond.get_acq_rate()) cond_dict["acq_rate"].append(agipd_cond.get_acq_rate())
if mem_cells == 0: if mem_cells == 0:
cond_dict["mem_cells"].append(agipd_cond.get_num_cells()) cond_dict["mem_cells"].append(agipd_cond.get_num_cells())
if gain_setting == -1: if gain_setting == -1:
cond_dict["gain_setting"].append( cond_dict["gain_setting"].append(
agipd_cond.get_gain_setting(creation_time)) agipd_cond.get_gain_setting(creation_time))
if bias_voltage == 0.: if bias_voltage == 0.:
cond_dict["bias_voltage"].append( cond_dict["bias_voltage"].append(
agipd_cond.get_bias_voltage(karabo_id_control)) agipd_cond.get_bias_voltage(karabo_id_control))
if integration_time == -1: if integration_time == -1:
cond_dict["integration_time"].append( cond_dict["integration_time"].append(
agipd_cond.get_integration_time()) agipd_cond.get_integration_time())
if gain_mode == -1: if gain_mode == -1:
cond_dict["gain_mode"].append(agipd_cond.get_gain_mode()) cond_dict["gain_mode"].append(agipd_cond.get_gain_mode())
else: else:
cond_dict["gain_mode"].append(AgipdGainMode(gain_mode)) cond_dict["gain_mode"].append(AgipdGainMode(gain_mode))
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
def validate_gain_modes(gain_modes: List[AgipdGainMode]): def validate_gain_modes(gain_modes: List[AgipdGainMode]):
# Validate that gain modes are not a mix of adaptive and fixed gain. # Validate that gain modes are not a mix of adaptive and fixed gain.
if all( if all(
gm == AgipdGainMode.ADAPTIVE_GAIN for gm in gain_modes gm == AgipdGainMode.ADAPTIVE_GAIN for gm in gain_modes
): ):
fixed_gain_mode = False fixed_gain_mode = False
# Some runs are adaptive by mistake.
elif any( elif any(
gm == AgipdGainMode.ADAPTIVE_GAIN for gm in gain_modes gm == AgipdGainMode.ADAPTIVE_GAIN for gm in gain_modes
): ):
raise ValueError( raise ValueError(
f"ERROR: Given runs {self.read_conditions['run_number']}" f"ERROR: Given runs {run_numbers}"
" have a mix of ADAPTIVE and FIXED gain modes: " " have a mix of ADAPTIVE and FIXED gain modes: "
f"{self.read_conditions['gain_mode']}." f"{gain_modes}."
) )
else: elif list(gain_modes) == [
AgipdGainMode.FIXED_HIGH_GAIN,
AgipdGainMode.FIXED_MEDIUM_GAIN,
AgipdGainMode.FIXED_LOW_GAIN
]:
fixed_gain_mode = True fixed_gain_mode = True
else:
raise ValueError(
"ERROR: Wrong arrangment of given dark runs. "
f"Given runs' gain_modes are {gain_modes} for runs: {run_numbers}."
)
return fixed_gain_mode return fixed_gain_mode
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Read slow data from 1st channel only. # Read slow data from 1st channel only.
# Read all modules in one notebook and validate the conditions across detectors? # Read all modules in one notebook and validate the conditions across detectors?
# Currently slurm jobs run per one module. # Currently slurm jobs run per one module.
# TODO: what if first module is not available. Maybe only channel 2 available # TODO: what if first module is not available. Maybe only channel 2 available
instrument_src_mod = instrument_src.format(modules[0]) instrument_src_mod = instrument_src.format(modules[0])
cond_dict = dict() cond_dict = dict()
fixed_gain_mode = None fixed_gain_mode = None
with multiprocessing.Manager() as manager: with multiprocessing.Manager() as manager:
cond_dict["runs"] = manager.list() cond_dict["runs"] = manager.list()
cond_dict["acq_rate"] = manager.list() cond_dict["acq_rate"] = manager.list()
cond_dict["mem_cells"] = manager.list() cond_dict["mem_cells"] = manager.list()
cond_dict["gain_setting"] = manager.list() cond_dict["gain_setting"] = manager.list()
cond_dict["gain_mode"] = manager.list() cond_dict["gain_mode"] = manager.list()
cond_dict["bias_voltage"] = manager.list() cond_dict["bias_voltage"] = manager.list()
cond_dict["integration_time"] = manager.list() cond_dict["integration_time"] = manager.list()
with multiprocessing.Pool(processes=len(modules)) as pool: with multiprocessing.Pool(processes=len(modules)) as pool:
pool.starmap(read_run_conditions, zip(runs_dict.values())) pool.starmap(read_run_conditions, zip(runs_dict.values()))
for cond, vlist in cond_dict.items(): for cond, vlist in cond_dict.items():
if cond == "runs": if cond == "runs":
continue continue
elif cond == "gain_mode": elif cond == "gain_mode":
fixed_gain_mode = validate_gain_modes(cond_dict["gain_mode"]) fixed_gain_mode = validate_gain_modes(cond_dict["gain_mode"])
if not all(x == vlist[0] for x in vlist): elif not all(x == vlist[0] for x in vlist):
# TODO: raise ERROR?? # TODO: raise ERROR??
print( print(
f"WARNING: {cond} is not the same for the runs " f"WARNING: {cond} is not the same for the runs "
f"{cond_dict['runs']} with values" f"{cond_dict['runs']} with values"
f" of {cond_dict[cond]}, respectively." f" of {cond_dict[cond]}, respectively."
) )
if cond_dict["acq_rate"]: acq_rate = cond_dict["acq_rate"][0] if cond_dict["acq_rate"]: acq_rate = cond_dict["acq_rate"][0]
if cond_dict["mem_cells"]: mem_cells = cond_dict["mem_cells"][0] if cond_dict["mem_cells"]: mem_cells = cond_dict["mem_cells"][0]
if cond_dict["gain_setting"]: gain_setting = cond_dict["gain_setting"][0] if cond_dict["gain_setting"]: gain_setting = cond_dict["gain_setting"][0]
if cond_dict["gain_mode"]: gain_mode = list(cond_dict["gain_mode"]) if cond_dict["gain_mode"]: gain_mode = list(cond_dict["gain_mode"])
if cond_dict["bias_voltage"]: bias_voltage = cond_dict["bias_voltage"][0] if cond_dict["bias_voltage"]: bias_voltage = cond_dict["bias_voltage"][0]
if cond_dict["integration_time"]: integration_time = cond_dict["integration_time"][0] if cond_dict["integration_time"]: integration_time = cond_dict["integration_time"][0]
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Determine the gain operation mode based on the gain_mode stored in control h5file. # Determine the gain operation mode based on the gain_mode stored in control h5file.
if operation_mode not in ("ADAPTIVE_GAIN", "FIXED_GAIN"): if operation_mode not in ("ADAPTIVE_GAIN", "FIXED_GAIN"):
print(f"WARNING: unknown operation_mode \"{operation_mode}\" parameter set") print(f"WARNING: unknown operation_mode \"{operation_mode}\" parameter set")
if ( if fixed_gain_mode and operation_mode == "ADAPTIVE_GAIN":
gain_mode == [
AgipdGainMode.FIXED_HIGH_GAIN,
AgipdGainMode.FIXED_MEDIUM_GAIN,
AgipdGainMode.FIXED_LOW_GAIN
] and
operation_mode == "ADAPTIVE_GAIN"
):
print( print(
"WARNING: operation_mode parameter is ADAPTIVE_GAIN, " "WARNING: Operation_mode parameter is ADAPTIVE_GAIN, but"
"slow data indicates FIXED_GAIN.") "slow data indicates FIXED_GAIN. Processing fixed gain constants.")
elif not fixed_gain_mode and operation_mode == "FIXED_GAIN": elif not fixed_gain_mode and operation_mode == "FIXED_GAIN":
print( print(
"WARNING: operation_mode parameter is FIXED_GAIN, " "WARNING: Operation_mode parameter is FIXED_GAIN, "
"slow data indicates ADAPTIVE_GAIN") "slow data indicates ADAPTIVE_GAIN. Processing adaptive gain constants.")
elif not all(gm == AgipdGainMode.ADAPTIVE_GAIN for gm in gain_mode):
raise ValueError(
"ERROR: Wrong arrangment of given dark runs. "
f"Given runs' gain_modes are {gain_mode} for runs: {runs}."
)
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
print("Parameters are:") print("Parameters are:")
print(f"Proposal: {prop}") print(f"Proposal: {prop}")
print(f"Acquisition rate: {acq_rate}") print(f"Acquisition rate: {acq_rate}")
print(f"Memory cells: {mem_cells}") print(f"Memory cells: {mem_cells}")
print(f"Runs: {run_numbers}") print(f"Runs: {run_numbers}")
print(f"Interlaced mode: {interlaced}") print(f"Interlaced mode: {interlaced}")
print(f"Using DB: {db_output}") print(f"Using DB: {db_output}")
print(f"Input: {in_folder}") print(f"Input: {in_folder}")
print(f"Output: {out_folder}") print(f"Output: {out_folder}")
print(f"Bias voltage: {bias_voltage}V") print(f"Bias voltage: {bias_voltage}V")
print(f"Gain setting: {gain_setting}") print(f"Gain setting: {gain_setting}")
print(f"Integration time: {integration_time}") print(f"Integration time: {integration_time}")
print(f"Operation mode is {'fixed' if fixed_gain_mode else 'adaptive'} gain mode") print(f"Operation mode is {'fixed' if fixed_gain_mode else 'adaptive'} gain mode")
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
if thresholds_offset_hard != [0, 0]: if thresholds_offset_hard != [0, 0]:
# if set, this will override the individual parameters # if set, this will override the individual parameters
thresholds_offset_hard = [thresholds_offset_hard] * 3 thresholds_offset_hard = [thresholds_offset_hard] * 3
elif fixed_gain_mode: elif fixed_gain_mode:
thresholds_offset_hard = [ thresholds_offset_hard = [
thresholds_offset_hard_hg_fixed, thresholds_offset_hard_hg_fixed,
thresholds_offset_hard_mg_fixed, thresholds_offset_hard_mg_fixed,
thresholds_offset_hard_lg_fixed, thresholds_offset_hard_lg_fixed,
] ]
else: else:
thresholds_offset_hard = [ thresholds_offset_hard = [
thresholds_offset_hard_hg, thresholds_offset_hard_hg,
thresholds_offset_hard_mg, thresholds_offset_hard_mg,
thresholds_offset_hard_lg, thresholds_offset_hard_lg,
] ]
print("Will use the following hard offset thresholds") print("Will use the following hard offset thresholds")
for name, value in zip(("High", "Medium", "Low"), thresholds_offset_hard): for name, value in zip(("High", "Medium", "Low"), thresholds_offset_hard):
print(f"- {name} gain: {value}") print(f"- {name} gain: {value}")
if thresholds_noise_hard != [0, 0]: if thresholds_noise_hard != [0, 0]:
thresholds_noise_hard = [thresholds_noise_hard] * 3 thresholds_noise_hard = [thresholds_noise_hard] * 3
else: else:
thresholds_noise_hard = [ thresholds_noise_hard = [
thresholds_noise_hard_hg, thresholds_noise_hard_hg,
thresholds_noise_hard_mg, thresholds_noise_hard_mg,
thresholds_noise_hard_lg, thresholds_noise_hard_lg,
] ]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Calculate Offsets, Noise and Thresholds ## ## Calculate Offsets, Noise and Thresholds ##
The calculation is performed per-pixel and per-memory-cell. Offsets are simply the median value for a set of dark data taken at a given gain, noise the standard deviation, and gain-bit values the medians of the gain array. The calculation is performed per-pixel and per-memory-cell. Offsets are simply the median value for a set of dark data taken at a given gain, noise the standard deviation, and gain-bit values the medians of the gain array.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
parallel_num_procs = min(6, len(modules)*3) parallel_num_procs = min(6, len(modules)*3)
parallel_num_threads = multiprocessing.cpu_count() // parallel_num_procs parallel_num_threads = multiprocessing.cpu_count() // parallel_num_procs
print(f"Will use {parallel_num_procs} processes with {parallel_num_threads} threads each") print(f"Will use {parallel_num_procs} processes with {parallel_num_threads} threads each")
def characterize_module( def characterize_module(
channel: int, runs_dict: dict, channel: int, runs_dict: dict,
) -> Tuple[int, int, np.array, np.array, np.array, np.array, np.array]: ) -> Tuple[int, int, np.array, np.array, np.array, np.array, np.array]:
# Select the corresponding module channel. # Select the corresponding module channel.
instrument_src_mod = instrument_src.format(channel) instrument_src_mod = instrument_src.format(channel)
run_dc = runs_dict["dc"].select(instrument_src_mod, require_all=True) run_dc = runs_dict["dc"].select(instrument_src_mod, require_all=True)
if max_trains != 0: if max_trains != 0:
run_dc = run_dc.select_trains(np.s_[:max_trains]) run_dc = run_dc.select_trains(np.s_[:max_trains])
gain_index = runs_dict["gain"] gain_index = runs_dict["gain"]
# Read module's image and cellId data. # Read module's image and cellId data.
im = run_dc[instrument_src_mod, "image.data"].ndarray() im = run_dc[instrument_src_mod, "image.data"].ndarray()
cell_ids = np.squeeze(run_dc[instrument_src_mod, "image.cellId"].ndarray()) cell_ids = np.squeeze(run_dc[instrument_src_mod, "image.cellId"].ndarray())
local_thresholds_offset_hard = thresholds_offset_hard[gain_index] local_thresholds_offset_hard = thresholds_offset_hard[gain_index]
local_thresholds_noise_hard = thresholds_noise_hard[gain_index] local_thresholds_noise_hard = thresholds_noise_hard[gain_index]
if interlaced: if interlaced:
if not fixed_gain_mode: if not fixed_gain_mode:
ga = im[1::2, 0, ...] ga = im[1::2, 0, ...]
im = im[0::2, 0, ...].astype(np.float32) im = im[0::2, 0, ...].astype(np.float32)
cell_ids = cell_ids[::2] cell_ids = cell_ids[::2]
else: else:
if not fixed_gain_mode: if not fixed_gain_mode:
ga = im[:, 1, ...] ga = im[:, 1, ...]
im = im[:, 0, ...].astype(np.float32) im = im[:, 0, ...].astype(np.float32)
im = np.transpose(im) im = np.transpose(im)
if not fixed_gain_mode: if not fixed_gain_mode:
ga = np.transpose(ga) ga = np.transpose(ga)
context = psh.context.ThreadContext(num_workers=parallel_num_threads) context = psh.context.ThreadContext(num_workers=parallel_num_threads)
offset = context.alloc(shape=(im.shape[0], im.shape[1], mem_cells), dtype=np.float64) offset = context.alloc(shape=(im.shape[0], im.shape[1], mem_cells), dtype=np.float64)
noise = context.alloc(like=offset) noise = context.alloc(like=offset)
if fixed_gain_mode: if fixed_gain_mode:
gains = None gains = None
gains_std = None gains_std = None
else: else:
gains = context.alloc(like=offset) gains = context.alloc(like=offset)
gains_std = context.alloc(like=offset) gains_std = context.alloc(like=offset)
def process_cell(worker_id, array_index, cell_number): def process_cell(worker_id, array_index, cell_number):
cell_slice_index = (cell_ids == cell_number) cell_slice_index = (cell_ids == cell_number)
im_slice = im[..., cell_slice_index] im_slice = im[..., cell_slice_index]
offset[..., cell_number] = np.median(im_slice, axis=2) offset[..., cell_number] = np.median(im_slice, axis=2)
noise[..., cell_number] = np.std(im_slice, axis=2) noise[..., cell_number] = np.std(im_slice, axis=2)
if not fixed_gain_mode: if not fixed_gain_mode:
ga_slice = ga[..., cell_slice_index] ga_slice = ga[..., cell_slice_index]
gains[..., cell_number] = np.median(ga_slice, axis=2) gains[..., cell_number] = np.median(ga_slice, axis=2)
gains_std[..., cell_number] = np.std(ga_slice, axis=2) gains_std[..., cell_number] = np.std(ga_slice, axis=2)
context.map(process_cell, np.unique(cell_ids)) context.map(process_cell, np.unique(cell_ids))
# bad pixels # bad pixels
bp = np.zeros_like(offset, dtype=np.uint32) bp = np.zeros_like(offset, dtype=np.uint32)
# offset related bad pixels # offset related bad pixels
offset_mn = np.nanmedian(offset, axis=(0,1)) offset_mn = np.nanmedian(offset, axis=(0,1))
offset_std = np.nanstd(offset, axis=(0,1)) offset_std = np.nanstd(offset, axis=(0,1))
bp[(offset < offset_mn-thresholds_offset_sigma*offset_std) | bp[(offset < offset_mn-thresholds_offset_sigma*offset_std) |
(offset > offset_mn+thresholds_offset_sigma*offset_std)] |= BadPixels.OFFSET_OUT_OF_THRESHOLD (offset > offset_mn+thresholds_offset_sigma*offset_std)] |= BadPixels.OFFSET_OUT_OF_THRESHOLD
bp[(offset < local_thresholds_offset_hard[0]) | bp[(offset < local_thresholds_offset_hard[0]) |
(offset > local_thresholds_offset_hard[1])] |= BadPixels.OFFSET_OUT_OF_THRESHOLD (offset > local_thresholds_offset_hard[1])] |= BadPixels.OFFSET_OUT_OF_THRESHOLD
bp[~np.isfinite(offset)] |= BadPixels.OFFSET_NOISE_EVAL_ERROR bp[~np.isfinite(offset)] |= BadPixels.OFFSET_NOISE_EVAL_ERROR
# noise related bad pixels # noise related bad pixels
noise_mn = np.nanmedian(noise, axis=(0,1)) noise_mn = np.nanmedian(noise, axis=(0,1))
noise_std = np.nanstd(noise, axis=(0,1)) noise_std = np.nanstd(noise, axis=(0,1))
bp[(noise < noise_mn-thresholds_noise_sigma*noise_std) | bp[(noise < noise_mn-thresholds_noise_sigma*noise_std) |
(noise > noise_mn+thresholds_noise_sigma*noise_std)] |= BadPixels.NOISE_OUT_OF_THRESHOLD (noise > noise_mn+thresholds_noise_sigma*noise_std)] |= BadPixels.NOISE_OUT_OF_THRESHOLD
bp[(noise < local_thresholds_noise_hard[0]) | (noise > local_thresholds_noise_hard[1])] |= BadPixels.NOISE_OUT_OF_THRESHOLD bp[(noise < local_thresholds_noise_hard[0]) | (noise > local_thresholds_noise_hard[1])] |= BadPixels.NOISE_OUT_OF_THRESHOLD
bp[~np.isfinite(noise)] |= BadPixels.OFFSET_NOISE_EVAL_ERROR bp[~np.isfinite(noise)] |= BadPixels.OFFSET_NOISE_EVAL_ERROR
return channel, gain_index, offset, noise, gains, gains_std, bp return channel, gain_index, offset, noise, gains, gains_std, bp
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
with multiprocessing.Pool(processes=parallel_num_procs) as pool: with multiprocessing.Pool(processes=parallel_num_procs) as pool:
results = pool.starmap( results = pool.starmap(
characterize_module, itertools.product(modules, list(runs_dict.values()))) characterize_module, itertools.product(modules, list(runs_dict.values())))
# mapped values for processing 2 modules example: # mapped values for processing 2 modules example:
# [ # [
# 0, {"gain": 0, "run_number": <run-high>, "dc": <high-dc>}, # 0, {"gain": 0, "run_number": <run-high>, "dc": <high-dc>},
# 0, {"gain": 1, "run_number": <run-med>, "dc": <med-dc>}, # 0, {"gain": 1, "run_number": <run-med>, "dc": <med-dc>},
# 0, {"gain": 2, "run_number": <run-low>, "dc": <low-dc>}, # 0, {"gain": 2, "run_number": <run-low>, "dc": <low-dc>},
# 1, {"gain": 0, "run_number": <run-high>, "dc": <high-dc>}, # 1, {"gain": 0, "run_number": <run-high>, "dc": <high-dc>},
# 1, {"gain": 1, "run_number": <run-med>, "dc": <med-dc>}, # 1, {"gain": 1, "run_number": <run-med>, "dc": <med-dc>},
# 1, {"gain": 2, "run_number": <run-low>, "dc": <low-dc>}, # 1, {"gain": 2, "run_number": <run-low>, "dc": <low-dc>},
# ] # ]
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
offset_g = OrderedDict() offset_g = OrderedDict()
noise_g = OrderedDict() noise_g = OrderedDict()
badpix_g = OrderedDict() badpix_g = OrderedDict()
if not fixed_gain_mode: if not fixed_gain_mode:
gain_g = OrderedDict() gain_g = OrderedDict()
gainstd_g = OrderedDict() gainstd_g = OrderedDict()
for module_index, gain_index, offset, noise, gains, gains_std, bp in results: for module_index, gain_index, offset, noise, gains, gains_std, bp in results:
qm = module_index_to_qm(module_index) qm = module_index_to_qm(module_index)
if qm not in offset_g: if qm not in offset_g:
offset_g[qm] = np.zeros((offset.shape[0], offset.shape[1], offset.shape[2], 3)) offset_g[qm] = np.zeros((offset.shape[0], offset.shape[1], offset.shape[2], 3))
noise_g[qm] = np.zeros_like(offset_g[qm]) noise_g[qm] = np.zeros_like(offset_g[qm])
badpix_g[qm] = np.zeros_like(offset_g[qm], np.uint32) badpix_g[qm] = np.zeros_like(offset_g[qm], np.uint32)
if not fixed_gain_mode: if not fixed_gain_mode:
gain_g[qm] = np.zeros_like(offset_g[qm]) gain_g[qm] = np.zeros_like(offset_g[qm])
gainstd_g[qm] = np.zeros_like(offset_g[qm]) gainstd_g[qm] = np.zeros_like(offset_g[qm])
offset_g[qm][..., gain_index] = offset offset_g[qm][..., gain_index] = offset
noise_g[qm][..., gain_index] = noise noise_g[qm][..., gain_index] = noise
badpix_g[qm][..., gain_index] = bp badpix_g[qm][..., gain_index] = bp
if not fixed_gain_mode: if not fixed_gain_mode:
gain_g[qm][..., gain_index] = gains gain_g[qm][..., gain_index] = gains
gainstd_g[qm][..., gain_index] = gains_std gainstd_g[qm][..., gain_index] = gains_std
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Add bad pixels due to bad gain separation # Add bad pixels due to bad gain separation
if not fixed_gain_mode: if not fixed_gain_mode:
for qm in gain_g.keys(): for qm in gain_g.keys():
for g in range(2): for g in range(2):
# Bad pixels during bad gain separation. # Bad pixels during bad gain separation.
# Fraction of pixels in the module with separation lower than "thresholds_gain_sigma". # Fraction of pixels in the module with separation lower than "thresholds_gain_sigma".
bad_sep = (gain_g[qm][..., g+1] - gain_g[qm][..., g]) / \ bad_sep = (gain_g[qm][..., g+1] - gain_g[qm][..., g]) / \
np.sqrt(gainstd_g[qm][..., g+1]**2 + gainstd_g[qm][..., g]**2) np.sqrt(gainstd_g[qm][..., g+1]**2 + gainstd_g[qm][..., g]**2)
badpix_g[qm][...,g+1][bad_sep<thresholds_gain_sigma] |= \ badpix_g[qm][...,g+1][bad_sep<thresholds_gain_sigma] |= \
BadPixels.GAIN_THRESHOLDING_ERROR BadPixels.GAIN_THRESHOLDING_ERROR
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The thresholds for gain switching are then defined as the mean value between in individual gain bit levels. Note that these thresholds need to be refined with charge induced thresholds, as the two are not the same. The thresholds for gain switching are then defined as the mean value between in individual gain bit levels. Note that these thresholds need to be refined with charge induced thresholds, as the two are not the same.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
if not fixed_gain_mode: if not fixed_gain_mode:
thresholds_g = {} thresholds_g = {}
for qm in gain_g.keys(): for qm in gain_g.keys():
thresholds_g[qm] = np.zeros((gain_g[qm].shape[0], gain_g[qm].shape[1], gain_g[qm].shape[2], 5)) thresholds_g[qm] = np.zeros((gain_g[qm].shape[0], gain_g[qm].shape[1], gain_g[qm].shape[2], 5))
thresholds_g[qm][...,0] = (gain_g[qm][...,1]+gain_g[qm][...,0])/2 thresholds_g[qm][...,0] = (gain_g[qm][...,1]+gain_g[qm][...,0])/2
thresholds_g[qm][...,1] = (gain_g[qm][...,2]+gain_g[qm][...,1])/2 thresholds_g[qm][...,1] = (gain_g[qm][...,2]+gain_g[qm][...,1])/2
for i in range(3): for i in range(3):
thresholds_g[qm][...,2+i] = gain_g[qm][...,i] thresholds_g[qm][...,2+i] = gain_g[qm][...,i]
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
res = OrderedDict() res = OrderedDict()
for i in modules: for i in modules:
qm = module_index_to_qm(i) qm = module_index_to_qm(i)
res[qm] = { res[qm] = {
'Offset': offset_g[qm], 'Offset': offset_g[qm],
'Noise': noise_g[qm], 'Noise': noise_g[qm],
'BadPixelsDark': badpix_g[qm] 'BadPixelsDark': badpix_g[qm]
} }
if not fixed_gain_mode: if not fixed_gain_mode:
res[qm]['ThresholdsDark'] = thresholds_g[qm] res[qm]['ThresholdsDark'] = thresholds_g[qm]
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# set the operating condition # set the operating condition
# note: iCalibrationDB only adds gain_mode if it is truthy, so we don't need to handle None # note: iCalibrationDB only adds gain_mode if it is truthy, so we don't need to handle None
condition = iCalibrationDB.Conditions.Dark.AGIPD( condition = iCalibrationDB.Conditions.Dark.AGIPD(
memory_cells=mem_cells, memory_cells=mem_cells,
bias_voltage=bias_voltage, bias_voltage=bias_voltage,
acquisition_rate=acq_rate, acquisition_rate=acq_rate,
gain_setting=gain_setting, gain_setting=gain_setting,
gain_mode=fixed_gain_mode, gain_mode=fixed_gain_mode,
integration_time=integration_time integration_time=integration_time
) )
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Create mapping from module(s) (qm) to karabo_da(s) and PDU(s) # Create mapping from module(s) (qm) to karabo_da(s) and PDU(s)
qm_dict = OrderedDict() qm_dict = OrderedDict()
all_pdus = get_pdu_from_db( all_pdus = get_pdu_from_db(
karabo_id, karabo_id,
karabo_da, karabo_da,
constant=iCalibrationDB.CalibrationConstant(), constant=iCalibrationDB.CalibrationConstant(),
condition=condition, condition=condition,
cal_db_interface=cal_db_interface, cal_db_interface=cal_db_interface,
snapshot_at=creation_time.isoformat() if creation_time else None, snapshot_at=creation_time.isoformat() if creation_time else None,
timeout=cal_db_timeout timeout=cal_db_timeout
) )
for module_index, module_da, module_pdu in zip(modules, karabo_da, all_pdus): for module_index, module_da, module_pdu in zip(modules, karabo_da, all_pdus):
qm = module_index_to_qm(module_index) qm = module_index_to_qm(module_index)
qm_dict[qm] = { qm_dict[qm] = {
"karabo_da": module_da, "karabo_da": module_da,
"db_module": module_pdu "db_module": module_pdu
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Sending calibration constants to the database. ## Sending calibration constants to the database.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
md = None md = None
for qm in res: for qm in res:
db_module = qm_dict[qm]["db_module"] db_module = qm_dict[qm]["db_module"]
for const in res[qm]: for const in res[qm]:
dconst = getattr(iCalibrationDB.Constants.AGIPD, const)() dconst = getattr(iCalibrationDB.Constants.AGIPD, const)()
dconst.data = res[qm][const] dconst.data = res[qm][const]
if db_output: if db_output:
md = send_to_db(db_module, karabo_id, dconst, condition, file_loc, md = send_to_db(db_module, karabo_id, dconst, condition, file_loc,
report, cal_db_interface, creation_time=creation_time, report, cal_db_interface, creation_time=creation_time,
timeout=cal_db_timeout) timeout=cal_db_timeout)
if local_output: if local_output:
md = save_const_to_h5(db_module, karabo_id, dconst, condition, dconst.data, md = save_const_to_h5(db_module, karabo_id, dconst, condition, dconst.data,
file_loc, report, creation_time, out_folder) file_loc, report, creation_time, out_folder)
print(f"Calibration constant {const} for {qm} is stored locally in {file_loc}.\n") print(f"Calibration constant {const} for {qm} is stored locally in {file_loc}.\n")
print("Constants parameter conditions are:\n") print("Constants parameter conditions are:\n")
print(f"• memory_cells: {mem_cells}\n• bias_voltage: {bias_voltage}\n" print(f"• memory_cells: {mem_cells}\n• bias_voltage: {bias_voltage}\n"
f"• acquisition_rate: {acq_rate}\n• gain_setting: {gain_setting}\n" f"• acquisition_rate: {acq_rate}\n• gain_setting: {gain_setting}\n"
f"• gain_mode: {fixed_gain_mode}\n• integration_time: {integration_time}\n" f"• gain_mode: {fixed_gain_mode}\n• integration_time: {integration_time}\n"
f"• creation_time: {md.calibration_constant_version.begin_at if md is not None else creation_time}\n") f"• creation_time: {md.calibration_constant_version.begin_at if md is not None else creation_time}\n")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Retrieving previous calibration constants for comparison. ## Retrieving previous calibration constants for comparison.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# Start retrieving existing constants for comparison # Start retrieving existing constants for comparison
qm_x_const = [(qm, const) for const in res[qm] for qm in res] qm_x_const = [(qm, const) for const in res[qm] for qm in res]
def retrieve_old_constant(qm, const): def retrieve_old_constant(qm, const):
dconst = getattr(iCalibrationDB.Constants.AGIPD, const)() dconst = getattr(iCalibrationDB.Constants.AGIPD, const)()
data, mdata = get_from_db( data, mdata = get_from_db(
karabo_id=karabo_id, karabo_id=karabo_id,
karabo_da=qm_dict[qm]["karabo_da"], karabo_da=qm_dict[qm]["karabo_da"],
constant=dconst, constant=dconst,
condition=condition, condition=condition,
empty_constant=None, empty_constant=None,
cal_db_interface=cal_db_interface, cal_db_interface=cal_db_interface,
creation_time=creation_time-timedelta(seconds=1) if creation_time else None, creation_time=creation_time-timedelta(seconds=1) if creation_time else None,
strategy="pdu_prior_in_time", strategy="pdu_prior_in_time",
verbosity=1, verbosity=1,
timeout=cal_db_timeout timeout=cal_db_timeout
) )
if mdata is None or data is None: if mdata is None or data is None:
timestamp = "Not found" timestamp = "Not found"
filepath = None filepath = None
h5path = None h5path = None
else: else:
timestamp = mdata.calibration_constant_version.begin_at.isoformat() timestamp = mdata.calibration_constant_version.begin_at.isoformat()
filepath = os.path.join( filepath = os.path.join(
mdata.calibration_constant_version.hdf5path, mdata.calibration_constant_version.hdf5path,
mdata.calibration_constant_version.filename mdata.calibration_constant_version.filename
) )
h5path = mdata.calibration_constant_version.h5path h5path = mdata.calibration_constant_version.h5path
return data, timestamp, filepath, h5path return data, timestamp, filepath, h5path
old_retrieval_pool = multiprocessing.Pool() old_retrieval_pool = multiprocessing.Pool()
old_retrieval_res = old_retrieval_pool.starmap_async( old_retrieval_res = old_retrieval_pool.starmap_async(
retrieve_old_constant, qm_x_const retrieve_old_constant, qm_x_const
) )
old_retrieval_pool.close() old_retrieval_pool.close()
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
mnames=[] mnames=[]
for i in modules: for i in modules:
qm = module_index_to_qm(i) qm = module_index_to_qm(i)
mnames.append(qm) mnames.append(qm)
display(Markdown(f'## Position of the module {qm} and its ASICs')) display(Markdown(f'## Position of the module {qm} and its ASICs'))
show_processed_modules(dinstance, constants=None, mnames=mnames, mode="position") show_processed_modules(dinstance, constants=None, mnames=mnames, mode="position")
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Single-Cell Overviews ## ## Single-Cell Overviews ##
Single cell overviews allow to identify potential effects on all memory cells, e.g. on sensor level. Additionally, they should serve as a first sanity check on expected behaviour, e.g. if structuring on the ASIC level is visible in the offsets, but otherwise no immediate artifacts are visible. Single cell overviews allow to identify potential effects on all memory cells, e.g. on sensor level. Additionally, they should serve as a first sanity check on expected behaviour, e.g. if structuring on the ASIC level is visible in the offsets, but otherwise no immediate artifacts are visible.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### High Gain ### ### High Gain ###
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
cell = 3 cell = 3
gain = 0 gain = 0
show_overview(res, cell, gain, infix="{}-{}-{}".format(*run_numbers)) show_overview(res, cell, gain, infix="{}-{}-{}".format(*run_numbers))
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Medium Gain ### ### Medium Gain ###
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
cell = 3 cell = 3
gain = 1 gain = 1
show_overview(res, cell, gain, infix="{}-{}-{}".format(*run_numbers)) show_overview(res, cell, gain, infix="{}-{}-{}".format(*run_numbers))
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Low Gain ### ### Low Gain ###
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
cell = 3 cell = 3
gain = 2 gain = 2
show_overview(res, cell, gain, infix="{}-{}-{}".format(*run_numbers)) show_overview(res, cell, gain, infix="{}-{}-{}".format(*run_numbers))
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
if high_res_badpix_3d: if high_res_badpix_3d:
cols = { cols = {
BadPixels.NOISE_OUT_OF_THRESHOLD: (BadPixels.NOISE_OUT_OF_THRESHOLD.name, '#FF000080'), BadPixels.NOISE_OUT_OF_THRESHOLD: (BadPixels.NOISE_OUT_OF_THRESHOLD.name, '#FF000080'),
BadPixels.OFFSET_NOISE_EVAL_ERROR: (BadPixels.OFFSET_NOISE_EVAL_ERROR.name, '#0000FF80'), BadPixels.OFFSET_NOISE_EVAL_ERROR: (BadPixels.OFFSET_NOISE_EVAL_ERROR.name, '#0000FF80'),
BadPixels.OFFSET_OUT_OF_THRESHOLD: (BadPixels.OFFSET_OUT_OF_THRESHOLD.name, '#00FF0080'), BadPixels.OFFSET_OUT_OF_THRESHOLD: (BadPixels.OFFSET_OUT_OF_THRESHOLD.name, '#00FF0080'),
BadPixels.GAIN_THRESHOLDING_ERROR: (BadPixels.GAIN_THRESHOLDING_ERROR.name, '#FF40FF40'), BadPixels.GAIN_THRESHOLDING_ERROR: (BadPixels.GAIN_THRESHOLDING_ERROR.name, '#FF40FF40'),
BadPixels.OFFSET_OUT_OF_THRESHOLD | BadPixels.NOISE_OUT_OF_THRESHOLD: ('OFFSET_OUT_OF_THRESHOLD + NOISE_OUT_OF_THRESHOLD', '#DD00DD80'), BadPixels.OFFSET_OUT_OF_THRESHOLD | BadPixels.NOISE_OUT_OF_THRESHOLD: ('OFFSET_OUT_OF_THRESHOLD + NOISE_OUT_OF_THRESHOLD', '#DD00DD80'),
BadPixels.OFFSET_OUT_OF_THRESHOLD | BadPixels.NOISE_OUT_OF_THRESHOLD | BadPixels.OFFSET_OUT_OF_THRESHOLD | BadPixels.NOISE_OUT_OF_THRESHOLD |
BadPixels.GAIN_THRESHOLDING_ERROR: ('MIXED', '#BFDF009F') BadPixels.GAIN_THRESHOLDING_ERROR: ('MIXED', '#BFDF009F')
} }
display(Markdown(""" display(Markdown("""
## Global Bad Pixel Behaviour ## ## Global Bad Pixel Behaviour ##
The following plots show the results of bad pixel evaluation for all evaluated memory cells. The following plots show the results of bad pixel evaluation for all evaluated memory cells.
Cells are stacked in the Z-dimension, while pixels values in x/y are rebinned with a factor of 2. Cells are stacked in the Z-dimension, while pixels values in x/y are rebinned with a factor of 2.
This excludes single bad pixels present only in disconnected pixels. This excludes single bad pixels present only in disconnected pixels.
Hence, any bad pixels spanning at least 4 pixels in the x/y-plane, or across at least two memory cells are indicated. Hence, any bad pixels spanning at least 4 pixels in the x/y-plane, or across at least two memory cells are indicated.
Colors encode the bad pixel type, or mixed type. Colors encode the bad pixel type, or mixed type.
""")) """))
gnames = ['High Gain', 'Medium Gain', 'Low Gain'] gnames = ['High Gain', 'Medium Gain', 'Low Gain']
for gain in range(3): for gain in range(3):
display(Markdown(f'### {gnames[gain]} ###')) display(Markdown(f'### {gnames[gain]} ###'))
for mod, data in badpix_g.items(): for mod, data in badpix_g.items():
plot_badpix_3d(data[...,gain], cols, title=mod, rebin_fac=1) plot_badpix_3d(data[...,gain], cols, title=mod, rebin_fac=1)
plt.show() plt.show()
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Aggregate values, and per Cell behaviour ## ## Aggregate values, and per Cell behaviour ##
The following tables and plots give an overview of statistical aggregates for each constant, as well as per cell behavior. The following tables and plots give an overview of statistical aggregates for each constant, as well as per cell behavior.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
create_constant_overview(offset_g, "Offset (ADU)", mem_cells, 4000, 8000, create_constant_overview(offset_g, "Offset (ADU)", mem_cells, 4000, 8000,
badpixels=[badpix_g, np.nan]) badpixels=[badpix_g, np.nan])
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
create_constant_overview(noise_g, "Noise (ADU)", mem_cells, 0, 100, create_constant_overview(noise_g, "Noise (ADU)", mem_cells, 0, 100,
badpixels=[badpix_g, np.nan]) badpixels=[badpix_g, np.nan])
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
if not fixed_gain_mode: if not fixed_gain_mode:
# Plot only three gain threshold maps. # Plot only three gain threshold maps.
bp_thresh = OrderedDict() bp_thresh = OrderedDict()
for mod, con in badpix_g.items(): for mod, con in badpix_g.items():
bp_thresh[mod] = np.zeros((con.shape[0], con.shape[1], con.shape[2], 5), dtype=con.dtype) bp_thresh[mod] = np.zeros((con.shape[0], con.shape[1], con.shape[2], 5), dtype=con.dtype)
bp_thresh[mod][...,:2] = con[...,:2] bp_thresh[mod][...,:2] = con[...,:2]
bp_thresh[mod][...,2:] = con bp_thresh[mod][...,2:] = con
create_constant_overview(thresholds_g, "Threshold (ADU)", mem_cells, 4000, 10000, 5, create_constant_overview(thresholds_g, "Threshold (ADU)", mem_cells, 4000, 10000, 5,
badpixels=[bp_thresh, np.nan], badpixels=[bp_thresh, np.nan],
gmap=['HG-MG Threshold', 'MG-LG Threshold', 'High gain', 'Medium gain', 'low gain'], gmap=['HG-MG Threshold', 'MG-LG Threshold', 'High gain', 'Medium gain', 'low gain'],
marker=['d','d','','',''] marker=['d','d','','','']
) )
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
bad_pixel_aggregate_g = OrderedDict() bad_pixel_aggregate_g = OrderedDict()
for m, d in badpix_g.items(): for m, d in badpix_g.items():
bad_pixel_aggregate_g[m] = d.astype(np.bool).astype(np.float) bad_pixel_aggregate_g[m] = d.astype(np.bool).astype(np.float)
create_constant_overview(bad_pixel_aggregate_g, "Bad pixel fraction", mem_cells, 0, 0.10, 3) create_constant_overview(bad_pixel_aggregate_g, "Bad pixel fraction", mem_cells, 0, 0.10, 3)
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Summary tables ## ## Summary tables ##
The following tables show summary information for the evaluated module. Values for currently evaluated constants are compared with values for pre-existing constants retrieved from the calibration database. The following tables show summary information for the evaluated module. Values for currently evaluated constants are compared with values for pre-existing constants retrieved from the calibration database.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
# now we need the old constants # now we need the old constants
old_const = {} old_const = {}
old_mdata = {} old_mdata = {}
old_retrieval_res.wait() old_retrieval_res.wait()
for (qm, const), (data, timestamp, filepath, h5path) in zip(qm_x_const, old_retrieval_res.get()): for (qm, const), (data, timestamp, filepath, h5path) in zip(qm_x_const, old_retrieval_res.get()):
old_const.setdefault(qm, {})[const] = data old_const.setdefault(qm, {})[const] = data
old_mdata.setdefault(qm, {})[const] = { old_mdata.setdefault(qm, {})[const] = {
"timestamp": timestamp, "timestamp": timestamp,
"filepath": filepath, "filepath": filepath,
"h5path": h5path "h5path": h5path
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
display(Markdown("The following pre-existing constants are used for comparison:")) display(Markdown("The following pre-existing constants are used for comparison:"))
for qm, consts in old_mdata.items(): for qm, consts in old_mdata.items():
display(Markdown(f"- {qm}")) display(Markdown(f"- {qm}"))
for const in consts: for const in consts:
display(Markdown(f" - {const} at {consts[const]['timestamp']}")) display(Markdown(f" - {const} at {consts[const]['timestamp']}"))
# saving locations of old constants for summary notebook # saving locations of old constants for summary notebook
with open(f"{out_folder}/module_metadata_{qm}.yml", "w") as fd: with open(f"{out_folder}/module_metadata_{qm}.yml", "w") as fd:
yaml.safe_dump( yaml.safe_dump(
{ {
"module": qm, "module": qm,
"pdu": qm_dict[qm]["db_module"], "pdu": qm_dict[qm]["db_module"],
"old-constants": old_mdata[qm] "old-constants": old_mdata[qm]
}, },
fd, fd,
) )
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
table = [] table = []
gain_names = ['High', 'Medium', 'Low'] gain_names = ['High', 'Medium', 'Low']
bits = [BadPixels.NOISE_OUT_OF_THRESHOLD, BadPixels.OFFSET_OUT_OF_THRESHOLD, BadPixels.OFFSET_NOISE_EVAL_ERROR, BadPixels.GAIN_THRESHOLDING_ERROR] bits = [BadPixels.NOISE_OUT_OF_THRESHOLD, BadPixels.OFFSET_OUT_OF_THRESHOLD, BadPixels.OFFSET_NOISE_EVAL_ERROR, BadPixels.GAIN_THRESHOLDING_ERROR]
for qm in badpix_g.keys(): for qm in badpix_g.keys():
for gain in range(3): for gain in range(3):
l_data = [] l_data = []
l_data_old = [] l_data_old = []
data = np.copy(badpix_g[qm][:,:,:,gain]) data = np.copy(badpix_g[qm][:,:,:,gain])
datau32 = data.astype(np.uint32) datau32 = data.astype(np.uint32)
l_data.append(len(datau32[datau32>0].flatten())) l_data.append(len(datau32[datau32>0].flatten()))
for bit in bits: for bit in bits:
l_data.append(np.count_nonzero(badpix_g[qm][:,:,:,gain] & bit)) l_data.append(np.count_nonzero(badpix_g[qm][:,:,:,gain] & bit))
if old_const[qm]['BadPixelsDark'] is not None: if old_const[qm]['BadPixelsDark'] is not None:
dataold = np.copy(old_const[qm]['BadPixelsDark'][:, :, :, gain]) dataold = np.copy(old_const[qm]['BadPixelsDark'][:, :, :, gain])
datau32old = dataold.astype(np.uint32) datau32old = dataold.astype(np.uint32)
l_data_old.append(len(datau32old[datau32old>0].flatten())) l_data_old.append(len(datau32old[datau32old>0].flatten()))
for bit in bits: for bit in bits:
l_data_old.append(np.count_nonzero(old_const[qm]['BadPixelsDark'][:, :, :, gain] & bit)) l_data_old.append(np.count_nonzero(old_const[qm]['BadPixelsDark'][:, :, :, gain] & bit))
l_data_name = ['All bad pixels', 'NOISE_OUT_OF_THRESHOLD', l_data_name = ['All bad pixels', 'NOISE_OUT_OF_THRESHOLD',
'OFFSET_OUT_OF_THRESHOLD', 'OFFSET_NOISE_EVAL_ERROR', 'GAIN_THRESHOLDING_ERROR'] 'OFFSET_OUT_OF_THRESHOLD', 'OFFSET_NOISE_EVAL_ERROR', 'GAIN_THRESHOLDING_ERROR']
l_threshold = ['', f'{thresholds_noise_sigma}' f'{thresholds_noise_hard[gain]}', l_threshold = ['', f'{thresholds_noise_sigma}' f'{thresholds_noise_hard[gain]}',
f'{thresholds_offset_sigma}' f'{thresholds_offset_hard[gain]}', f'{thresholds_offset_sigma}' f'{thresholds_offset_hard[gain]}',
'', f'{thresholds_gain_sigma}'] '', f'{thresholds_gain_sigma}']
for i in range(len(l_data)): for i in range(len(l_data)):
line = [f'{l_data_name[i]}, {gain_names[gain]} gain', l_threshold[i], l_data[i]] line = [f'{l_data_name[i]}, {gain_names[gain]} gain', l_threshold[i], l_data[i]]
if old_const[qm]['BadPixelsDark'] is not None: if old_const[qm]['BadPixelsDark'] is not None:
line += [l_data_old[i]] line += [l_data_old[i]]
else: else:
line += ['-'] line += ['-']
table.append(line) table.append(line)
table.append(['', '', '', '']) table.append(['', '', '', ''])
display(Markdown(''' display(Markdown('''
### Number of bad pixels ### Number of bad pixels
One pixel can be bad for different reasons, therefore, the sum of all types of bad pixels can be more than the number of all bad pixels. One pixel can be bad for different reasons, therefore, the sum of all types of bad pixels can be more than the number of all bad pixels.
''')) '''))
if len(table)>0: if len(table)>0:
md = display(Latex(tabulate.tabulate(table, tablefmt='latex', md = display(Latex(tabulate.tabulate(table, tablefmt='latex',
headers=["Pixel type", "Threshold", headers=["Pixel type", "Threshold",
"New constant", "Old constant"]))) "New constant", "Old constant"])))
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` python ``` python
header = ['Parameter', header = ['Parameter',
"New constant", "Old constant ", "New constant", "Old constant ",
"New constant", "Old constant ", "New constant", "Old constant ",
"New constant", "Old constant ", "New constant", "Old constant ",
"New constant", "Old constant "] "New constant", "Old constant "]
if fixed_gain_mode: if fixed_gain_mode:
constants = ['Offset', 'Noise'] constants = ['Offset', 'Noise']
else: else:
constants = ['Offset', 'Noise', 'ThresholdsDark'] constants = ['Offset', 'Noise', 'ThresholdsDark']
constants_x_qms = list(itertools.product(constants, res.keys())) constants_x_qms = list(itertools.product(constants, res.keys()))
def compute_table(const, qm): def compute_table(const, qm):
if const == 'ThresholdsDark': if const == 'ThresholdsDark':
table = [['','HG-MG threshold', 'HG-MG threshold', 'MG-LG threshold', 'MG-LG threshold']] table = [['','HG-MG threshold', 'HG-MG threshold', 'MG-LG threshold', 'MG-LG threshold']]
else: else:
table = [['','High gain', 'High gain', 'Medium gain', 'Medium gain', 'Low gain', 'Low gain']] table = [['','High gain', 'High gain', 'Medium gain', 'Medium gain', 'Low gain', 'Low gain']]
compare_with_old_constant = old_const[qm][const] is not None and \ compare_with_old_constant = old_const[qm][const] is not None and \
old_const[qm]['BadPixelsDark'] is not None old_const[qm]['BadPixelsDark'] is not None
data = np.copy(res[qm][const]) data = np.copy(res[qm][const])
if const == 'ThresholdsDark': if const == 'ThresholdsDark':
data[...,0][res[qm]['BadPixelsDark'][...,0]>0] = np.nan data[...,0][res[qm]['BadPixelsDark'][...,0]>0] = np.nan
data[...,1][res[qm]['BadPixelsDark'][...,1]>0] = np.nan data[...,1][res[qm]['BadPixelsDark'][...,1]>0] = np.nan
else: else:
data[res[qm]['BadPixelsDark']>0] = np.nan data[res[qm]['BadPixelsDark']>0] = np.nan
if compare_with_old_constant: if compare_with_old_constant:
data_old = np.copy(old_const[qm][const]) data_old = np.copy(old_const[qm][const])
if const == 'ThresholdsDark': if const == 'ThresholdsDark':
data_old[...,0][old_const[qm]['BadPixelsDark'][...,0]>0] = np.nan data_old[...,0][old_const[qm]['BadPixelsDark'][...,0]>0] = np.nan
data_old[...,1][old_const[qm]['BadPixelsDark'][...,1]>0] = np.nan data_old[...,1][old_const[qm]['BadPixelsDark'][...,1]>0] = np.nan
else: else:
data_old[old_const[qm]['BadPixelsDark']>0] = np.nan data_old[old_const[qm]['BadPixelsDark']>0] = np.nan
f_list = [np.nanmedian, np.nanmean, np.nanstd, np.nanmin, np.nanmax] f_list = [np.nanmedian, np.nanmean, np.nanstd, np.nanmin, np.nanmax]
n_list = ['Median', 'Mean', 'Std', 'Min', 'Max'] n_list = ['Median', 'Mean', 'Std', 'Min', 'Max']
def compute_row(i): def compute_row(i):
line = [n_list[i]] line = [n_list[i]]
for gain in range(3): for gain in range(3):
# Compare only 3 threshold gain-maps # Compare only 3 threshold gain-maps
if gain == 2 and const == 'ThresholdsDark': if gain == 2 and const == 'ThresholdsDark':
continue continue
stat_measure = f_list[i](data[...,gain]) stat_measure = f_list[i](data[...,gain])
line.append(f"{stat_measure:6.1f}") line.append(f"{stat_measure:6.1f}")
if compare_with_old_constant: if compare_with_old_constant:
old_stat_measure = f_list[i](data_old[...,gain]) old_stat_measure = f_list[i](data_old[...,gain])
line.append(f"{old_stat_measure:6.1f}") line.append(f"{old_stat_measure:6.1f}")
else: else:
line.append("-") line.append("-")
return line return line
with multiprocessing.pool.ThreadPool(processes=multiprocessing.cpu_count() // len(constants_x_qms)) as pool: with multiprocessing.pool.ThreadPool(processes=multiprocessing.cpu_count() // len(constants_x_qms)) as pool:
rows = pool.map(compute_row, range(len(f_list))) rows = pool.map(compute_row, range(len(f_list)))
table.extend(rows) table.extend(rows)
return table return table
with multiprocessing.Pool(processes=len(constants_x_qms)) as pool: with multiprocessing.Pool(processes=len(constants_x_qms)) as pool:
tables = pool.starmap(compute_table, constants_x_qms) tables = pool.starmap(compute_table, constants_x_qms)
for (const, qm), table in zip(constants_x_qms, tables): for (const, qm), table in zip(constants_x_qms, tables):
display(Markdown(f"### {qm}: {const} [ADU], good pixels only")) display(Markdown(f"### {qm}: {const} [ADU], good pixels only"))
display(Latex(tabulate.tabulate(table, tablefmt='latex', headers=header))) display(Latex(tabulate.tabulate(table, tablefmt='latex', headers=header)))
``` ```
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment