Skip to content
Snippets Groups Projects

[GH2][CORRECT][DARK] Feat/add support for gh2 25um

Merged Karim Ahmed requested to merge feat/add_support_for_GH2_25um into master
10 unresolved threads
Compare and
2 files
+ 175
120
Compare changes
  • Side-by-side
  • Inline
Files
2
%% Cell type:markdown id:49b6577f-96a5-4dd2-bdd9-da661b2c4619 tags:
# Gotthard2 Dark Image Characterization #
Author: European XFEL Detector Group, Version: 1.0
The following is a processing for offset, noise, and Badpixels maps using dark images taken with Gotthard2 detector.
All constants are evaluated per strip, per pulse, and per memory cell. The maps are calculated for each gain stage that is acquired in 3 separate runs.
The three maps (calibration constants) can be injected to the database and stored locally.
%% Cell type:code id:818e24e8 tags:
``` python
in_folder = "/gpfs/exfel/exp/FXE/202231/p900298/raw" # the folder to read data from, required
out_folder = "/gpfs/exfel/data/scratch/ahmedk/test/gotthard2/darks" # the folder to output to, required
run_high = 7 # run number for G0 dark run, required
run_med = 8 # run number for G1 dark run, required
run_low = 9 # run number for G2 dark run, required
# Parameters used to access raw data.
karabo_id = "FXE_XAD_G2XES" # karabo prefix of Gotthard-II devices
karabo_da = ["GH201"] # data aggregators
receiver_template = "RECEIVER" # receiver template used to read INSTRUMENT keys.
control_template = "CONTROL" # control template used to read CONTROL keys.
instrument_source_template = '{}/DET/{}:daqOutput' # template for source name (filled with karabo_id & receiver_id). e.g. 'SPB_IRDA_JF4M/DET/JNGFR01:daqOutput' # noqa
ctrl_source_template = '{}/DET/{}' # template for control source name (filled with karabo_id_control)
karabo_id_control = "" # Control karabo ID. Set to empty string to use the karabo-id
# Parameters for the calibration database.
use_dir_creation_date = True
cal_db_interface = "tcp://max-exfl016:8020" # calibration DB interface to use
cal_db_timeout = 300000 # timeout on caldb requests
overwrite_creation_time = "" # To overwrite the measured creation_time. Required Format: YYYY-MM-DD HR:MN:SC.00 e.g. "2022-06-28 13:00:00.00"
db_output = False # Output constants to the calibration database
local_output = True # Output constants locally
# Conditions used for injected calibration constants.
bias_voltage = -1 # Detector bias voltage, set to -1 to use value in raw file.
exposure_time = -1. # Detector exposure time, set to -1 to use value in raw file.
exposure_period = -1. # Detector exposure period, set to -1 to use value in raw file.
acquisition_rate = -1. # Detector acquisition rate (1.1/4.5), set to -1 to use value in raw file.
single_photon = -1 # Detector single photon mode (High/Low CDS), set to -1 to use value in raw file.
# Parameters used during selecting raw data trains.
min_trains = 1 # Minimum number of trains that should be available to process dark constants. Default 1.
max_trains = 1000 # Maximum number of trains to use for processing dark constants. Set to 0 to use all available trains.
badpixel_threshold_sigma = 5. # bad pixels defined by values outside n times this std from median
# Don't delete! myMDC sends this by default.
operation_mode = '' # Detector dark run acquiring operation mode, optional
```
%% Cell type:code id:8085f9aa tags:
``` python
import datetime
import numpy as np
import matplotlib.pyplot as plt
import pasha as psh
from IPython.display import Markdown, display
from extra_data import RunDirectory
from pathlib import Path
from cal_tools.enums import BadPixels
from cal_tools.gotthard2 import gotthard2algs, gotthard2lib
from cal_tools.step_timing import StepTimer
from cal_tools.tools import (
get_dir_creation_date,
get_constant_from_db_and_time,
get_pdu_from_db,
get_report,
save_const_to_h5,
send_to_db,
)
from iCalibrationDB import Conditions, Constants
%matplotlib inline
```
%% Cell type:code id:18fe4379 tags:
``` python
run_nums = [run_high, run_med, run_low]
in_folder = Path(in_folder)
out_folder = Path(out_folder)
out_folder.mkdir(exist_ok=True)
out_folder.mkdir(parents=True, exist_ok=True)
print(f"Process modules: {karabo_da}")
run_dc = RunDirectory(in_folder / f"r{run_high:04d}")
file_loc = f"proposal:{run_dc.run_metadata()['proposalNumber']} runs:{run_high} {run_med} {run_low}" # noqa
instrument_src = instrument_source_template.format(karabo_id, receiver_template)
ctrl_src = ctrl_source_template.format(karabo_id_control, control_template)
# Read report path to associate it later with injected constants.
report = get_report(out_folder)
creation_time = None
if overwrite_creation_time:
creation_time = datetime.datetime.strptime(
overwrite_creation_time, "%Y-%m-%d %H:%M:%S.%f"
)
elif use_dir_creation_date:
creation_time = get_dir_creation_date(in_folder, run_high)
print(f"Using {creation_time.isoformat()} as creation time")
if not karabo_id_control:
karabo_id_control = karabo_id
```
%% Cell type:code id:c176a86f tags:
``` python
run_dc = RunDirectory(in_folder / f"r{run_high:04d}")
file_loc = f"proposal:{run_dc.run_metadata()['proposalNumber']} runs:{run_high} {run_med} {run_low}" # noqa
receivers = list(run_dc.select(f'{karabo_id}/DET/{receiver_template}*').all_sources)
```
%% Cell type:code id:108be688 tags:
``` python
step_timer = StepTimer()
```
%% Cell type:code id:ff9149fc tags:
``` python
# Read parameter conditions and validate the values for the three runs.
step_timer.start()
run_dcs_dict = dict()
ctrl_src = ctrl_source_template.format(karabo_id_control, control_template)
conditions = {
"bias_voltage": set(),
"exposure_time": set(),
"exposure_period": set(),
"acquisition_rate": set(),
"single_photon": set(),
}
for gain, run in enumerate(run_nums):
run_dc = RunDirectory(in_folder / f"r{run:04d}/")
run_dcs_dict[run] = [gain, run_dc]
# Read slow data.
g2ctrl = gotthard2lib.Gotthard2Ctrl(run_dc=run_dc, ctrl_src=ctrl_src)
conditions["bias_voltage"].add(
g2ctrl.get_bias_voltage() if bias_voltage == -1 else bias_voltage
)
conditions["exposure_time"].add(
g2ctrl.get_exposure_time() if exposure_time == -1 else exposure_time
)
conditions["exposure_period"].add(
g2ctrl.get_exposure_period() if exposure_period == -1 else exposure_period
)
conditions["single_photon"].add(
g2ctrl.get_single_photon() if single_photon == -1 else single_photon
)
conditions["acquisition_rate"].add(
g2ctrl.get_acquisition_rate() if acquisition_rate == -1 else acquisition_rate
)
for c, v in conditions.items():
assert len(v) == 1, f"{c} value is not the same for the three runs."
bias_voltage = conditions["bias_voltage"].pop()
print("Bias voltage: ", bias_voltage)
exposure_time = conditions["exposure_time"].pop()
print("Exposure time: ", exposure_time)
exposure_period = conditions["exposure_period"].pop()
print("Exposure period: ", exposure_period)
single_photon = conditions["single_photon"].pop()
print("Single photon: ", single_photon)
acquisition_rate = conditions["acquisition_rate"].pop()
print("Acquisition rate: ", acquisition_rate)
# Decide if GH2 is 25um or 50um
gh2_hostname = run_dc.get_run_value(ctrl_src, "rxHostname")
# gh2_hostname is a vector of bytes objects.
# GH2 25um has two host-names unlike 50um which has one.
if gh2_hostname[1].decode("utf-8"): # For 25um use virtual karabo_das for CALCAT data mapping.
karabo_da = [f"{karabo_da[0]}/1", f"{karabo_da[0]}/2"]
print("Processing 25um Gotthard2.")
```
%% Cell type:code id:ac9c5dc3-bc66-4e7e-b6a1-360259be535c tags:
``` python
def specify_trains_to_process(
img_key_data: "extra_data.KeyData", # noqa
max_trains: int = 0,
min_trains: int = 0,
img_key_data: "extra_data.KeyData",
):
"""Specify total number of trains to process.
Based on given min_trains and max_trains, if given.
Print number of trains to process and number of empty trains.
Raise ValueError if specified trains are less than min_trains.
"""
# Specifies total number of trains to proccess.
n_trains = img_key_data.shape[0]
all_trains = len(img_key_data.train_ids)
print(
f"{mod} has {all_trains - n_trains} "
f"{receiver} has {all_trains - n_trains} "
f"trains with empty frames out of {all_trains} trains"
)
if n_trains < min_trains:
raise ValueError(
f"Less than {min_trains} trains are available in RAW data."
" Not enough data to process darks."
)
if max_trains > 0:
n_trains = min(n_trains, max_trains)
print(f"Processing {n_trains} trains.")
return n_trains
```
%% Cell type:code id:3c59c11d tags:
``` python
# set the operating condition
condition = Conditions.Dark.Gotthard2(
bias_voltage=bias_voltage,
exposure_time=exposure_time,
exposure_period=exposure_period,
acquisition_rate=acquisition_rate,
single_photon=single_photon,
)
db_modules = get_pdu_from_db(
karabo_id=karabo_id,
karabo_da=karabo_da,
constant=Constants.Gotthard2.LUT(),
condition=condition,
cal_db_interface=cal_db_interface,
snapshot_at=creation_time,
)
```
%% Cell type:code id:e2eb2fc0-df9c-4887-9691-f81474f8c131 tags:
``` python
def convert_train(wid, index, tid, d):
"""Convert a Gotthard2 train from 12bit to 10bit."""
gotthard2algs.convert_to_10bit(
d[instr_mod_src]["data.adc"], lut, data_10bit[index, ...]
d[receiver]["data.adc"], lut, data_10bit[index, ...]
)
```
%% Cell type:code id:4e8ffeae tags:
``` python
# Calculate noise and offset per pixel and global average, std and median
noise_map = dict()
offset_map = dict()
badpixels_map = dict()
context = psh.ProcessContext(num_workers=25)
empty_lut = (np.arange(2 ** 12).astype(np.float64) * 2 ** 10 / 2 ** 12).astype(
np.uint16
)
empty_lut = np.stack(1280 * [np.stack([empty_lut] * 2)], axis=0)
for mod in karabo_da:
for mod, receiver in zip(karabo_da, receivers):
# Retrieve LUT constant
lut, time = get_constant_from_db_and_time(
constant=Constants.Gotthard2.LUT(),
condition=condition,
empty_constant=empty_lut,
karabo_id=karabo_id,
karabo_da=mod,
cal_db_interface=cal_db_interface,
creation_time=creation_time,
timeout=cal_db_timeout,
print_once=False,
)
print(f"Retrieved LUT constant with creation-time {time}")
# Path to pixels ADC values
instr_mod_src = instrument_src.format(int(mod[-2:]))
# TODO: Validate the final shape to store constants.
cshape = (1280, 2, 3)
offset_map[mod] = context.alloc(shape=cshape, dtype=np.float32)
noise_map[mod] = context.alloc(like=offset_map[mod])
badpixels_map[mod] = context.alloc(like=offset_map[mod], dtype=np.uint32)
for run_num, [gain, run_dc] in run_dcs_dict.items():
step_timer.start()
n_trains = specify_trains_to_process(run_dc[instr_mod_src, "data.adc"])
n_trains = specify_trains_to_process(run_dc[receiver, "data.adc"])
# Select requested number of trains to process.
dc = run_dc.select(instr_mod_src, require_all=True).select_trains(
dc = run_dc.select(receiver, require_all=True).select_trains(
np.s_[:n_trains]
)
step_timer.done_step("preparing raw data")
step_timer.start()
# Convert 12bit data to 10bit
data_10bit = context.alloc(
shape=dc[instr_mod_src, "data.adc"].shape, dtype=np.float32
shape=dc[receiver, "data.adc"].shape, dtype=np.float32
)
context.map(convert_train, dc)
step_timer.done_step("convert to 10bit")
step_timer.start()
# The first ~20 frames of each train are excluded.
# The electronics needs some time to reach stable operational conditions
# at the beginning of each acquisition cycle,
# hence the first ~20 images have lower quality and should not be used.
# The rough number of 20 is correct at 4.5 MHz acquisition rate,
# 5 should be enough at 1.1 MHz. Sticking with 20 excluded frames, as there isn't
# much of expected difference.
# Split even and odd data to calculate the two storage cell constants.
# Detector always operates in burst mode.
even_data = data_10bit[:, 20::2, :]
odd_data = data_10bit[:, 21::2, :]
def offset_noise_cell(wid, index, d):
offset_map[mod][:, index, gain] = np.mean(d, axis=(0, 1))
noise_map[mod][:, index, gain] = np.std(d, axis=(0, 1))
# Calculate Offset and Noise Maps.
context.map(offset_noise_cell, (even_data, odd_data))
# Split even and odd gain data.
data_gain = dc[instr_mod_src, "data.gain"].ndarray()
data_gain = dc[receiver, "data.gain"].ndarray()
even_gain = data_gain[:, 20::2, :]
odd_gain = data_gain[:, 21::2, :]
raw_g = 3 if gain == 2 else gain
def badpixels_cell(wid, index, g):
"""Check if there are wrong bad gain values.
Indicate pixels with wrong gain value across all trains for each cell."""
badpixels_map[mod][
np.mean(g, axis=(0, 1)) != raw_g, index, gain
] |= BadPixels.WRONG_GAIN_VALUE.value
context.map(badpixels_cell, (even_gain, odd_gain))
step_timer.done_step("Processing darks")
```
%% Cell type:code id:8e410ca2 tags:
``` python
print(f"Total processing time {step_timer.timespan():.01f} s")
step_timer.print_summary()
```
%% Cell type:code id:3fc17e05-17ab-4ac4-9e79-c95399278bb9 tags:
``` python
def print_bp_entry(bp):
print(f"{bp.name:<30s} {bp.value:032b} -> {int(bp.value)}")
print_bp_entry(BadPixels.NOISE_OUT_OF_THRESHOLD)
print_bp_entry(BadPixels.OFFSET_NOISE_EVAL_ERROR)
print_bp_entry(BadPixels.WRONG_GAIN_VALUE)
def eval_bpidx(d):
mdn = np.nanmedian(d, axis=(0))[None, :, :]
std = np.nanstd(d, axis=(0))[None, :, :]
idx = (d > badpixel_threshold_sigma * std + mdn) | (
d < (-badpixel_threshold_sigma) * std + mdn
)
return idx
```
%% Cell type:code id:40c34cc5-fe93-4b83-bf39-f465f37c40b4 tags:
``` python
step_timer.start()
g_name = ["G0", "G1", "G2"]
for mod, pdu in zip(karabo_da, db_modules):
display(Markdown(f"### Badpixels for module {mod}:"))
badpixels_map[mod][
~np.isfinite(offset_map[mod])
] |= BadPixels.OFFSET_NOISE_EVAL_ERROR.value
badpixels_map[mod][
eval_bpidx(noise_map[mod])
] |= BadPixels.NOISE_OUT_OF_THRESHOLD.value
badpixels_map[mod][
~np.isfinite(noise_map[mod])
] |= BadPixels.OFFSET_NOISE_EVAL_ERROR.value
for cell in [0, 1]:
fig, ax = plt.subplots(figsize=(10, 5))
for g_idx in [0, 1, 2]:
ax.plot(badpixels_map[mod][:, cell, g_idx], label=f"G{g_idx} Bad pixel map")
ax.set_xticks(np.arange(0, 1281, 80))
ax.set_xlabel("Stripes #")
ax.set_xlabel("BadPixels")
ax.set_title(f"Cell {cell} - Module {mod} ({pdu})")
ax.set_title(f"BadPixels map - Cell {cell} - Module {mod} ({pdu})")
ax.set_ylim([0, 5])
ax.legend()
pass
plt.show()
step_timer.done_step(f"Creating bad pixels constant and plotting it.")
```
%% Cell type:code id:c8777cfe tags:
``` python
for mod, pdu in zip(karabo_da, db_modules):
for cons, cname in zip([offset_map, noise_map], ["Offset", "Noise"]):
for cons, cname in zip([offset_map, noise_map], ["Offset", "Noise"]):
for mod, pdu in zip(karabo_da, db_modules):
display(Markdown(f"### {cname} for module {mod}:"))
for cell in [0, 1]:
fig, ax = plt.subplots(figsize=(10, 5))
for g_idx in [0, 1, 2]:
ax.plot(cons[mod][:, cell, g_idx], label=f"G{g_idx} {cname} map")
ax.set_xlabel("Stripes #")
ax.set_xlabel(cname)
ax.set_title(f"{cname} map - Cell {cell} - Module {mod} ({pdu})")
ax.legend()
pass
plt.show()
```
%% Cell type:code id:1c4eddf7-7d6e-49f4-8cbb-12d2bc496a8f tags:
``` python
step_timer.start()
for mod, db_mod in zip(karabo_da, db_modules):
constants = {
"Offset": offset_map[mod],
"Noise": noise_map[mod],
"BadPixelsDark": badpixels_map[mod],
}
md = None
for key, const_data in constants.items():
const = getattr(Constants.Gotthard2, key)()
const.data = const_data
if db_output:
md = send_to_db(
db_module=db_mod,
karabo_id=karabo_id,
constant=const,
condition=condition,
file_loc=file_loc,
report_path=report,
cal_db_interface=cal_db_interface,
creation_time=creation_time,
timeout=cal_db_timeout,
)
if local_output:
md = save_const_to_h5(
db_module=db_mod,
karabo_id=karabo_id,
constant=const,
condition=condition,
data=const.data,
file_loc=file_loc,
report=report,
creation_time=creation_time,
out_folder=out_folder,
)
print(f"Calibration constant {key} is stored locally at {out_folder}.\n")
print("Constants parameter conditions are:\n")
print(
f"• Bias voltage: {bias_voltage}\n"
f"• Exposure time: {exposure_time}\n"
f"• Exposure period: {exposure_period}\n"
f"• Acquisition rate: {acquisition_rate}\n"
f"• Single photon: {single_photon}\n"
f"• Creation time: {md.calibration_constant_version.begin_at if md is not None else creation_time}\n"
)
step_timer.done_step("Injecting constants.")
```
Loading