{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bed7bd15-21d9-4735-82c1-c27c1a5e3346",
   "metadata": {},
   "source": [
    "# Gotthard2 Offline Correction #\n",
    "\n",
    "Author: European XFEL Detector Group, Version: 1.0\n",
    "\n",
    "Offline Calibration for the Gothard2 Detector"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "570322ed-f611-4fd1-b2ec-c12c13d55843",
   "metadata": {},
   "outputs": [],
   "source": [
    "in_folder = \"/gpfs/exfel/exp/FXE/202221/p003225/raw\"  # the folder to read data from, required\n",
    "out_folder = \"/gpfs/exfel/data/scratch/ahmedk/test/gotthard2\"  # the folder to output to, required\n",
    "metadata_folder = \"\"  # Directory containing calibration_metadata.yml when run by xfel-calibrate\n",
    "run = 50  # run to process, required\n",
    "sequences = [-1]  # sequences to correct, set to [-1] for all, range allowed\n",
    "sequences_per_node = 1  # number of sequence files per node if notebook executed through xfel-calibrate, set to 0 to not run SLURM parallel\n",
    "\n",
    "# Parameters used to access raw data.\n",
    "karabo_id = \"FXE_XAD_G2XES\"  # karabo prefix of Gotthard-II devices\n",
    "karabo_da = [\"GH201\"]  # data aggregators\n",
    "receiver_template = \"RECEIVER\"  # receiver template used to read INSTRUMENT keys.\n",
    "control_template = \"CONTROL\"  # control template used to read CONTROL keys.\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/{}\"  # 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",
    "# Parameters for calibration database.\n",
    "cal_db_interface = \"tcp://max-exfl-cal001:8016#8025\"  # the database interface to use.\n",
    "cal_db_timeout = 180000  # timeout on caldb requests.\n",
    "creation_time = \"\"  # To overwrite the measured creation_time. Required Format: YYYY-MM-DD HR:MN:SC e.g. \"2022-06-28 13:00:00\"\n",
    "\n",
    "# Parameters affecting corrected data.\n",
    "constants_file = \"\"  # Use constants in given constant file path. /gpfs/exfel/data/scratch/ahmedk/dont_remove/gotthard2/constants/calibration_constants_GH2.h5\n",
    "offset_correction = True  # apply offset correction. This can be disabled to only apply LUT or apply LUT and gain correction for non-linear differential results.\n",
    "gain_correction = True  # apply gain correction.\n",
    "chunks_data = 1  # HDF chunk size for pixel data in number of frames.\n",
    "\n",
    "# Parameter conditions.\n",
    "bias_voltage = -1  # Detector bias voltage, set to -1 to use value in raw file.\n",
    "exposure_time = -1.  # Detector exposure time, set to -1 to use value in raw file.\n",
    "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",
    "\n",
    "# Parameters for plotting\n",
    "skip_plots = False  # exit after writing corrected files\n",
    "pulse_idx_preview = 3  # pulse index to preview. The following even/odd pulse index is used for preview. # TODO: update to pulseId preview.\n",
    "\n",
    "\n",
    "def balance_sequences(in_folder, run, sequences, sequences_per_node, karabo_da):\n",
    "    from xfel_calibrate.calibrate import balance_sequences as bs\n",
    "    return bs(in_folder, run, sequences, sequences_per_node, karabo_da)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e9730d8-3908-41d7-abe2-d78e046d5de2",
   "metadata": {},
   "outputs": [],
   "source": [
    "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",
    "from IPython.display import Markdown, display\n",
    "from extra_data import RunDirectory, H5File\n",
    "from pathlib import Path\n",
    "\n",
    "import cal_tools.restful_config as rest_cfg\n",
    "from cal_tools.calcat_interface import CalCatError, GOTTHARD2_CalibrationData\n",
    "from cal_tools.files import DataFile\n",
    "from cal_tools.gotthard2 import gotthard2algs, gotthard2lib\n",
    "from cal_tools.step_timing import StepTimer\n",
    "from cal_tools.tools import (\n",
    "    calcat_creation_time,\n",
    "    write_constants_fragment,\n",
    ")\n",
    "from XFELDetAna.plotting.heatmap import heatmapPlot\n",
    "\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7c02c48-4429-42ea-a42e-de45366d7fa3",
   "metadata": {},
   "outputs": [],
   "source": [
    "in_folder = Path(in_folder)\n",
    "run_folder = in_folder / f\"r{run:04d}\"\n",
    "out_folder = Path(out_folder)\n",
    "out_folder.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "if not karabo_id_control:\n",
    "    karabo_id_control = karabo_id\n",
    "\n",
    "instrument_src = instrument_source_template.format(karabo_id, receiver_template)\n",
    "ctrl_src = ctrl_source_template.format(karabo_id_control, control_template)\n",
    "\n",
    "print(f\"Process modules: {karabo_da} for run {run}\")\n",
    "\n",
    "# Run's creation time:\n",
    "creation_time = calcat_creation_time(in_folder, run, creation_time)\n",
    "print(f\"Creation time: {creation_time}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b5eb816e-b5f2-44ce-9907-0273d82341b6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Select only sequence files to process for the selected detector.\n",
    "if sequences == [-1]:\n",
    "    possible_patterns = list(f\"*{mod}*.h5\" for mod in karabo_da)\n",
    "else:\n",
    "    possible_patterns = list(\n",
    "        f\"*{mod}-S{s:05d}.h5\" for mod in karabo_da for s in sequences\n",
    "    )\n",
    "\n",
    "run_folder = Path(in_folder / f\"r{run:04d}\")\n",
    "seq_files = [\n",
    "    f for f in run_folder.glob(\"*.h5\") if any(f.match(p) for p in possible_patterns)\n",
    "]\n",
    "\n",
    "seq_files = sorted(seq_files)\n",
    "\n",
    "if not seq_files:\n",
    "    raise IndexError(\"No sequence files available for the selected sequences.\")\n",
    "\n",
    "print(f\"Processing a total of {len(seq_files)} sequence files\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f9a8d1eb-ce6a-4ed0-abf4-4a6029734672",
   "metadata": {},
   "outputs": [],
   "source": [
    "step_timer = StepTimer()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "892172d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Read slow data\n",
    "run_dc = RunDirectory(run_folder)\n",
    "g2ctrl = gotthard2lib.Gotthard2Ctrl(run_dc=run_dc, ctrl_src=ctrl_src)\n",
    "\n",
    "if bias_voltage == -1:\n",
    "    bias_voltage = g2ctrl.get_bias_voltage()\n",
    "if exposure_time == -1:\n",
    "    exposure_time = g2ctrl.get_exposure_time()\n",
    "if exposure_period == -1:\n",
    "    exposure_period = g2ctrl.get_exposure_period()\n",
    "if acquisition_rate == -1:\n",
    "    acquisition_rate = g2ctrl.get_acquisition_rate()\n",
    "if single_photon == -1:\n",
    "    single_photon = g2ctrl.get_single_photon()\n",
    "\n",
    "print(\"Bias Voltage:\", bias_voltage)\n",
    "print(\"Exposure Time:\", exposure_time)\n",
    "print(\"Exposure Period:\", exposure_period)\n",
    "print(\"Acquisition Rate:\", acquisition_rate)\n",
    "print(\"Single Photon:\", single_photon)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8c852392-bb19-4c40-b2ce-3b787538a92d",
   "metadata": {},
   "source": [
    "### Retrieving calibration constants"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5717d722",
   "metadata": {},
   "outputs": [],
   "source": [
    "da_to_pdu = {}\n",
    "# Used for old FXE (p003225) runs before adding Gotthard2 to CALCAT\n",
    "const_data = dict()\n",
    "\n",
    "g2_cal = GOTTHARD2_CalibrationData(\n",
    "    detector_name=karabo_id,\n",
    "    sensor_bias_voltage=bias_voltage,\n",
    "    exposure_time=exposure_time,\n",
    "    exposure_period=exposure_period,\n",
    "    acquisition_rate=acquisition_rate,\n",
    "    single_photon=single_photon,\n",
    "    event_at=creation_time,\n",
    "    client=rest_cfg.calibration_client(),\n",
    ")\n",
    "# Keep as long as it is essential to correct\n",
    "# RAW data (FXE p003225) before the data mapping was added to CALCAT.\n",
    "try:  # in case local constants are used with old RAW data. This can be removed in the future.\n",
    "    for mod_info in g2_cal.physical_detector_units.values():\n",
    "        da_to_pdu[mod_info[\"karabo_da\"]] = mod_info[\"physical_name\"]\n",
    "    db_modules = [da_to_pdu[da] for da in karabo_da]\n",
    "except CalCatError as e:\n",
    "    print(e)\n",
    "    db_modules = [None] * len(karabo_da)\n",
    "\n",
    "if constants_file:\n",
    "    for mod in karabo_da:\n",
    "        const_data[mod] = dict()\n",
    "        # load constants temporarily using defined local paths.\n",
    "        with h5py.File(constants_file, \"r\") as cfile:\n",
    "            const_data[mod][\"LUTGotthard2\"] = cfile[\"LUT\"][()]\n",
    "            const_data[mod][\"OffsetGotthard2\"] = cfile[\"offset_map\"][()].astype(np.float32)\n",
    "            const_data[mod][\"RelativeGainGotthard2\"] = cfile[\"gain_map\"][()].astype(np.float32)\n",
    "            const_data[mod][\"Mask\"] = cfile[\"bpix_ff\"][()].astype(np.uint32)\n",
    "else:\n",
    "    constant_names = [\"LUTGotthard2\", \"OffsetGotthard2\", \"BadPixelsDarkGotthard2\"]\n",
    "    if gain_correction:\n",
    "        constant_names += [\"RelativeGainGotthard2\", \"BadPixelsFFGotthard2\"]\n",
    "\n",
    "    g2_metadata = g2_cal.metadata(calibrations=constant_names)\n",
    "\n",
    "    # Validate the constants availability and raise/warn correspondingly.\n",
    "    for mod, calibrations in g2_metadata.items():\n",
    "\n",
    "        dark_constants = {\"LUTGotthard2\"}\n",
    "        if offset_correction:\n",
    "            dark_constants |= {\"OffsetGotthard2\", \"BadPixelsDarkGotthard2\"}\n",
    "\n",
    "        missing_dark_constants = dark_constants - set(calibrations)\n",
    "        if missing_dark_constants:\n",
    "            karabo_da.remove(mod)\n",
    "            warning(f\"Dark constants {missing_dark_constants} are not available to correct {mod}.\")  # noqa\n",
    "\n",
    "        missing_gain_constants = {\n",
    "            \"BadPixelsFFGotthard2\", \"RelativeGainGotthard2\"} - set(calibrations)\n",
    "        if gain_correction and missing_gain_constants:\n",
    "            warning(f\"Gain constants {missing_gain_constants} are not retrieved for mod {mod}.\")\n",
    "\n",
    "if not karabo_da:\n",
    "    raise ValueError(\"Dark constants are not available for all modules.\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac1cdec5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Record constant details in YAML metadata.\n",
    "write_constants_fragment(\n",
    "    out_folder=(metadata_folder or out_folder),\n",
    "    det_metadata=g2_metadata,\n",
    "    caldb_root=g2_cal.caldb_root)\n",
    "\n",
    "# Load constants data for all constants.\n",
    "const_data = g2_cal.ndarray_map(metadata=g2_metadata)\n",
    "\n",
    "# Prepare constant arrays.\n",
    "if not constants_file:\n",
    "    # Create the mask array.\n",
    "    bpix = const_data[mod].get(\"BadPixelsDarkGotthard2\")\n",
    "    if bpix is None:\n",
    "        bpix = np.zeros((1280, 2, 3), dtype=np.uint32)\n",
    "    if const_data[mod].get(\"BadPixelsFFGotthard2\") is not None:\n",
    "        bpix |= const_data[mod][\"BadPixelsFFGotthard2\"]\n",
    "    const_data[mod][\"Mask\"] = bpix\n",
    "\n",
    "    # Prepare empty arrays for missing constants.\n",
    "    if const_data[mod].get(\"OffsetGotthard2\") is None:\n",
    "        const_data[mod][\"OffsetGotthard2\"] = np.zeros(\n",
    "            (1280, 2, 3), dtype=np.float32)\n",
    "\n",
    "    if const_data[mod].get(\"RelativeGainGotthard2\") is None:\n",
    "        const_data[mod][\"RelativeGainGotthard2\"] = np.ones(\n",
    "            (1280, 2, 3), dtype=np.float32)\n",
    "    const_data[mod][\"RelativeGainGotthard2\"] = const_data[mod][\"RelativeGainGotthard2\"].astype(  # noqa\n",
    "        np.float32, copy=False)  # Old gain constants are not float32."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "23fcf7f4-351a-4df7-8829-d8497d94fecc",
   "metadata": {},
   "outputs": [],
   "source": [
    "context = psh.ProcessContext(num_workers=23)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "daecd662-26d2-4cb8-aa70-383a579cf9f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def correct_train(wid, index, d):\n",
    "    g = gain[index]\n",
    "    gotthard2algs.convert_to_10bit(d, const_data[mod][\"LUTGotthard2\"], data_corr[index, ...])\n",
    "    gotthard2algs.correct_train(\n",
    "        data_corr[index, ...],\n",
    "        mask[index, ...],\n",
    "        g,\n",
    "        const_data[mod][\"OffsetGotthard2\"],\n",
    "        const_data[mod][\"RelativeGainGotthard2\"],  \n",
    "        const_data[mod][\"Mask\"],\n",
    "        apply_offset=offset_correction,\n",
    "        apply_gain=gain_correction,\n",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f88c1aa6-a735-4b72-adce-b30162f5daea",
   "metadata": {},
   "outputs": [],
   "source": [
    "for mod in karabo_da:\n",
    "    # This is used in case receiver template consists of\n",
    "    # karabo data aggregator index. e.g. detector at DETLAB\n",
    "    instr_mod_src = instrument_src.format(mod[-2:])\n",
    "    data_path = \"INSTRUMENT/\" + instr_mod_src + \"/data\"\n",
    "    for raw_file in seq_files:\n",
    "        step_timer.start()\n",
    "\n",
    "        dc = H5File(raw_file)\n",
    "        out_file = out_folder / raw_file.name.replace(\"RAW\", \"CORR\")\n",
    "\n",
    "        # Select module INSTRUMENT source and deselect empty trains.\n",
    "        dc = dc.select(instr_mod_src, require_all=True)\n",
    "        data = dc[instr_mod_src, \"data.adc\"].ndarray()\n",
    "        gain = dc[instr_mod_src, \"data.gain\"].ndarray()\n",
    "        step_timer.done_step(\"preparing raw data\")\n",
    "        dshape = data.shape\n",
    "\n",
    "        step_timer.start()\n",
    "\n",
    "        # Allocate shared arrays.\n",
    "        data_corr = context.alloc(shape=dshape, dtype=np.float32)\n",
    "        mask = context.alloc(shape=dshape, dtype=np.uint32)\n",
    "        context.map(correct_train, data)\n",
    "        step_timer.done_step(\"Correcting one sequence file\")\n",
    "\n",
    "        step_timer.start()\n",
    "\n",
    "        # Provided PSI gain map has 0 values. Set inf values to nan.\n",
    "        # TODO: This can maybe be removed after creating XFEL gain maps.?\n",
    "        data_corr[np.isinf(data_corr)] = np.nan\n",
    "\n",
    "        # Create CORR files and add corrected data sections.\n",
    "        image_counts = dc[instrument_src, \"data.adc\"].data_counts(labelled=False)\n",
    "\n",
    "        with DataFile(out_file, \"w\") as ofile:\n",
    "            # Create INDEX datasets.\n",
    "            ofile.create_index(dc.train_ids, from_file=dc.files[0])\n",
    "            # Create METDATA datasets\n",
    "            ofile.create_metadata(\n",
    "                like=dc,\n",
    "                sequence=dc.run_metadata()[\"sequenceNumber\"],\n",
    "                instrument_channels=(f\"{instrument_src}/data\",)\n",
    "            )\n",
    "\n",
    "            # Create Instrument section to later add corrected datasets.\n",
    "            outp_source = ofile.create_instrument_source(instrument_src)\n",
    "\n",
    "            # Create count/first datasets at INDEX source.\n",
    "            outp_source.create_index(data=image_counts)\n",
    "\n",
    "            # Store uncorrected trainId in the corrected file.\n",
    "            outp_source.create_key(\n",
    "                    f\"data.trainId\", data=dc.train_ids,\n",
    "                    chunks=min(50, len(dc.train_ids))\n",
    "                )\n",
    "\n",
    "            # Create datasets with the available corrected data\n",
    "            for field_name, field_data in {\n",
    "                \"adc\": data_corr,\n",
    "                \"gain\": gain,\n",
    "            }.items():\n",
    "                outp_source.create_key(\n",
    "                    f\"data.{field_name}\", data=field_data,\n",
    "                    chunks=((chunks_data,) + data_corr.shape[1:])\n",
    "            )\n",
    "\n",
    "            for field in [\"bunchId\", \"memoryCell\", \"frameNumber\", \"timestamp\"]:\n",
    "                outp_source.create_key(\n",
    "                    f\"data.{field}\", data=dc[instr_mod_src, f\"data.{field}\"].ndarray(),\n",
    "                    chunks=(chunks_data, data_corr.shape[1])\n",
    "            )\n",
    "            outp_source.create_compressed_key(f\"data.mask\", data=mask)\n",
    "\n",
    "        step_timer.done_step(\"Storing data\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "94b8e4d2-9f8c-4c23-a509-39238dd8435c",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(f\"Total processing time {step_timer.timespan():.01f} s\")\n",
    "step_timer.print_summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0ccc7f7e-2a3f-4ac0-b854-7d505410d2fd",
   "metadata": {},
   "outputs": [],
   "source": [
    "if skip_plots:\n",
    "    print(\"Skipping plots\")\n",
    "    import sys\n",
    "\n",
    "    sys.exit(0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ff203f77-3811-46f3-bf7d-226d2dcab13f",
   "metadata": {},
   "outputs": [],
   "source": [
    "mod_dcs = {}\n",
    "first_seq_raw = seq_files[0]\n",
    "first_seq_corr = out_folder / first_seq_raw.name.replace(\"RAW\", \"CORR\")\n",
    "for mod in karabo_da:\n",
    "    mod_dcs[mod] = {}\n",
    "    with H5File(first_seq_corr) as out_dc:\n",
    "        tid, mod_dcs[mod][\"train_corr_data\"] = next(\n",
    "            out_dc[instr_mod_src, \"data.adc\"].trains()\n",
    "        )\n",
    "    with H5File(first_seq_raw) as in_dc:\n",
    "        train_dict = in_dc.train_from_id(tid)[1][instr_mod_src]\n",
    "        mod_dcs[mod][\"train_raw_data\"] = train_dict[\"data.adc\"]\n",
    "        mod_dcs[mod][\"train_raw_gain\"] = train_dict[\"data.gain\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1b379438-eb1d-42b2-ac83-eb8cf88c46db",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(Markdown(\"### Mean RAW and CORRECTED across pulses for one train:\"))\n",
    "display(Markdown(f\"Train: {tid}\"))\n",
    "\n",
    "step_timer.start()\n",
    "for mod, pdu in zip(karabo_da, db_modules):\n",
    "\n",
    "    fig, ax = plt.subplots(figsize=(20, 10))\n",
    "    raw_data = mod_dcs[mod][\"train_raw_data\"]\n",
    "    im = ax.plot(np.mean(raw_data, axis=0))\n",
    "    ax.set_title(f\"RAW module {mod} ({pdu})\")\n",
    "    ax.set_xlabel(\"Strip #\", size=20)\n",
    "    ax.set_ylabel(\"12-bit ADC output\", size=20)\n",
    "    plt.xticks(fontsize=20)\n",
    "    plt.yticks(fontsize=20)\n",
    "    pass\n",
    "\n",
    "    fig, ax = plt.subplots(figsize=(20, 10))\n",
    "    corr_data = mod_dcs[mod][\"train_corr_data\"]\n",
    "    im = ax.plot(np.mean(corr_data, axis=0))\n",
    "    ax.set_title(f\"CORRECTED module {mod} ({pdu})\")\n",
    "    ax.set_xlabel(\"Strip #\", size=20)\n",
    "    ax.set_ylabel(\"10-bit KeV. output\", size=20)\n",
    "    plt.xticks(fontsize=20)\n",
    "    plt.yticks(fontsize=20)\n",
    "    pass\n",
    "step_timer.done_step(\"Plotting mean data\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "58a6a276",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(Markdown(f\"### RAW and CORRECTED strips across pulses for train {tid}\"))\n",
    "\n",
    "step_timer.start()\n",
    "for mod, pdu in zip(karabo_da, db_modules):\n",
    "    for plt_data, dname in zip(\n",
    "        [\"train_raw_data\", \"train_corr_data\"], [\"RAW\", \"CORRECTED\"]\n",
    "    ):\n",
    "        fig, ax = plt.subplots(figsize=(15, 20))\n",
    "        plt.rcParams.update({\"font.size\": 20})\n",
    "\n",
    "        heatmapPlot(\n",
    "            mod_dcs[mod][plt_data],\n",
    "            y_label=\"Pulses\",\n",
    "            x_label=\"Strips\",\n",
    "            title=f\"{dname} module {mod} ({pdu})\",\n",
    "            use_axis=ax,\n",
    "        )\n",
    "        pass\n",
    "step_timer.done_step(\"Plotting RAW and CORRECTED data for one train\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd8f5e08-fcee-4bff-ba63-6452b3d892a2",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Validate given \"pulse_idx_preview\"\n",
    "\n",
    "if pulse_idx_preview + 1 > data.shape[1]:\n",
    "    print(\n",
    "        f\"WARNING: selected pulse_idx_preview {pulse_idx_preview} is not available in data.\"\n",
    "        \" Previewing 1st pulse.\"\n",
    "    )\n",
    "    pulse_idx_preview = 1\n",
    "\n",
    "if data.shape[1] == 1:\n",
    "    odd_pulse = 1\n",
    "    even_pulse = None\n",
    "else:\n",
    "    odd_pulse = pulse_idx_preview if pulse_idx_preview % 2 else pulse_idx_preview + 1\n",
    "    even_pulse = (\n",
    "        pulse_idx_preview if not (pulse_idx_preview % 2) else pulse_idx_preview + 1\n",
    "    )\n",
    "\n",
    "if pulse_idx_preview + 1 > data.shape[1]:\n",
    "    pulse_idx_preview = 1\n",
    "    if data.shape[1] > 1:\n",
    "        pulse_idx_preview = 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e5f0d4d8-e32c-4f2c-8469-4ebbfd3f644c",
   "metadata": {},
   "outputs": [],
   "source": [
    "display(Markdown(\"### RAW and CORRECTED even/odd pulses for one train:\"))\n",
    "display(Markdown(f\"Train: {tid}\"))\n",
    "for mod, pdu in zip(karabo_da, db_modules):\n",
    "    fig, ax = plt.subplots(figsize=(20, 20))\n",
    "    raw_data = mod_dcs[mod][\"train_raw_data\"]\n",
    "    corr_data = mod_dcs[mod][\"train_corr_data\"]\n",
    "\n",
    "    ax.plot(raw_data[odd_pulse], label=f\"Odd Pulse {odd_pulse}\")\n",
    "    if even_pulse:\n",
    "        ax.plot(raw_data[even_pulse], label=f\"Even Pulse {even_pulse}\")\n",
    "\n",
    "    ax.set_title(f\"RAW module {mod} ({pdu})\")\n",
    "    ax.set_xlabel(\"Strip #\", size=20)\n",
    "    ax.set_ylabel(\"12-bit ADC RAW\", size=20)\n",
    "    plt.xticks(fontsize=20)\n",
    "    plt.yticks(fontsize=20)\n",
    "    ax.legend()\n",
    "    pass\n",
    "\n",
    "    fig, ax = plt.subplots(figsize=(20, 20))\n",
    "    ax.plot(corr_data[odd_pulse], label=f\"Odd Pulse {odd_pulse}\")\n",
    "    if even_pulse:\n",
    "        ax.plot(corr_data[even_pulse], label=f\"Even Pulse {even_pulse}\")\n",
    "    ax.set_title(f\"CORRECTED module {mod} ({pdu})\")\n",
    "    ax.set_xlabel(\"Strip #\", size=20)\n",
    "    ax.set_ylabel(\"10-bit KeV CORRECTED\", size=20)\n",
    "    plt.xticks(fontsize=20)\n",
    "    plt.yticks(fontsize=20)\n",
    "    ax.legend()\n",
    "    pass\n",
    "step_timer.done_step(\"Plotting RAW and CORRECTED odd/even pulses.\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "cal4_venv",
   "language": "python",
   "name": "cal4_venv"
  },
  "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"
  },
  "vscode": {
   "interpreter": {
    "hash": "916dbcbb3f70747c44a77c7bcd40155683ae19c65e1c03b4aa3499c5328201f1"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}