{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "0c2cfd5c-c428-46fe-b8cf-29f70c2e4b35",
   "metadata": {},
   "outputs": [],
   "source": [
    "import imageio.v2 as imageio\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "from scipy import ndimage\n",
    "\n",
    "# sensor horizontal size\n",
    "sensor_width = 7.75\n",
    "\n",
    "# image file\n",
    "laser = imageio.imread(\"laser-img/2025-09-07-012621-2mm-aperture.jpg\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "c62bb371-fa65-4d10-a372-84f1ee0a70d4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Center of the beam is at -0.088/0.238\n",
      "FWHM in X/Y: 2.16/1.46\n",
      "Eccentricity (along cartesian axes): 0.73687\n"
     ]
    }
   ],
   "source": [
    "dimensions = np.array([sensor_width * laser.shape[0] / laser.shape[1], sensor_width])\n",
    "half_size = dimensions / 2\n",
    "\n",
    "# Mean of all color channels as an approximation of the monochrome image\n",
    "laser_monochrome = np.mean(laser, axis=2)\n",
    "# Normalized intensity map\n",
    "intensity_map = laser_monochrome / np.max(laser_monochrome)\n",
    "\n",
    "# Summed intensity in each axis\n",
    "intensity_x = np.sum(laser_monochrome, axis=0)\n",
    "intensity_y = np.sum(laser_monochrome, axis=1)\n",
    "\n",
    "# Center of mass of the laser beam\n",
    "center = (\n",
    "    (ndimage.center_of_mass(laser_monochrome) / np.array(laser_monochrome.shape) - 0.5)\n",
    "    * dimensions\n",
    "    * [-1, 1]\n",
    ")\n",
    "print(f\"Center of the beam is at {center[0]:.3f}/{center[1]:.3f}\")\n",
    "\n",
    "\n",
    "# Calculate FWHM in each axis\n",
    "def fwhm(curve, width):\n",
    "    assert curve.ndim == 1\n",
    "    half_max = (np.max(curve) + np.min(curve)) / 2\n",
    "    diff = curve - half_max\n",
    "    indices = np.where(diff > 0)[0]\n",
    "    return (indices[-1] - indices[0]) / curve.size * width\n",
    "\n",
    "\n",
    "fwhm_y = fwhm(intensity_y, dimensions[0])\n",
    "fwhm_x = fwhm(intensity_x, dimensions[1])\n",
    "print(f\"FWHM in X/Y: {fwhm_x:.2f}/{fwhm_y:.2f}\")\n",
    "\n",
    "eccentricity = np.sqrt(1 - (min(fwhm_y, fwhm_x) ** 2 / max(fwhm_y, fwhm_x) ** 2))\n",
    "print(f\"Eccentricity (along cartesian axes): {eccentricity:.5f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ee324d4a-c1d6-41dc-84cd-97fceeda9094",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 459432 characters]",
      "text/plain": [
       "<Figure size 1400x1400 with 2 Axes>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    },
    {
     "data": {
      "image/png": "[encoded data, 459432 characters]",
      "text/plain": [
       "<Figure size 1400x1400 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "with plt.style.context(\"dark_background\"):\n",
    "    fig, ax = plt.subplots(figsize=(14, 14))\n",
    "    im = ax.imshow(\n",
    "        intensity_map,\n",
    "        cmap=\"magma\",\n",
    "        extent=(-half_size[1], half_size[1], -half_size[0], half_size[0]),\n",
    "        interpolation=\"none\",\n",
    "        vmin=0,\n",
    "    )\n",
    "    ax.set_xlim(-np.max(half_size), np.max(half_size))\n",
    "    ax.set_ylim(-np.max(half_size), np.max(half_size))\n",
    "\n",
    "    xy_scale = max(np.max(intensity_x), np.max(intensity_y))\n",
    "    ax.plot(\n",
    "        np.linspace(-half_size[1], half_size[1], intensity_x.size),\n",
    "        intensity_x / xy_scale - np.max(half_size),\n",
    "        color=\"red\",\n",
    "    )\n",
    "    ax.plot(\n",
    "        intensity_y / xy_scale - np.max(half_size),\n",
    "        np.linspace(half_size[0], -half_size[0], intensity_y.size),\n",
    "        color=\"red\",\n",
    "    )\n",
    "\n",
    "    # reticle\n",
    "    ax.axvline(x=center[1], color=\"white\", linestyle=\":\")\n",
    "    ax.axhline(y=center[0], color=\"white\", linestyle=\":\")\n",
    "\n",
    "    ax.axvline(x=center[1] - fwhm_x / 2, color=\"gray\", linestyle=\":\")\n",
    "    ax.axvline(x=center[1] + fwhm_x / 2, color=\"gray\", linestyle=\":\")\n",
    "    ax.axhline(y=center[0] - fwhm_y / 2, color=\"gray\", linestyle=\":\")\n",
    "    ax.axhline(y=center[0] + fwhm_y / 2, color=\"gray\", linestyle=\":\")\n",
    "\n",
    "    cax = fig.add_axes(\n",
    "        [\n",
    "            ax.get_position().x1 + 0.01,\n",
    "            ax.get_position().y0,\n",
    "            0.02,\n",
    "            ax.get_position().height,\n",
    "        ]\n",
    "    )\n",
    "    fig.colorbar(im, cax=cax)\n",
    "\n",
    "    text = (\n",
    "        f\"FWHM X/Y: {fwhm_x:.2f} mm/{fwhm_y:.2f} mm\\n\"\n",
    "        f\"Eccentricity: {eccentricity:.5f}\\n\"\n",
    "        f\"Offset X/Y: {center[1]:+.3f} mm/{center[0]:+.3f} mm\"\n",
    "    )\n",
    "    fig.text(0.5, 0.05, text, ha=\"center\")\n",
    "\n",
    "fig"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "146a71ae-ba39-4f2a-aba0-1d511a74934a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "stm-env",
   "language": "python",
   "name": "stm-env"
  },
  "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.13.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
