diff --git a/notebooks/DynamicFF/Characterize_DynamicFF_NBC.ipynb b/notebooks/DynamicFF/Characterize_DynamicFF_NBC.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..9339eae9c535260072335592a462bbcff1d162e6 --- /dev/null +++ b/notebooks/DynamicFF/Characterize_DynamicFF_NBC.ipynb @@ -0,0 +1,351 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Characterization of dark and flat field for Dynamic Flat Field correction\n", + "\n", + "Author: Egor Sobolev\n", + "\n", + "Computation of dark offsets and flat-field principal components" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "in_folder = \"/gpfs/exfel/exp/SPB/202430/p900425/raw\" # input folder, required\n", + "out_folder = '/gpfs/exfel/data/scratch/esobolev/test/shimadzu' # output folder, required\n", + "metadata_folder = \"\" # Directory containing calibration_metadata.yml when run by xfel-calibrate\n", + "run_high = 1 # run number in which dark data was recorded, required\n", + "run_low = 2 # run number in which flat-field data was recorded, required\n", + "operation_mode = \"PCA_DynamicFF\" # Detector operation mode, optional (defaults to \"PCA_DynamicFF\")\n", + "\n", + "# Data files parameters.\n", + "karabo_da = ['-1'] # data aggregators\n", + "karabo_id = \"SPB_MIC_HPVX2\" # karabo prefix of Shimadzu HPV-X2 devices\n", + "\n", + "# Database access parameters.\n", + "cal_db_interface = \"tcp://max-exfl-cal001:8021\" # Unused, calibration DB interface to use\n", + "cal_db_timeout = 30000 # Unused, calibration DB timeout\n", + "db_output = False # if True, the notebook sends dark constants to the calibration database\n", + "local_output = True # if True, the notebook saves dark constants locally\n", + "\n", + "# Calibration constants parameters\n", + "n_components = 50 # Number of principal components of flat-field to compute (default: 50)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import datetime\n", + "import os\n", + "import warnings\n", + "from logging import warning\n", + "from shutil import copyfile\n", + "from tempfile import NamedTemporaryFile\n", + "\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "import time\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import display, Markdown\n", + "\n", + "from extra_data import RunDirectory\n", + "\n", + "%matplotlib inline\n", + "from cal_tools.step_timing import StepTimer\n", + "from cal_tools.tools import (\n", + " get_dir_creation_date,\n", + " run_prop_seq_from_path,\n", + " save_dict_to_hdf5\n", + ")\n", + "from cal_tools.restful_config import calibration_client, extra_calibration_client\n", + "from cal_tools.shimadzu import ShimadzuHPVX2\n", + "from cal_tools.constants import write_ccv, inject_ccv\n", + "\n", + "import dynflatfield as dffc\n", + "from dynflatfield.draw import plot_images, plot_camera_image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "extra_calibration_client() # Configure CalibrationData.\n", + "\n", + "cc = calibration_client()\n", + "pdus = cc.get_all_phy_det_units_from_detector(\n", + " {\"detector_identifier\": karabo_id}) # TODO: Use creation_time for snapshot_at\n", + "\n", + "if not pdus[\"success\"]:\n", + " raise ValueError(\"Failed to retrieve PDUs\")\n", + "\n", + "detector_info = pdus['data'][0]['detector']\n", + "detector = ShimadzuHPVX2(detector_info[\"source_name_pattern\"])\n", + "\n", + "print(f\"Instrument {detector.instrument}\")\n", + "print(f\"Detector in use is {karabo_id}\")\n", + "\n", + "modules = {}\n", + "for pdu_no, pdu in enumerate(pdus[\"data\"]):\n", + " db_module = pdu[\"physical_name\"]\n", + " module = pdu[\"module_number\"]\n", + " da = pdu[\"karabo_da\"]\n", + " if karabo_da[0] != \"-1\" and da not in karabo_da:\n", + " continue\n", + "\n", + " instrument_source_name = detector.instrument_source(module)\n", + " print('-', da, db_module, module, instrument_source_name)\n", + "\n", + " modules[da] = dict(\n", + " db_module=db_module,\n", + " module=module,\n", + " raw_source_name=instrument_source_name,\n", + " pdu_no=pdu_no,\n", + " )\n", + "\n", + "constants = {}\n", + "\n", + "step_timer = StepTimer()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Offset map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "dark_run = run_high\n", + "dark_creation_time = get_dir_creation_date(in_folder, dark_run)\n", + "print(f\"Using {dark_creation_time} as creation time of Offset constant.\")\n", + "\n", + "for da, meta in modules.items():\n", + " source_name = detector.instrument_source(meta[\"module\"])\n", + " image_key = detector.image_key\n", + "\n", + " display(Markdown(f\"## {source_name}\"))\n", + "\n", + " # read\n", + " step_timer.start()\n", + " file_da, _, _ = da.partition('/')\n", + " dark_dc = RunDirectory(f\"{in_folder}/r{dark_run:04d}\",\n", + " include=f\"RAW-R{dark_run:04d}-{file_da}-S*.h5\")\n", + "\n", + " if source_name not in dark_dc.all_sources:\n", + " raise ValueError(f\"Could not find source {source_name} for module {da} in dark data\")\n", + "\n", + " dark_dc = dark_dc.select([(source_name, image_key)])\n", + " conditions = detector.conditions(dark_dc, meta[\"module\"])\n", + "\n", + " key_data = dark_dc[source_name, image_key]\n", + " images_dark = key_data.ndarray()\n", + " ntrain, npulse, ny, nx = images_dark.shape\n", + "\n", + " print(f\"N image: {ntrain * npulse} (ntrain: {ntrain}, npulse: {npulse})\")\n", + " print(f\"Image size: {ny} x {nx} px\")\n", + " step_timer.done_step(\"Read dark images\")\n", + "\n", + " # process\n", + " step_timer.start()\n", + " dark = dffc.process_dark(images_dark) # Amounts to a per-pixel mean right now.\n", + "\n", + " # put results in the dict\n", + " module_constants = constants.setdefault(meta[\"db_module\"], {})\n", + " module_constants[\"Offset\"] = dict(\n", + " conditions=conditions, data=dark, pdu_no=meta[\"pdu_no\"],\n", + " creation_time=dark_creation_time, dims=['ss', 'fs']\n", + " )\n", + " step_timer.done_step(\"Process dark images\")\n", + " display()\n", + "\n", + " # draw plots\n", + " step_timer.start()\n", + " plot_camera_image(dark)\n", + " plt.show()\n", + " step_timer.done_step(\"Draw offsets\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Flat-field PCA decomposition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "flat_run = run_low\n", + "flat_creation_time = get_dir_creation_date(in_folder, flat_run)\n", + "print(f\"Using {flat_creation_time} as creation time of DynamicFF constant.\")\n", + "\n", + "for da, meta in modules.items():\n", + " source_name = detector.instrument_source(meta[\"module\"])\n", + " image_key = detector.image_key\n", + "\n", + " display(Markdown(f\"## {source_name}\"))\n", + "\n", + " # read\n", + " step_timer.start()\n", + " file_da, _, _ = da.partition('/')\n", + " flat_dc = RunDirectory(f\"{in_folder}/r{flat_run:04d}\",\n", + " include=f\"RAW-R{flat_run:04d}-{file_da}-S*.h5\")\n", + "\n", + " if source_name not in flat_dc.all_sources:\n", + " raise ValueError(f\"Could not find source {source_name} for module {da} in flatfield data\")\n", + "\n", + " flat_dc = flat_dc.select([(source_name, image_key)])\n", + " conditions = detector.conditions(flat_dc, meta[\"module\"])\n", + "\n", + " dark = constants[meta[\"db_module\"]][\"Offset\"][\"data\"]\n", + " dark_conditions = constants[meta[\"db_module\"]][\"Offset\"][\"conditions\"]\n", + "\n", + " if conditions != dark_conditions:\n", + " raise ValueError(f\"The conditions for flat-field run {conditions}) do not match \"\n", + " f\"the dark run conditions ({dark_conditions}). Skip flat-field characterization.\")\n", + "\n", + " key_data = flat_dc[source_name][image_key]\n", + " images_flat = key_data.ndarray()\n", + " ntrain, npulse, ny, nx = images_flat.shape\n", + "\n", + " print(f\"N image: {ntrain * npulse} (ntrain: {ntrain}, npulse: {npulse})\")\n", + " print(f\"Image size: {ny} x {nx} px\")\n", + " step_timer.done_step(\"Read flat-field images\")\n", + "\n", + " # process\n", + " step_timer.start()\n", + " flat, components, explained_variance_ratio = dffc.process_flat(\n", + " images_flat, dark, n_components)\n", + " flat_data = np.concatenate([flat[None, ...], components])\n", + "\n", + " # put results in the dict\n", + " conditions = detector.conditions(flat_dc, meta[\"module\"])\n", + " module_constants = constants.setdefault(meta[\"db_module\"], {})\n", + " module_constants[\"DynamicFF\"] = dict(\n", + " conditions=conditions, data=flat_data, pdu_no=meta[\"pdu_no\"],\n", + " creation_time=flat_creation_time, dims=['component', 'ss', 'fs']\n", + " )\n", + " step_timer.done_step(\"Process flat-field images\")\n", + "\n", + " # draw plots\n", + " step_timer.start()\n", + " display(Markdown(\"### Average flat-field\"))\n", + " plot_camera_image(flat)\n", + " plt.show()\n", + "\n", + " display(Markdown(\"### Explained variance ratio\"))\n", + " fig, ax = plt.subplots(1, 1, figsize=(10,4), tight_layout=True)\n", + " ax.semilogy(explained_variance_ratio, 'o')\n", + " ax.set_xticks(np.arange(len(explained_variance_ratio)))\n", + " ax.set_xlabel(\"Component no.\")\n", + " ax.set_ylabel(\"Variance fraction\")\n", + " plt.show()\n", + "\n", + " display(Markdown(\"### The first principal components (up to 20)\"))\n", + " plot_images(components[:20], figsize=(13, 8))\n", + " plt.show()\n", + "\n", + " step_timer.done_step(\"Draw flat-field\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Calibration constants" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "step_timer.start()\n", + "\n", + "_, proposal, _ = run_prop_seq_from_path(in_folder)\n", + "\n", + "# Output Folder Creation:\n", + "if local_output:\n", + " os.makedirs(out_folder, exist_ok=True)\n", + "\n", + "for db_module, module_constants in constants.items():\n", + " for constant_name, constant in module_constants.items():\n", + " conditions = constant[\"conditions\"]\n", + " pdu = pdus[\"data\"][constant[\"pdu_no\"]]\n", + "\n", + " with NamedTemporaryFile() as tempf:\n", + " ccv_root = write_ccv(\n", + " tempf.name,\n", + " pdu['physical_name'], pdu['uuid'], pdu['detector_type']['name'],\n", + " constant_name, conditions, constant['creation_time'],\n", + " proposal, [dark_run, flat_run],\n", + " constant[\"data\"], constant['dims'])\n", + " \n", + " if db_output:\n", + " inject_ccv(tempf.name, ccv_root, metadata_folder)\n", + " \n", + " if local_output:\n", + " ofile = f\"{out_folder}/const_{constant_name}_{db_module}.h5\"\n", + " \n", + " if os.path.isfile(ofile):\n", + " print(f'File {ofile} already exists and will be overwritten')\n", + " \n", + " copyfile(tempf.name, ofile)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Total processing time {step_timer.timespan():.01f} s\")\n", + "step_timer.print_summary()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/notebooks/DynamicFF/Correct_DynamicFF_NBC.ipynb b/notebooks/DynamicFF/Correct_DynamicFF_NBC.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..3e6a1875c027ef1796d7f5687588becf8539fdf1 --- /dev/null +++ b/notebooks/DynamicFF/Correct_DynamicFF_NBC.ipynb @@ -0,0 +1,343 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dynamic Flat-field Offline Correction\n", + "\n", + "Author: Egor Sobolev\n", + "\n", + "Offline dynamic flat-field correction" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "in_folder = \"/gpfs/exfel/exp/SPB/202430/p900425/raw\" # input folder, required\n", + "out_folder =\"/gpfs/exfel/exp/SPB/202430/p900425/scratch/proc/r0003\" # output folder, required\n", + "metadata_folder = \"\" # Directory containing calibration_metadata.yml when run by xfel-calibrate\n", + "run = 3 # which run to read data from, required\n", + "\n", + "# Data files parameters.\n", + "karabo_da = ['-1'] # data aggregators\n", + "karabo_id = \"SPB_MIC_HPVX2\" # karabo prefix of Shimadzu HPV-X2 devices\n", + "\n", + "# Database access parameters.\n", + "cal_db_interface = \"tcp://max-exfl-cal001:8021\" # Unused, calibration DB interface to use\n", + "cal_db_timeout = 30000 # Unused, calibration DB timeout\n", + "\n", + "# Correction parameters\n", + "n_components = 20 # number of principal components of flat-field to use in correction\n", + "downsample_factors = [1, 1] # list of downsample factors for each image dimention (y, x)\n", + "\n", + "num_proc = 32 # number of processes running correction in parallel" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import h5py\n", + "import warnings\n", + "from logging import warning\n", + "\n", + "warnings.filterwarnings('ignore')\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "from IPython.display import display, Markdown\n", + "from datetime import datetime\n", + "\n", + "from extra_data import RunDirectory, by_id\n", + "\n", + "%matplotlib inline\n", + "from cal_tools.step_timing import StepTimer\n", + "from cal_tools.files import sequence_trains, DataFile\n", + "from cal_tools.tools import get_dir_creation_date\n", + "\n", + "from cal_tools.restful_config import calibration_client, extra_calibration_client\n", + "from cal_tools.calcat_interface2 import CalibrationData\n", + "from cal_tools.shimadzu import ShimadzuHPVX2\n", + "\n", + "from dynflatfield import (\n", + " DynamicFlatFieldCorrectionCython as DynamicFlatFieldCorrection,\n", + " FlatFieldCorrectionFileProcessor\n", + ")\n", + "from dynflatfield.draw import plot_images, plot_camera_image" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "creation_time = get_dir_creation_date(in_folder, run)\n", + "print(f\"Creation time is {creation_time}\")\n", + "\n", + "extra_calibration_client() # Configure CalibrationData API.\n", + "\n", + "cc = calibration_client()\n", + "pdus = cc.get_all_phy_det_units_from_detector(\n", + " {\"detector_identifier\": karabo_id}) # TODO: Use creation_time for snapshot_at\n", + "\n", + "if not pdus[\"success\"]:\n", + " raise ValueError(\"Failed to retrieve PDUs\")\n", + "\n", + "detector_info = pdus['data'][0]['detector']\n", + "detector = ShimadzuHPVX2(detector_info[\"source_name_pattern\"])\n", + "index_group = detector.image_index_group\n", + "image_key = detector.image_key\n", + "\n", + "print(f\"Instrument {detector.instrument}\")\n", + "print(f\"Detector in use is {karabo_id}\")\n", + "\n", + "modules = {}\n", + "for pdu in pdus[\"data\"]:\n", + " db_module = pdu[\"physical_name\"]\n", + " module = pdu[\"module_number\"]\n", + " da = pdu[\"karabo_da\"]\n", + " if karabo_da[0] != \"-1\" and da not in karabo_da:\n", + " continue\n", + "\n", + " instrument_source_name = detector.instrument_source(module)\n", + " corrected_source_name = detector.corrected_source(module)\n", + " print('-', da, db_module, module, instrument_source_name)\n", + " \n", + " modules[da] = dict(\n", + " db_module=db_module,\n", + " module=module,\n", + " raw_source_name=instrument_source_name,\n", + " corrected_source_name=corrected_source_name,\n", + " )\n", + "\n", + "step_timer = StepTimer()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Calibration constants" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "step_timer.start()\n", + "\n", + "dc = RunDirectory(f\"{in_folder}/r{run:04d}\")\n", + "conditions = detector.conditions(dc)\n", + "\n", + "caldata = CalibrationData.from_condition(\n", + " conditions, 'SPB_MIC_HPVX2', event_at=creation_time)\n", + "\n", + "aggregators = {}\n", + "corrections = {}\n", + "for da in modules:\n", + " try:\n", + " dark = caldata[\"Offset\", da].ndarray()\n", + " flat = caldata[\"DynamicFF\", da].ndarray()\n", + " \n", + " components = flat[1:][:n_components]\n", + " flat = flat[0]\n", + "\n", + " dffc = DynamicFlatFieldCorrection.from_constants(\n", + " dark, flat, components, downsample_factors)\n", + "\n", + " corrections[da] = dffc\n", + " \n", + " file_da, _, _ = da.partition('/')\n", + " aggregators.setdefault(file_da, []).append(da)\n", + " except (KeyError, FileNotFoundError):\n", + " warning(f\"Constants are not found for module {da}. \"\n", + " \"The module will not calibrated\")\n", + "\n", + "step_timer.done_step(\"Load calibration constants\") " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Correction" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Output Folder Creation:\n", + "os.makedirs(out_folder, exist_ok=True)\n", + "\n", + "report = []\n", + "for file_da, file_modules in aggregators.items():\n", + " dc = RunDirectory(f\"{in_folder}/r{run:04d}\", f\"RAW-R{run:04d}-{file_da}-S*.h5\")\n", + "\n", + " # build train IDs\n", + " train_ids = set()\n", + " process_modules = []\n", + " for da in file_modules:\n", + " instrument_source = modules[da][\"raw_source_name\"]\n", + " if instrument_source in dc.all_sources:\n", + " keydata = dc[instrument_source][image_key].drop_empty_trains()\n", + " train_ids.update(keydata.train_ids)\n", + " process_modules.append(da)\n", + " else:\n", + " print(f\"Source {instrument_source} for module {da} is missed\")\n", + " \n", + " train_ids = np.array(sorted(train_ids))\n", + " ts = dc.select_trains(by_id[train_ids]).train_timestamps().astype(np.uint64)\n", + "\n", + " # correct and write sequence files\n", + " for seq_id, train_mask in sequence_trains(train_ids, 200):\n", + " step_timer.start()\n", + " print('* sequence', seq_id)\n", + " seq_train_ids = train_ids[train_mask]\n", + " seq_timestamps = ts[train_mask]\n", + " dc_seq = dc.select_trains(by_id[seq_train_ids])\n", + " ntrains = len(seq_train_ids)\n", + "\n", + " # create output file\n", + " channels = [f\"{modules[da]['corrected_source_name']}/{index_group}\"\n", + " for da in process_modules]\n", + "\n", + " f = DataFile.from_details(out_folder, file_da, run, seq_id)\n", + " f.create_metadata(like=dc, instrument_channels=channels)\n", + " f.create_index(seq_train_ids, timestamps=seq_timestamps)\n", + "\n", + " # create file structure\n", + " seq_report = {}\n", + " file_datasets = {}\n", + " for da in process_modules:\n", + " instrument_source = modules[da][\"raw_source_name\"]\n", + " keydata = dc_seq[instrument_source][image_key].drop_empty_trains()\n", + " count = keydata.data_counts(labelled=False)\n", + " i = np.flatnonzero(count)\n", + " raw_images = keydata.select_trains(np.s_[i]).ndarray()\n", + "\n", + " # not pulse resolved\n", + " shape = keydata.shape\n", + " count = np.in1d(seq_train_ids, keydata.train_ids).astype(int)\n", + "\n", + " corrected_source = modules[da][\"corrected_source_name\"]\n", + " src = f.create_instrument_source(corrected_source)\n", + " src.create_index(index_group=count)\n", + "\n", + " # create key for images\n", + " ds_data = src.create_key(image_key, shape=shape, dtype=np.float32)\n", + " module_datasets = {image_key: ds_data}\n", + "\n", + " # create keys for image parameters\n", + " for key in detector.copy_keys:\n", + " keydata = dc_seq[instrument_source][key].drop_empty_trains()\n", + " module_datasets[key] = (keydata, src.create_key(\n", + " key, shape=keydata.shape, dtype=keydata.dtype))\n", + "\n", + " file_datasets[da] = module_datasets\n", + "\n", + " step_timer.done_step(\"Create output file\")\n", + "\n", + " # correct and write data to file\n", + " for da in process_modules:\n", + " step_timer.start()\n", + " dc_seq = dc.select_trains(by_id[seq_train_ids])\n", + "\n", + " dffc = corrections[da]\n", + " instrument_source = modules[da][\"raw_source_name\"]\n", + " proc = FlatFieldCorrectionFileProcessor(dffc, num_proc, instrument_source, image_key)\n", + "\n", + " proc.start_workers()\n", + " proc.run(dc_seq)\n", + " proc.join_workers()\n", + "\n", + " # not pulse resolved\n", + " corrected_images = np.stack(proc.rdr.results, 0)\n", + " file_datasets[da][image_key][:] = corrected_images\n", + "\n", + " # copy image parameters\n", + " for key in detector.copy_keys:\n", + " keydata, ds = file_datasets[da][key]\n", + " ds[:] = keydata.ndarray()\n", + "\n", + " seq_report[da] = (raw_images[0, 0], corrected_images[:20, 0])\n", + " step_timer.done_step(\"Correct flat-field\")\n", + "\n", + " f.close()\n", + " report.append(seq_report)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "scrolled": false + }, + "outputs": [], + "source": [ + "step_timer.start()\n", + "if report:\n", + " for da, (raw_image, corrected_images) in report[0].items():\n", + " source = modules[da][\"raw_source_name\"]\n", + " display(Markdown(f\"## {source}\"))\n", + "\n", + " display(Markdown(\"### The first raw image\"))\n", + " plot_camera_image(raw_images[0, 0])\n", + " plt.show()\n", + "\n", + " display(Markdown(\"### The first corrected image\"))\n", + " plot_camera_image(corrected_images[0])\n", + " plt.show()\n", + "\n", + " display(Markdown(\"### The first corrected images in the trains (up to 20)\"))\n", + " plot_images(corrected_images, figsize=(13, 8))\n", + " plt.show()\n", + "\n", + "step_timer.done_step(\"Draw images\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Total processing time {step_timer.timespan():.01f} s\")\n", + "step_timer.print_summary()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/setup.py b/setup.py index 9177c82c32ff8cceab356c6a180d548940c27b5f..84a7bfdca72eb808eab9349b1fa2809ad30997b8 100644 --- a/setup.py +++ b/setup.py @@ -62,6 +62,7 @@ install_requires = [ "dill==0.3.0", "docutils==0.17.1", "dynaconf==3.1.4", + "dynflatfield==1.0.0", "env_cache==0.1", "extra_data==1.15.1", "extra_geom==1.10.0", diff --git a/src/cal_tools/calcat_interface2.py b/src/cal_tools/calcat_interface2.py index 9048db129d60e69991e63edd14f659dc902643f7..bc598d5726741c11a0a9a01983b640687702e7dc 100644 --- a/src/cal_tools/calcat_interface2.py +++ b/src/cal_tools/calcat_interface2.py @@ -837,3 +837,13 @@ class DSSCConditions(ConditionsBase): "Offset": _params, "Noise": _params, } + + +@dataclass +class ShimadzuHPVX2Conditions(ConditionsBase): + burst_frame_count: float + + calibration_types = { + 'Offset': ['Burst Frame Count'], + 'DynamicFF': ['Burst Frame Count'], + } diff --git a/src/cal_tools/constants.py b/src/cal_tools/constants.py new file mode 100644 index 0000000000000000000000000000000000000000..cd4abb00f819ef75f2da7b3315adaf4f863bd3c6 --- /dev/null +++ b/src/cal_tools/constants.py @@ -0,0 +1,199 @@ + +from datetime import datetime, timezone +from struct import pack, unpack +from pathlib import Path +from shutil import copyfile +from hashlib import md5 +import binascii +import time + +import numpy as np +import h5py + +from calibration_client import CalibrationClient +from cal_tools.calcat_interface2 import _get_default_caldb_root, get_client +from cal_tools.tools import run_prop_seq_from_path +from cal_tools.restful_config import calibration_client + + +def write_ccv( + const_path, + pdu_name, pdu_uuid, detector_type, + calibration, conditions, created_at, proposal, runs, + data, dims, key='0' +): + """Write CCV data file. + + Args: + const_path (os.PathLike): Path to CCV file to write + pdu_name (str): Physical detector unit name + pdu_uuid (int): Physical detector unit UUID + detector_type (str): Detector type name + calibration (str): Calibration name + conditions (ConditionsBase): Detector operating conditions + created_at (datetime): Validity start for calibration + proposal (int): Raw data proposal the calibration data is + generated from + runs (Iterable of int): Raw data runs the calibration data is + generated from + data (ndarray): Calibration constant data + dims (Iterable of str): + key (str, optional): + + Returns: + (str) CCV HDF group name. + + """ + + if data.ndim != len(dims): + raise ValueError('data.ndims != len(dims)') + + with h5py.File(const_path, 'a') as const_file: + const_file.attrs['version'] = 0 + + pdu_group = const_file.require_group(pdu_name) + pdu_group.attrs['uuid'] = pdu_uuid + pdu_group.attrs['detector_type'] = detector_type + + calibration_group = pdu_group.require_group(calibration) + + if key is None: + key = str(len(calibration_group)) + + ccv_group = calibration_group.create_group(key) + ccv_group.attrs['begin_at'] = created_at.isoformat() + ccv_group.attrs['proposal'] = proposal + ccv_group.attrs['runs'] = np.array(runs, dtype=np.int32) + ccv_group_name = ccv_group.name + + opcond_group = ccv_group.create_group('operating_condition') + opcond_dict = conditions.make_dict( + conditions.calibration_types[calibration]) + for db_name, value in opcond_dict.items(): + key = db_name.lower().replace(' ', '_') + dset = opcond_group.create_dataset(key, data=value, + dtype=np.float64) + dset.attrs['lower_deviation'] = 0.0 + dset.attrs['upper_deviation'] = 0.0 + dset.attrs['database_name'] = db_name + + dset = ccv_group.create_dataset('data', data=data) + dset.attrs['dims'] = dims + + return ccv_group_name + + +def inject_ccv(const_src, ccv_root, report_to=None): + """Inject new CCV into CalCat. + + Args: + const_path (str or Path): Path to CCV data file. + ccv_root (str): CCV HDF group name. + report_to (str): Metadata location. + + Returns: + None + + Raises: + RuntimeError: If CalCat POST request fails. + """ + + pdu_name, calibration, key = ccv_root.lstrip('/').split('/') + + with h5py.File(const_src, 'r') as const_file: + pdu_group = const_file[pdu_name] + pdu_uuid = pdu_group.attrs['uuid'] + detector_type = pdu_group.attrs['detector_type'] + + ccv_group = const_file[ccv_root] + proposal, runs = ccv_group.attrs['proposal'], ccv_group.attrs['runs'] + begin_at_str = ccv_group.attrs['begin_at'] + + condition_group = ccv_group['operating_condition'] + + cond_params = [] + + # It's really not ideal we're mixing conditionS and condition now. + for parameter in condition_group: + param_dset = condition_group[parameter] + cond_params.append({ + 'parameter_name': param_dset.attrs['database_name'], + 'value': float(param_dset[()]), + 'lower_deviation_value': param_dset.attrs['lower_deviation'], + 'upper_deviation_value': param_dset.attrs['upper_deviation'], + 'flg_available': True + }) + + const_rel_path = f'xfel/cal/{detector_type.lower()}/{pdu_name.lower()}' + const_filename = f'cal.{time.time()}.h5' + + if proposal and len(runs) > 0: + raw_data_location = 'proposal:{} runs: {}'.format( + proposal, ' '.join([str(x) for x in runs])) + else: + pass # Fallback for non-run based constants + + # Generate condition name. + unique_name = detector_type[:detector_type.index('-Type')] + ' Def' + cond_hash = md5(pdu_name.encode()) + cond_hash.update(int(pdu_uuid).to_bytes( + length=8, byteorder='little', signed=False)) + + for param_dict in cond_params: + cond_hash.update(str(param_dict['parameter_name']).encode()) + cond_hash.update(str(param_dict['value']).encode()) + + unique_name += binascii.b2a_base64(cond_hash.digest()).decode() + unique_name = unique_name[:60] + + # Add PDU "UUID" to parameters. + cond_params.append({ + 'parameter_name': 'Detector UUID', + 'value': unpack('d', pack('q', pdu_uuid))[0], + 'lower_deviation_value': 0.0, + 'upper_deviation_value': 0.0, + 'flg_available': True + }) + + inject_h = { + 'detector_condition': { + 'name': unique_name, + 'parameters': cond_params + }, + 'calibration_constant': { + 'calibration_name': calibration, + 'detector_type_name': detector_type, + 'flg_auto_approve': True + }, + 'calibration_constant_version': { + 'raw_data_location': raw_data_location, + 'file_name': const_filename, + 'path_to_file': const_rel_path, + 'data_set_name': f'{pdu_name}/{calibration}/0', + 'start_idx': '0', + 'end_idx': '0', + 'begin_validity_at': begin_at_str, + 'end_validity_at': '', + 'begin_at': begin_at_str, + 'pdu_physical_name': pdu_name, + 'flg_good_quality': True + } + } + + if report_to: + report_path = Path(report_to).absolute().with_suffix('.pdf') + inject_h['report'] = { + 'name': report_path.stem, + 'file_path': str(report_path) + } + + const_dest = _get_default_caldb_root() / const_rel_path / const_filename + const_dest.parent.mkdir(parents=True, exist_ok=True) + copyfile(const_src, const_dest) + + resp = CalibrationClient.inject_new_calibration_constant_version( + calibration_client(), inject_h) + + if not resp['success']: + const_dest.unlink() # Delete already copied CCV file. + raise RuntimeError(resp) diff --git a/src/cal_tools/restful_config.py b/src/cal_tools/restful_config.py index 618140c0b372cfd344ca1341697a8ec50eba63b3..5a6d2e9e5245e6d4355ec40ae20c6ef557590c14 100644 --- a/src/cal_tools/restful_config.py +++ b/src/cal_tools/restful_config.py @@ -1,21 +1,33 @@ + from pathlib import Path +from os import getenv from dynaconf import Dynaconf config_dir = Path(__file__).parent.resolve() +# Default fles. +settings_files = [ + config_dir / "restful_config.yaml", + config_dir / "restful_config.secrets.yaml", + Path("~/.config/pycalibration/cal_tools/restful_config.yaml").expanduser(), +] + +if getenv('CAL_RESTFUL_TEST_CONFIG', None) == '1': + settings_files.append(Path( + "~/.config/pycalibration/cal_tools/restful_test_config.yaml").expanduser()) + + restful_config = Dynaconf( envvar_prefix="CAL_CAL_TOOLS", - settings_files=[ - config_dir / "restful_config.yaml", - config_dir / "restful_config.secrets.yaml", - Path("~/.config/pycalibration/cal_tools/restful_config.yaml").expanduser(), - ], + settings_files=settings_files, merge_enabled=True, ) def calibration_client(): + """Obtain an initialized CalibrationClient object.""" + from calibration_client import CalibrationClient # Create client for CalCat. @@ -30,3 +42,33 @@ def calibration_client(): refresh_url=calcat_config['refresh-url'], auth_url=calcat_config['auth-url'], scope='') + + +def extra_calibration_client(): + """Obtain an initialized CalCatAPIClient object.""" + + from cal_tools import calcat_interface2 + + calcat_config = restful_config.get('calcat') + if calcat_config['use-oauth2']: + from oauth2_xfel_client import Oauth2ClientBackend + oauth_client = Oauth2ClientBackend( + client_id=calcat_config['user-id'], + client_secret=calcat_config['user-secret'], + token_url=calcat_config['token-url'], + scope='', + ) + else: + oauth_client = None + + if calcat_config['caldb-root']: + calcat_interface2._default_caldb_root = Path(calcat_config['caldb-root']) + + client = calcat_interface2.CalCatAPIClient( + base_api_url=calcat_config['base-api-url'], + oauth_client=oauth_client, + user_email=calcat_config['user-email'], + ) + + calcat_interface2.global_client = client + return client diff --git a/src/cal_tools/restful_config.yaml b/src/cal_tools/restful_config.yaml index 899fb07d0886b1f4dd4951edb26b93280d3b9bfc..0cecfd4a3844edcf8b850d733580cc2dc9fab4e3 100644 --- a/src/cal_tools/restful_config.yaml +++ b/src/cal_tools/restful_config.yaml @@ -19,3 +19,4 @@ calcat: user-email: calibration@example.com user-id: '@note add this to secrets file' user-secret: '@note add this to secrets file' + caldb-root: '' diff --git a/src/cal_tools/shimadzu.py b/src/cal_tools/shimadzu.py new file mode 100644 index 0000000000000000000000000000000000000000..d34d8e85f9d63c39a7c84f93903f24f6f72cf187 --- /dev/null +++ b/src/cal_tools/shimadzu.py @@ -0,0 +1,55 @@ +from dataclasses import dataclass + +from cal_tools.calcat_interface2 import ShimadzuHPVX2Conditions + + +class ShimadzuHPVX2: + image_key = "data.image.pixels" + copy_keys = [ + "data.image.binning", + "data.image.dimTypes", + "data.image.dims", + "data.image.flipX", + "data.image.flipY", + "data.image.roiOffsets", + "data.image.rotation", + ] + + def __init__(self, source_name_pattern: str, channel=None, image_key=None): + self.source_name_pattern = source_name_pattern + if channel is not None: + self.channel = channel + if image_key is not None: + self.image_key = image_key + self.image_index_group = self.image_key.partition('.')[0] + self.instrument = source_name_pattern.split('_')[0] + + def conditions(self, dc: "DataCollection", module=None): # noqa: F821 + if module is None: + source_pattern = self.source_name_pattern.format(modno='*') + det_dc = dc.select(source_pattern) + if not det_dc.instrument_sources: + raise ValueError("No detector sources are found") + + source_name = list(det_dc.instrument_sources)[0] + else: + source_name = self.instrument_source(module) + keydata = dc[source_name, self.image_key] + num_frames = keydata.shape[-3] + return ShimadzuHPVX2Conditions(burst_frame_count=float(num_frames)) + + def instrument_source(self, module: int): + return self.source_name_pattern.format(modno=module) + + def corrected_source(self, module: int): + source_name = self.source_name_pattern.format(modno=module) + + # Replace type with CORR. + parts = source_name.split('/') + parts[1] = "CORR" + source_name = '/'.join(parts) + + # Replace channel with output. + source_name = source_name[:source_name.index(':')] + ':output' + + return source_name diff --git a/src/xfel_calibrate/notebooks.py b/src/xfel_calibrate/notebooks.py index e1f308faab5228ef145cfa19b57f891d43630e4d..cf23aa919da4dce0a2fba1af87170fc75ce31eeb 100644 --- a/src/xfel_calibrate/notebooks.py +++ b/src/xfel_calibrate/notebooks.py @@ -304,6 +304,26 @@ notebooks = { }, }, }, + "DYNAMICFF": { + "DARK": { + "notebook": "notebooks/DynamicFF/Characterize_DynamicFF_NBC.ipynb", + "concurrency": { + "parameter": None, + "use function": None, + "default concurrency": None, + "cluster cores": 1, + }, + }, + "CORRECT": { + "notebook": "notebooks/DynamicFF/Correct_DynamicFF_NBC.ipynb", + "concurrency": { + "parameter": None, + "use function": None, + "default concurrency": None, + "cluster cores": 1, + }, + }, + }, "TEST": { "TEST-CLI": { "notebook": "notebooks/test/test-cli.ipynb",