{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "ad962ceb-45bc-42bf-9e5b-c39661dc34dc",
   "metadata": {
    "lines_to_next_cell": 0
   },
   "source": [
    "This tutorial deals with __reshaping a 2D ensemble to a 3D cube__. The cube can then, for instance, be displayed using mayavi. \n",
    "\n",
    "Let's start by importing some packages (including the seisio package itself) and setting up the logging config."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "6ddae925-e467-4a99-8917-3db6ef66c9c1",
   "metadata": {},
   "outputs": [],
   "source": [
    "import logging\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "import sys\n",
    "sys.path.insert(1, '../')\n",
    "\n",
    "import seisio\n",
    "\n",
    "logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s', force=True)\n",
    "log = logging.getLogger(\"main\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "68f98349-8d42-432b-a88b-ab5608d9a7cf",
   "metadata": {},
   "source": [
    "Let's load a small test data set. For an actual field data example, this would most likely be poststack data, migrated or unmigrated. It could also be a cross-spread gather or any other data set or subset that has an inherent 3D nature. We need some trace headers to define the dimensions of the cube. Typically for 3D poststack data, that's an inline and crossline number but other header values can also be used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "84079133-557c-4f39-a284-b55d1bdb6f58",
   "metadata": {},
   "outputs": [],
   "source": [
    "dataset = np.load(\"data/cube.npy\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8b82f95f-95af-46d3-9c38-200f90544bbf",
   "metadata": {},
   "source": [
    "Now let's check on what we've got. We can output a trace header statistics:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e10db39d-dfdc-4647-8b28-61019f7b245a",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO: Summary of trace header statistics (zeros excluded):\n",
      "+-------+-------+-------+------------+------------+---------+---------+\n",
      "|       |   min |   max |       mean |        std |     25% |     75% |\n",
      "|-------+-------+-------+------------+------------+---------+---------|\n",
      "| tracl |     1 |  3068 | 1365.44    | 1168.67    |    9.75 | 2047.25 |\n",
      "| trid  |     1 |     1 |    1       |    0       |    1    |    1    |\n",
      "| ns    |   601 |   601 |  601       |    0       |  601    |  601    |\n",
      "| dt    |  5000 |  5000 | 5000       |    0       | 5000    | 5000    |\n",
      "| iline |   101 |   107 |  103.667   |    2.29285 |  101    |  105    |\n",
      "| xline |     1 |    11 |    5.44444 |    3.02791 |    3    |    8    |\n",
      "+-------+-------+-------+------------+------------+---------+---------+\n",
      "INFO: Number of traces: 36\n"
     ]
    }
   ],
   "source": [
    "thstat = seisio.log_thstat(dataset)\n",
    "ntraces = len(dataset)\n",
    "log.info(\"Number of traces: %d\", ntraces)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6fbc15ef-2f59-4985-904d-451bd3619ce5",
   "metadata": {},
   "source": [
    "This is a very small poststack data set, consisting of four inlines and eleven crosslines. However, as we will find out shortly, not all inlines/crosslines are fully populated (the total number of traces already indicates this fact as there are only 36 and not 44 traces). In case all lines were fully populated, we could just use np.sort and np.reshape to convert the 2D ensemble of data into a 3D cube - in fact, the function __ensemble2trace__ will do so if the shape of the cube is consistent with the number of traces in the original data set. All traces are \"live\", as the trace ID header (\"trid\") shows. Let's check on the data geometry by creating a crossplot of inline-vs-crossline numbers:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "533f4465-ede6-494b-9b39-0e4edaa5e874",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 32812 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig1, ax1 = plt.subplots(1, 1)\n",
    "sc1 = ax1.scatter(dataset[\"xline\"], dataset[\"iline\"], c=dataset[\"trid\"], s=100, cmap=\"jet\", vmin=0, vmax=3)\n",
    "ax1.set_xlabel(\"crossline #\")\n",
    "ax1.set_ylabel(\"inline #\")\n",
    "plt.colorbar(sc1, ax=ax1, orientation=\"vertical\", label=\"trace ID\", format=\"%d\", ticks=[0, 1, 2, 3], shrink=0.7)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a6a5083e-fe0e-4dc7-80a2-b9a47771b46d",
   "metadata": {},
   "source": [
    "We can now plot, for instance, inline 101 and crossline 10. We can simply use trace header logic to extract the corresponding traces from the data set:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6e116300-7be8-43bc-b1b2-1812b7d38663",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 27672 characters]",
      "text/plain": [
       "<Figure size 800x300 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "il101 = np.sort(dataset[dataset[\"iline\"] == 101], order=\"xline\")\n",
    "xl10 = np.sort(dataset[dataset[\"xline\"] == 10], order=\"iline\")\n",
    "nti, nsi = il101[\"data\"].shape\n",
    "ntx, nsx = xl10[\"data\"].shape\n",
    "\n",
    "fig1a, ax1a = plt.subplots(1, 2, figsize=(8,3))\n",
    "im10 = ax1a[0].imshow(il101[\"data\"].T, aspect=\"auto\", interpolation=\"nearest\",\n",
    "                      extent=(il101[\"xline\"][0]-0.49, il101[\"xline\"][-1]+0.49, nsi-1, 0))\n",
    "ax1a[0].set_title(\"inline 101\")\n",
    "plt.colorbar(im10, ax=ax1a[0], orientation=\"vertical\", shrink=0.7, label=\"Data value\")\n",
    "im11 = ax1a[1].imshow(xl10[\"data\"].T, aspect=\"auto\", interpolation=\"nearest\",\n",
    "                      extent=(xl10[\"iline\"][0]-0.99, xl10[\"iline\"][-1]+0.99, nsx-1, 0))\n",
    "ax1a[1].set_title(\"crossline 10\")\n",
    "plt.colorbar(im11, ax=ax1a[1], orientation=\"vertical\", shrink=0.7, label=\"Data value\")\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "165526b8-b7ed-4e78-84da-bcef2e1aec36",
   "metadata": {},
   "source": [
    "In order to form a 3D cube, we can call the __ensemble2cube__ function. In this case, we would not even have to specify the trace header mnemonics to form the cube as these are the default mnemonics anyway. If the data were already sorted by `order=[\"xline\", \"iline\"]`, we could use the parameter `is_sorted=True` to avoid additional sorting or copying."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "307a4a24-b133-4864-8be7-2f431b02dc65",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO: Cube dimensions: (11, 4, 601)\n",
      "INFO: I defined by: 'xline' (1 to 11, increment 1)\n",
      "INFO: J defined by: 'iline' (101 to 107, increment 2)\n",
      "INFO: Ensemble2cube is padding 8 trace(s).\n"
     ]
    }
   ],
   "source": [
    "cube = seisio.tools.ensemble2cube(dataset, idef=\"xline\", jdef=\"iline\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "7da0c5b9-72c4-45ca-9084-9ea0dc1b3a21",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO: Number of traces in cube: 44\n"
     ]
    }
   ],
   "source": [
    "nx, ni = cube.shape\n",
    "log.info(\"Number of traces in cube: %d\", ni*nx)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58c2fe6c-298c-4904-b4a5-d78084fe7d98",
   "metadata": {},
   "source": [
    "We now have 44 traces instead of 36. Let's plot the geometry again using the same crossplot approach as before:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "a8670a14-c113-479d-8e79-dbd840143591",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 36496 characters]",
      "text/plain": [
       "<Figure size 640x480 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "fig2, ax2 = plt.subplots(1, 1)\n",
    "sc2 = ax2.scatter(cube[\"xline\"], cube[\"iline\"], c=cube[\"trid\"], s=100, cmap=\"jet\", vmin=0, vmax=3)\n",
    "ax2.set_xlabel(\"crossline #\")\n",
    "ax2.set_ylabel(\"inline #\")\n",
    "plt.colorbar(sc2, ax=ax2, orientation=\"vertical\", label=\"trace ID\", format=\"%d\", ticks=[0, 1, 2, 3], shrink=0.7)\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6460dd3-4d05-4923-a530-1ff3d5d3932b",
   "metadata": {},
   "source": [
    "It is obvious that six traces have been padded to form a regular cube. The trace ID is set to three for those traces, and the actual data values are np.nan, i.e., when plotting the data using matplotlib, the values will be ignored. Let's plot the same inline and crossline again as shown above. Inline 101 is the first entry on the inline-axis, crossline 10 is the ninth entry on the crossline-axis. This could also be determined automatically as the ranges and increments are known (see INFO logging output of the ensemble2cube function call above)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "926cbcbc-374d-4125-a074-2867ae731af7",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 27848 characters]",
      "text/plain": [
       "<Figure size 800x300 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cil101 = cube[:, 0]\n",
    "cxl10 = cube[9, :]\n",
    "cnti, cnsi = cil101[\"data\"].shape\n",
    "cntx, cnsx = cxl10[\"data\"].shape\n",
    "\n",
    "fig2a, ax2a = plt.subplots(1, 2, figsize=(8,3))\n",
    "im20 = ax2a[0].imshow(cil101[\"data\"].T, aspect=\"auto\", interpolation=\"nearest\",\n",
    "                      extent=(cil101[\"xline\"][0]-0.49, cil101[\"xline\"][-1]+0.49, cnsi-1, 0))\n",
    "ax2a[0].set_title(\"inline 101\")\n",
    "plt.colorbar(im20, ax=ax2a[0], orientation=\"vertical\", shrink=0.7, label=\"Data value\")\n",
    "im21 = ax2a[1].imshow(cxl10[\"data\"].T, aspect=\"auto\", interpolation=\"nearest\",\n",
    "                      extent=(cxl10[\"iline\"][0]-0.99, cxl10[\"iline\"][-1]+0.99, cnsx-1, 0))\n",
    "ax2a[1].set_title(\"crossline 10\")\n",
    "plt.colorbar(im21, ax=ax2a[1], orientation=\"vertical\", shrink=0.7, label=\"Data value\")\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2becf828-811c-4041-b169-856f9dcc1449",
   "metadata": {},
   "source": [
    "As expected, this gives basically the same result as before but the padding for crossline 10 can clearly be seen. The cube could easily be displayed as chair plot using mayavi or other 3D plotting tools. Let's also plot inlines 103 and 105 which should have padded traces. The data values are the inline number plus the crossline number, i.e., for inline 103 the values should range from 104 to 114 and for inline 105 from 106 to 117 (but as the larger crossline numbers are missing, the actual largest value is 105+8=113."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "86dc76d8-e4ff-43ce-a432-f735c5d13d4d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 24124 characters]",
      "text/plain": [
       "<Figure size 800x300 with 4 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "cil103 = cube[:, 1]\n",
    "cnti, cnsi = cil103[\"data\"].shape\n",
    "cil105 = cube[:, 2]\n",
    "\n",
    "fig3, ax3 = plt.subplots(1, 2, figsize=(8,3))\n",
    "im30 = ax3[0].imshow(cil103[\"data\"].T, aspect=\"auto\", interpolation=\"nearest\",\n",
    "                     extent=(cil103[\"xline\"][0]-0.49, cil103[\"xline\"][-1]+0.49, cnsi-1, 0))\n",
    "ax3[0].set_title(\"inline 103\")\n",
    "plt.colorbar(im30, ax=ax3[0], orientation=\"vertical\", shrink=0.7, label=\"Data value\")\n",
    "im31 = ax3[1].imshow(cil105[\"data\"].T, aspect=\"auto\", interpolation=\"nearest\",\n",
    "                     extent=(cil105[\"xline\"][0]-0.49, cil105[\"xline\"][-1]+0.49, cnsi-1, 0))\n",
    "ax3[1].set_title(\"inline 105\")\n",
    "plt.colorbar(im31, ax=ax3[1], orientation=\"vertical\", shrink=0.7, label=\"Data value\")\n",
    "\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f0f4b9c-3dfe-4afb-aead-dfe1d709f6cb",
   "metadata": {},
   "source": [
    "This looks as expected. A quick check on the trace headers of iline 103:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "914ca32f-0a75-4008-b471-a86b3da04b4d",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "INFO: Summary of trace header statistics (zeros excluded):\n",
      "+-------+-------+-------+------------+------------+--------+--------+\n",
      "|       |   min |   max |       mean |        std |    25% |    75% |\n",
      "|-------+-------+-------+------------+------------+--------+--------|\n",
      "| tracl |     0 |  1031 |  932.909   | 309.428    | 1022.5 | 1028.5 |\n",
      "| trid  |     1 |     3 |    1.18182 |   0.603023 |    1   |    1   |\n",
      "| ns    |   601 |   601 |  601       |   0        |  601   |  601   |\n",
      "| dt    |  5000 |  5000 | 5000       |   0        | 5000   | 5000   |\n",
      "| iline |   103 |   103 |  103       |   0        |  103   |  103   |\n",
      "| xline |     1 |    11 |    6       |   3.31662  |    3.5 |    8.5 |\n",
      "+-------+-------+-------+------------+------------+--------+--------+\n"
     ]
    }
   ],
   "source": [
    "_ = seisio.log_thstat(traces=cil103)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f0022f3-f82f-4d66-a500-db6ad6f19ad9",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "cell_metadata_filter": "-all",
   "executable": "/usr/bin/env python3",
   "main_language": "python",
   "notebook_metadata_filter": "-all"
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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.9.19"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
