{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import spiceypy as spice\n",
    "import pvl\n",
    "import os\n",
    "import re\n",
    "import subprocess\n",
    "from ale import util\n",
    "from itertools import chain\n",
    "import io\n",
    "import networkx as nx\n",
    "\n",
    "\n",
    "# These should be provided when running this script. \n",
    "cube = \"leisa/lsb_0296962438_0x53c_eng/lsb_0296962438_0x53c_eng-spiced.cub\"\n",
    "output_dir = \"leisa/lsb_0296962438_0x53c_eng/\" # Output dir for created kernel files\n",
    "data_dir = \"isis3/isis_data/\" # Dir of where to pull original kernels from\n",
    "\n",
    "def merge_intervals(intervals):\n",
    "    \"\"\"\n",
    "    Merge a set of intervals. The intervals are assumed to be closed, that is they include the end-points.\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    intervals : list\n",
    "                The input list of intrvals where each interval is a tuple of (start, end)\n",
    "    \n",
    "    Returns\n",
    "    -------\n",
    "    : list\n",
    "      A sorted list of the merged intervals\n",
    "    \"\"\"\n",
    "    sorted_intervals = sorted(intervals, key=lambda tup: tup[0])\n",
    "    merged = [sorted_intervals[0]]\n",
    "    for interval in sorted_intervals[1:]:\n",
    "        # No intersection\n",
    "        if interval[0] > merged[-1][1]:\n",
    "            merged.append(interval)\n",
    "        # Intersection, but new interval isn't wholey contained\n",
    "        elif interval[1] > merged[-1][1]:\n",
    "            merged[-1] = (merged[-1][0], interval[1])\n",
    "    return merged\n",
    "\n",
    "def add_light_time_correction(cube_info, padding=120):\n",
    "    \"\"\"\n",
    "    Compute the time intervals for the image and any light time correction\n",
    "    \n",
    "    Parameters\n",
    "    ----------\n",
    "    cube_info : ordered dict\n",
    "                The cube info from ale.util.generate_kernels_from_cube\n",
    "    padding : float\n",
    "              Time padding in seconds to add to each interval\n",
    "    \n",
    "    Returns\n",
    "    -------\n",
    "    : list\n",
    "      A sorted list of the intervals as (start_et, stop_et)\n",
    "    \"\"\"\n",
    "    image_start_et = spice.scs2e(cube_info['SpacecraftID'], cube_info['SpacecraftClockCount'])\n",
    " \n",
    "    image_end_et = image_start_et + cube_info['ExposureDuration'] * cube_info['Lines']\n",
    "\n",
    "    inst_state, inst_lt = spice.spkez(cube_info['SpacecraftID'], image_start_et, 'J2000', 'NONE', 0)\n",
    "    target_state, target_lt = spice.spkez(cube_info['TargetID'], image_start_et, 'J2000', 'NONE', 0)\n",
    "    sun_state, sun_lt = spice.spkez(10, image_start_et, 'J2000', 'NONE', cube_info['TargetID'])\n",
    "    \n",
    "    intervals = [\n",
    "        (image_start_et - padding, image_end_et + padding),\n",
    "        (image_start_et - padding - inst_lt, image_end_et + padding - inst_lt),\n",
    "        (image_start_et - padding - target_lt, image_end_et + padding - target_lt),\n",
    "        (image_start_et - padding - sun_lt, image_end_et + padding - sun_lt)]\n",
    "    return merge_intervals(intervals)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# These are the processing steps. This will make use of the cube provided further up to create smaller, \n",
    "# more manageable kernel files for ale testing purposes. This currently only handles ck and spk files.\n",
    "\n",
    "# Get dictionary of kernel lists from cube\n",
    "cube_info = util.generate_kernels_from_cube(cube, format_as = 'dict')\n",
    "\n",
    "# Replace path variables with absolute paths for kernels\n",
    "for kernel_list in cube_info:\n",
    "    for index, kern in enumerate(cube_info[kernel_list]):\n",
    "        if kern is not None:\n",
    "            cube_info[kernel_list][index] = data_dir + kern.strip('$')\n",
    "            \n",
    "# Create ordered list of kernels for furnishing\n",
    "kernels = [kernel for kernel in chain.from_iterable(cube_info.values()) if isinstance(kernel, str)]\n",
    "spice.furnsh(kernels)\n",
    "                \n",
    "# Loads cube as pvl to extract rest of data\n",
    "cube_pvl = pvl.load(cube)\n",
    "               \n",
    "# Save other necesary info in cube_info dict\n",
    "cube_info.update(Lines = cube_pvl['IsisCube']['Core']['Dimensions']['Lines'])\n",
    "cube_info.update(SpacecraftClockCount = cube_pvl['IsisCube']['Instrument']['SpacecraftClockStartCount'])\n",
    "cube_info.update(ExposureDuration = cube_pvl['IsisCube']['Instrument']['ExposureDuration'])\n",
    "cube_info.update(TargetID = spice.bods2c(cube_pvl['IsisCube']['Instrument']['TargetName']))\n",
    "cube_info.update(SpacecraftID = spice.bods2c(cube_pvl['IsisCube']['Instrument']['SpacecraftName']))\n",
    "\n",
    "# Account for light time correction\n",
    "intervals = add_light_time_correction(cube_info)\n",
    "\n",
    "# For each binary ck kernel specified in cube, run the ckslicer, comment and to-transfer commands\n",
    "for ck in [k for k in kernels if k.lower().endswith('.bc')]:\n",
    "    ck_path, ck_file_extension = os.path.splitext(ck)\n",
    "    ck_basename = os.path.basename(ck_path)\n",
    "    for index, interval in enumerate(intervals):\n",
    "        for frame in util.get_ck_frames(ck):\n",
    "            output_basename = os.path.join(output_dir, ck_basename + '_' + str(index) + '_sliced_' + str(frame))\n",
    "            output_kern = output_basename + ck_file_extension\n",
    "            output_comments = output_basename + '.cmt'\n",
    "            start_sclk = spice.sce2s(cube_info['SpacecraftID'], interval[0])\n",
    "            end_sclk = spice.sce2s(cube_info['SpacecraftID'], interval[1])\n",
    "            # Create new sliced ck kernel\n",
    "            ckslicer_command = [\"ckslicer\", \n",
    "                                    '-LSK {}'.format(cube_info['LeapSecond'][0]), \n",
    "                                    '-SCLK {}'.format(cube_info['SpacecraftClock'][0]), \n",
    "                                    '-INPUTCK {}'.format(ck), \n",
    "                                    '-OUTPUTCK {}'.format(output_kern),\n",
    "                                    '-ID {}'.format(str(frame)),\n",
    "                                    '-TIMETYPE {}'.format('SCLK'),\n",
    "                                    '-START {}'.format(start_sclk),\n",
    "                                    '-STOP {}'.format(end_sclk)]\n",
    "            subprocess.run(ckslicer_command, check=True)\n",
    "\n",
    "            # Remove old comments from new ck kernel\n",
    "            commnt_command = ['commnt', '-d {}'.format(output_kern)]\n",
    "            subprocess.run(commnt_command, check=True)\n",
    "\n",
    "            with open(output_comments, 'w+') as comment_file:\n",
    "                comment_file.write(\"This CK is for testing with the image: {}\\n\".format(cube))\n",
    "                comment_file.write(\"\\nThis CK was generated using the following command: {}\\n\")\n",
    "                comment_file.write(\" \".join(ckslicer_command))\n",
    "\n",
    "            # Add new comments to new ck kernel\n",
    "            new_commnts_command = [\"commnt\", \"-a {}\".format(output_kern), output_comments]\n",
    "            subprocess.run(new_commnts_command, check=True)\n",
    "\n",
    "            # Create the transfer file of the new ck kernel\n",
    "            subprocess.run([\"toxfr\", output_kern], check=True)\n",
    "\n",
    "# Create the config file for the spkmerge command\n",
    "for index, interval in enumerate(intervals):\n",
    "    output_spk_basename = os.path.join(output_dir, os.path.basename(os.path.splitext(cube)[0]) + '_' + str(index))\n",
    "    output_spk = output_spk_basename + '.bsp'\n",
    "    start_utc = spice.et2utc(interval[0], 'c', 3)\n",
    "    end_utc = spice.et2utc(interval[1], 'c', 3)\n",
    "    spk_dep_tree = util.create_spk_dependency_tree([k for k in kernels if k.lower().endswith('.bsp')])\n",
    "    config_string = util.spkmerge_config_string(spk_dep_tree,\n",
    "                                                output_spk,\n",
    "                                                [cube_info['TargetID'], cube_info['SpacecraftID'], 10],\n",
    "                                                cube_info['LeapSecond'][0],\n",
    "                                                start_utc,\n",
    "                                                end_utc)\n",
    "    with open(output_spk_basename + '.conf', 'w+') as spk_config:\n",
    "        spk_config.write(config_string)\n",
    "\n",
    "    # Create the new SPK\n",
    "    spkmerge_command = [\"spkmerge\", spk_config.name]\n",
    "    subprocess.run(spkmerge_command, check=True)\n",
    "\n",
    "    # Create the transfer file of the new SPK kernel\n",
    "    subprocess.run([\"toxfr\", output_spk], check=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "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.10.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
