{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "Updated version of the ePix100 Dark Characterization notebook:\n", "\n", "- Generates the bad pixel map.\n", "- Added badpixel_threshold_sigma input variable: Number of standard deviations considered for bad pixel classification.\n", "- Sensor size is read from hdf5 file instead of being manually input.\n", "- Detector temperature can be read from hdf5 file instead of having a fixed value only.\n", "- Removed the xcal.HistogramCalculator() function, which calculation output was not in use.\n", "- Set db_module to \"\" by default in the first cell (TODO note of previous notebook version)." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "in_folder = '/gpfs/exfel/exp/HED/202030/p900136/raw' # input folder, required\n", "out_folder = '' # output folder, required\n", "sequence = 0 # sequence file to use\n", "run = 182 # which run to read data from, required\n", "\n", "# Parameters for accessing the raw data.\n", "karabo_id = \"HED_IA1_EPX100-2\" # karabo karabo_id\n", "karabo_da = [\"EPIX02\"] # data aggregators\n", "receiver_template = \"RECEIVER\" # detector receiver template for accessing raw data files\n", "path_template = 'RAW-R{:04d}-{}-S{{:05d}}.h5' # the template to use to access data\n", "instrument_source_template = '{}/DET/{}:daqOutput' # instrument detector data source in h5files\n", "\n", "# Parameters for the calibration database.\n", "use_dir_creation_date = True\n", "cal_db_interface = \"tcp://max-exfl016:8020\" # calibration DB interface to use\n", "cal_db_timeout = 300000 # timeout on caldb requests\n", "db_output = False # Output constants to the calibration database\n", "local_output = True # output constants locally\n", "\n", "badpixel_threshold_sigma = 5. # bad pixels defined by values outside n times this std from median\n", "number_dark_frames = 0 # number of images to be used, if set to 0 all available images are used\n", "temp_limits = 5 # limit for parameter Operational temperature\n", "\n", "# Parameters used during selecting raw data trains.\n", "min_trains = 1 # Minimum number of trains that should be available to process dark constants. Default 1.\n", "max_trains = 1000 # Maximum number of trains to use for processing dark constants. Set to 0 to use all available trains.\n", "\n", "# Don't delete! myMDC sends this by default.\n", "operation_mode = '' # Detector operation mode, optional\n", "\n", "# TODO: delete after removing from calibration_configurations\n", "db_module = '' # ID of module in calibration database, this parameter is ignore in the notebook. TODO: remove from calibration_configurations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "import warnings\n", "\n", "import numpy as np\n", "import pasha as psh\n", "from extra_data import RunDirectory\n", "\n", "import XFELDetAna.xfelprofiler as xprof\n", "from XFELDetAna import xfelpyanatools as xana\n", "from XFELDetAna.plotting.util import prettyPlotting\n", "from cal_tools.tools import (\n", " get_dir_creation_date,\n", " get_pdu_from_db,\n", " get_random_db_interface,\n", " get_report,\n", " save_const_to_h5,\n", " send_to_db,\n", ")\n", "from iCalibrationDB import Conditions, Constants" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline\n", "\n", "warnings.filterwarnings('ignore')\n", "\n", "prettyPlotting = True\n", "\n", "profiler = xprof.Profiler()\n", "profiler.disable()\n", "\n", "instrument_src = instrument_source_template.format(karabo_id, receiver_template)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Read report path and create file location tuple to add with the injection\n", "proposal = list(filter(None, in_folder.strip('/').split('/')))[-2]\n", "file_loc = f'proposal:{proposal} runs:{run}'\n", "pixels_x = 708\n", "pixels_y = 768\n", "report = get_report(out_folder)\n", "\n", "ped_dir = os.path.join(in_folder, f\"r{run:04d}\")\n", "fp_name = path_template.format(run, karabo_da[0]).format(sequence)\n", "run_dir = RunDirectory(ped_dir)\n", "\n", "print(f\"Run number: {run}\")\n", "print(f\"Instrument H5File source: {instrument_src}\")\n", "if use_dir_creation_date:\n", " creation_time = get_dir_creation_date(in_folder, run)\n", " print(f\"Using {creation_time.isoformat()} as creation time\")\n", "os.makedirs(out_folder, exist_ok=True)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "pixel_data = (instrument_src, \"data.image.pixels\")\n", "\n", "# Specifies total number of images to proceed\n", "n_trains = run_dir.get_data_counts(*pixel_data).shape[0]\n", "\n", "# Modify n_trains to process based on given maximum\n", "# and minimun number of trains.\n", "if max_trains:\n", " n_trains = min(max_trains, n_trains)\n", "\n", "if n_trains < min_trains:\n", " raise ValueError(\n", " f\"Less than {min_trains} trains are available in RAW data.\"\n", " \" Not enough data to process darks.\")\n", "\n", "print(f\"Number of dark images to analyze: {n_trains}.\")\n", "\n", "integration_time = int(run_dir.get_array(\n", " f\"{karabo_id}/DET/CONTROL\",\n", " \"expTime.value\")[0])\n", "temperature = np.mean(run_dir.get_array(\n", " instrument_src,\n", " \"data.backTemp\").values) / 100.\n", "\n", "if fix_temperature != 0:\n", " temperature_k = fix_temperature\n", " print(\"Temperature is fixed!\")\n", "else:\n", " temperature_k = temperature + 273.15\n", "\n", "print(f\"Bias voltage is {bias_voltage} V\")\n", "print(f\"Detector integration time is set to {integration_time}\")\n", "print(f\"Mean temperature was {temperature:0.2f} °C / {temperature_k:0.2f} K\")\n", "print(f\"Operated in vacuum: {in_vacuum} \")" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "tags": [] }, "outputs": [], "source": [ "data_dc = run_dir.select(\n", " *pixel_data, require_all=True).select_trains(np.s_[:n_trains])\n", "print(f\"Reading data from: {data_dc.files}\\n\")\n", "\n", "data = data_dc[pixel_data].ndarray()\n", "\n", "noise_data = np.std(data, axis=0)\n", "offset_data = np.mean(data, axis=0)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "constant_maps = {}\n", "constant_maps['Offset'] = offset_data[..., np.newaxis]\n", "constant_maps['Noise'] = noise_data[..., np.newaxis]\n", "print(\"Initial constant maps are created\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "noise_mean = np.mean(constant_maps['Noise'].data.flatten())\n", "noise_sigma = np.std(constant_maps['Noise'].data.flatten())\n", "noise_median = np.median(constant_maps['Noise'].data.flatten())\n", "\n", "offset_mean = np.mean(constant_maps['Offset'].data.flatten())\n", "offset_sigma = np.std(constant_maps['Offset'].data.flatten())\n", "offset_median = np.median(constant_maps['Offset'].data.flatten())" ] }, { "cell_type": "code", "execution_count": null, "id": "87b73246-9080-4303-adbd-d8708886e775", "metadata": {}, "outputs": [], "source": [ "#************** OFFSET HEAT MAP **************#\n", "plt.figure(1)\n", "fig1 = xana.heatmapPlot(constant_maps['Offset'][:, :, 0].data,\n", " lut_label='[ADU]',\n", " x_label = 'Column',\n", " y_label = 'Row',\n", " x_range=(0, sensorSize[1]),\n", " y_range=(0, sensorSize[0]), \n", " vmin=max(0,offset_median - badpixel_threshold_sigma*offset_sigma), \n", " vmax=min(np.power(2,14)-1,offset_median + badpixel_threshold_sigma*offset_sigma))\n", "\n", "fig1.suptitle('Offset Map',x=.48,y=.9,fontsize=16)\n", "fig1.set_size_inches(h=15,w=15)\n", "\n", "#************** OFFSET HISTOGRAM **************#\n", "binwidth = offset_sigma/100\n", "ho, co = np.histogram(constant_maps['Offset'].data.flatten(),\n", " bins=np.arange((min(constant_maps['Offset'].data.flatten())),\n", " max(constant_maps['Offset'].data.flatten()) + binwidth,\n", " binwidth))\n", "do = {'x': co[:-1],\n", " 'y': ho,\n", " 'y_err': np.sqrt(ho[:]),\n", " 'drawstyle': 'bars',\n", " 'color': 'cornflowerblue'}\n", "\n", "fig2 = xana.simplePlot(do, \n", " aspect=1.5,\n", " x_label='Offset [ADU]',\n", " y_label='Counts',\n", " x_range=(max(0,offset_median - badpixel_threshold_sigma*offset_sigma), \n", " min(np.power(2,14)-1,offset_median + badpixel_threshold_sigma*offset_sigma)),\n", " y_range=(0,max(ho)*1.1),\n", " y_log=True)\n", "\n", "fig2.suptitle('Offset Distribution',x=.5,y=.92,fontsize=16)\n", "\n", "stats_str = 'mean : ' + \"{:.2f}\".format(np.round(offset_mean,2)) \\\n", " + '\\nstd : ' + \"{:.2f}\".format(np.round(offset_sigma,2)) \\\n", " + '\\nmedian: ' + \"{:.2f}\".format(np.round(offset_median,2)) \\\n", " + '\\nmin: ' + \"{:.2f}\".format(np.min(constant_maps['Offset'].data.flatten())) \\\n", " + '\\nmax: ' + \"{:.2f}\".format(np.max(constant_maps['Offset'].data.flatten()))\n", "fig2.text(s=stats_str,x=.7,y=.7,fontsize=14,bbox=dict(facecolor='yellow', edgecolor='black', alpha=.1));\n", "\n", "#************** NOISE HEAT MAP **************#\n", "fig3 = xana.heatmapPlot(constant_maps['Noise'][:, :, 0],\n", " x_label='Columns', y_label='Rows',\n", " lut_label='Noise [ADU]',\n", " x_range=(0, sensorSize[1]),\n", " y_range=(0, sensorSize[0]),\n", " vmin=max(0,noise_median - badpixel_threshold_sigma*noise_sigma), \n", " vmax=noise_median + badpixel_threshold_sigma*noise_sigma)\n", "fig3.suptitle('Noise Map',x=.48,y=.9,fontsize=16)\n", "fig3.set_size_inches(h=15,w=15)\n", "\n", "#************** NOISE HISTOGRAM **************#\n", "binwidth = noise_sigma/100\n", "hn, cn = np.histogram(constant_maps['Noise'].data.flatten(),\n", " bins=np.arange((min(constant_maps['Noise'].data.flatten())),\n", " max(constant_maps['Noise'].data.flatten()) + binwidth,\n", " binwidth))\n", "dn = {'x': cn[:-1],\n", " 'y': hn,\n", " 'y_err': np.sqrt(hn[:]),\n", " 'drawstyle': 'bars',\n", " 'color': 'cornflowerblue'}\n", "\n", "fig4 = xana.simplePlot(dn,\n", " aspect=1.5,\n", " x_range=(max(0,noise_median - badpixel_threshold_sigma*noise_sigma), \n", " noise_median + badpixel_threshold_sigma*noise_sigma),\n", " y_range=(0,max(hn)*1.1),\n", " x_label='Noise [ADU]',\n", " y_label='Counts',\n", " y_log=True)\n", "\n", "fig4.suptitle('Noise Distribution',x=.5,y=.92,fontsize=16);\n", "\n", "stats_str = 'mean : ' + \"{:.2f}\".format(np.round(noise_mean,2)) \\\n", " + '\\nstd : ' + \"{:.2f}\".format(np.round(noise_sigma,2)) \\\n", " + '\\nmedian: ' + \"{:.2f}\".format(np.round(noise_median,2)) \\\n", " + '\\nmin : ' + \"{:.2f}\".format(np.min(constant_maps['Noise'].data.flatten())) \\\n", " + '\\nmax : ' + \"{:.2f}\".format(np.max(constant_maps['Noise'].data.flatten()))\n", "fig4.text(s=stats_str,x=.72,y=.7,fontsize=14, bbox=dict(facecolor='yellow', edgecolor='black', alpha=.1));" ] }, { "cell_type": "markdown", "id": "02702ec6-0dbc-4cbf-9421-7cef2a6290c4", "metadata": {}, "source": [ "## Bad Pixel Map ###\n", "\n", "The bad pixel map is deduced by comparing offset and noise of each pixel ($v_i$) against its median value:\n", "\n", "$$ \n", "v_i > \\mathrm{median}(v_{k}) + n \\sigma_{v_{k}}\n", "$$\n", "or\n", "$$\n", "v_i < \\mathrm{median}(v_{k}) - n \\sigma_{v_{k}} \n", "$$\n", "\n", "Values are encoded in a 32 bit mask, where for the dark image deduced bad pixels the following non-zero entries are relevant:" ] }, { "cell_type": "code", "execution_count": null, "id": "0edad7cd-e719-4274-a1f0-9d0ed0937ef2", "metadata": {}, "outputs": [], "source": [ "def print_bp_entry(bp):\n", " print(\"{:<30s} {:032b}\".format(bp.name, 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)" ] }, { "cell_type": "code", "execution_count": null, "id": "b71b82c2-206b-4862-a134-8ae3cc065780", "metadata": {}, "outputs": [], "source": [ "def eval_bpidx(d):\n", "\n", " mdn = np.nanmedian(d, axis=(0, 1))\n", " std = np.nanstd(d, axis=(0, 1)) \n", " idx = (d > mdn + badpixel_threshold_sigma*std) | (d < mdn - badpixel_threshold_sigma*std)\n", "\n", " return idx" ] }, { "cell_type": "code", "execution_count": null, "id": "f23ae7ca-cf5b-457c-9457-57e38f12a484", "metadata": {}, "outputs": [], "source": [ "constant_maps['BadPixels'] = np.zeros(constant_maps['Offset'].shape, np.uint32)\n", "\n", "constant_maps['BadPixels'][eval_bpidx(constant_maps['Offset'])] = BadPixels.OFFSET_OUT_OF_THRESHOLD.value\n", "constant_maps['BadPixels'][eval_bpidx(constant_maps['Noise'])] = BadPixels.NOISE_OUT_OF_THRESHOLD.value\n", "constant_maps['BadPixels'][~np.isfinite(constant_maps['Offset'])] = BadPixels.OFFSET_NOISE_EVAL_ERROR.value\n", "constant_maps['BadPixels'][~np.isfinite(constant_maps['Noise'])] = BadPixels.OFFSET_NOISE_EVAL_ERROR.value\n", "\n", "#************** BAD PIXLES HEAT MAP **************#\n", "fig5 = xana.heatmapPlot(constant_maps['BadPixels'][:, :, 0],\n", " x_label='Columns', y_label='Rows',\n", " lut_label='Noise (ADU)',\n", " x_range=(0, pixels_y),\n", " y_range=(0, pixels_x), vmax=2 * np.mean(constant_maps['Noise']))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Save constants to DB\n", "dclass=\"ePix100\"\n", "md = None\n", "\n", "for const_name in constant_maps.keys():\n", " det = getattr(Constants, dclass)\n", " const = getattr(det, const_name)()\n", " const.data = constant_maps[const_name].data\n", "\n", " # set the operating condition\n", " condition = getattr(Conditions.Dark, dclass)(bias_voltage=bias_voltage,\n", " integration_time=integration_time,\n", " temperature=temperature_k,\n", " in_vacuum=in_vacuum)\n", " for parm in condition.parameters:\n", " if parm.name == \"Sensor Temperature\":\n", " parm.lower_deviation = temp_limits\n", " parm.upper_deviation = temp_limits\n", "\n", " db_module = get_pdu_from_db(karabo_id, karabo_da, const,\n", " condition, cal_db_interface,\n", " snapshot_at=creation_time)[0]\n", "\n", " if db_output:\n", " md = send_to_db(db_module, karabo_id, const, condition,\n", " file_loc=file_loc, report_path=report,\n", " cal_db_interface=cal_db_interface,\n", " creation_time=creation_time,\n", " timeout=cal_db_timeout)\n", " if local_output:\n", " md = save_const_to_h5(db_module, karabo_id, const, condition,\n", " const.data, file_loc, report,\n", " creation_time, out_folder)\n", " print(f\"Calibration constant {const_name} is stored locally at {out_folder} \\n\")\n", "\n", "print(\"Constants parameter conditions are:\\n\")\n", "print(f\"• Bias voltage: {bias_voltage}\\n• Integration time: {integration_time}\\n\"\n", " f\"• Temperature: {temperature_k}\\n• In Vacuum: {in_vacuum}\\n\"\n", " f\"• Creation time: {md.calibration_constant_version.begin_at if md is not None else creation_time}\\n\")" ] } ], "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" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false } }, "nbformat": 4, "nbformat_minor": 1 }