diff --git a/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb b/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb index 4189dd347f4deb7bdd4c744a2e26e0b4894ceff4..2c485c79b17d0a474e0a85d59c2da8407c678218 100644 --- a/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb +++ b/notebooks/Jungfrau/Jungfrau_Gain_Correct_and_Verify_NBC.ipynb @@ -27,11 +27,9 @@ "karabo_id = \"SPB_IRDA_JF4M\" # karabo prefix of Jungfrau devices\n", "karabo_da = ['JNGFR01', 'JNGFR02', 'JNGFR03', 'JNGFR04', 'JNGFR05', 'JNGFR06', 'JNGFR07', 'JNGFR08'] # data aggregators\n", "receiver_template = \"JNGFR{:02d}\" # Detector receiver template for accessing raw data files. e.g. \"JNGFR{:02d}\"\n", - "path_template = 'RAW-R{:04d}-{}-S{:05d}.h5' # template to use for file name\n", "instrument_source_template = '{}/DET/{}:daqOutput' # template for source name (filled with karabo_id & receiver_id). e.g. 'SPB_IRDA_JF4M/DET/JNGFR01:daqOutput'\n", - "ctrl_source_template = '{}/DET/CONTROL' # template for control source name (filled with karabo_id).\n", + "ctrl_source_template = '{}/DET/CONTROL' # template for control source name (filled with karabo_id_control)\n", "karabo_id_control = \"\" # if control is on a different ID, set to empty string if it is the same a karabo-id\n", - "karabo_da_control = \"JNGFRCTRL00\" # file inset for control data\n", "\n", "# Parameters for calibration database.\n", "use_dir_creation_date = True # use the creation data of the input dir for database queries\n", @@ -44,11 +42,12 @@ "plt_images = 100 # Number of images to plot after applying selected corrections.\n", "limit_images = 0 # ONLY FOR TESTING. process only first N images, Use 0 to process all.\n", "\n", - "\n", "# Parameters for retrieving calibration constants\n", "manual_slow_data = False # if true, use manually entered bias_voltage and integration_time values\n", "integration_time = 4.96 # integration time in us, will be overwritten by value in file\n", "gain_setting = 0 # 0 for dynamic gain, 1 for dynamic HG0, will be overwritten by value in file\n", + "gain_mode = 0 # 0 for runs with dynamic gain setting, 1 for fixgain. It will be overwritten by value in file, if manual_slow_data is set to True. +", "mem_cells = 0 # leave memory cells equal 0, as it is saved in control information starting 2019.\n", "bias_voltage = 180 # will be overwritten by value in file\n", "\n", @@ -196,12 +195,15 @@ "if not manual_slow_data:\n", " integration_time = ctrl_data.get_integration_time()\n", " bias_voltage = ctrl_data.get_bias_voltage()\n", - " gain_str, gain_setting = ctrl_data.get_gain_setting()\n", + " gain_setting = ctrl_data.get_gain_setting()\n", + " gain_mode = ctrl_data.get_gain_mode()\n", "\n", "print(f\"Integration time is {integration_time} us\")\n", - "print(f\"Gain setting is {gain_setting} ({gain_str})\")\n", + "print(f\"Gain setting is {gain_setting} (run settings: \"\n", + " f\"{ctrl_data.run_settings.value if ctrl_data.run_settings else ctrl_data.run_settings})\") # noqa\n", + "print(f\"Gain mode is {gain_mode}\")\n", "print(f\"Bias voltage is {bias_voltage} V\")\n", - "print(f\"Number of memory cells is {memory_cells}\")" + "print(f\"Number of memory cells are {memory_cells}\")" ] }, { @@ -217,12 +219,19 @@ "metadata": {}, "outputs": [], "source": [ - "condition = Conditions.Dark.jungfrau(\n", - " memory_cells=memory_cells,\n", - " bias_voltage=bias_voltage,\n", - " integration_time=integration_time,\n", - " gain_setting=gain_setting,\n", - ")\n", + "condition = {\n", + " \"memory_cells\": memory_cells,\n", + " \"bias_voltage\": bias_voltage,\n", + " \"integration_time\": integration_time,\n", + " \"gain_setting\": gain_setting,\n", + "}\n", + "# TODO: Currently there are no gain constants for fixed gain mode.\n", + "# This should not be the case in the future.\n", + "gain_condition = Conditions.Dark.jungfrau(**condition)\n", + "\n", + "condition[\"gain_mode\"] = gain_mode\n", + "dark_condition = Conditions.Dark.jungfrau(**condition)\n", + "\n", "\n", "def get_constants_for_module(karabo_da: str):\n", " \"\"\" Get calibration constants for given module of Jungfrau\n", @@ -240,25 +249,28 @@ " get_constant_from_db_and_time,\n", " karabo_id=karabo_id,\n", " karabo_da=karabo_da,\n", - " condition=condition,\n", " cal_db_interface=cal_db_interface,\n", " creation_time=creation_time,\n", " timeout=cal_db_timeout,\n", " print_once=False,\n", " )\n", " offset_map, when[\"Offset\"] = retrieval_function(\n", + " condition=dark_condition,\n", " constant=Constants.jungfrau.Offset(),\n", - " empty_constant=np.zeros((1024, 512, 1, 3))\n", + " empty_constant=np.zeros((512, 1024, memory_cells, 3))\n", " )\n", " mask, when[\"BadPixelsDark\"] = retrieval_function(\n", + " condition=dark_condition,\n", " constant=Constants.jungfrau.BadPixelsDark(),\n", - " empty_constant=np.zeros((1024, 512, 1, 3)),\n", + " empty_constant=np.zeros((512, 1024, memory_cells, 3), dtype=np.uint32),\n", " )\n", " mask_ff, when[\"BadPixelsFF\"] = retrieval_function(\n", + " condition=gain_condition,\n", " constant=Constants.jungfrau.BadPixelsFF(),\n", " empty_constant=None\n", " )\n", " gain_map, when[\"Gain\"] = retrieval_function(\n", + " condition=gain_condition,\n", " constant=Constants.jungfrau.RelativeGain(),\n", " empty_constant=None\n", " )\n", @@ -267,20 +279,22 @@ " if mask_ff is not None:\n", " mask |= np.moveaxis(mask_ff, 0, 1)\n", "\n", - " # move from x,y,cell,gain to cell,x,y,gain\n", - " offset_map = np.squeeze(offset_map)\n", - " mask = np.squeeze(mask)\n", - "\n", " if memory_cells > 1:\n", - " offset_map = np.moveaxis(np.moveaxis(offset_map, 0, 2), 0, 2)\n", - " mask = np.moveaxis(np.moveaxis(mask, 0, 2), 0, 2)\n", + " # move from x, y, cell, gain to cell, x, y, gain\n", + " offset_map = np.moveaxis(offset_map, [0, 1], [1, 2])\n", + " mask = np.moveaxis(mask, [0, 1], [1, 2])\n", + " else:\n", + " offset_map = np.squeeze(offset_map)\n", + " mask = np.squeeze(mask)\n", "\n", " if gain_map is not None:\n", " if memory_cells > 1:\n", - " gain_map = np.moveaxis(np.moveaxis(gain_map, 0, 2), 0, 1)\n", + " gain_map = np.moveaxis(gain_map, [0, 2], [2, 0])\n", + " # add extra empty cell constant\n", + " b = np.ones(((1,)+gain_map.shape[1:]))\n", + " gain_map = np.concatenate((gain_map, b), axis=0)\n", " else:\n", - " gain_map = np.squeeze(gain_map)\n", - " gain_map = np.moveaxis(gain_map, 1, 0)\n", + " gain_map = np.moveaxis(np.squeeze(gain_map), 1, 0)\n", "\n", " return offset_map, mask, gain_map, karabo_da, when\n", "\n", @@ -309,28 +323,27 @@ "source": [ "# Correct a chunk of images for offset and gain\n", "def correct_train(wid, index, d):\n", - " d = d.astype(np.float32) # [2, x, y]\n", + " d = d.astype(np.float32) # [cells, x, y]\n", " g = gain[index]\n", - " m = memcells[index]\n", "\n", + " # Jungfrau gains 0[00], 1[01], 3[11]\n", " g[g==3] = 2\n", "\n", - " if 0 <= index < plt_images:\n", - " r_data[index, ...] = d[0, ...]\n", - " if memory_cells == 1:\n", - " g_data[index, ...] = g\n", - " else:\n", - " g_data[index, ...] = g[0, ...]\n", " # Select memory cells\n", - " \n", - " # TODO: This needs to be revisited.\n", - " # As this result in copying data to a new array on every train,\n", - " # even when there's the same pattern of memory cells on every train.\n", " if memory_cells > 1:\n", - " m[m>16] = 0 # TODO: this is wrong and needs to be updated with burst mode.\n", - " # For an invalid image a memory cell of 255 is set.\n", - " # These images doesn't need to be processed.\n", - " offset_map_cell = offset_map[m, ...]\n", + " \"\"\"\n", + " Even though it is correct to assume that memory cells pattern \n", + " can be the same across all trains (for one correction run\n", + " taken with one acquisition), it is preferred to not assume\n", + " this to account for exceptions that can happen.\n", + " \"\"\"\n", + " m = memcells[index].copy()\n", + " # 255 is a cell value pointing to no cell image data (image of 0 pixels).\n", + " # Corresponding image will be corrected with constant of cell 0. To avoid values of 0.\n", + " # This line is depending on not storing the modified memory cells in the corrected data.\n", + " m[m==255] = 0\n", + "\n", + " offset_map_cell = offset_map[m, ...] # [16 + empty cell, x, y]\n", " mask_cell = mask[m, ...]\n", " else:\n", " offset_map_cell = offset_map\n", @@ -338,6 +351,7 @@ "\n", " # Offset correction\n", " offset = np.choose(g, np.moveaxis(offset_map_cell, -1, 0))\n", + "\n", " d -= offset\n", "\n", " # Gain correction\n", @@ -347,7 +361,6 @@ " else:\n", " gain_map_cell = gain_map\n", " cal = np.choose(g, np.moveaxis(gain_map_cell, -1, 0))\n", - "\n", " d /= cal\n", "\n", " msk = np.choose(g, np.moveaxis(mask_cell, -1, 0))\n", @@ -379,9 +392,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, + "metadata": {}, "outputs": [], "source": [ "fim_data = {}\n", @@ -432,14 +443,6 @@ " offset_map, mask, gain_map = constants[local_karabo_da]\n", "\n", " # Allocate shared arrays.\n", - "\n", - " # Shared arrays for plotting.\n", - " g_data = context.alloc(\n", - " shape=(plt_images, dshape[2], dshape[3]), dtype=np.uint8)\n", - " r_data = context.alloc(\n", - " shape=(plt_images, dshape[2], dshape[3]), dtype=np.float32)\n", - "\n", - " # shared arrays for corrected data.\n", " data_corr = context.alloc(shape=dshape, dtype=np.float32)\n", " mask_corr = context.alloc(shape=dshape, dtype=np.uint32)\n", "\n", @@ -451,6 +454,8 @@ " gain = seq_dc[instrument_src_kda, \"data.gain\"].ndarray()\n", " memcells = seq_dc[instrument_src_kda, \"data.memoryCell\"].ndarray()\n", "\n", + " rim_data[local_karabo_da] = data[:plt_images, 0, ...].copy()\n", + "\n", " context.map(correct_train, data)\n", " step_timer.done_step(f'Correction time.')\n", "\n", @@ -482,12 +487,13 @@ "\n", " # Prepare plotting arrays\n", " step_timer.start()\n", + "\n", " # TODO: Print out which cell is being selected for plotting\n", " fim_data[local_karabo_da] = data_corr[:plt_images, 0, ...].copy()\n", " msk_data[local_karabo_da] = mask_corr[:plt_images, 0, ...].copy()\n", - " gim_data[local_karabo_da] = g_data\n", - " rim_data[local_karabo_da] = r_data\n", - " step_timer.done_step(f'Preparing plotting data of {plt_images} images:.')" + " gim_data[local_karabo_da] = gain[:plt_images, 0, ...].copy()\n", + "\n", + " step_timer.done_step(f'Preparing plotting data of {plt_images} images.')" ] }, { @@ -552,7 +558,7 @@ " do_2d_plot(\n", " h, (ex, ey),\n", " \"Signal (ADU)\",\n", - " \"Gain Bit Value\",\n", + " \"Gain Bit Value (high gain=0[00], medium gain=1[01], low gain=3[11])\",\n", " f\"Module {mod}\")" ] }, diff --git a/notebooks/Jungfrau/Jungfrau_dark_analysis_all_gains_burst_mode_NBC.ipynb b/notebooks/Jungfrau/Jungfrau_dark_analysis_all_gains_burst_mode_NBC.ipynb index f756d2cb3260ba34ff6cd488940e6a8e04017477..b90a78ed89c02977a660005112e699aac5d4cc47 100644 --- a/notebooks/Jungfrau/Jungfrau_dark_analysis_all_gains_burst_mode_NBC.ipynb +++ b/notebooks/Jungfrau/Jungfrau_dark_analysis_all_gains_burst_mode_NBC.ipynb @@ -28,11 +28,8 @@ "karabo_id = 'SPB_IRDA_JF4M' # karabo_id (detector identifier) prefix of Jungfrau detector to process.\n", "karabo_id_control = '' # if control is on a different ID, set to empty string if it is the same a karabo-id\n", "receiver_template = 'JNGFR{:02}' # inset for receiver devices\n", - "receiver_control_id = \"CONTROL\" # inset for control devices\n", - "path_template = 'RAW-R{:04d}-{}-S{{:05d}}.h5' # template to use for file name, double escape sequence number\n", "instrument_source_template = '{}/DET/{}:daqOutput' # template for instrument source name (filled with karabo_id & receiver_id). e.g. 'SPB_IRDA_JF4M/DET/JNGFR01:daqOutput'\n", - "ctrl_source_template = '{}/DET/CONTROL' # template for control source name (filled with karabo_id).\n", - "karabo_da_control = 'JNGFRCTRL00' # file inset for control data\n", + "ctrl_source_template = '{}/DET/CONTROL' # template for control source name (filled with karabo_id_control)\n", "\n", "# Parameters for calibration database and storing constants.\n", "use_dir_creation_date = True # use dir creation date\n", @@ -45,19 +42,21 @@ "badpixel_threshold_sigma = 5. # bad pixels defined by values outside n times this std from median\n", "offset_abs_threshold_low = [1000, 10000, 10000] # absolute bad pixel threshold in terms of offset, lower values\n", "offset_abs_threshold_high = [8000, 15000, 15000] # absolute bad pixel threshold in terms of offset, upper values\n", - "max_trains = 500 # maximum trains to process darks from.\n", + "max_trains = 0 # Maximum trains to process darks. Set to 0 to process all available train images.\n", "min_trains = 1 # Minimum number of trains that should be available to process dark constants. Default 1.\n", "manual_slow_data = False # if true, use manually entered bias_voltage and integration_time values\n", "time_limits = 0.025 # to find calibration constants later on, the integration time is allowed to vary by 0.5 us\n", "\n", "# Parameters to be used for injecting dark calibration constants.\n", "integration_time = 1000 # integration time in us, will be overwritten by value in file\n", - "gain_setting = 0 # 0 for dynamic, forceswitchg1, forceswitchg2, 1 for dynamichg0, fixedgain1, fixgain2. Will be overwritten by value in file\n", + "gain_setting = 0 # 0 for dynamic, forceswitchg1, forceswitchg2, 1 for dynamichg0, fixgain1, fixgain2. Will be overwritten by value in file\n", + "gain_mode = 0 # 1 if medium and low runs are fixgain1 and fixgain2, otherwise 0. It will be overwritten by value in file, if manual_slow_data\n", "bias_voltage = 90 # sensor bias voltage in V, will be overwritten by value in file\n", "memory_cells = 16 # number of memory cells\n", "\n", - "# Don't remove. myMDC sends this by default.\n", - "operation_mode = '' # Detector operation mode, optional\n", + "# TODO: this is used for only Warning check at AGIPD dark.\n", + "# Need to rethink if it makes sense to use it here as well.\n", + "operation_mode = 'ADAPTIVE_GAIN' # Detector operation mode, optional\n", "\n", "# TODO: Remove\n", "db_module = [\"\"] # ID of module in calibration database. TODO: remove from calibration_configurations." @@ -75,12 +74,14 @@ "import os\n", "import warnings\n", "from pathlib import Path\n", - "\n", "warnings.filterwarnings('ignore')\n", "\n", "import matplotlib\n", "import matplotlib.pyplot as plt\n", + "import multiprocessing\n", "import numpy as np\n", + "import pasha as psh\n", + "from IPython.display import Markdown, display\n", "from extra_data import RunDirectory\n", "\n", "matplotlib.use('agg')\n", @@ -90,7 +91,7 @@ "from XFELDetAna.plotting.histogram import histPlot\n", "from cal_tools import jungfraulib, step_timing\n", "from cal_tools.ana_tools import save_dict_to_hdf5\n", - "from cal_tools.enums import BadPixels\n", + "from cal_tools.enums import BadPixels, JungfrauSettings\n", "from cal_tools.tools import (\n", " get_dir_creation_date,\n", " get_pdu_from_db,\n", @@ -113,6 +114,11 @@ "sensor_size = (1024, 512)\n", "gains = [0, 1, 2]\n", "\n", + "fixed_settings = [\n", + " JungfrauSettings.FIX_GAIN_1.value, JungfrauSettings.FIX_GAIN_2.value] # noqa\n", + "dynamic_settings = [\n", + " JungfrauSettings.FORCE_SWITCH_HG1.value, JungfrauSettings.FORCE_SWITCH_HG2.value] # noqa\n", + "\n", "creation_time = None\n", "if use_dir_creation_date:\n", " creation_time = get_dir_creation_date(in_folder, run_high)\n", @@ -122,8 +128,6 @@ "cal_db_interface = get_random_db_interface(cal_db_interface)\n", "print(f'Calibration database interface: {cal_db_interface}')\n", "\n", - "offset_abs_threshold = [offset_abs_threshold_low, offset_abs_threshold_high]\n", - "\n", "if karabo_id_control == \"\":\n", " karabo_id_control = karabo_id" ] @@ -157,29 +161,28 @@ "source": [ "step_timer.start()\n", "gain_runs = dict()\n", - "noise_map = dict()\n", - "offset_map = dict()\n", - "gain_str = None\n", + "\n", + "med_low_settings = []\n", + "\n", "ctrl_src = ctrl_source_template.format(karabo_id_control)\n", "\n", "for gain, run_n in enumerate(run_nums):\n", " run_dc = RunDirectory(f\"{in_folder}/r{run_n:04d}/\")\n", " gain_runs[run_n] = [gain, run_dc]\n", - "\n", + " ctrl_data = jungfraulib.JungfrauCtrl(run_dc, ctrl_src)\n", + " run_settings = ctrl_data.run_settings.value if ctrl_data.run_settings else ctrl_data.run_settings # noqa\n", " # Read control data for the high gain run only.\n", " if run_n == run_high:\n", - " ctrl_data = jungfraulib.JungfrauCtrl(run_dc, ctrl_src)\n", + "\n", " run_mcells, sc_start = ctrl_data.get_memory_cells()\n", "\n", " if not manual_slow_data:\n", " integration_time = ctrl_data.get_integration_time()\n", " bias_voltage = ctrl_data.get_bias_voltage()\n", - " gain_str, gain_setting = ctrl_data.get_gain_setting()\n", - "\n", - " print(f\"Gain setting is {gain_setting} ({gain_str})\")\n", - " print(f\"Integration time is {integration_time} us\")\n", - " print(f\"Bias voltage is {bias_voltage} V\")\n", - "\n", + " gain_setting = ctrl_data.get_gain_setting()\n", + " print(f\"Gain setting is {gain_setting} ({run_settings})\")\n", + " print(f\"Integration time is {integration_time} us\")\n", + " print(f\"Bias voltage is {bias_voltage} V\")\n", " if run_mcells == 1:\n", " memory_cells = 1\n", " print('Dark runs in single cell mode, '\n", @@ -188,21 +191,76 @@ " memory_cells = 16\n", " print('Dark runs in burst mode, '\n", " f'storage cell start: {sc_start:02d}')\n", - "step_timer.done_step(f'Reading control data.')\n", - "# Initialize noise_map and offset_map module arrays.\n", - "for mod in karabo_da:\n", - " noise_map[mod] = np.zeros(sensor_size+(memory_cells, 3))\n", - " offset_map[mod] = np.zeros(sensor_size+(memory_cells, 3))" + " else:\n", + " gain_mode = ctrl_data.get_gain_mode()\n", + "\n", + " med_low_settings.append(run_settings)\n", + "\n", + "# A transperent workaround for old raw data with wrong/missing medium and low settings\n", + "if med_low_settings == [None, None]:\n", + " print(\"WARNING: run.settings is not stored in the data to read. \"\n", + " f\"Hence assuming gain_mode = {gain_mode} for adaptive old data.\")\n", + "elif med_low_settings == [\"dynamicgain\", \"forceswitchg1\"]:\n", + " print(f\"WARNING: run.settings for medium and low gain runs are wrong {med_low_settings}. \"\n", + " f\"This is an expected bug for old raw data. Setting gain_mode to {gain_mode}.\")\n", + "# Validate that low_med_settings is not a mix of adaptive and fixed settings.\n", + "elif not (sorted(med_low_settings) in [fixed_settings, dynamic_settings]): # noqa\n", + " raise ValueError(\n", + " \"Medium and low run settings are not as expected. \"\n", + " f\"Either {dynamic_settings} or {fixed_settings} are expected.\\n\"\n", + " f\"Got {sorted(med_low_settings)} for both runs, respectively.\")\n", + "\n", + "print(f\"Gain mode is {gain_mode} ({med_low_settings})\")\n", + "\n", + "step_timer.done_step(f'Reading control data.')" ] }, { "cell_type": "code", "execution_count": null, - "metadata": { - "tags": [] - }, + "metadata": {}, + "outputs": [], + "source": [ + "# Use only high gain threshold for all gains in case of fixed_gain.\n", + "\n", + "if gain_mode: # fixed_gain\n", + " offset_abs_threshold = [[offset_abs_threshold_low[0]]*3, [offset_abs_threshold_high[0]]*3]\n", + "else:\n", + " offset_abs_threshold = [offset_abs_threshold_low, offset_abs_threshold_high]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "context = psh.context.ThreadContext(num_workers=multiprocessing.cpu_count())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ + "\"\"\"\n", + "All jungfrau runs are taken through one acquisition, except for the forceswitch runs.\n", + "While taking non-fixed dark runs, a procedure of multiple acquisitions is used to switch the storage cell indices.\n", + "\n", + "This is done for medium and low gain dark dynamic runs, only [forceswitchg1, forceswitchg2]:\n", + "Switching the cell indices in burst mode is a work around for hardware procedure\n", + "deficiency that produces wrong data for dark runs except for the first storage cell.\n", + "This is why multiple acquisitions are taken to switch the used storage cells and\n", + "acquire data through two cells for each of the 16 cells instead of acquiring darks through all 16 cells.\n", + "\"\"\"\n", + "\n", + "print(f\"Maximum trains to process is set to {max_trains}\")\n", + "\n", + "noise_map = dict()\n", + "offset_map = dict()\n", + "bad_pixels_map = dict()\n", + "\n", "for mod in karabo_da:\n", " step_timer.start()\n", " instrument_src = instrument_source_template.format(\n", @@ -210,8 +268,23 @@ "\n", " print(f\"\\n- Instrument data path for {mod} is {instrument_src}.\")\n", "\n", + " offset_map[mod] = context.alloc(shape=(sensor_size+(memory_cells, 3)), fill=0)\n", + " noise_map[mod] = context.alloc(like=offset_map[mod], fill=0)\n", + " bad_pixels_map[mod] = context.alloc(like=offset_map[mod], dtype=np.uint32, fill=0)\n", + "\n", " for run_n, [gain, run_dc] in gain_runs.items():\n", "\n", + " def process_cell(worker_id, array_index, cell_number):\n", + " cell_slice_idx = acelltable == cell_number\n", + " thiscell = images[..., cell_slice_idx]\n", + "\n", + " offset_map[mod][..., cell_number, gain] = np.mean(thiscell, axis=2)\n", + " noise_map[mod][..., cell_number, gain] = np.std(thiscell, axis=2)\n", + "\n", + " # Check if there are wrong bad gain values.\n", + " # Indicate pixels with wrong gain value across all trains for each cell.\n", + " bad_pixels_map[mod][\n", + " np.average(gain_vals[..., cell_slice_idx], axis=2) != raw_g] |= BadPixels.WRONG_GAIN_VALUE.value\n", " print(f\"Gain stage {gain}, run {run_n}\")\n", "\n", " # load shape of data for memory cells, and detector size (imgs, cells, x, y)\n", @@ -219,14 +292,14 @@ " # load number of data available, including trains with empty data.\n", " n_trains = len(run_dc.train_ids)\n", " instr_dc = run_dc.select(instrument_src, require_all=True)\n", - " if n_trains-n_imgs != 0:\n", + " empty_trains = n_trains - n_imgs\n", + " if empty_trains != 0:\n", " print(\n", - " f\"\\tWARNING: {Path(run_dc.files[0].filename).name} has {n_trains-n_imgs} \" # noqa\n", - " f\"trains with empty data out of {n_trains} trains.\")\n", - "\n", + " f\"\\tWARNING: {mod} has {empty_trains} trains with empty data out of {n_trains} trains at \" # noqa\n", + " f\"{Path(run_dc[instrument_src, 'data.adc'].files[0].filename).parent}.\")\n", " if max_trains > 0:\n", " n_imgs = min(n_imgs, max_trains)\n", - " print(f\"Processing {n_imgs} images based on the given max_trains: {max_trains}.\") # noqa\n", + " print(f\"Processing {n_imgs} images.\")\n", " # Select only requested number of images to process darks.\n", " instr_dc = instr_dc.select_trains(np.s_[:n_imgs])\n", "\n", @@ -236,20 +309,28 @@ " \" Not enough data to process darks.\")\n", "\n", " images = np.transpose(\n", - " instr_dc[instrument_src, \"data.adc\"].ndarray(), (3, 2, 1, 0))\n", + " instr_dc[instrument_src, \"data.adc\"].ndarray().astype(np.float64), (3, 2, 1, 0))\n", " acelltable = np.transpose(instr_dc[instrument_src, \"data.memoryCell\"].ndarray())\n", + " gain_vals = np.transpose(\n", + " instr_dc[instrument_src, \"data.gain\"].ndarray(), (3, 2, 1, 0))\n", + "\n", + " # define gain value as saved in raw gain map\n", + " raw_g = 3 if gain == 2 else gain\n", "\n", " if memory_cells == 1:\n", " acelltable -= sc_start\n", - " if gain > 0 and memory_cells == 16:\n", - " # 255 is used as the detector sets 255 as well for\n", - " # cell images identified as bad.\n", - " acelltable[1:] = 255 \n", + " # Only for dynamic medium and low gain runs [forceswitchg1, forceswitchg2] in burst mode.\n", + "\n", + " if gain_mode == 0 and gain > 0 and memory_cells == 16:\n", + " # 255 similar to the receiver which uses the 255\n", + " # value to indicate a cell without an image.\n", + " # image shape for forceswitchg1 and forceswitchg2 = (1024, 512, 2, trains)\n", + " # compared to expected shape of (1024, 512, 16, trains) for high gain run.\n", + " acelltable[1:] = 255\n", + "\n", + " # Calculate offset and noise maps\n", + " context.map(process_cell, range(memory_cells))\n", "\n", - " for cell in range(memory_cells):\n", - " thiscell = images[..., acelltable == cell]\n", - " noise_map[mod][..., cell, gain] = np.std(thiscell, axis=2)\n", - " offset_map[mod][..., cell, gain] = np.mean(thiscell, axis=2)\n", " step_timer.done_step(f'Creating Offset and noise constants for a module.')" ] }, @@ -371,11 +452,12 @@ "outputs": [], "source": [ "def print_bp_entry(bp):\n", - " print(\"{:<30s} {:032b}\".format(bp.name, bp.value))\n", + " print(\"{:<30s} {:032b} -> {}\".format(bp.name, bp.value, int(bp.value)))\n", "\n", "print_bp_entry(BadPixels.OFFSET_OUT_OF_THRESHOLD)\n", "print_bp_entry(BadPixels.NOISE_OUT_OF_THRESHOLD)\n", "print_bp_entry(BadPixels.OFFSET_NOISE_EVAL_ERROR)\n", + "print_bp_entry(BadPixels.WRONG_GAIN_VALUE)\n", "\n", "def eval_bpidx(d):\n", "\n", @@ -395,13 +477,12 @@ "outputs": [], "source": [ "step_timer.start()\n", - "bad_pixels_map = dict()\n", "\n", "for mod in karabo_da:\n", - " bad_pixels_map[mod] = np.zeros(noise_map[mod].shape, np.uint32)\n", + " display(Markdown(f\"### Badpixels for module {mod}:\"))\n", " offset_abs_threshold = np.array(offset_abs_threshold)\n", "\n", - " bad_pixels_map[mod][eval_bpidx(offset_map[mod])] = BadPixels.OFFSET_OUT_OF_THRESHOLD.value\n", + " bad_pixels_map[mod][eval_bpidx(offset_map[mod])] |= BadPixels.OFFSET_OUT_OF_THRESHOLD.value\n", "\n", " bad_pixels_map[mod][~np.isfinite(offset_map[mod])] |= BadPixels.OFFSET_NOISE_EVAL_ERROR.value\n", "\n", @@ -420,7 +501,8 @@ " x_label=\"Column\",\n", " lut_label=f\"Badpixels {g_name[g_idx]} [ADCu]\",\n", " aspect=1.,\n", - " vmin=0, title=f'G{g_idx} Bad pixel map - Cell {cell:02d} - Module {mod}')\n", + " vmin=0, vmax=5,\n", + " title=f'G{g_idx} Bad pixel map - Cell {cell:02d} - Module {mod}')\n", "step_timer.done_step(f'Creating bad pixels constant and plotting it for a module.')" ] }, @@ -435,7 +517,9 @@ " memory_cells=memory_cells,\n", " bias_voltage=bias_voltage,\n", " integration_time=integration_time,\n", - " gain_setting=gain_setting)\n", + " gain_setting=gain_setting,\n", + " gain_mode=gain_mode,\n", + ")\n", "\n", "db_modules = get_pdu_from_db(\n", " karabo_id=karabo_id,\n", @@ -511,7 +595,19 @@ " f\"• Memory cells: {memory_cells}\\n\"\n", " f\"• Integration time: {integration_time}\\n\"\n", " f\"• Gain setting: {gain_setting}\\n\"\n", - " f\"• Creation time: {md.calibration_constant_version.begin_at if md is not None else creation_time}\\n\") # noqa" + " f\"• Gain mode: {gain_mode}\\n\"\n", + " f\"• Creation time: {md.calibration_constant_version.begin_at if md is not None else creation_time}\\n\") # noqa\n", + "step_timer.done_step(\"Injecting constants.\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Total processing time {step_timer.timespan():.01f} s\")\n", + "step_timer.print_summary()" ] } ], @@ -531,7 +627,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.12" } }, "nbformat": 4, diff --git a/setup.py b/setup.py index 926dde6e6b04e67518b2151e5e596e04fd54f4c7..60090e3420b1fdcbd78e5bc250c44452ab17bab2 100644 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ install_requires = [ if "readthedocs.org" not in sys.executable: install_requires += [ - "iCalibrationDB @ git+ssh://git@git.xfel.eu:10022/detectors/cal_db_interactive.git@2.0.9", # noqa + "iCalibrationDB @ git+ssh://git@git.xfel.eu:10022/detectors/cal_db_interactive.git@2.1.0", # noqa "XFELDetectorAnalysis @ git+ssh://git@git.xfel.eu:10022/karaboDevices/pyDetLib.git@2.7.0", # noqa ] diff --git a/src/cal_tools/enums.py b/src/cal_tools/enums.py index e02d59994925aa5530d265ff944839fb160c9613..a9bfc0182f4972c41259992b1e31cd50b04fba7d 100644 --- a/src/cal_tools/enums.py +++ b/src/cal_tools/enums.py @@ -5,28 +5,28 @@ class BadPixels(IntFlag): """ The European XFEL Bad Pixel Encoding """ - OFFSET_OUT_OF_THRESHOLD = 0b000000000000000000001 # bit 1 - NOISE_OUT_OF_THRESHOLD = 0b000000000000000000010 # bit 2 - OFFSET_NOISE_EVAL_ERROR = 0b000000000000000000100 # bit 3 - NO_DARK_DATA = 0b000000000000000001000 # bit 4 - CI_GAIN_OF_OF_THRESHOLD = 0b000000000000000010000 # bit 5 - CI_LINEAR_DEVIATION = 0b000000000000000100000 # bit 6 - CI_EVAL_ERROR = 0b000000000000001000000 # bit 7 - FF_GAIN_EVAL_ERROR = 0b000000000000010000000 # bit 8 - FF_GAIN_DEVIATION = 0b000000000000100000000 # bit 9 - FF_NO_ENTRIES = 0b000000000001000000000 # bit 10 - CI2_EVAL_ERROR = 0b000000000010000000000 # bit 11 - VALUE_IS_NAN = 0b000000000100000000000 # bit 12 - VALUE_OUT_OF_RANGE = 0b000000001000000000000 # bit 13 - GAIN_THRESHOLDING_ERROR = 0b000000010000000000000 # bit 14 - DATA_STD_IS_ZERO = 0b000000100000000000000 # bit 15 - ASIC_STD_BELOW_NOISE = 0b000001000000000000000 # bit 16 - INTERPOLATED = 0b000010000000000000000 # bit 17 - NOISY_ADC = 0b000100000000000000000 # bit 18 - OVERSCAN = 0b001000000000000000000 # bit 19 - NON_SENSITIVE = 0b010000000000000000000 # bit 20 - NON_LIN_RESPONSE_REGION = 0b100000000000000000000 # bit 21 - + OFFSET_OUT_OF_THRESHOLD = 1 << 0 # bit 1 + NOISE_OUT_OF_THRESHOLD = 1 << 1 # bit 2 + OFFSET_NOISE_EVAL_ERROR = 1 << 2 # bit 3 + NO_DARK_DATA = 1 << 3 # bit 4 + CI_GAIN_OF_OF_THRESHOLD = 1 << 4 # bit 5 + CI_LINEAR_DEVIATION = 1 << 5 # bit 6 + CI_EVAL_ERROR = 1 << 6 # bit 7 + FF_GAIN_EVAL_ERROR = 1 << 7 # bit 8 + FF_GAIN_DEVIATION = 1 << 8 # bit 9 + FF_NO_ENTRIES = 1 << 9 # bit 10 + CI2_EVAL_ERROR = 1 << 10 # bit 11 + VALUE_IS_NAN = 1 << 11 # bit 12 + VALUE_OUT_OF_RANGE = 1 << 12 # bit 13 + GAIN_THRESHOLDING_ERROR = 1 << 13 # bit 14 + DATA_STD_IS_ZERO = 1 << 14 # bit 15 + ASIC_STD_BELOW_NOISE = 1 << 15 # bit 16 + INTERPOLATED = 1 << 16 # bit 17 + NOISY_ADC = 1 << 17 # bit 18 + OVERSCAN = 1 << 18 # bit 19 + NON_SENSITIVE = 1 << 19 # bit 20 + NON_LIN_RESPONSE_REGION = 1 << 20 # bit 21 + WRONG_GAIN_VALUE = 1 << 21 # bit 22 class SnowResolution(Enum): """ An Enum specifying how to resolve snowy pixels @@ -42,3 +42,13 @@ class AgipdGainMode(IntEnum): FIXED_HIGH_GAIN = 1 FIXED_MEDIUM_GAIN = 2 FIXED_LOW_GAIN = 3 + + +class JungfrauSettings(Enum): + """ Jungfrau run gain settings.""" + DYNAMIC_GAIN = "dynamicgain" + DYNAMIC_GAIN_HG0 = "dynamichg0" + FIX_GAIN_1 = "fixgain1" + FIX_GAIN_2 = "fixgain2" + FORCE_SWITCH_HG1 = "forceswitchg1" + FORCE_SWITCH_HG2 = "forceswitchg2" diff --git a/src/cal_tools/jungfraulib.py b/src/cal_tools/jungfraulib.py index ec04f2414b8fad34482e05b78e7ff2e0b2e9b96e..402888dfa58068ccd173800834dfd165c206fcdc 100644 --- a/src/cal_tools/jungfraulib.py +++ b/src/cal_tools/jungfraulib.py @@ -1,3 +1,19 @@ +from typing import Tuple + +from cal_tools.enums import JungfrauSettings + + +def _get_settings(run_dc, ctrl_src) -> str: + try: + return(JungfrauSettings( + run_dc.get_run_value(ctrl_src, "settings.value"))) + except KeyError: + print( + "WARNING: \'settings.value\' key is not available at " + f"{run_dc.select(ctrl_src).files[0].filename},\n") + return + + class JungfrauCtrl(): def __init__( self, @@ -7,37 +23,40 @@ class JungfrauCtrl(): """Read slow data from RUN source. :param run_dir: EXtra-data RunDirectory DataCollection object. - :param karabo_id_control: Karabo ID for control h5file with slow data. + :param ctrl_src: Control source name for accessing run slow data. """ self.run_dc = run_dc self.ctrl_src = ctrl_src + self.run_settings = _get_settings(run_dc, ctrl_src) - def get_memory_cells(self): + def get_memory_cells(self) -> Tuple[int, int]: n_storage_cells = int(self.run_dc.get_run_value( self.ctrl_src, "storageCells.value")) + 1 storage_cell_st = int(self.run_dc.get_run_value( self.ctrl_src, "storageCellStart.value")) return n_storage_cells, storage_cell_st - def get_bias_voltage(self): + def get_bias_voltage(self) -> int: return(int(self.run_dc.get_run_value( self.ctrl_src, "vHighVoltage.value")[0])) - def get_integration_time(self): + def get_integration_time(self) -> float: return(float(self.run_dc.get_run_value( self.ctrl_src, "exposureTime.value")) * 1e6) - def get_gain_setting(self): - try: - gain_str = str(self.run_dc.get_run_value( - self.ctrl_src, "settings.value")) - except KeyError: - print( - "gain_setting is not available for h5 ctrl path " - f"/RUN/{self.karabo_id_control}/DET/CONTROL/settings/value,\n" - "WARNING: Setting gain_setting to 0," - "assuming that this is an old run.\n" - ) - gain_str = "KeyError" - gain_setting = 1 if gain_str == 'dynamichg0' else 0 # 'dynamicgain' - return gain_str, gain_setting \ No newline at end of file + def get_gain_setting(self) -> int: + if self.run_settings == JungfrauSettings.DYNAMIC_GAIN_HG0: + gain_setting = 1 + else: # JungfrauSettings.DYNAMIC_GAIN + gain_setting = 0 + if self.run_settings != JungfrauSettings.DYNAMIC_GAIN: + print( + "WARNING: Setting gain_setting to 0, " + "assuming that this is an old run.\n") + return gain_setting + + def get_gain_mode(self) -> int: + if self.run_settings in [JungfrauSettings.FIX_GAIN_1, JungfrauSettings.FIX_GAIN_2]: # noqa + return 1 + else: + return 0