diff --git a/notebooks/Gotthard2/Characterize_Darks_Gotthard2_NBC.ipynb b/notebooks/Gotthard2/Characterize_Darks_Gotthard2_NBC.ipynb index 32a4f221cf4e6fcbb67ffdaf8f8b9877000d69b4..3ecf9f72b38e5f15c8a8e3895f62160140f9b0f4 100644 --- a/notebooks/Gotthard2/Characterize_Darks_Gotthard2_NBC.ipynb +++ b/notebooks/Gotthard2/Characterize_Darks_Gotthard2_NBC.ipynb @@ -51,6 +51,8 @@ "exposure_period = -1. # Detector exposure period, set to -1 to use value in raw file.\n", "acquisition_rate = -1. # Detector acquisition rate (1.1/4.5), set to -1 to use value in raw file.\n", "single_photon = -1 # Detector single photon mode (High/Low CDS), set to -1 to use value in raw file.\n", + "# This is used for the summary notebook\n", + "sensor_type = \"\" # Specifies the GH2 sensor type: '50um', '25um', or \"\" to automatically decide from RUN data.\n", "\n", "# Parameters used for calculating calibration constants\n", "bpix_noise_nitrs = 5 # number of maximum iterations to actively try to reach correct number of badpixels of NOISE_OUT_OF_THRESHOLD type. Leave -1 to keep iterating until finding maximum number of badpixels.\n", @@ -97,6 +99,17 @@ "%matplotlib inline" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "bc4a9824", + "metadata": {}, + "outputs": [], + "source": [ + "# Input parameters validation\n", + "gotthard2lib.validate_sensor_type(sensor_type)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -206,9 +219,9 @@ "print(\"Single photon: \", single_photon)\n", "acquisition_rate = conditions[\"acquisition_rate\"].pop()\n", "print(\"Acquisition rate: \", acquisition_rate)\n", - "\n", - "gh2_detector = g2ctrl.get_det_type()\n", - "print(f\"Processing {gh2_detector} Gotthard2.\")\n" + "if not sensor_type:\n", + " sensor_type = g2ctrl.get_sensor_type()\n", + "print(f\"Processing {sensor_type} Gotthard2.\")" ] }, { diff --git a/notebooks/Gotthard2/Correction_Gotthard2_NBC.ipynb b/notebooks/Gotthard2/Correction_Gotthard2_NBC.ipynb index c81046453cef6cf3afe02690b99059114407972c..b44d20e869d32179b79deb47c9d6e666ec330c7a 100644 --- a/notebooks/Gotthard2/Correction_Gotthard2_NBC.ipynb +++ b/notebooks/Gotthard2/Correction_Gotthard2_NBC.ipynb @@ -84,6 +84,7 @@ "acquisition_rate = -1. # Detector acquisition rate (1.1/4.5), set to -1 to use value in raw file.\n", "single_photon = -1 # Detector single photon mode (High/Low CDS), set to -1 to use value in raw file.\n", "reverse_second_module = -1 # Reverse 25um GH2 second module before interleaving. set to -1 to use value in raw file to reverse based on `CTRL/reverseSlaveReadOutMode`'s value.\n", + "sensor_type = \"\" # Specifies the GH2 sensor type: '50um', '25um', or \"\" to automatically decide from RUN data.\n", "\n", "# Parameters for plotting\n", "skip_plots = False # exit after writing corrected files\n", @@ -105,7 +106,6 @@ "import warnings\n", "from logging import warning\n", "\n", - "import h5py\n", "import pasha as psh\n", "import numpy as np\n", "import matplotlib.pyplot as plt\n", @@ -131,6 +131,17 @@ "%matplotlib inline" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "fc893eff", + "metadata": {}, + "outputs": [], + "source": [ + "# Input parameters validation\n", + "gotthard2lib.validate_sensor_type(sensor_type)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -188,9 +199,17 @@ " acquisition_rate = g2ctrl.get_acquisition_rate()\n", "if single_photon == -1:\n", " single_photon = g2ctrl.get_single_photon()\n", + "if not sensor_type:\n", + " sensor_type = g2ctrl.get_sensor_type()\n", + "\n", + "# Check if the GH2 detector type is \"50um\" and validate that only one data aggregator is provided\n", + "if sensor_type == \"50um\" and len(karabo_da) > 1:\n", + " raise ValueError(\n", + " f\"karabo_da has more than one aggregator {karabo_da}, \"\n", + " \"but the GH2 detector type is specified as '50um', which expects only one aggregator.\"\n", + " )\n", "\n", - "gh2_detector = g2ctrl.get_det_type()\n", - "if reverse_second_module == -1 and gh2_detector == \"25um\":\n", + "if reverse_second_module == -1 and sensor_type == \"25um\":\n", " reverse_second_module = not g2ctrl.second_module_reversed()\n", " if reverse_second_module:\n", " warning(\n", @@ -202,7 +221,7 @@ "print(\"Exposure Period:\", exposure_period)\n", "print(\"Acquisition Rate:\", acquisition_rate)\n", "print(\"Single Photon:\", single_photon)\n", - "print(f\"Processing {gh2_detector} Gotthard2.\")" + "print(f\"Processing {sensor_type} Gotthard2.\")" ] }, { @@ -435,7 +454,7 @@ " print(f\"Correcting {n_trains} for {raw_file}.\")\n", "\n", " # Initialize GH2 data and gain arrays to store in corrected files.\n", - " if gh2_detector == \"25um\":\n", + " if sensor_type == \"25um\":\n", " dshape_stored = (dc[data_sources[0], \"data.adc\"].shape[:2] + (1280 * 2,))\n", " data_stored = np.zeros(dshape_stored, dtype=np.float32)\n", " gain_stored = np.zeros(dshape_stored, dtype=np.uint8)\n", @@ -477,7 +496,7 @@ " # Create CORR files and add corrected data sections.\n", " image_counts = dc[src, \"data.adc\"].data_counts(labelled=False)\n", "\n", - " if gh2_detector == \"25um\":\n", + " if sensor_type == \"25um\":\n", " data_stored[..., i::2] = data_corr\n", " gain_stored[..., i::2] = gain\n", " mask_stored[..., i::2] = mask\n", @@ -568,14 +587,14 @@ " )\n", " mask = out_dc[corr_data_source, \"data.mask\"].train_from_id(tid)[1]\n", "\n", - "if gh2_detector == \"25um\":\n", + "if sensor_type == \"25um\":\n", " mod_dcs[corr_data_source][\"train_raw_data\"] = np.zeros((data_corr.shape[1], 1280 * 2), dtype=np.float32)\n", " mod_dcs[corr_data_source][\"train_raw_gain\"] = np.zeros((data_corr.shape[1], 1280 * 2), dtype=np.uint8)\n", "\n", "for i, src in enumerate(data_sources):\n", " with H5File(first_seq_raw) as in_dc:\n", " train_dict = in_dc.train_from_id(tid)[1][src]\n", - " if gh2_detector == \"25um\":\n", + " if sensor_type == \"25um\":\n", " if reverse_second_module and i == 1:\n", " data = np.flip(train_dict[\"data.adc\"], axis=-1)\n", " gain = np.flip(train_dict[\"data.gain\"], axis=-1)\n", @@ -612,8 +631,8 @@ "display(Markdown(\"### Mean RAW and CORRECTED across pulses for one train:\"))\n", "display(Markdown(f\"Train: {tid}\"))\n", "\n", - "# Set title format based on gh2_detector\n", - "if gh2_detector == \"50um\":\n", + "# Set title format based on sensor_type\n", + "if sensor_type == \"50um\":\n", " title_format = f\"{{}} data for {karabo_da} ({db_modules})\"\n", "else:\n", " title_format = f\"Interleaved {{}} data for {karabo_da} ({db_modules})\"\n", diff --git a/notebooks/Gotthard2/Summary_Darks_Gotthard2_NBC.ipynb b/notebooks/Gotthard2/Summary_Darks_Gotthard2_NBC.ipynb index 3780f9266e4eb90cd65fc85e31116f3aea8fd465..adda2211f2a982e967940dc40818b5eb480cd6cd 100644 --- a/notebooks/Gotthard2/Summary_Darks_Gotthard2_NBC.ipynb +++ b/notebooks/Gotthard2/Summary_Darks_Gotthard2_NBC.ipynb @@ -31,6 +31,8 @@ "ctrl_source_template = '{karabo_id}/DET/{control_template}' # template for control source name (filled with karabo_id_control)\n", "karabo_id_control = \"\" # Control karabo ID. Set to empty string to use the karabo-id\n", "\n", + "sensor_type = \"\" # Specifies the GH2 sensor type: '50um', '25um', or \"\" to automatically decide from RUN data.\n", + "\n", "# Parameters to be used for injecting dark calibration constants.\n", "local_output = True # Boolean indicating that local constants were stored in the out_folder\n", "\n", @@ -67,6 +69,16 @@ "from cal_tools.tools import CalibrationMetadata" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Input parameters validation\n", + "gotthard2lib.validate_sensor_type(sensor_type)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -91,8 +103,9 @@ " karabo_id_control=karabo_id_control,\n", " control_template=control_template\n", " ))\n", - "gh2_detector = g2ctrl.get_det_type()\n", - "print(f\"Processing {gh2_detector} Gotthard2.\")" + "if not sensor_type:\n", + " sensor_type = g2ctrl.get_sensor_type()\n", + "print(f\"Processing {sensor_type} Gotthard2.\")" ] }, { @@ -118,7 +131,7 @@ "source": [ "plt_map = dict()\n", "dark_constants = [\"Offset\", \"Noise\", \"BadPixelsDark\"]\n", - "if gh2_detector == \"25um\":\n", + "if sensor_type == \"25um\":\n", " for cname in dark_constants:\n", " plt_map[cname] = np.zeros(\n", " (1280 * 2, 2, 3),\n", @@ -138,7 +151,7 @@ " module = mod.replace(\"-\", \"/\")\n", "\n", " with h5py.File(out_folder / f\"const_{cname}_{pdu}.h5\", 'r') as f:\n", - " if gh2_detector == \"25um\":\n", + " if sensor_type == \"25um\":\n", " plt_map[cname][i::2] = f[\"data\"][()]\n", " else:\n", " plt_map[cname] = f[\"data\"][()]" @@ -150,7 +163,7 @@ "metadata": {}, "outputs": [], "source": [ - "if gh2_detector == \"50um\":\n", + "if sensor_type == \"50um\":\n", " title = (\n", " f\"{{}} data for \"\n", " f\"{[f'{mod}({pdu})' for mod, pdu in mod_mapping.items()]}\"\n", diff --git a/src/cal_tools/gotthard2/gotthard2lib.py b/src/cal_tools/gotthard2/gotthard2lib.py index e6a8267c86de2b567a1e613120f6cde3f16fd226..faacc8add6096bc8b733b3c3d6aac80fcf98f60e 100644 --- a/src/cal_tools/gotthard2/gotthard2lib.py +++ b/src/cal_tools/gotthard2/gotthard2lib.py @@ -3,6 +3,16 @@ from pathlib import Path from typing import List, Union import extra_data +import numpy as np + + +def validate_sensor_type(sensor_type): + choices = ["25um", "50um"] + if sensor_type and sensor_type not in choices: + raise ValueError( + f"Unexpected Gotthard2 `sensor_type` given: '{sensor_type}'. " + f"Valid options are: {', '.join(choices)}." + ) def map_da_to_source(dc, das, source_template, karabo_id, affixes): @@ -63,16 +73,29 @@ class Gotthard2Ctrl(): return bool( self.run_dc[self.ctrl_src, "singlePhoton"].as_single_value()) - def get_det_type(self): + def get_sensor_type(self): """GH2 rxHostname is a vector of bytes objects. GH2 25um has two host names unlike 50um which has one. Returns: str: return if its a 25um or 50um detector. """ - - hostname = self.run_dc.get_run_value(self.ctrl_src, "rxHostname") - return "25um" if hostname[1].decode("utf-8") else "50um" + hostnames = self.run_dc.get_run_value(self.ctrl_src, "rxHostname") + num_hosts = np.count_nonzero(hostnames != b'') + if num_hosts == 0: + raise ValueError( + "No hosts are present in RUN/rxHostname. " + "Unable to determine the sensor type for this data. ") + + if num_hosts == 1: + return "50um" + elif num_hosts == 2: + return "25um" + else: + raise ValueError( + f"RUN/rxHostname has more host names ({num_hosts}) than expected. " + "Expected either 1 hostname for 50um or 2 host names for 25um." + ) def second_module_reversed(self): """Check if reverseSlaveReadOutMode is True."""