{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# LPD Radial X-ray Gain Evaluation #\n",
    "\n",
    "Author: S. Hauf, Version 0.5\n",
    "\n",
    "Taking proper flat field for LPD can be difficult, as air scattering will always be present. Additionally, the large detector mandates a large distance to the source, in order to reduce $1/r$ effects. \n",
    "\n",
    "Because of this a radial evaluation method is used, which assumes that pixels a the same radial distance $r$ should on average have the same signal $S(r)$."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "in_folder = \"/gpfs/exfel/exp/FXE/201831/p900038/proc/\" # path to already corrected input data, required\n",
    "out_folder = \"/gpfs/exfel/exp/FXE/201831/p900038/usr/calibration0818/FF/\" # path to output to, required\n",
    "run = 125 # runs to process, required\n",
    "sequences = [0] # which sequence files to use\n",
    "capacitor_setting = 5 # capacitor_setting for which data was taken\n",
    "\n",
    "mem_cells = 512 # number of memory cells used\n",
    "local_output = True # output constants locally\n",
    "db_output = False # output constants to database\n",
    "bias_voltage = 250 # detector bias voltage\n",
    "cal_db_interface = \"tcp://max-exfl015:5005\" # the database interface to use\n",
    "\n",
    "use_dir_creation_date = True # use the creation date of the directory for database time derivation\n",
    "instrument = \"FXE\"\n",
    "limit_trains = 10 # limit the number of train for the evaluation\n",
    "geometry_file = \"/gpfs/exfel/d/cal/exchange/lpdMF_00.h5\" # the geometry file to use, MAR 2018\n",
    "beam_center_offset =  [1.5, 1] # offset from the beam center, MAR 2018\n",
    "allowed_gain_thresholds = [0.5, 1.5] # relative gain values within these bounds are valid\n",
    "badpix_entire_asic_threshold = 0.25 # if more than this fraction of pixels on an ASIC are \"bad\" the entire ASIC is flagged\n",
    "photon_energy = 9.2 # photon enery in keV\n",
    "max_images = 1024 # maximum number of images to use in evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import warnings\n",
    "\n",
    "warnings.filterwarnings('ignore')\n",
    "\n",
    "# make sure a cluster is running with ipcluster start --n=32, give it a while to start\n",
    "\n",
    "import os\n",
    "\n",
    "import h5py\n",
    "import matplotlib\n",
    "import numpy as np\n",
    "\n",
    "matplotlib.use(\"agg\")\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "%matplotlib inline\n",
    "\n",
    "\n",
    "from collections import OrderedDict\n",
    "from datetime import datetime\n",
    "\n",
    "import cal_tools.metrology as metro\n",
    "from cal_tools.enums import BadPixels\n",
    "from cal_tools.plotting import create_constant_overview, plot_badpix_3d, show_overview\n",
    "from cal_tools.tools import (\n",
    "    gain_map_files,\n",
    "    get_constant_from_db,\n",
    "    get_dir_creation_date,\n",
    "    get_notebook_name,\n",
    "    parse_runs,\n",
    "    run_prop_seq_from_path,\n",
    ")\n",
    "from iCalibrationDB import Conditions, ConstantMetaData, Constants, Detectors, Versions\n",
    "\n",
    "creation_time = None\n",
    "if use_dir_creation_date:\n",
    "    creation_time = get_dir_creation_date(in_folder, run)\n",
    "    print(\"Using {} as creation time\".format(creation_time))\n",
    "\n",
    "run = \"r{:04d}\".format(run)\n",
    "in_folder = \"{}/{}\".format(in_folder, run)\n",
    "\n",
    "gains = np.arange(3)\n",
    "max_cells = mem_cells\n",
    "cells = np.arange(max_cells)\n",
    "\n",
    "\n",
    "QUADRANTS = 4\n",
    "MODULES_PER_QUAD = 4\n",
    "DET_FILE_INSET = \"LPD\"\n",
    "\n",
    "cap = \"{}pf\".format(capacitor_setting)\n",
    "\n",
    "\n",
    "if not os.path.exists(out_folder):\n",
    "    os.makedirs(out_folder)\n",
    "\n",
    "run, proposal, seq = run_prop_seq_from_path(in_folder)\n",
    "\n",
    "# start lower right and then counter-clock-wise\n",
    "dc = beam_center_offset\n",
    "\n",
    "#d_quads = [(-15,-300),(10,-10),(-255,15),(-280,-275)]\n",
    "#d_quads = [(10,-275),(-15,15),(-280,-10),(-255,-300)] # DEC 2017\n",
    "d_quads = [(-14+dc[0],-300+dc[1]),(10+dc[0],-9+dc[1]),(-256+dc[0],15+dc[1]),(-280+dc[0],-276+dc[1])] # MAR 2018\n",
    "#d_quads = [(-16+dc[0],-300+dc[1]),(10+dc[0],-9+dc[1]),(-256+dc[0],15+dc[1]),(-280+dc[0],-276+dc[1])] # MAY 2018"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# set everything up filewise\n",
    "from queue import Queue\n",
    "\n",
    "if not os.path.exists(out_folder):\n",
    "    os.makedirs(out_folder)\n",
    "    \n",
    "def map_modules_from_files(filelist):\n",
    "    module_files = {}\n",
    "    mod_ids = {}\n",
    "    for quadrant in range(0, QUADRANTS):\n",
    "        for module in range(0, MODULES_PER_QUAD):\n",
    "            name = \"Q{}M{}\".format(quadrant + 1, module + 1)\n",
    "            module_files[name] = Queue()\n",
    "            num = quadrant * 4 + module\n",
    "            mod_ids[name] = num\n",
    "            file_infix = \"{}{:02d}\".format(DET_FILE_INSET, num)\n",
    "            for file in filelist:\n",
    "                if file_infix in file:\n",
    "                    module_files[name].put(file)\n",
    "                \n",
    "    return module_files, mod_ids\n",
    "\n",
    "dirlist = os.listdir(in_folder)\n",
    "file_list = []\n",
    "for entry in dirlist:\n",
    "    #only h5 file\n",
    "    abs_entry = \"{}/{}\".format(in_folder, entry)\n",
    "    if os.path.isfile(abs_entry) and os.path.splitext(abs_entry)[1] == \".h5\":\n",
    "        \n",
    "        if sequences is None:\n",
    "            file_list.append(abs_entry)\n",
    "        else:\n",
    "            for seq in sequences:\n",
    "                if \"{:05d}.h5\".format(seq) in abs_entry:\n",
    "                    file_list.append(os.path.abspath(abs_entry))\n",
    "                    \n",
    "mapped_files, mod_ids = map_modules_from_files(file_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import copy\n",
    "\n",
    "import tabulate\n",
    "from IPython.display import HTML, Latex, Markdown, display\n",
    "\n",
    "table = []\n",
    "mfc = copy.copy(mapped_files)\n",
    "ti = 0\n",
    "for k, files in mfc.items():\n",
    "    i = 0\n",
    "    while not files.empty():\n",
    "        f = files.get()\n",
    "        if i == 0:\n",
    "            table.append((ti, k, i, f))\n",
    "        else:\n",
    "            table.append((ti, \"\", i,  f))\n",
    "        i += 1\n",
    "        ti += 1\n",
    "md = display(Latex(tabulate.tabulate(table, tablefmt='latex', headers=[\"#\", \"module\", \"# module\", \"file\"])))      \n",
    "mapped_files, mod_ids = map_modules_from_files(file_list)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "first_files = []\n",
    "for i in range(16):\n",
    "    qm = \"Q{}M{}\".format(i//4 +1, i % 4 + 1)\n",
    "    if qm in mapped_files and not mapped_files[qm].empty():\n",
    "        fname_in = str(mapped_files[qm].get())\n",
    "    else:\n",
    "        print(\"Skipping {}\".format(qm))\n",
    "        first_files.append((None, None))\n",
    "        continue\n",
    "    fout = os.path.abspath(\"{}/{}\".format(in_folder, (os.path.split(fname_in)[-1]).replace(\"RAW\", \"CORR\")))\n",
    "    \n",
    "    first_files.append((fname_in, fout))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For the flat fields all correction up to relative gain are applied. If for a given module no data exists, zero-length data is used instead."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "corrected = []\n",
    "raw = []\n",
    "gains = []\n",
    "\n",
    "for i, ff in enumerate(first_files):\n",
    "    try:\n",
    "        rf, cf = ff\n",
    "        \n",
    "        if rf is not None:\n",
    "        \n",
    "            infile = h5py.File(rf, \"r\")\n",
    "            raw.append(np.array(infile[\"/INSTRUMENT/FXE_DET_LPD1M-1/DET/{}CH0:xtdf/image/data\".format(i)][:max_images,0,...]))\n",
    "            infile.close()\n",
    "\n",
    "            infile = h5py.File(cf, \"r\")\n",
    "            corrected.append(np.array(infile[\"/INSTRUMENT/FXE_DET_LPD1M-1/DET/{}CH0:xtdf/image/data\".format(i)][:max_images,...]))\n",
    "            gains.append(np.array(infile[\"/INSTRUMENT/FXE_DET_LPD1M-1/DET/{}CH0:xtdf/image/gain\".format(i)][:max_images,...]))\n",
    "            infile.close()\n",
    "        else:\n",
    "            raise\n",
    "        \n",
    "    except Exception as e:\n",
    "        corrected.append(np.zeros((max_images, 256, 256)))\n",
    "        raw.append(np.zeros((max_images, 256, 256)))\n",
    "        gains.append(np.zeros((max_images, 256, 256)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Data Inspection ##\n",
    "\n",
    "It is important that the intensity in the image is radially symetric, and shows now strong intensity gradiants, e.g. through pronounced diffraction rings."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import cal_tools.metrology as metro\n",
    "\n",
    "in_files = \"{}/CORR*S{:05d}*.h5\".format(in_folder, sequences[0] if sequences else 0)\n",
    "datapath = \"INSTRUMENT/FXE_DET_LPD1M-1/DET/{}CH0:xtdf/image/data\"\n",
    "posarr = metro.positionFileList(in_files, datapath, geometry_file, d_quads, nImages = 100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "fig = plt.figure(figsize=(15,15))\n",
    "ax = fig.add_subplot(111)\n",
    "posarr[posarr > 1e6] = np.nan\n",
    "posarr[posarr < -1e4] = np.nan\n",
    "parr = np.nanmean(posarr, axis=0)\n",
    "im=ax.imshow((parr),  vmin=0, vmax=max(3*np.median(parr[parr > 0]), 100))\n",
    "cb = fig.colorbar(im)\n",
    "cb.set_label(\"Intensity (ADU)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Additionally, data should ideally only be present in the highest gain stage. This can be verfied in the following plot, showing the maximum pixel gain value for the first 100 images."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "datapath = \"INSTRUMENT/FXE_DET_LPD1M-1/DET/{}CH0:xtdf/image/gain\"\n",
    "posarr = metro.positionFileList(in_files, datapath, geometry_file, d_quads, nImages = 100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "fig = plt.figure(figsize=(15,15))\n",
    "ax = fig.add_subplot(111)\n",
    "parr = np.nanmax(posarr, axis=0)\n",
    "im=ax.imshow((parr),  vmin=0, vmax=3)\n",
    "cb = fig.colorbar(im)\n",
    "cb.set_label(\"Gain Bit\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# create median value maps\n",
    "means = []\n",
    "max_gains = []\n",
    "for i, c in enumerate(corrected):\n",
    "    #c[c>2**12] = np.nan\n",
    "    means.append(np.nanmean(c, axis=0))\n",
    "    max_gains.append(np.nanmax(gains[i], axis=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "def splitChannelDataIntoTiles(channelData, clockwiseOrder=False):\n",
    "    \"\"\"Splits the raw channel data into indiviual tiles\n",
    "    \n",
    "    Args\n",
    "    ----\n",
    "    \n",
    "    channelData : ndarray\n",
    "        Raw channel data. Must have shape (256, 256)\n",
    "        \n",
    "    clockwiseOrder : bool, optional\n",
    "        If set to True, the sequence of tiles is given\n",
    "        in the clockwise order starting with the top\n",
    "        right tile (LPD standard). If set to false, tile\n",
    "        data is returned in reading order\n",
    "        \n",
    "    Returns\n",
    "    -------\n",
    "    \n",
    "    ndarray\n",
    "        Same data, but reshaped into (12, 32, 128)\n",
    "    \"\"\"\n",
    "    tiles = np.asarray(np.split(channelData, 8, axis=1))\n",
    "    tiles = np.asarray(np.split(tiles, 2, axis=1))\n",
    "    orderedTiles = np.moveaxis(tiles.reshape(16, 128, 32), 2, 1)\n",
    "    if clockwiseOrder:\n",
    "        # Naturally, the tile data after splitting is in reading\n",
    "        # order (i.e. top left tile is first, top right tile is second, \n",
    "        # etc.). The official LPD tile order however is clockwise, \n",
    "        # starting with the top right tile. The following array\n",
    "        # contains indices of tiles in reading order as they would\n",
    "        # be iterated in clockwise order (starting from the top right)\n",
    "        readingOrderToClockwise = list(range(8,16))+list(range(7,-1,-1))\n",
    "        # Return tiles in reading order\n",
    "        orderedTiles = orderedTiles[readingOrderToClockwise]\n",
    "    return orderedTiles"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "positions = []\n",
    "mn_tiles = []\n",
    "gn_tiles = []\n",
    "\n",
    "tile_order = [1,2,3,4]\n",
    "for sm, mn in enumerate(means):\n",
    "    position = np.asarray([metro.getModulePosition(geometry_file,\n",
    "                                                   'Q4/M{:d}/T{:02d}'.format(tile_order[sm%4], idx+1))\n",
    "                           for idx in range(16)])\n",
    "    positions.append(position)\n",
    "    mn_tile = metro.splitChannelDataIntoTiles(mn[::-1,::-1], clockwiseOrder=True)\n",
    "    mn_tiles.append(mn_tile)\n",
    "    \n",
    "    gn_tile = metro.splitChannelDataIntoTiles(max_gains[sm][::-1,::-1], clockwiseOrder=True)\n",
    "    gn_tiles.append(gn_tile)\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from matplotlib.colors import LogNorm, PowerNorm\n",
    "\n",
    "\n",
    "def translateToModuleBL(tilePositions):\n",
    "    tileHeight = 17.7 # mm\n",
    "    # The module origin is the top left corner of the\n",
    "    # top left tile. So in the clockwise order of LPD\n",
    "    # tiles, it is the last tile in the list\n",
    "    moduleOrigin = tilePositions[-1]\n",
    "    # np.asarray([1., -1.]): inverts the y-axis, since\n",
    "    # matplotlib.figure coordinate system is on the\n",
    "    # bottom left pointing up and to the right\n",
    "    moduleCoords = np.asarray([1., -1.])*(\n",
    "        # np.asarray([0., tileHeight]): Tile positions\n",
    "        # are translated from the top left corner to the\n",
    "        # bottom left corner, i.e. 0 in x, tileHeight in y\n",
    "        tilePositions + np.asarray([0., tileHeight]) - moduleOrigin\n",
    "    )\n",
    "    # In the clockwise order of LPD tiles, the 8th\n",
    "    # tile in the list is the bottom left tile\n",
    "    bottomLeft8th = np.asarray([0., moduleCoords[8][1]])\n",
    "    # Translate coordinates to the bottom left corner \n",
    "    # of the bottom left tile\n",
    "    bottomLeft = moduleCoords - bottomLeft8th\n",
    "    return bottomLeft\n",
    "\n",
    "\n",
    "def plotSupermoduleData(smData, smPositions, dquads, zoom=1., vmin=1., vmax=2**16*0.5, norm=\"power\", gamma=0.3):\n",
    "    \n",
    "    # Conversion coefficient, required since\n",
    "    # matplotlib does its business in inches\n",
    "    mmToInch = 1./25.4 # inch/mm\n",
    "    \n",
    "    # Some constants\n",
    "    \n",
    "    numberOfRows = 16\n",
    "    numberOfCols = 4\n",
    "    tileWidth = 65.7 # in mm\n",
    "    tileHeight = 17.7 # in mm\n",
    "    \n",
    "    # Base width and height are given by spatial\n",
    "    # extend of the modules. The constants 3.4 and 1\n",
    "    # are estimated as a best guess for gaps between\n",
    "    # modules.\n",
    "    figureWidth = zoom * numberOfCols*(tileWidth + 3.4)*mmToInch\n",
    "    figureHeight = zoom * numberOfRows*(tileHeight + 1.)*mmToInch\n",
    "    fig = plt.figure(figsize=(figureWidth, figureHeight))\n",
    "    \n",
    "    quads = []\n",
    "    quad_pos = []\n",
    "    for q in range(len(smData)//4):\n",
    "        dq = min(4, len(smData)-4*q)\n",
    "        qdata = np.concatenate(smData[4*q:4*q+dq], axis=0)\n",
    "        qpos = np.concatenate(smPositions[4*q:4*q+dq], axis=0)\n",
    "        quads.append(qdata)\n",
    "        quad_pos.append(qpos)\n",
    "    \n",
    "    \n",
    "    for q, tileData in enumerate(quads):\n",
    "        \n",
    "        metrologyPositions = quad_pos[q]\n",
    "        numberOfTiles = tileData.shape[0]\n",
    "        \n",
    "        # The metrology file references module positions \n",
    "        bottomRightCornerCoordinates = metrologyPositions #translateToModuleBL(metrologyPositions)\n",
    "        \n",
    "        # The offset here accounts for the fact that there\n",
    "        # might be negative x,y values\n",
    "        offset = np.asarray(\n",
    "            [min(bottomRightCornerCoordinates[:, 0]), \n",
    "             min(bottomRightCornerCoordinates[:, 1])]\n",
    "        )\n",
    "\n",
    "        # Account for blank borders in the plot\n",
    "        borderLeft = 0.5 * mmToInch\n",
    "        borderBottom = 0.5 * mmToInch\n",
    "\n",
    "        # The height and width of the plot remain\n",
    "        # constant for a given supermodule\n",
    "        width = zoom * 65.7 * mmToInch / (figureWidth - 2.*borderLeft)\n",
    "        height = zoom * 17.7 * mmToInch / (figureHeight - 2.*borderBottom)\n",
    "\n",
    "        for i in range(numberOfTiles):\n",
    "            # This is the top left corner of the tile with\n",
    "            # respect to the top left corner of the supermodule\n",
    "            x0, y0 = bottomRightCornerCoordinates[i] + dquads[q]\n",
    "            # Transform to figure coordinates\n",
    "            ax0 = borderLeft + zoom * x0 * mmToInch / (figureWidth - 2.*borderLeft)\n",
    "            ay0 = borderBottom + zoom * y0 * mmToInch / (figureHeight - 2.*borderBottom)\n",
    "            # Place the plot in the figure\n",
    "            ax = fig.add_axes((ax0, ay0, width, height), frameon=False)\n",
    "            # Do not display axes, tick markers or labels\n",
    "            ax.tick_params(\n",
    "                axis='both', left='off', top='off', right='off', bottom='off', \n",
    "                labelleft='off', labeltop='off', labelright='off', labelbottom='off'\n",
    "            )\n",
    "            # Plot the image. If one wanted to have a colorbar\n",
    "            # the img object would be needed to produce one\n",
    "            if norm ==\"power\":\n",
    "                img = ax.imshow(\n",
    "                    tileData[i][::-1,:], \n",
    "                    interpolation='nearest', \n",
    "                    norm=PowerNorm(vmin=vmin, vmax=vmax, gamma=gamma)\n",
    "                )\n",
    "            else:\n",
    "                img = ax.imshow(\n",
    "                    tileData[i][::-1,:], \n",
    "                    interpolation='nearest', \n",
    "                    vmin=vmin, vmax=vmax\n",
    "                )\n",
    "    #fig.subplots_adjust(right=0.8)\n",
    "    cbar_ax = fig.add_axes([1.05, 0.15, 0.05, 0.7])\n",
    "    cb = fig.colorbar(img, cax=cbar_ax)\n",
    "    cb.ax.tick_params(labelsize=40) \n",
    "    \n",
    "    \n",
    "        \n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "# convert the Carthesian coordinates of the detector to polar coordinates\n",
    "def mod_cart_to_pol(d, dx, dy, filter_by_val=True):\n",
    "    \"\"\" Convert carthesian coords to polar coords\n",
    "    \"\"\"\n",
    "    cx, cy = d.shape\n",
    "    x = np.arange(cx)+dx\n",
    "    y = np.arange(cy)+dy\n",
    "    x = np.repeat(x[:,None], cy, axis=1)\n",
    "    y = np.repeat(y[None,:], cx, axis=0)\n",
    "    \n",
    "    rho = np.sqrt(x**2 + y**2).flatten()\n",
    "    phi = np.arctan2(y, x).flatten()\n",
    "    flat = d.flatten()\n",
    "    \n",
    "    # we also perform a bit of filtering here\n",
    "    \n",
    "    if filter_by_val:\n",
    "        good = np.isfinite(flat) & (flat > 0) & (flat < 1e5)\n",
    "    \n",
    "        return rho[good], phi[good], flat[good], good\n",
    "    \n",
    "    return rho, phi, flat, None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Here we create histograms of the data in a polar coordinate system.\n",
    "# We use numpys hist2d function, giving it the polar coordinates of \n",
    "# each pixels, and weighing that coordinate with the pixel's value.\n",
    "# We obtain a histogram for each module, according to its position defined\n",
    "# in the coord_list.\n",
    "from scipy.stats import binned_statistic_2d\n",
    "\n",
    "hs = []\n",
    "bins_nums = []\n",
    "edges = []\n",
    "#d_quads = [(-15,-300),(10,-10),(-255,15),(-280,-275)]\n",
    "#d_quads = [(15,-275),(-15,15),(-280,-10),(-250,-300)]\n",
    "goods = []\n",
    "bins = 5000\n",
    "for q in range(4):\n",
    "    quads = []\n",
    "    quad_pos = []\n",
    "\n",
    "    dq = 4\n",
    "    qdata = np.concatenate(mn_tiles[4*q:4*q+dq], axis=0)\n",
    "    qpos = np.concatenate(positions[4*q:4*q+dq], axis=0)\n",
    "    \n",
    "    for s in range(qdata.shape[0]):\n",
    "                \n",
    "           \n",
    "        dx, dy = qpos[s,...].tolist()\n",
    "        dx += d_quads[q][0]\n",
    "        dy += d_quads[q][1]\n",
    "        dy /= 0.5\n",
    "        dx /= 0.5\n",
    "\n",
    "        td = qdata[s,...]\n",
    "        rho, phi, weights, good = mod_cart_to_pol(td, dy, dx)\n",
    "        #h, phi_edges, rho_edges = np.histogram2d(phi, rho, bins=(1000,1000),\n",
    "        #                                         range=((-np.pi, np.pi), (0, 1000)),\n",
    "        #                                         weights=weights)\n",
    "        h, phi_edges, rho_edges, bns = binned_statistic_2d(phi, rho, weights, bins=(bins,bins),\n",
    "                                                               range=((-np.pi, np.pi), (0, 1000)),\n",
    "                                                               statistic = \"sum\")\n",
    "        bins_nums.append(bns)\n",
    "        hs.append(h)\n",
    "        edges.append((phi_edges, rho_edges))\n",
    "        goods.append(good)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Polar Plot ##\n",
    "\n",
    "Plotting the data in polar coordinates should show an intensity gradiant from left to right with regions of similar intensity extending vertically."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# Plot data in polar coordinates. Diffraction rings should now show as vertical\n",
    "# lines, connected over all modules - if the positions in the coordinate list\n",
    "# are indeed correct that is.\n",
    "fig = plt.figure(figsize=(20,20))\n",
    "ax = fig.add_subplot(111)\n",
    "x = np.arange(bins)/bins*1000*500e-6\n",
    "y = np.arange(bins)/bins*2.\n",
    "ds = np.array(hs).sum(axis=0)\n",
    "im = plt.pcolormesh(x, y, ds, vmin=0, vmax=5e2)\n",
    "cb = fig.colorbar(im)\n",
    "ax.set_xlim(0, np.max(x))\n",
    "ax.set_ylim(np.min(y), np.max(y))\n",
    "ax.set_xlabel(\"r (m)\")\n",
    "ax.set_ylabel(\"$\\phi$ ($\\pi$)\")\n",
    "cb.set_label(\"Intensity (arb. units)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Accordingly, an azimutally integrated profile should should be smooth, without any sharp peaks or artifacts. Additionally, the mean intensity should be at least 150 ADU in all reagions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# With appropriate coordinates given, plotting a profile along the\n",
    "# vertical axis should give us the positions of the diffraction peaks,\n",
    "# Here still as distances on the detector plane. With knowledge of the\n",
    "# detector to sample distance, these could then be converted in \n",
    "# reciprocal coordinates.\n",
    "ds[ds == 0] = np.nan\n",
    "profile = np.nanmedian(ds, axis=0)\n",
    "fig = plt.figure(figsize=(15,5))\n",
    "ax = fig.add_subplot(111)\n",
    "ax.plot(x, profile)\n",
    "ax.set_xlabel(\"r (m)\")\n",
    "ax.set_ylabel(\"Median intensity (arb. units)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Relative Gain Calculation ##\n",
    "\n",
    "The relative gain is then given by each (polar) pixels intensity w.r.t to the mean instensity of all pixels at the same radial distance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "relgain = ds/profile[None,:]\n",
    "\n",
    "fig = plt.figure(figsize=(20,20))\n",
    "ax = fig.add_subplot(111)\n",
    "x = np.arange(bins)/bins*1000*500e-6\n",
    "y = np.arange(bins)/bins*2.\n",
    "ds = np.array(hs).sum(axis=0)\n",
    "im = plt.pcolormesh(x, y, relgain, vmin=0.8, vmax=1.2)\n",
    "cb = fig.colorbar(im)\n",
    "ax.set_xlim(0, np.max(x))\n",
    "ax.set_ylim(np.min(y), np.max(y))\n",
    "ax.set_xlabel(\"r (m)\")\n",
    "ax.set_ylabel(\"$\\phi$ ($\\pi$)\")\n",
    "cb.set_label(\"Intensity (arb. units)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Correcting the input data witht this gain map should yield a homogenoeous image in polar coordinates"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "fig = plt.figure(figsize=(20,20))\n",
    "ax = fig.add_subplot(111)\n",
    "x = np.arange(bins)/bins*1000*500e-6\n",
    "y = np.arange(bins)/bins*2.\n",
    "ds = np.array(hs).sum(axis=0)\n",
    "im = plt.pcolormesh(x, y, ds/relgain, vmin=0, vmax=1e3)\n",
    "cb = fig.colorbar(im)\n",
    "ax.set_xlim(0, np.max(x))\n",
    "ax.set_ylim(np.min(y), np.max(y))\n",
    "ax.set_xlabel(\"r (m)\")\n",
    "ax.set_ylabel(\"$\\phi$ ($\\pi$)\")\n",
    "cb.set_label(\"Intensity (arb. units)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, the gain map can be tranferred back into carthesian coordinates, and re-attributed to the individual tiles and supermodules"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "i = 0\n",
    "rgain_tiles = []\n",
    "for q in range(4):\n",
    "    quads = []\n",
    "    quad_pos = []\n",
    "\n",
    "    dq = 4\n",
    "    qdata = np.zeros_like(np.concatenate(mn_tiles[4*q:4*q+dq], axis=0))    \n",
    "    qpos = np.concatenate(positions[4*q:4*q+dq], axis=0)\n",
    "    \n",
    "    for s in range(qdata.shape[0]):\n",
    "                           \n",
    "        dx, dy = qpos[s,...].tolist()\n",
    "        dx += d_quads[q][0]\n",
    "        dy += d_quads[q][1]\n",
    "        dy /= 0.5\n",
    "        dx /= 0.5\n",
    "                \n",
    "        rgain = hs[i]/profile[None,:]\n",
    "        good = goods[i]\n",
    "        this_tile = np.zeros_like(qdata[s,...])\n",
    "        \n",
    "        rho, phi, _, _ = mod_cart_to_pol(this_tile, dy, dx, filter_by_val=False)\n",
    "        # bin edges will contain lower and upper limits, which go past the range values\n",
    "        x_inds = np.searchsorted(phi_edges[1:-1], phi[good])\n",
    "        y_inds = np.searchsorted(rho_edges[1:-1] , rho[good])\n",
    "        #x_inds[x_inds >= rgain.shape[0]] = rgain.shape[0]-1\n",
    "        #y_inds[y_inds >= rgain.shape[1]] = rgain.shape[1]-1\n",
    "        \n",
    "        this_tile.flat[good] = rgain[x_inds, y_inds].flat\n",
    "        \n",
    "        qdata[s,...] = this_tile\n",
    "        i += 1\n",
    "    \n",
    "    rgain_tiles += np.split(qdata, 4, axis=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plotSupermoduleData(rgain_tiles, positions, d_quads, vmin=0.5, vmax=1.5, norm=\"lin\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "corr_tiles = []\n",
    "photon_gain = 22.4 # ADU per photon\n",
    "for i, tile in enumerate(mn_tiles):\n",
    "    ctile = tile/rgain_tiles[i]/photon_gain\n",
    "    corr_tiles.append(ctile)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "max_corr_tiles = []\n",
    "mean_corr_tiles = []\n",
    "for m, sm in enumerate(corrected):\n",
    "    ind_cor = []\n",
    "    for i in range(sm.shape[0]):\n",
    "        tiled = metro.splitChannelDataIntoTiles(sm[i, ::-1,::-1], clockwiseOrder=True)\n",
    "        tiled /= rgain_tiles[m]\n",
    "        ind_cor.append(tiled/photon_gain)\n",
    "    max_corr_tiles.append(np.nanmax(np.array(ind_cor), axis=0))\n",
    "    mean_corr_tiles.append(np.nanmean(np.array(ind_cor), axis=0))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Verification ##\n",
    "\n",
    "Plotting the maximum pixel value of the input data, corrected with the gain map derived from it will enhance any artifacts."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plotSupermoduleData(max_corr_tiles, positions, d_quads, vmin=0, vmax=5*np.nanmedian(max_corr_tiles), gamma=0.7)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Mean pixel value ###"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plotSupermoduleData(mean_corr_tiles, positions, d_quads, vmin=0, vmax=1.5*np.nanmedian(max_corr_tiles), gamma=0.7)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "# now arrange back to supermodules\n",
    "sm_gains = []\n",
    "for i, rgain in enumerate(rgain_tiles):\n",
    "    sm_gain = np.zeros((256,256))\n",
    "    for j in range(8):\n",
    "        sm_gain[j*32:(j+1)*32,128:] = rgain[j,...]\n",
    "        sm_gain[256-(j+1)*32:256-j*32,:128] = rgain[j+8,...]\n",
    "    sm_gains.append(sm_gain)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Bad Pixels ##\n",
    "\n",
    "Bad pixels are defined by thresholds and non-converging evaluation. Additionally, full ASICs are masked if a significant fraction of pixels on a given ASIC is considered \"bad\"."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "bad_pixels = []\n",
    "num_asics_discarded = 0\n",
    "full_modules_missing = 0\n",
    "bpix_total = 0\n",
    "for i, gain in enumerate(sm_gains):\n",
    "    bpix = np.zeros(gain.shape, np.uint32)\n",
    "    bpix[~np.isfinite(gain)] |= BadPixels.FF_GAIN_EVAL_ERROR.value\n",
    "    bpix[gain == 0] |= BadPixels.FF_NO_ENTRIES.value\n",
    "    bpix[(gain < allowed_gain_thresholds[0]) | (gain > allowed_gain_thresholds[1])] |= BadPixels.FF_GAIN_DEVIATION.value\n",
    "    for j in range(8):\n",
    "        for k in range(16):\n",
    "            asic_data = bpix[j*32:(j+1)*32, k*16:(k+1)*16]\n",
    "            bp_frac = float(np.count_nonzero(asic_data))/asic_data.size\n",
    "            \n",
    "            if bp_frac > badpix_entire_asic_threshold:\n",
    "                num_asics_discarded += 1\n",
    "                bpix[j*32:(j+1)*32, k*16:(k+1)*16] |= BadPixels.FF_GAIN_DEVIATION.value\n",
    "    if np.all(bpix != 0):\n",
    "        full_modules_missing += 1\n",
    "    bpix_total += np.count_nonzero(bpix)    \n",
    "    bad_pixels.append(bpix)\n",
    "print(\"ASICs with bad pixel fraction over {}: {}\".format(badpix_entire_asic_threshold,\n",
    "                                                              num_asics_discarded-full_modules_missing*16*8))\n",
    "print(\"Full modules considered bad: {}\".format(full_modules_missing))\n",
    "print(\"Bad pixel fraction (excluding missing modules): {:0.2f}\".format((bpix_total-full_modules_missing*256*256)/\n",
    "                                                                        (1024*1024-full_modules_missing*256*256)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "bad_pixels_tiles = []\n",
    "for m, sm in enumerate(bad_pixels):\n",
    "    bp = []\n",
    "    \n",
    "    tiled = metro.splitChannelDataIntoTiles(sm[::-1,::-1], clockwiseOrder=True)\n",
    "    bp.append(np.log2(tiled))\n",
    "    bad_pixels_tiles.append(np.nanmax(np.array(bp), axis=0))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "plotSupermoduleData(bad_pixels_tiles, positions, d_quads, vmin=0, vmax=32)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "ofile = \"{}/lpd_flatfield_store_{}.h5\".format(out_folder, run)\n",
    "store_file = h5py.File(ofile, \"w\")\n",
    "for i, sgain in enumerate(sm_gains):\n",
    "    qm = \"Q{}M{}\".format(i // 4 + 1, i % 4 +1)\n",
    "    store_file[\"{}/FlatField/0/data\".format(qm)] = sgain \n",
    "    store_file[\"{}/BadPixelsFF/0/data\".format(qm)] = bad_pixels[i] \n",
    "store_file.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "if db_output:\n",
    "    for i, sgain in enumerate(sm_gains):\n",
    "        qm = \"Q{}M{}\".format(i // 4 + 1, i % 4 +1)\n",
    "        \n",
    "        metadata = ConstantMetaData()        \n",
    "        slopes = Constants.LPD.SlopesFF()\n",
    "        slopes.data = sgain\n",
    "        metadata.calibration_constant = slopes\n",
    "\n",
    "        # set the operating condition\n",
    "        condition = Conditions.Illuminated.LPD(1., bias_voltage, photon_energy,\n",
    "                                               capacitor=capacitor_setting, beam_energy=None)\n",
    "        device = getattr(Detectors.LPD1M1, qm)\n",
    "\n",
    "        if device:\n",
    "\n",
    "            metadata.detector_condition = condition\n",
    "\n",
    "            # specify the a version for this constant\n",
    "            if creation_time is None:\n",
    "                metadata.calibration_constant_version = Versions.Now(device=device)\n",
    "            else:\n",
    "                metadata.calibration_constant_version = Versions.Timespan(device=device,\n",
    "                                                                          start=creation_time)\n",
    "            metadata.send(cal_db_interface)\n",
    "        \n",
    "        metadata = ConstantMetaData()        \n",
    "        badpix = Constants.LPD.BadPixelsFF()\n",
    "        badpix.data = bad_pixels[i] \n",
    "        metadata.calibration_constant = badpix\n",
    "\n",
    "        # set the operating condition\n",
    "        condition = Conditions.Illuminated.LPD(1., bias_voltage, photon_energy,\n",
    "                                               capacitor=capacitor_setting, beam_energy=None)\n",
    "        device = getattr(Detectors.LPD1M1, qm)\n",
    "\n",
    "        if device:\n",
    "            metadata.detector_condition = condition\n",
    "\n",
    "            # specify the a version for this constant\n",
    "            if creation_time is None:\n",
    "                metadata.calibration_constant_version = Versions.Now(device=device)\n",
    "            else:\n",
    "                metadata.calibration_constant_version = Versions.Timespan(device=device,\n",
    "                                                                          start=creation_time)\n",
    "            metadata.send(cal_db_interface)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  }
 ],
 "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.6.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}