{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# H-A-Alpha Wishart evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%load_ext autoreload\n",
    "%autoreload 2\n",
    "\n",
    "import os\n",
    "\n",
    "from pathlib import Path\n",
    "import xarray as xr\n",
    "from polsarpro.io import open_netcdf_beam\n",
    "from polsarpro.dev.io import read_psp_bin\n",
    "from polsarpro.decompositions import h_a_alpha\n",
    "from polsarpro.classification import wishart_h_a_alpha\n",
    "from polsarpro.util import S_to_T3\n",
    "import shutil\n",
    "\n",
    "# optional import for progress bar\n",
    "from dask.diagnostics import ProgressBar\n",
    "\n",
    "# change to your local C-PolSARpro install dir\n",
    "c_psp_dir = \"/home/c_psp/Soft/bin/\"\n",
    "os.environ[\"PATH\"]+=os.pathsep+f\"{c_psp_dir}/data_process_sngl/\"\n",
    "os.environ[\"PATH\"]+=os.pathsep+f\"{c_psp_dir}/data_convert/\"\n",
    "\n",
    "# change to your data paths\n",
    "# original dataset\n",
    "input_alos_data = Path(\"/data/psp/test_files/SAN_FRANCISCO_ALOS1_slc.nc\")\n",
    "\n",
    "# input files from C\n",
    "input_test_dir = Path(\"/data/psp/SAN_FRANCISCO_ALOS1/T3\")\n",
    "# input_test_dir = Path(\"/data/psp/SAN_FRANCISCO_ALOS1/\")\n",
    "\n",
    "# output files from C\n",
    "output_test_dir = Path(\"/data/psp/res/wishart_c\")\n",
    "if not os.path.isdir(output_test_dir):\n",
    "    os.mkdir(output_test_dir)\n",
    "\n",
    "\n",
    "def write_config_file(directory: Path, nrow: int, ncol: int) -> Path:\n",
    "    \"\"\"\n",
    "    Create a config.txt file in the given directory with specified dimensions.\n",
    "\n",
    "    Args:\n",
    "        directory: Target directory.\n",
    "        nrow: Number of rows.\n",
    "        ncol: Number of columns.\n",
    "\n",
    "    Returns:\n",
    "        Path to the created config file.\n",
    "    \"\"\"\n",
    "    directory = Path(directory)\n",
    "    directory.mkdir(parents=True, exist_ok=True)\n",
    "\n",
    "    config_path = directory / \"config.txt\"\n",
    "\n",
    "    content = f\"\"\"Nrow\n",
    "{nrow}\n",
    "---------\n",
    "Ncol\n",
    "{ncol}\n",
    "---------\n",
    "PolarCase\n",
    "bistatic\n",
    "---------\n",
    "PolarType\n",
    "full \n",
    "\"\"\"\n",
    "\n",
    "    config_path.write_text(content)\n",
    "\n",
    "    return config_path\n",
    "\n",
    "def summarize_agreement(reference , candidate):\n",
    "    import numpy as np\n",
    "    reference = np.asarray(reference)\n",
    "    candidate = np.asarray(candidate)\n",
    "    summary = {\n",
    "        \"overall_agreement\": float((reference == candidate).mean()),\n",
    "    }\n",
    "\n",
    "    classes = np.unique(np.concatenate([reference.ravel(), candidate.ravel()]))\n",
    "    per_class = []\n",
    "    for class_value in classes:\n",
    "        mask = reference == class_value\n",
    "        if mask.any():\n",
    "            per_class.append((int(class_value), int(mask.sum()), float((candidate[mask] == class_value).mean())))\n",
    "\n",
    "    return summary, per_class\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "# compute large image\n",
    "row1, row2 = 0, 18432\n",
    "col1, col2 = 0, 1248\n",
    "\n",
    "# smaller window\n",
    "# row1, row2 = 8000, 8500\n",
    "# row1, row2 = 8000, 12000\n",
    "# col1, col2 = 200, 1100"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Run the C-version on some test data\n",
    "### Compute H-A-alpha\n",
    "\n",
    "#### Note: \n",
    "We compute the decomposition on the whole image and then apply the window to the classification.  \n",
    "This is to avoid offset problems (classification uses the same offsets on input image a decmposition results.)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# only activate flags for H, A & Alpha\n",
    "\n",
    "input_dict = dict(\n",
    "    id=input_test_dir,\n",
    "    od=output_test_dir,\n",
    "    iodf=\"S2T3\",\n",
    "    nwr=7,\n",
    "    nwc=7,\n",
    "    ofr=0,\n",
    "    ofc=0,\n",
    "    fnr=18432,\n",
    "    fnc=1248,\n",
    "    fl1=0,\n",
    "    fl2=0,\n",
    "    fl3=1,\n",
    "    fl4=1,\n",
    "    fl5=1,\n",
    "    fl6=0,\n",
    "    fl7=0,\n",
    "    fl8=0,\n",
    "    fl9=0,\n",
    "    errf=\"/tmp/MemoryAllocError.txt\",\n",
    "    mask=f\"{input_test_dir}/mask_valid_pixels.bin\",\n",
    ")\n",
    "\n",
    "# making input string: -param1 value1 -param2 value2 etc\n",
    "param_str = \" \".join([f\"-{k} {input_dict[k]}\" for k in input_dict])\n",
    "cmd = f\"h_a_alpha_decomposition.exe {param_str}\"\n",
    "print(cmd)\n",
    "# os.system(cmd)\n",
    "\n",
    "# CPSP does not create another config file in output dir\n",
    "# shutil.copy(input_test_dir / \"config.txt\", output_test_dir)\n",
    "\n",
    "# write_config_file(directory=output_test_dir, nrow=row2-row1, ncol=col2-col1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Compute Wishart using H-A-Alpha results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "input_dict = dict(\n",
    "    id=input_test_dir,\n",
    "    od=output_test_dir,\n",
    "    # iodf=\"S2\",\n",
    "    iodf=\"T3\",\n",
    "    nwr=7,\n",
    "    nwc=7,\n",
    "    ofr=row1, # offsets are applied to input data and HAA results\n",
    "    ofc=col1,\n",
    "    fnr=row2-row1,\n",
    "    fnc=col2-col1,\n",
    "    hf=f\"{output_test_dir}/entropy.bin\",\n",
    "    af=f\"{output_test_dir}/anisotropy.bin\",\n",
    "    alf=f\"{output_test_dir}/alpha.bin\",\n",
    "    nit=\"10\",\n",
    "    pct=\"10\",\n",
    "    bmp=0,\n",
    "    errf=\"/tmp/MemoryAllocError.txt\",\n",
    "    mask=f\"{input_test_dir}/mask_valid_pixels.bin\",\n",
    ")\n",
    "\n",
    "# making input string: -param1 value1 -param2 value2 etc\n",
    "param_str = \" \".join([f\"-{k} {input_dict[k]}\" for k in input_dict])\n",
    "cmd = f\"wishart_h_a_alpha_classifier.exe {param_str}\"\n",
    "print(cmd)\n",
    "# WARNING: this takes long!\n",
    "# os.system(cmd)\n",
    "\n",
    "# need adapted conf file to read in python\n",
    "write_config_file(directory=output_test_dir, nrow=row2-row1, ncol=col2-col1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load ALOS data and C outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# uncomment to test on S matrix made with SNAP\n",
    "S = open_netcdf_beam(input_alos_data).persist()#.chunk(\"auto\")\n",
    "\n",
    "flags = (\"entropy\", \"anisotropy\", \"alpha\")\n",
    "\n",
    "dct = {}\n",
    "for it in flags:\n",
    "    dat = read_psp_bin(output_test_dir / f\"haa_full/{it}.bin\")\n",
    "    dct[it] = ((\"y\", \"x\"), dat)\n",
    "\n",
    "haa = xr.Dataset(dct, attrs={\"poltype\":\"h_a_alpha\"})#.chunk(\"auto\")\n",
    "# haa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Apply the decomposition"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "file_out = \"/data/psp/res/test_wishart_h_a_alpha.nc\"\n",
    "# netcdf writer cannot overwrite\n",
    "if os.path.isfile(file_out):\n",
    "    os.remove(file_out)\n",
    "\n",
    "# to stay consistent with C, we first compute HAA \n",
    "# on the whole image then crop before Wishart\n",
    "with ProgressBar():\n",
    "    # haa = h_a_alpha(S, boxcar_size=[7, 7], flags=flags)#.chunk(\"auto\")\n",
    "\n",
    "    # haa = haa.isel(y=slice(row1, row2), x=slice(col1, col2))\n",
    "    # T3 = S_to_T3(S) \n",
    "    res = wishart_h_a_alpha(\n",
    "        # T3.isel(y=slice(row1, row2), x=slice(col1, col2)),\n",
    "        S.isel(y=slice(row1, row2), x=slice(col1, col2)),\n",
    "        boxcar_size=[7, 7],\n",
    "        max_iter=10,\n",
    "        tol_pct=10,\n",
    "        h_a_alpha_result=haa.isel(y=slice(row1, row2), x=slice(col1, col2)),\n",
    "    ).to_netcdf(file_out)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Numerical evaluation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# open python output (comment if using compute)\n",
    "out_py = xr.open_dataset(\"/data/psp/res/test_wishart_h_a_alpha.nc\")\n",
    "\n",
    "# open C-PolSARPro outputs\n",
    "out_c = {}\n",
    "\n",
    "out_names = {\n",
    "    \"wishart_h_a_alpha_class\": \"wishart_H_A_alpha_class_7x7\",\n",
    "    \"wishart_h_alpha_class\": \"wishart_H_alpha_class_7x7\",\n",
    "}\n",
    "\n",
    "for name in out_names:\n",
    "    file_name = output_test_dir / f\"{out_names[name]}.bin\"\n",
    "    out_c[name] = read_psp_bin(file_name)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "var_name = \"wishart_h_alpha_class\"\n",
    "arr_c = out_c[var_name]\n",
    "arr_py = out_py[var_name]\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "plt.figure(figsize=(15, 10))\n",
    "plt.subplot(131)\n",
    "plt.imshow((arr_c - arr_py)[::4], interpolation=\"none\", cmap=\"Dark2\")\n",
    "plt.title(\"diff\")\n",
    "plt.subplot(132)\n",
    "plt.imshow(arr_c[::4], interpolation=\"none\", cmap=\"Dark2\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"c\")\n",
    "plt.subplot(133)\n",
    "plt.imshow(arr_py[::4], interpolation=\"none\", cmap=\"Dark2\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"py\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "summary, per_class = summarize_agreement(arr_c, arr_py)\n",
    "\n",
    "print(\"Agreement summary\")\n",
    "for key, value in summary.items():\n",
    "    print(f\"- {key}: {value:.4f}\")\n",
    "\n",
    "print(\"\\nAgreement by C class\")\n",
    "for class_value, npix, agreement in per_class:\n",
    "    print(f\"- class {class_value}: {npix} px, agreement={agreement:.4f}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "var_name = \"wishart_h_a_alpha_class\"\n",
    "arr_c = out_c[var_name]\n",
    "arr_py = out_py[var_name]\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "plt.figure(figsize=(15, 10))\n",
    "plt.subplot(131)\n",
    "plt.imshow((arr_c - arr_py)[::4], interpolation=\"none\", cmap=\"tab20\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"diff\")\n",
    "plt.subplot(132)\n",
    "plt.imshow(arr_c[::4], interpolation=\"none\", cmap=\"tab20\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"c\")\n",
    "plt.subplot(133)\n",
    "plt.imshow(arr_py[::4], interpolation=\"none\", cmap=\"tab20\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"py\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "summary, per_class = summarize_agreement(arr_c, arr_py)\n",
    "\n",
    "print(\"Agreement summary\")\n",
    "for key, value in summary.items():\n",
    "    print(f\"- {key}: {value:.4f}\")\n",
    "\n",
    "print(\"\\nAgreement by C class\")\n",
    "for class_value, npix, agreement in per_class:\n",
    "    print(f\"- class {class_value}: {npix} px, agreement={agreement:.4f}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Interpretation:\n",
    "- Difference in agreement are likely due to differences in math computation and precision errors that are amplified in matrices with small determinants. Besides, C boxcar implementation is recursive, which also causes precision errors. In python we use convolutions which are not prone to this type of error.\n",
    "- In 16 class classification, the sea is artificially divided into 2 classes which likely have very close centers, therefore small numeric errors will provoke class switching.\n",
    "- We have tested with both numpy based determinant and matrix inversions and hard-coded 3x3 versions that align with C implementation and got the same results. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Additional analysis\n",
    "Display center to center distances \n",
    "- between class pairs for each implementation\n",
    "- between C and python classes "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from polsarpro.util import S_to_T3\n",
    "from polsarpro.classification import _update_wishart_class_centers\n",
    "T3 = S_to_T3(S)\n",
    "nclass = 16\n",
    "centers_c = _update_wishart_class_centers(T3, arr_c, nclass=nclass).compute()\n",
    "centers_py = _update_wishart_class_centers(T3, arr_py.data, nclass=nclass).compute()\n",
    "\n",
    "def dist(m1, m2):\n",
    "    return float(np.sqrt(np.mean(abs(m1 - m2)**2)))\n",
    "\n",
    "# matrices storing the inter center distances\n",
    "dmat_py = np.zeros((16, 16))\n",
    "dmat_c = np.zeros((16, 16))\n",
    "for i in range(0, nclass):\n",
    "    for j in range(0, nclass):\n",
    "        dmat_py[i, j] = dist(centers_py[i], centers_py[j])\n",
    "        dmat_c[i, j] = dist(centers_c[i], centers_c[j])\n",
    "\n",
    "# vector storing comparing C and python class centers\n",
    "diff_vec = np.zeros(nclass)\n",
    "for i in range(0, nclass):\n",
    "    diff_vec[i] = dist(centers_c[i], centers_py[i]) "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.subplot(1,2,1)\n",
    "plt.imshow(dmat_c+1e-30, interpolation=\"none\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"Center distances (C)\")\n",
    "plt.subplot(1,2,2)\n",
    "plt.imshow(dmat_py, interpolation=\"none\")\n",
    "plt.colorbar(fraction=0.046, pad=0.04)\n",
    "plt.title(\"Center distances (PY)\")\n",
    "plt.tight_layout()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# unique center to center distances\n",
    "plt.plot(np.unique(dmat_py))\n",
    "plt.plot(np.unique(dmat_c), \"--\")\n",
    "plt.legend([\"py\", \"C\"])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# center distances between C and python\n",
    "plt.plot(np.arange(1, nclass + 1), diff_vec)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "out_py"
   ]
  }
 ],
 "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.12.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
