Repository: SatelliteShorelines/CoastSeg (language: python)

Issue:
ImportError: cannot import name 'exception_handler' from 'coastseg' 
With a refresh clone and a refresh install of the coastseg conda environment, I get the following error when running cell 9

```
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
Cell In [9], line 1
----> 1 from src.coastseg.map_UI import UI
      2 coastseg_ui = UI(coastsegmap)
      3 coastseg_ui.create_dashboard()

File ~\github_clones\CoastSeg\src\coastseg\map_UI.py:10
      8 from coastseg.tkinter_window_creator import Tkinter_Window_Creator
      9 from coastseg import exceptions
---> 10 from coastseg import exception_handler
     12 # external python imports
     13 import ipywidgets

ImportError: cannot import name 'exception_handler' from 'coastseg' (C:\Users\dbuscombe\Anaconda3\envs\coastseg\lib\site-packages\coastseg\__init__.py)
```
pydensecrf no longer a dependency for Gym or Zoo
As of release [v0.0.14](https://pypi.org/manage/project/doodleverse-utils/release/0.0.14/) doodleverse-utils no longer has `pydensecrf` as a dependency. It is a rarely used function that affords little benefit, and it is not available on pip which could cause deployment problems. See https://github.com/Doodleverse/doodleverse_utils/issues/16

Update [src/coastseg/UI_models.py](https://github.com/SatelliteShorelines/CoastSeg/blob/5cf7a250d75a09ed01c5333957cad013396cf355/src/coastseg/UI_models.py) to remove `use_CRF` option

Update [src/coastseg/zoo_model.py](https://github.com/SatelliteShorelines/CoastSeg/blob/5cf7a250d75a09ed01c5333957cad013396cf355/src/coastseg/zoo_model.py) to remove `do_crf` option

Update [/refactored_custom_map_official.ipynb](https://github.com/SatelliteShorelines/CoastSeg/blob/5cf7a250d75a09ed01c5333957cad013396cf355/refactored_custom_map_official.ipynb) to remove reference to CRF

Update [/custom_map.ipynb](https://github.com/SatelliteShorelines/CoastSeg/blob/5cf7a250d75a09ed01c5333957cad013396cf355/custom_map.ipynb) to remove reference to CRF


Discussion / hints:

Fix patch:
diff --git a/.github/.gitignore b/.github/.gitignore
index 4e936eb6..27339ceb 100644
--- a/.github/.gitignore
+++ b/.github/.gitignore
@@ -1,3 +1,24 @@
 data\*
 src\*\downloaded_models\*
-*segmentation_data_*
\ No newline at end of file
+*segmentation_data_*
+# Distribution / packaging
+md2rst.md
+md2rst.rst
+testing/
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
\ No newline at end of file
diff --git a/.github/workflows/pip_install_e.yml b/.github/workflows/pip_install_e.yml
index 25da2223..39af71b3 100644
--- a/.github/workflows/pip_install_e.yml
+++ b/.github/workflows/pip_install_e.yml
@@ -1,8 +1,8 @@
 on:
   push:
-    branches: [main, workflows]
+    branches: [main]
   pull_request:
-    branches: [main, workflows]
+    branches: [main]
 
 name: Test CoastSeg with Multiple OS python 3.9 and pip install -e .
 jobs:
diff --git a/MNDWI_model_error_notebook.ipynb b/MNDWI_model_error_notebook.ipynb
deleted file mode 100644
index 56cd0d23..00000000
--- a/MNDWI_model_error_notebook.ipynb
+++ /dev/null
@@ -1,152 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {},
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "Version:  2.9.1\n",
-      "Eager mode:  True\n",
-      "Version:  2.9.1\n",
-      "Eager mode:  True\n",
-      "GPU name:  []\n",
-      "Num GPUs Available:  0\n"
-     ]
-    }
-   ],
-   "source": [
-    "import warnings\n",
-    "import os\n",
-    "import datetime\n",
-    "import glob\n",
-    "import json\n",
-    "# Local Imports \n",
-    "from CoastSeg import file_functions, zoo_model_module\n",
-    "\n",
-    "# suppress tensorflow warnings\n",
-    "os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Call the MNDWI model with the MNDWI Dataset\n",
-    "- Make sure you modify the variable `sample_direc` to be the location of the MNDWI ouputs you will be running the model on. "
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 3,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "sample_direc =r'C:\\1_USGS\\1_CoastSeg\\1_official_CoastSeg_repo\\CoastSeg\\MNDWI_ouputs_2022-07-21__07_hr_57_min14'"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 4,
-   "metadata": {
-    "scrolled": true
-   },
-   "outputs": [
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "\r",
-      "  0%|                                                                                           | 0/16 [00:00<?, ?it/s]"
-     ]
-    },
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "WARNING:tensorflow:Model was constructed with shape (None, 256, 256, 1) for input KerasTensor(type_spec=TensorSpec(shape=(None, 256, 256, 1), dtype=tf.float32, name='input_4'), name='input_4', description=\"created by layer 'input_4'\"), but it was called on an input with incompatible shape (1, 256, 256, 3).\n"
-     ]
-    },
-    {
-     "name": "stderr",
-     "output_type": "stream",
-     "text": [
-      "  0%|                                                                                           | 0/16 [00:00<?, ?it/s]\n"
-     ]
-    },
-    {
-     "ename": "ValueError",
-     "evalue": "in user code:\n\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1845, in predict_function  *\n        return step_function(self, iterator)\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1834, in step_function  **\n        outputs = model.distribute_strategy.run(run_step, args=(data,))\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1823, in run_step  **\n        outputs = model.predict_step(data)\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1791, in predict_step\n        return self(x, training=False)\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\utils\\traceback_utils.py\", line 67, in error_handler\n        raise e.with_traceback(filtered_tb) from None\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\input_spec.py\", line 249, in assert_input_compatibility\n        f'Input {input_index} of layer \"{layer_name}\" is '\n\n    ValueError: Exception encountered when calling layer \"model_3\" (type Functional).\n    \n    Input 0 of layer \"conv2d_90\" is incompatible with the layer: expected axis -1 of input shape to have value 1, but received input with shape (1, 256, 256, 3)\n    \n    Call arguments received by layer \"model_3\" (type Functional):\n      • inputs=tf.Tensor(shape=(1, 256, 256, 3), dtype=float64)\n      • training=False\n      • mask=None\n",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[1;31mValueError\u001b[0m                                Traceback (most recent call last)",
-      "\u001b[1;32m~\\AppData\\Local\\Temp\\ipykernel_23420\\3557917149.py\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     17\u001b[0m \u001b[0mmetadatadict\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mzoo_model\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mget_metadatadict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mWw\u001b[0m\u001b[1;33m,\u001b[0m\u001b[0mconfig_files\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmodel_types\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     18\u001b[0m \u001b[1;31m# # Compute the segmentation\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 19\u001b[1;33m \u001b[0mzoo_model\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mcompute_segmentation\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mmodel_dict\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m\"sample_direc\"\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmodel_list\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mmetadatadict\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
-      "\u001b[1;32mC:\\1_USGS\\1_CoastSeg\\1_official_CoastSeg_repo\\CoastSeg\\CoastSeg\\zoo_model_module.py\u001b[0m in \u001b[0;36mcompute_segmentation\u001b[1;34m(self, sample_direc, model_list, metadatadict)\u001b[0m\n\u001b[0;32m     51\u001b[0m                 \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mTARGET_SIZE\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     52\u001b[0m                 \u001b[0mTESTTIMEAUG\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 53\u001b[1;33m                 WRITE_MODELMETADATA)\n\u001b[0m\u001b[0;32m     54\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     55\u001b[0m     \u001b[1;32mdef\u001b[0m \u001b[0mget_model\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mWw\u001b[0m\u001b[1;33m:\u001b[0m \u001b[0mlist\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;32m~\\anaconda\\envs\\coastseg\\lib\\site-packages\\doodleverse_utils\\prediction_imports.py\u001b[0m in \u001b[0;36mdo_seg\u001b[1;34m(f, M, metadatadict, sample_direc, NCLASSES, N_DATA_BANDS, TARGET_SIZE, TESTTIMEAUG, WRITE_MODELMETADATA)\u001b[0m\n\u001b[0;32m    169\u001b[0m             \u001b[1;31m# heatmap = make_gradcam_heatmap(tf.expand_dims(image, 0) , model)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    170\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 171\u001b[1;33m             \u001b[0mest_label\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mmodel\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mpredict\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mtf\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mexpand_dims\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mimage\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;36m0\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mbatch_size\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;36m1\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0msqueeze\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    172\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    173\u001b[0m             \u001b[1;32mif\u001b[0m \u001b[0mTESTTIMEAUG\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;32m~\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\utils\\traceback_utils.py\u001b[0m in \u001b[0;36merror_handler\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m     65\u001b[0m     \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m  \u001b[1;31m# pylint: disable=broad-except\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     66\u001b[0m       \u001b[0mfiltered_tb\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0m_process_traceback_frames\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0me\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m__traceback__\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 67\u001b[1;33m       \u001b[1;32mraise\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwith_traceback\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mfiltered_tb\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     68\u001b[0m     \u001b[1;32mfinally\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     69\u001b[0m       \u001b[1;32mdel\u001b[0m \u001b[0mfiltered_tb\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;32m~\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\u001b[0m in \u001b[0;36mtf__predict_function\u001b[1;34m(iterator)\u001b[0m\n\u001b[0;32m     13\u001b[0m                 \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     14\u001b[0m                     \u001b[0mdo_return\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 15\u001b[1;33m                     \u001b[0mretval_\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mag__\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mconverted_call\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mag__\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mld\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mstep_function\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;33m(\u001b[0m\u001b[0mag__\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mld\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mself\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mag__\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mld\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0miterator\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m,\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mfscope\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     16\u001b[0m                 \u001b[1;32mexcept\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     17\u001b[0m                     \u001b[0mdo_return\u001b[0m \u001b[1;33m=\u001b[0m \u001b[1;32mFalse\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;31mValueError\u001b[0m: in user code:\n\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1845, in predict_function  *\n        return step_function(self, iterator)\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1834, in step_function  **\n        outputs = model.distribute_strategy.run(run_step, args=(data,))\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1823, in run_step  **\n        outputs = model.predict_step(data)\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\training.py\", line 1791, in predict_step\n        return self(x, training=False)\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\utils\\traceback_utils.py\", line 67, in error_handler\n        raise e.with_traceback(filtered_tb) from None\n    File \"C:\\Users\\Sharon\\anaconda\\envs\\coastseg\\lib\\site-packages\\keras\\engine\\input_spec.py\", line 249, in assert_input_compatibility\n        f'Input {input_index} of layer \"{layer_name}\" is '\n\n    ValueError: Exception encountered when calling layer \"model_3\" (type Functional).\n    \n    Input 0 of layer \"conv2d_90\" is incompatible with the layer: expected axis -1 of input shape to have value 1, but received input with shape (1, 256, 256, 3)\n    \n    Call arguments received by layer \"model_3\" (type Functional):\n      • inputs=tf.Tensor(shape=(1, 256, 256, 3), dtype=float64)\n      • training=False\n      • mask=None\n"
-     ]
-    }
-   ],
-   "source": [
-    "model_dict={\"use_GPU\":False, 'implementation':'ENSEMBLE','model_type':'SWED-MNDWI_6824342'}\n",
-    "# Not use the GPU\n",
-    "os.environ['CUDA_VISIBLE_DEVICES'] = '-1'\n",
-    "dataset='MNDWI'\n",
-    "# Location where you stored the MNDWI ouputs\n",
-    "model_dict[\"sample_direc\"]=sample_direc\n",
-    "dataset_id=model_dict['model_type']\n",
-    "model_choice=model_dict['implementation']\n",
-    "dataset='MNDWI'\n",
-    "zoo_model=zoo_model_module.Zoo_Model()\n",
-    " # # First download the specified model\n",
-    "zoo_model.download_model(dataset,dataset_id)\n",
-    "# # Get weights as list\n",
-    "Ww=zoo_model.get_weights_list(model_choice)\n",
-    "# Load the model from the config files\n",
-    "model, model_list, config_files, model_types=zoo_model.get_model(Ww)\n",
-    "metadatadict=zoo_model.get_metadatadict(Ww,config_files, model_types)\n",
-    "# # Compute the segmentation\n",
-    "zoo_model.compute_segmentation(model_dict[\"sample_direc\"], model_list, metadatadict)"
-   ]
-  },
-  {
-   "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.7.12"
-  },
-  "vscode": {
-   "interpreter": {
-    "hash": "9d097efbdf24db87a2507c17c632c6d564e7aff5ba5cfc04e62a44a8b3d6a20a"
-   }
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 2
-}
diff --git a/README.md b/README.md
index 9d107345..a1a3f770 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,7 @@ Create a new environment named `coastseg` with all the required packages by ente
 1. Create an Anaconda environment
 
 - We will install the CoastSeg package and its dependencies in this environment.
-  ```bash 
+  ```bash
   conda create --name coastseg python=3.9 -y
   ```
 
@@ -70,12 +70,11 @@ Create a new environment named `coastseg` with all the required packages by ente
      alt="coastseg activated in anaconda prompt" width="350" height="150">
 
 3. Install geopandas with Conda
-   - install [pydensecrf](https://anaconda.org/conda-forge/pydensecrf) because it is a dependency of doodleverse_utils
    - [Geopandas](https://geopandas.org/en/stable/) has [GDAL](https://gdal.org/) as a dependency so its best to install it with conda.
    - Make sure to install geopandas from the `conda-forge` channel to ensure you get the latest version.
    - Make sure to install both jupyter and geopandas from the conda forge channel to avoid dependency conflicts
      ```bash
-     conda install -c conda-forge jupyter geopandas pydensecrf -y
+     conda install -c conda-forge jupyter geopandas -y
      ```
 4. Install the CoastSeg from PyPi
    ```bash
@@ -87,7 +86,7 @@ Create a new environment named `coastseg` with all the required packages by ente
 ```bash
 conda create --name coastseg python=3.9 -y
 conda activate coastseg
-conda install -c conda-forge jupyter geopandas pydensecrf -y
+conda install -c conda-forge jupyter geopandas -y
 pip install coastseg
 ```
 
@@ -122,13 +121,15 @@ pip install coastseg
 3. Install geopandas with Conda
    - [Geopandas](https://geopandas.org/en/stable/) has [GDAL](https://gdal.org/) as a dependency so its best to install it with conda.
    - Make sure to install geopandas from the `conda-forge` channel to ensure you get the latest version.
-    ```bash
-    conda install -c conda-forge jupyter geopandas -y
-    ```
+   ```bash
+   conda install -c conda-forge jupyter geopandas -y
+   ```
 4. Clone the CoastSeg Repository
-```bash 
+
+```bash
   git clone https://github.com/SatelliteShorelines/CoastSeg.git
 ```
+
 6. Install the coastseg pip package
 
 - `-e` (editable install flag) install coastseg using the `pyproject.toml` located in coastseg's directory to install the coastseg package. See [pip documentation for -e](https://pip.pypa.io/en/stable/topics/local-project-installs/#:~:text=Editable%20installs%20allow%20you%20to,added%20to%20Python's%20import%20path.) for more information on how editable installations work.
diff --git a/custom_map.ipynb b/custom_map.ipynb
index b2596b47..b2139564 100644
--- a/custom_map.ipynb
+++ b/custom_map.ipynb
@@ -7,7 +7,6 @@
    "outputs": [],
    "source": [
     "import os\n",
-    "import datetime\n",
     "# Local Imports\n",
     "import coastseg\n",
     "from coastseg import coastseg_map\n",
@@ -16,9 +15,6 @@
     "# External Imports\n",
     "import ee\n",
     "from google.auth import exceptions as google_auth_exceptions\n",
-    "from IPython.display import display, clear_output\n",
-    "from tkinter import Tk,filedialog\n",
-    "from ipywidgets import Button, HBox, VBox, Layout, DatePicker, HTML, RadioButtons, SelectMultiple, Output, Checkbox\n",
     "\n",
     "# suppress tensorflow warnings\n",
     "os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'"
@@ -55,182 +51,6 @@
     "    ee.Initialize()"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# Date Widgets\n",
-    "start_date=DatePicker(\n",
-    "    description='Start Date',\n",
-    "    value=datetime.date(2018, 12, 1),\n",
-    "    disabled=False,\n",
-    ")\n",
-    "end_date=DatePicker(\n",
-    "    description='End Date',\n",
-    "    value=datetime.date(2019, 3, 1), #2019, 1, 1\n",
-    "    disabled=False,\n",
-    ")\n",
-    "date_instr=HTML(\n",
-    "    value=\"<b>Pick a date:</b>\",\n",
-    "    layout=Layout(padding='10px')\n",
-    ")\n",
-    "dates_box=HBox([start_date,end_date])\n",
-    "dates_vbox=VBox([date_instr,dates_box])\n",
-    "\n",
-    "\n",
-    "collection_instr=HTML(\n",
-    "    value=\"<b>Pick a collection</b>\\\n",
-    "        <br> C01: LandSat Collection 1 \\\n",
-    "        <br> C02: LandSat Collection 2  [2022/01/01 and beyond] \\\n",
-    "        <br>      - Landsat 9 is only available in C02\",\n",
-    "    layout=Layout(padding='10px')\n",
-    ")\n",
-    "collection_radio=RadioButtons(\n",
-    "    options=['C01', 'C02'],\n",
-    "    value='C01',\n",
-    "    description='Collection:',\n",
-    "    disabled=False\n",
-    ")\n",
-    "collection_vbox=VBox([collection_instr,collection_radio])\n",
-    "\n",
-    "sat_instr=HTML(\n",
-    "    value=\"<b>Pick multiple satellites by holding the control key:</b> \\\n",
-    "        <br> - images prior to 2022/01/01 will be downloaded from Collection 1 \\\n",
-    "        <br> - images after 2022/01/01 will be automatically downloaded from Collection 2 \\\n",
-    "        <br> C01: LandSat Collection 1 : All dates before 2022/01/01\\\n",
-    "        <br> C02: LandSat Collection 2 : All dates after 2022/01/01 \\\n",
-    "        <br> - Landsat 9 is only available in C02\",\n",
-    "    layout=Layout(padding='10px')\n",
-    ")\n",
-    "\n",
-    "\n",
-    "satellite_selection=SelectMultiple(\n",
-    "    options=['L5', 'L7', 'L8', 'S2'],\n",
-    "    value=['L8'],\n",
-    "    description='Satellites',\n",
-    "    disabled=False\n",
-    ")\n",
-    "# value=['S2'],\n",
-    "sat_vbox = VBox([sat_instr,satellite_selection])\n",
-    "\n",
-    "def handle_collection_change(change):\n",
-    "    if change['new'] == 'C02':\n",
-    "        satellite_selection.options=['L5', 'L7', 'L8', 'L9', 'S2']\n",
-    "    else:\n",
-    "        satellite_selection.options=['L5', 'L7', 'L8', 'S2']\n",
-    "\n",
-    "collection_radio.observe(handle_collection_change,\"value\")\n",
-    "\n",
-    "controls_vbox=VBox([dates_vbox, collection_vbox, sat_vbox])\n",
-    "controls_vbox"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "scrolled": true
-   },
-   "outputs": [],
-   "source": [
-    "# CoastSat Download Variables\n",
-    "# ------------------------------\n",
-    "# Save the satellites selected to sat_list\n",
-    "if satellite_selection.value:\n",
-    "    sat_list = list(satellite_selection.value)\n",
-    "elif not satellite_selection.value:\n",
-    "    print(\"ERROR: You must select at least one satellite first\")\n",
-    "# Save the dates selected by the user as well as the selected collection\n",
-    "dates = [str(start_date.value),str(end_date.value)]\n",
-    "landsat_collection = collection_radio.value\n",
-    "print(f\"dates: {dates}\")\n",
-    "print(f\"landsat_collection: {landsat_collection}\")\n",
-    "print(f\"sat_list: {sat_list}\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Settings for CoastSat\n",
-    "\n",
-    "Modify any of the following setting to determine how data is downloaded from CoastSat\n",
-    "## Future Plan\n",
-    " All of the settings and the widgets to control the settings will be incorperated into the main dashboard"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "settings = { \n",
-    "    # general parameters:\n",
-    "    'cloud_thresh': 0.5,        # threshold on maximum cloud cover\n",
-    "     'dist_clouds': 300,        # ditance around clouds where shoreline can't be mapped\n",
-    "    'output_epsg': 3857,        # epsg code of spatial reference system desired for the output   \n",
-    "    # quality control:\n",
-    "    'check_detection': False,    # if True, shows each shoreline detection to the user for validation\n",
-    "    'adjust_detection': False,  # if True, allows user to adjust the postion of each shoreline by changing the threhold\n",
-    "    'save_figure': True,        # if True, saves a figure showing the mapped shoreline for each image\n",
-    "    # [ONLY FOR ADVANCED USERS] shoreline detection parameters:\n",
-    "    'min_beach_area': 4500,     # minimum area (in metres^2) for an object to be labelled as a beach\n",
-    "    'buffer_size': 550,         # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection\n",
-    "    'min_length_sl': 100,       # minimum length (in metres) of shoreline perimeter to be valid\n",
-    "    'cloud_mask_issue': False,  # switch this parameter to True if sand pixels are masked (in black) on many images  \n",
-    "    'sand_color': 'default',    # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)\n",
-    "    'pan_off':'False',          # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery\n",
-    "    'create_plot':False,        # True create a matplotlib plot of the image with the datetime as the title\n",
-    "    'max_dist_ref':25,\n",
-    "    'along_dist' :25,\n",
-    "}"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Create the Map\n",
-    "\n",
-    "- Create the map here\n",
-    "- Save all the settings created earlier\n",
-    "- Save the list of satellites, dates, and the collection to coastseg_map \n",
-    "\n",
-    "\n",
-    "## Future Plan\n",
-    "Note: This save settings function will eventually be converted into a button.\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "coastsegmap=coastseg_map.CoastSeg_Map()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "coastsegmap.save_settings(sat_list=sat_list, landsat_collection = landsat_collection,dates=dates,**settings)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "coastsegmap.settings"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {
@@ -240,54 +60,17 @@
     "# How to Use The Map\n",
     "\n",
     "---\n",
-    "\n",
+    "1.  Click `Save Settings` Button\n",
     "1. Use the rectangle tool to draw a bounding box along the coastline.\n",
     "2. Click `Generate ROI` to create ROI rectangles along the coastline in the bounding box. This may take some time.\n",
     "- You should see a coastline appear in yellow and some rectangles along it.\n",
     "3. Load transects into your bounding box with the `Load Transects` button. If any exist for the bounding box you selected they should appear.\n",
     "4. Click the ROIs you want to download.\n",
-    "5. Once you've selected all the ROIs you want to download click `Downlod ROIs`\n",
+    "5. Once you've selected all the ROIs you want to download click `Downlod Imagery`\n",
     " - If any of the ROIs succesfully download they will have their own folder with all their data in the `data` directory in the `CoastSeg` directory\n",
     "6. To extract a timeseries of shorelines for the ROIs you downloaded click `Extract Shorelines`\n",
-    "- Note this feature is still under development. This instruction will be updated later\n",
-    "7. If any shorelines were extracted you can compute the cross distances along the transects and shoreline by clicking `Compute Transects` \n",
-    "- Note this feature is still under development. This instruction will be updated later\n",
-    "\n",
-    "## How to Use Config Files\n",
-    "---\n",
-    "Config files are used to save and restore the state of your map. When you click `save config` or `download ROI` all the selected ROIs, transects, shorelines and the current bounding box loaded onto the map are saved to two config files: `config.json` and `config_gdf.geojson`. Extracted shorelines and cross distances for transects are not saved to the config files.\n",
-    "\n",
-    "## Saving Config Files\n",
-    "---\n",
-    "Config files are automatically saved in the `data` directory within each ROI directory when `Download ROIs` is clicked. \n",
-    "Config files can be saved at any time when the `Save Config Files` button. If the ROIs selected on the map have been downloaded before and exist in the `data` directory then the config files will be stored in  within each ROI directory. If the ROIs on the map have not been downloaded before then the config files will be saved in the coastseg directory.\n",
-    "\n",
-    "#### Steps\n",
-    "1. Click the ROIs on the map you want to save\n",
-    "2. Click the `Save Config Files` button. \n",
-    "- If the ROIs have been downloaded the config file is in each ROI's folder in file: 'config_gdf.geojson'\n",
-    "- If the ROIs have not been downloaded the config file is in main CoastSeg directory in file: 'config_gdf.geojson'\n",
-    "- The 'config.json' will be saved in the same directory as the 'config_gdf.geojson'.\n",
-    "\n",
-    "## Loading Config Files\n",
-    "___\n",
-    "### Load Config Files For Download ROIs\n",
-    "To load config files for ROIs that have been downloaded before go to the `data` directory within `coastseg` then find the directory of the ROIs you want to upload. Inside that directory, for example the \n",
-    "`ID_3_datetime10-20-22__07_09_07` directory, there should be `geojson` file named `config_gdf_id_3.geojson`. You should notice the `ID_<ROI ID NUMBER>` in the directory name and the geojson file match, in this case this is ROI id 3. Upload the `config_gdf_id_<roi id number>.geojson`.\n",
-    "This will load the ROI in that directory and all the other selected ROIs,shorelines, transects, and the bounding box that were on the map when it was saved.\n",
-    "#### Steps\n",
-    "1. Click `Load Config`\n",
-    "2. Open `data` directory\n",
-    "3. Open ROI directory ex. **ID_3_datetime10-20-22__07_09_07**\n",
-    "3. Select `config_gdf_id_<roi id number>.geojson` ex. **config_gdf_id_3.geojson**\n",
-    "\n",
-    "### Load Config Files For ROIs That Have Not Been Downloaded\n",
-    "To load config files for ROIs that have not been downloaded before first click the `Load Config` button,  open the coastseg directory and select `config_gdf.geojson`. This will loaded the selected ROIs, shorelines, transects, and bounding box that was saved with the `Save Config Files` button. \n",
-    "NOTE: You will not be able to extract shorelines or compute transects until you download the data for these ROIs.\n",
-    "#### Steps\n",
-    "1. Click `Load Config`\n",
-    "2. Open `coastseg` application directory\n",
-    "3. Select `config_gdf.geojson`"
+    "7. If any shorelines were extracted you can compute the cross distances along the transects and shoreline by clicking `Compute Transects` and save the output to a json file in the roi directory\n",
+    "8. If any shorelines were extracted you can compute the cross distances along the transects and shoreline by clicking `Save Transects CSV`  and save the output to a csv in the roi directory"
    ]
   },
   {
@@ -297,81 +80,20 @@
    "outputs": [],
    "source": [
     "from src.coastseg.map_UI import UI\n",
+    "coastsegmap=coastseg_map.CoastSeg_Map()\n",
     "coastseg_ui = UI(coastsegmap)\n",
     "coastseg_ui.create_dashboard()"
    ]
   },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# The Rest of this Notebook is Still Under Active Development\n",
-    "---"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Extract Shorelines From Downloaded Imagery\n",
-    "------\n",
-    "Extracted Shorelines are shoreline vectors in the imagery you downloaded from GEE. The extracted shorelines are created by analyzing each `.tiff` file for each set of satellite imagery downloaded to check if a shoreline classifier can be applied to it. If the `.tiff` file meets the quality standards by being under the maxiumum theshold for cloud cover and other metrics the image is analyzed by a special image segmentation model which classifies the sand, water and surf in the image. Using the labeled images from the image segmentation model a shoreline vector will be detected in the image and saved to a dictionary entry for the ROI it belonged to. The extracted shoreline will appear in purple on the map.\n",
-    "\n",
-    "## Beta Shorelines Do Not Always Work Properly\n",
-    "The classifier to extract shorelines is still under development and fails for complex shorelines sometimes. If you see extracted shorelines that zig and zag this is likely the result of the classifier failing to classifiy your image. You may want to try changing the sand color parameter of the classifier if the sand on your coast is dark or light.\n",
-    "\n",
-    "## Current Bugs Under Development\n",
-    "1. Multiple shoreline vectors will be rendered onto the map at the same location because all the shorelines accross the time period you selected are calculated at the same time. In the next release of CoastSeg these will be rendered one at a time on the map and you will be able to select which dates are rendered.\n",
-    "2. Sometimes shorelines will be not be able to be extracted from any of the imagery downloaded for the ROI on the map due to image quality issues such as excessive cloud cover or too many 'no data'(black) pixels in the downloaded imagery. When this is the case the extracted shoreline will not appear on the map. In a future update of coastseg there will be error messages to tell you when this has occured.\n",
-    "\n",
-    "\n",
-    "## How to Extract Shorelines From Your Iamgery\n",
-    "See the extracted shorelines for the ROIs selected on the map by first selecting your ROIs, downloading their data from GEE and then clicking extract shorelines.\n",
-    "1. Click the ROIs to extract shorelines from on the map\n",
-    "2. Download the ROIs data from GEE or already have it downloaded.\n",
-    "3. Click `extract shorelines`\n",
-    "4. You should see extracted shorelines appear in purple within some of your ROIs.\n",
-    "5. Run the following code block to see the extracted shorelines for each ROI."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "if coastsegmap.rois is None or len(coastsegmap.selected_set) ==0:\n",
-    "    print(\"Please select ROIs on the map first\")\n",
-    "elif coastsegmap.rois is not None:\n",
-    "    if coastsegmap.data_downloaded == False:\n",
-    "        print(\"Download the data first to extract shorelines from it\")\n",
-    "    elif coastsegmap.data_downloaded == True:\n",
-    "        print(coastsegmap.rois.extracted_shorelines)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Compute Transect Cross Distance for Extracted Shorelines\n",
-    "--------\n",
-    "# How Compute Transect Cross Distance for Extracted Shorelines\n",
-    "1. Click `Extract Shorelines`\n",
-    "2.  Click `Compute Transects`"
-   ]
-  },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
     "## Run Models on Imagery\n",
     "1. Select if you want to use a GPU or not. By default you won't your computer's GPU\n",
-    "2. Select if you want to use CRF post-processing or not. By default CRF post-processing is not enabled\n",
-    "<br>**WARNING**: There is a bug with CRF post-processing and the models so you will need to enable CRF post-processing until it is fixed.\n",
-    "3. Select whether your data is the type RGB or MNDWI\n",
-    "4. Select the model you want to use\n",
-    "5. Select Ensemble or Best (Ensemble is recommended) \n",
-    "6. \n"
+    "2. Select whether your data is the type RGB or MNDWI\n",
+    "3. Select the model you want to use\n",
+    "4. Select Ensemble or Best (Ensemble is recommended) \n"
    ]
   },
   {
diff --git a/docs/config.md b/docs/config.md
index a3adcdfe..3e934d52 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1,45 +1,53 @@
 # Config Files
 
-Config files are used to save the current state of the map and all the data downloaded during a session using CoastSeg. The config file is actually composed of two separate files a `.geojson` file and `.json` file. The `.geojson` file contains a geodataframe with crs `espg 4326` that contains all the shorelines, transects,selected ROI, and the bounding box(bbox) that were present on the map when the download button was clicked. The `.json` file contains the settings (eg `preprocess settings`,`postprocess settings`, and `shoreline extraction settings`) and user specified inputs (eg `dates`, `landsat_collection`,`satellite list`) that were used to download the imagery from Google Earth Engine(GEE). Each set of config files is saved in the directory containing the imagery you downloaded. As you can see in the image there are three directories at the top whose names begin with `ID` followed by a number ex `ID0`. These directories are in the same order you clicked the ROIs on the map so `ID0` is the data from the first ROI.
+Config files are used to save the current state of the map and all the data downloaded during a session using CoastSeg. The config file is actually composed of two separate files a `.geojson` file and `.json` file.
+Within each ROI's directory you will find a `config.geojson` file containing all the ROIs, shorelines, transects, and the bounding box that were loaded on the map at the time the ROI was downloaded or when the `Save Config` button was pressed.
 
-Inside these directories you should see two config files `config_id_<ROI ID>.json` and `config_id_gdf_<ROI ID>.json`. The `<ROI ID>` is the unique identifier of the ROI which can be used to select it by id from the geodataframe in `config_id_gdf_<ROI ID>.json`. You don't need to memorize or do anything with the ROI id.
+The `.geojson` file contains a geodataframe with crs `espg 4326` that contains all the shorelines, transects,selected ROI, and the bounding box(bbox) that were present on the map when the download button was clicked. The `.json` file contains the settings (eg `preprocess settings`,`postprocess settings`, and `shoreline extraction settings`) and user specified inputs (eg `dates`, `landsat_collection`,`satellite list`) that were used to download the imagery from Google Earth Engine(GEE). Each set of config files are saved in the each ROI's directory.
 
 ## Saving Config Files
 
 ---
 
 Config files are automatically saved in the `data` directory within each ROI directory when `Download ROIs` is clicked.
-Config files can be saved at any time when the `Save Config Files` button. If the ROIs selected on the map have been downloaded before and exist in the `data` directory then the config files will be stored in within each ROI directory. If the ROIs on the map have not been downloaded before then the config files will be saved in the coastseg directory.
+Config files can be saved at any time when the `Save Config ` button is clicked.
 
-#### Steps
+**ROIs Downloaded**
+If the ROIs selected on the map have been downloaded before and exist in the `data` directory, then the config files will be stored in within each ROI directory.
+
+**ROIs Not Downloaded**
+</br>If the ROIs on the map have not been downloaded before then the config files will be saved in the coastseg directory.
+
+### Steps to Save Config Files
 
 1. Click the ROIs on the map you want to save
 2. Click the `Save Config Files` button.
 
-## Loading Config Files
-
----
+# Loading Config Files
 
-### Load Config Files For Download ROIs
+## Load Config Files For Download ROIs
 
 To load config files for ROIs that have been downloaded before go to the `data` directory within `coastseg` then find the directory of the ROIs you want to upload. Inside that directory, for example the
 `ID_3_datetime10-20-22__07_09_07` directory, there should be `geojson` file named `config_gdf_id_3.geojson`. You should notice the `ID_<ROI ID NUMBER>` in the directory name and the geojson file match, in this case this is ROI id 3. Upload the `config_gdf_id_<roi id number>.geojson`.
 This will load the ROI in that directory and all the other selected ROIs,shorelines, transects, and the bounding box that were on the map when it was saved.
 
-#### Steps
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/load_config_demo.gif)
+
+### Steps to Load Config File
 
 1. Click `Load Config`
 2. Open `data` directory
 3. Open ROI directory ex. **ID_3_datetime10-20-22\_\_07_09_07**
 4. Select `config_gdf_id_<roi id number>.geojson` ex. **config_gdf_id_3.geojson**
 
-### Load Config Files For ROIs That Have Not Been Downloaded
+## Load Config Files For ROIs that Have Not Been Downloaded
+
+To load config files for ROIs that have not been downloaded before first click the `Load Config` button, open the coastseg directory and select `config_gdf.geojson`. This will loaded the selected ROIs, shorelines, transects, and bounding box that was saved with the `Save Config` button.
 
-To load config files for ROIs that have not been downloaded before first click the `Load Config` button, open the coastseg directory and select `config_gdf.geojson`. This will loaded the selected ROIs, shorelines, transects, and bounding box that was saved with the `Save Config Files` button.
-NOTE: You will not be able to extract shorelines or compute transects until you download the data for these ROIs.
+**Note:** You will not be able to extract shorelines or compute transects until you download the data for these ROIs.
 
 #### Steps
 
 1. Click `Load Config`
-2. Open `coastseg` application directory
+2. Open `coastseg` directory
 3. Select `config_gdf.geojson`
diff --git a/docs/extract_shorelines.md b/docs/extract_shorelines.md
new file mode 100644
index 00000000..6533cf9a
--- /dev/null
+++ b/docs/extract_shorelines.md
@@ -0,0 +1,26 @@
+# Extract Shorelines From Downloaded Imagery
+
+---
+
+Extracted Shorelines are shoreline vectors in the imagery you downloaded from GEE. The extracted shorelines are created by analyzing each `.tiff` file for each set of satellite imagery downloaded to check if a shoreline classifier can be applied to it. If the `.tiff` file meets the quality standards by being under the maximum threshold for cloud cover and other metrics the image is analyzed by a special image segmentation model which classifies the sand, water and surf in the image. Using the labeled images from the image segmentation model a shoreline vector will be detected in the image and saved to a dictionary entry for the ROI it belonged to. The extracted shoreline will appear as colored vectors along the coastline.
+
+## Why does the Extract Shoreline Look Weird?
+
+The classifier to extract shorelines fails for complex shorelines sometimes. If the extracted shorelines zig and zags across the coastline this is likely the result of the classifier failing to classify an image. You may want to try changing the `sand color` parameter in the settings dropdown depending on if the sand on the coast is dark or light.
+
+## Why did isn't there an Extracted Shoreline
+
+1. Sometimes shorelines will be not be able to be extracted from any of the imagery downloaded for the ROI on the map due to image quality issues such as excessive cloud cover or too many 'no data'(black) pixels in the downloaded imagery. When this is the case the extracted shoreline will not appear on the map.
+   The message `The ROI id does not have a shoreline to extract. ` will print when this happens
+
+## How to Extract Shorelines From Your Imagery
+
+See the extracted shorelines for the ROIs selected on the map by first selecting your ROIs, downloading their data from GEE and then clicking extract shorelines.
+
+1. Click the ROIs to extract shorelines from on the map
+2. Download the ROIs data from GEE or already have it downloaded.
+3. Click the button `extract shorelines`
+4. You should see extracted shorelines appear within some of your ROIs.
+5. If this works you can compute the time-series of shoreline change along shore-normal transects by clicking the `Compute Transects` Button.
+
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/extract_shorelines_and_transects.gif)
diff --git a/docs/gifs/change_layer_demo.gif b/docs/gifs/change_layer_demo.gif
new file mode 100644
index 00000000..f43e4445
Binary files /dev/null and b/docs/gifs/change_layer_demo.gif differ
diff --git a/docs/gifs/extract_shorelines_and_transects.gif b/docs/gifs/extract_shorelines_and_transects.gif
new file mode 100644
index 00000000..ef5a9a06
Binary files /dev/null and b/docs/gifs/extract_shorelines_and_transects.gif differ
diff --git a/docs/gifs/generate_rois_and_display_area.gif b/docs/gifs/generate_rois_and_display_area.gif
new file mode 100644
index 00000000..453ed716
Binary files /dev/null and b/docs/gifs/generate_rois_and_display_area.gif differ
diff --git a/docs/gifs/load_config_demo.gif b/docs/gifs/load_config_demo.gif
new file mode 100644
index 00000000..4d74ab07
Binary files /dev/null and b/docs/gifs/load_config_demo.gif differ
diff --git a/docs/gifs/load_geojson_files_demo.gif b/docs/gifs/load_geojson_files_demo.gif
new file mode 100644
index 00000000..3694bad3
Binary files /dev/null and b/docs/gifs/load_geojson_files_demo.gif differ
diff --git a/docs/gifs/load_shoreline_transect_demo.gif b/docs/gifs/load_shoreline_transect_demo.gif
new file mode 100644
index 00000000..bd18ef8e
Binary files /dev/null and b/docs/gifs/load_shoreline_transect_demo.gif differ
diff --git a/docs/gifs/settings_demo.gif b/docs/gifs/settings_demo.gif
new file mode 100644
index 00000000..e76fad79
Binary files /dev/null and b/docs/gifs/settings_demo.gif differ
diff --git a/docs/load_features.md b/docs/load_features.md
new file mode 100644
index 00000000..e69de29b
diff --git a/docs/map_guide.md b/docs/map_guide.md
new file mode 100644
index 00000000..363db39f
--- /dev/null
+++ b/docs/map_guide.md
@@ -0,0 +1,24 @@
+## How to Deselect Map Layers
+
+---
+
+**Not able to hover over a feature on the map?** The problem is its under another layer on the map. Most likely the feature you want to hover over is under the ROI and selected ROI layers. Use the layers menu checkbox to deselect the ROI and selected ROI layers from the map and hover over your feature again.
+
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/change_layer_demo.gif)
+
+## How to Load Transects on the Map
+To load transects on the map first draw a bounding box along the coastline. Then click the button `Load Transects` to load transects within the bounding box.
+**WARNING**: Not all coastlines have transects available and if no transects exist within the bounding box a pop up will indicate that no transects were found. If this happens try drawing a bounding box somewhere else.
+
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/load_shoreline_transect_demo.gif)
+
+## How to Load Shorelines on the Map
+To load shorelines on the map first draw a bounding box along the coastline. Then click the button `Load Shoreline` to load shorelines within the bounding box.
+**WARNING**: Not all coastlines have shoreline vectors available and if no shorelines exist within the bounding box a pop up will indicate that no shorelines were found. If this happens try drawing a bounding box somewhere else.
+
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/load_shoreline_transect_demo.gif)
+
+## How to Load Features from Geojson Files on Map
+To load shorelines, transects or bounding boxes onto the map from a geojson file use the `Load <feature name> file` button. It will open a file dialog window where you can select the geojson file you want to load on the map. If the geojson file is too large or has too many large features the map may become very slow. It is highly recommended to only load small features onto the map.
+
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/load_geojson_files_demo.gif)
\ No newline at end of file
diff --git a/docs/roi.md b/docs/roi.md
new file mode 100644
index 00000000..75ff5179
--- /dev/null
+++ b/docs/roi.md
@@ -0,0 +1,57 @@
+# What is an ROI?
+
+An **ROI** is a **region of interest** it defines a rectangular area where satellite imagery will be downloaded from GEE (Google Earth Engine). GEE limits the area of land you are able to download to accommodate for this limitation ROIs of the sizes allowed by GEE are generated along the coastline within a bounding box drawn by the user. If the data for an ROI is downloaded it will have its own directory with the ROI ID in the directory name, for example `ID_3_datetime11-22-22__11_15_15` is the directory for ROI 3.
+
+## ROI Directory Organization
+
+Each ROI can have multiple sets of satellite imagery associated with it. For instance, a single ROI may have Sentinel 2, Landsat 8 and 9 imagery downloaded. Each type of satellite imagery will have its own directory within the ROI's directory. All the imagery and metadata associated with the Landsat 8 imagery for an ROI will be located in a subdirectory named `L8`. For the Sentinel 2 imagery this subdirectory would be named `S2`.
+
+The jpgs for all satellite imagery downloaded for a given ROI are in subdirectory `jpg_files`. `jpg_files` contains the RGB, NIR, and SWIR subdirectories which contain the jpg images for all the selected satellites. This means all the imagery from L8, L9, and S2 would be located at each subdirectory with the satellite name in the image name. For example the RGB image from S2 would be in subdirectories `jpg_files\RGB` with a filename similar to: `2018-12-06-19-04-16_RGB_S2.jpg`.
+
+### Sample ROI Directory Structure
+
+```
+├── CoastSeg
+|
+|___data
+|    |_ ID_3_datetime11-22-22__11_15_15
+│   |   |_ L8
+│   |   |_ L9
+│   |   |_ S2
+│   |   |_ jpg_files
+│   |   |  |_ preprocessed
+|   │   |   |  |_ RGB
+|   |   │   |  |  |_ 2018-12-06-19-04-16_RGB_S2.jpg
+|   |   │   |  |  |_ 2018-12-06-19-04-16_RGB_L8.jpg
+|   |   │   |  |  |_ 2018-12-06-19-04-16_RGB_L9.jpg
+|   │   |   |  |_ NIR
+|   |   │   |  |  |_ 2018-12-06-19-04-16_NIR_S2.jpg
+|   |   │   |  |  |_ <rest of images...>
+|   │   |   |  |_ SWIR
+|   |   │   |  |  |_ 2018-12-06-19-04-16_SWIR_S2.jpg
+|   |   │   |  |  |_ <rest of images...>
+│   |   |   |_detection
+|   │   |   |  |_<jpgs of detected shorelines>
+│   |   |_config.json
+│   |   |_config_gdf.json
+│   |   |_extracted_shorelines.geojson
+```
+
+# How to Generate ROI on the Map
+
+Use the sliders `Small ROI Length` and `Large ROI Length`
+to control the side length of the ROI squares you want to generate. Click the button `Generate ROIs` to create a grid of overlapping ROIs on the map. You can then click each ROI on the map to indicated that satellite imagery for this ROI should be downloaded when the button `Download Imagery` is clicked.
+
+![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/generate_rois_and_display_area.gif)
+
+## How to Change the Size of the ROI
+
+The size of the side lengths of the ROI can be controlled using the two sliders `Small ROI Length` and `Large ROI Length`. These sliders control the side lengths of the ROI squares. You can control the amount of overlap between ROIs by changing the side lengths of each ROI or have 0 overlap by setting `Small ROI Length` to 0.
+
+## How to View the Area of an ROI
+
+Hover over any ROI on the map and look under the `ROI Data` section on right side of the map to view its area and the ROI ID.
+
+## How to View the ID of an ROI
+
+Hover over any ROI on the map and look under the `ROI Data` section on right side of the map to view its ROI ID.
diff --git a/docs/settings.md b/docs/settings.md
new file mode 100644
index 00000000..04847c0a
--- /dev/null
+++ b/docs/settings.md
@@ -0,0 +1,48 @@
+# Settings
+
+Use the settings drop down to modify the settings used by CoastSeg to download ROIs, extract shorelines and compute transect and shoreline intersections.
+
+![Settings Demo](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/settings_demo.gif)
+
+## Settings Explained
+
+---
+
+`sat_list`: satellite missions to consider (e.g., sat_list = ['L5', 'L7', 'L8', 'L9', 'S2'] for Landsat 5, 7, 8, 9 and Sentinel-2 collections)
+
+`dates`: dates over which the images will be retrieved (e.g., dates = ['2017-12-01', '2018-01-01'])
+
+`cloud_thresh`: threshold on maximum cloud cover that is acceptable on the images (value between 0 and 1 - this may require some initial experimentation).
+
+`dist_clouds`: buffer around cloud pixels where shoreline is not mapped (in metres)
+
+`output_epsg`: epsg code defining the spatial reference system of the shoreline coordinates. It has to be a cartesian coordinate system (i.e. projected). See http://spatialreference.org/ to find the EPSG number corresponding to your local coordinate system.
+
+- If you use a CRS such as `4326` or `4327` geographical coordinate system which uses latitude and longitude shorelines cannot be extracted from imagery so CoastSeg will automatically convert the CRS to the most accurate CRS based on where the bounding box is drawn on the map.
+- All outputted geojson files will be in CRS `4326` as this is the CRS used to load features onto CoastSeg's map.
+
+`save_figure`: if set to True a figure of each mapped shoreline is saved under /filepath/sitename/jpg_files/detection, even if the two previous parameters are set to False. Note that this may slow down the process.
+
+`min_beach_area`: minimum allowable object area (in metres^2) for the class 'sand'. During the image classification, some features (for example, building roofs) may be incorrectly labelled as sand. To correct this, all the objects classified as sand containing less than a certain number of connected pixels are removed from the sand class. The default value is 4500 m^2, which corresponds to 20 connected pixels of 15 m^2. If you are looking at a very small beach (<20 connected pixels on the images), try decreasing the value of this parameter.
+
+`min_length_sl`: minimum length (in metres) of shoreline perimeter to be valid. This can be used to discard small features that are detected but do not correspond to the actual shoreline. The default value is 500 m. If the shoreline that you are trying to map is shorter than 500 m, decrease the value of this parameter.
+
+`cloud_mask_issue`: the cloud mask algorithm applied to Landsat images by USGS, namely CFMASK, does have difficulties sometimes with very bright features such as beaches or white-water in the ocean. This may result in pixels corresponding to a beach being identified as clouds and appear as masked pixels on your images. If this issue seems to be present in a large proportion of images from your local beach, you can switch this parameter to True and CoastSat will remove from the cloud mask the pixels that form very thin linear features, as often these are beaches and not clouds. Only activate this parameter if you observe this very specific cloud mask issue, otherwise leave to the default value of False.
+
+`sand_color`: this parameter can take 3 values: default, latest, dark or bright. Only change this parameter if you are seing that with the default the sand pixels are not being classified as sand (in orange). If your beach has dark sand (grey/black sand beaches), you can set this parameter to dark and the classifier will be able to pick up the dark sand. On the other hand, if your beach has white sand and the default classifier is not picking it up, switch this parameter to bright. The latest classifier contains all the training data and can pick up sand in most environments (but not as accurately). At this stage the different classifiers are only available for Landsat images (soon for Sentinel-2 as well).
+
+`pan_off`: by default Landsat 7, 8 and 9 images are pan-sharpened using the panchromatic band and a PCA algorithm. If for any reason you prefer not to pan-sharpen the Landsat images, switch it off by setting pan_off to True
+
+`along_dist`: defines the along-shore distance around the transect over which shoreline points are selected to compute the intersection. The default value is 25 m, which means that the intersection is computed as the median of the points located within 25 m of the transect (50 m alongshore-median). This helps to smooth out localized water levels in the swash zone.
+
+## Settings Controlled Automatically by CoastSeg
+---
+
+`sitename`: name of the site (this is the name of the subfolder where the images and other accompanying files will be stored)
+
+`filepath`: filepath to the directory where the data will be stored
+
+`polygon`: the coordinates of the region of interest (longitude/latitude pairs in WGS84)
+
+## Credits
+Thank you to the amazing developer @kvos of CoastSat for defining these parameters.
diff --git a/docs/transect_time_series b/docs/transect_time_series
new file mode 100644
index 00000000..445645ba
--- /dev/null
+++ b/docs/transect_time_series
@@ -0,0 +1,24 @@
+# Compute Transect Cross Distance for Extracted Shorelines
+
+## IMPORTANT
+
+You cannot compute time-series of shoreline change along shore-normal transects unless at least one shoreline was successfully extracted from the download imagery.
+
+---
+
+# How Compute Transect Cross Distance for Extracted Shorelines
+
+1. Click `Extract Shorelines` Button
+
+- if you see extracted shorelines within at least one ROI on the map it means you can compute time-series of shoreline change along shore-normal transects
+
+2.  Click `Compute Transects` Button
+
+- This will save a `transects_cross_distances<ROIID>.json` which contains the intersection between the transect and the shoreline on each date for each transect.
+
+3.  Click `Save Transects CSV` Button
+
+- This will save a `transect_time_series.csv` which contains the intersection between the transect and the shoreline on each date for each transect.
+- A message will print stating the location where the csv was saved
+- The csv will appear the ROI directory for each ROI that had extracted shorelines and valid intersections between the transect and the extracted shorelines
+  ![Alt text](../../../../../../../../C:/1_USGS/CoastSeg/repos/2_CoastSeg/CoastSeg_fork/Seg2Map/docs/gifs/extract_shorelines_and_transects.gif)
diff --git a/pyproject.toml b/pyproject.toml
index 1dfeb69a..c0026604 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
 [project]
 name = "coastseg"
 dynamic = ["readme"]
-version = "0.0.1"
+version = "0.0.3"
 authors = [
   { name=" Sharon Fitzpatrick", email="sharon.fitzpatrick23@gmail.com" },
 ]
@@ -15,7 +15,7 @@ dependencies = [
   "coastsat-package>=0.1.0",
   "area",
   "requests",
-  "doodleverse-utils>=0.0.11",
+  "doodleverse-utils>=0.0.14",
   "tqdm",
   "leafmap",
   "shapely",
diff --git a/refactored_custom_map_official.ipynb b/refactored_custom_map_official.ipynb
deleted file mode 100644
index b2596b47..00000000
--- a/refactored_custom_map_official.ipynb
+++ /dev/null
@@ -1,429 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "import os\n",
-    "import datetime\n",
-    "# Local Imports\n",
-    "import coastseg\n",
-    "from coastseg import coastseg_map\n",
-    "from coastseg import coastseg_logs #must be the first module loaded to create logs folder\n",
-    "\n",
-    "# External Imports\n",
-    "import ee\n",
-    "from google.auth import exceptions as google_auth_exceptions\n",
-    "from IPython.display import display, clear_output\n",
-    "from tkinter import Tk,filedialog\n",
-    "from ipywidgets import Button, HBox, VBox, Layout, DatePicker, HTML, RadioButtons, SelectMultiple, Output, Checkbox\n",
-    "\n",
-    "# suppress tensorflow warnings\n",
-    "os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Authenticate and Initialize with Google Earth Engine (GEE)\n",
-    "\n",
-    "- Run this cell to initialize with GEE which will allow you to download remote sensing data from GEE.\n",
-    "\n",
-    "### First Time Users\n",
-    "\n",
-    "- In order to use Google Earth Engine (GEE) you will need to sign up to request access to use Google Earth Engine.https://signup.earthengine.google.com. You will only need to do this once and it takes only a day to get your account verified.\n",
-    "\n",
-    "### How `ee.Authenticate()` works\n",
-    "\n",
-    "- In order to initialize with GEE you will need an authorization token with is obtained by running `ee.Authenticate()`.This token lasts 7 days and during those 7 days you will not need to authenticate with google earth engine with an access code. Once the 7 days are up you will need to reauthenticate to use GEE again.\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "try:\n",
-    "    ee.Initialize()\n",
-    "except google_auth_exceptions.RefreshError as exception:\n",
-    "    print(\"Please authenticate with Google:\\n\")\n",
-    "    ee.Authenticate()\n",
-    "    ee.Initialize()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "# Date Widgets\n",
-    "start_date=DatePicker(\n",
-    "    description='Start Date',\n",
-    "    value=datetime.date(2018, 12, 1),\n",
-    "    disabled=False,\n",
-    ")\n",
-    "end_date=DatePicker(\n",
-    "    description='End Date',\n",
-    "    value=datetime.date(2019, 3, 1), #2019, 1, 1\n",
-    "    disabled=False,\n",
-    ")\n",
-    "date_instr=HTML(\n",
-    "    value=\"<b>Pick a date:</b>\",\n",
-    "    layout=Layout(padding='10px')\n",
-    ")\n",
-    "dates_box=HBox([start_date,end_date])\n",
-    "dates_vbox=VBox([date_instr,dates_box])\n",
-    "\n",
-    "\n",
-    "collection_instr=HTML(\n",
-    "    value=\"<b>Pick a collection</b>\\\n",
-    "        <br> C01: LandSat Collection 1 \\\n",
-    "        <br> C02: LandSat Collection 2  [2022/01/01 and beyond] \\\n",
-    "        <br>      - Landsat 9 is only available in C02\",\n",
-    "    layout=Layout(padding='10px')\n",
-    ")\n",
-    "collection_radio=RadioButtons(\n",
-    "    options=['C01', 'C02'],\n",
-    "    value='C01',\n",
-    "    description='Collection:',\n",
-    "    disabled=False\n",
-    ")\n",
-    "collection_vbox=VBox([collection_instr,collection_radio])\n",
-    "\n",
-    "sat_instr=HTML(\n",
-    "    value=\"<b>Pick multiple satellites by holding the control key:</b> \\\n",
-    "        <br> - images prior to 2022/01/01 will be downloaded from Collection 1 \\\n",
-    "        <br> - images after 2022/01/01 will be automatically downloaded from Collection 2 \\\n",
-    "        <br> C01: LandSat Collection 1 : All dates before 2022/01/01\\\n",
-    "        <br> C02: LandSat Collection 2 : All dates after 2022/01/01 \\\n",
-    "        <br> - Landsat 9 is only available in C02\",\n",
-    "    layout=Layout(padding='10px')\n",
-    ")\n",
-    "\n",
-    "\n",
-    "satellite_selection=SelectMultiple(\n",
-    "    options=['L5', 'L7', 'L8', 'S2'],\n",
-    "    value=['L8'],\n",
-    "    description='Satellites',\n",
-    "    disabled=False\n",
-    ")\n",
-    "# value=['S2'],\n",
-    "sat_vbox = VBox([sat_instr,satellite_selection])\n",
-    "\n",
-    "def handle_collection_change(change):\n",
-    "    if change['new'] == 'C02':\n",
-    "        satellite_selection.options=['L5', 'L7', 'L8', 'L9', 'S2']\n",
-    "    else:\n",
-    "        satellite_selection.options=['L5', 'L7', 'L8', 'S2']\n",
-    "\n",
-    "collection_radio.observe(handle_collection_change,\"value\")\n",
-    "\n",
-    "controls_vbox=VBox([dates_vbox, collection_vbox, sat_vbox])\n",
-    "controls_vbox"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "scrolled": true
-   },
-   "outputs": [],
-   "source": [
-    "# CoastSat Download Variables\n",
-    "# ------------------------------\n",
-    "# Save the satellites selected to sat_list\n",
-    "if satellite_selection.value:\n",
-    "    sat_list = list(satellite_selection.value)\n",
-    "elif not satellite_selection.value:\n",
-    "    print(\"ERROR: You must select at least one satellite first\")\n",
-    "# Save the dates selected by the user as well as the selected collection\n",
-    "dates = [str(start_date.value),str(end_date.value)]\n",
-    "landsat_collection = collection_radio.value\n",
-    "print(f\"dates: {dates}\")\n",
-    "print(f\"landsat_collection: {landsat_collection}\")\n",
-    "print(f\"sat_list: {sat_list}\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Settings for CoastSat\n",
-    "\n",
-    "Modify any of the following setting to determine how data is downloaded from CoastSat\n",
-    "## Future Plan\n",
-    " All of the settings and the widgets to control the settings will be incorperated into the main dashboard"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "settings = { \n",
-    "    # general parameters:\n",
-    "    'cloud_thresh': 0.5,        # threshold on maximum cloud cover\n",
-    "     'dist_clouds': 300,        # ditance around clouds where shoreline can't be mapped\n",
-    "    'output_epsg': 3857,        # epsg code of spatial reference system desired for the output   \n",
-    "    # quality control:\n",
-    "    'check_detection': False,    # if True, shows each shoreline detection to the user for validation\n",
-    "    'adjust_detection': False,  # if True, allows user to adjust the postion of each shoreline by changing the threhold\n",
-    "    'save_figure': True,        # if True, saves a figure showing the mapped shoreline for each image\n",
-    "    # [ONLY FOR ADVANCED USERS] shoreline detection parameters:\n",
-    "    'min_beach_area': 4500,     # minimum area (in metres^2) for an object to be labelled as a beach\n",
-    "    'buffer_size': 550,         # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection\n",
-    "    'min_length_sl': 100,       # minimum length (in metres) of shoreline perimeter to be valid\n",
-    "    'cloud_mask_issue': False,  # switch this parameter to True if sand pixels are masked (in black) on many images  \n",
-    "    'sand_color': 'default',    # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)\n",
-    "    'pan_off':'False',          # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery\n",
-    "    'create_plot':False,        # True create a matplotlib plot of the image with the datetime as the title\n",
-    "    'max_dist_ref':25,\n",
-    "    'along_dist' :25,\n",
-    "}"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Create the Map\n",
-    "\n",
-    "- Create the map here\n",
-    "- Save all the settings created earlier\n",
-    "- Save the list of satellites, dates, and the collection to coastseg_map \n",
-    "\n",
-    "\n",
-    "## Future Plan\n",
-    "Note: This save settings function will eventually be converted into a button.\n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "coastsegmap=coastseg_map.CoastSeg_Map()"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "coastsegmap.save_settings(sat_list=sat_list, landsat_collection = landsat_collection,dates=dates,**settings)"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "coastsegmap.settings"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "tags": []
-   },
-   "source": [
-    "# How to Use The Map\n",
-    "\n",
-    "---\n",
-    "\n",
-    "1. Use the rectangle tool to draw a bounding box along the coastline.\n",
-    "2. Click `Generate ROI` to create ROI rectangles along the coastline in the bounding box. This may take some time.\n",
-    "- You should see a coastline appear in yellow and some rectangles along it.\n",
-    "3. Load transects into your bounding box with the `Load Transects` button. If any exist for the bounding box you selected they should appear.\n",
-    "4. Click the ROIs you want to download.\n",
-    "5. Once you've selected all the ROIs you want to download click `Downlod ROIs`\n",
-    " - If any of the ROIs succesfully download they will have their own folder with all their data in the `data` directory in the `CoastSeg` directory\n",
-    "6. To extract a timeseries of shorelines for the ROIs you downloaded click `Extract Shorelines`\n",
-    "- Note this feature is still under development. This instruction will be updated later\n",
-    "7. If any shorelines were extracted you can compute the cross distances along the transects and shoreline by clicking `Compute Transects` \n",
-    "- Note this feature is still under development. This instruction will be updated later\n",
-    "\n",
-    "## How to Use Config Files\n",
-    "---\n",
-    "Config files are used to save and restore the state of your map. When you click `save config` or `download ROI` all the selected ROIs, transects, shorelines and the current bounding box loaded onto the map are saved to two config files: `config.json` and `config_gdf.geojson`. Extracted shorelines and cross distances for transects are not saved to the config files.\n",
-    "\n",
-    "## Saving Config Files\n",
-    "---\n",
-    "Config files are automatically saved in the `data` directory within each ROI directory when `Download ROIs` is clicked. \n",
-    "Config files can be saved at any time when the `Save Config Files` button. If the ROIs selected on the map have been downloaded before and exist in the `data` directory then the config files will be stored in  within each ROI directory. If the ROIs on the map have not been downloaded before then the config files will be saved in the coastseg directory.\n",
-    "\n",
-    "#### Steps\n",
-    "1. Click the ROIs on the map you want to save\n",
-    "2. Click the `Save Config Files` button. \n",
-    "- If the ROIs have been downloaded the config file is in each ROI's folder in file: 'config_gdf.geojson'\n",
-    "- If the ROIs have not been downloaded the config file is in main CoastSeg directory in file: 'config_gdf.geojson'\n",
-    "- The 'config.json' will be saved in the same directory as the 'config_gdf.geojson'.\n",
-    "\n",
-    "## Loading Config Files\n",
-    "___\n",
-    "### Load Config Files For Download ROIs\n",
-    "To load config files for ROIs that have been downloaded before go to the `data` directory within `coastseg` then find the directory of the ROIs you want to upload. Inside that directory, for example the \n",
-    "`ID_3_datetime10-20-22__07_09_07` directory, there should be `geojson` file named `config_gdf_id_3.geojson`. You should notice the `ID_<ROI ID NUMBER>` in the directory name and the geojson file match, in this case this is ROI id 3. Upload the `config_gdf_id_<roi id number>.geojson`.\n",
-    "This will load the ROI in that directory and all the other selected ROIs,shorelines, transects, and the bounding box that were on the map when it was saved.\n",
-    "#### Steps\n",
-    "1. Click `Load Config`\n",
-    "2. Open `data` directory\n",
-    "3. Open ROI directory ex. **ID_3_datetime10-20-22__07_09_07**\n",
-    "3. Select `config_gdf_id_<roi id number>.geojson` ex. **config_gdf_id_3.geojson**\n",
-    "\n",
-    "### Load Config Files For ROIs That Have Not Been Downloaded\n",
-    "To load config files for ROIs that have not been downloaded before first click the `Load Config` button,  open the coastseg directory and select `config_gdf.geojson`. This will loaded the selected ROIs, shorelines, transects, and bounding box that was saved with the `Save Config Files` button. \n",
-    "NOTE: You will not be able to extract shorelines or compute transects until you download the data for these ROIs.\n",
-    "#### Steps\n",
-    "1. Click `Load Config`\n",
-    "2. Open `coastseg` application directory\n",
-    "3. Select `config_gdf.geojson`"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from src.coastseg.map_UI import UI\n",
-    "coastseg_ui = UI(coastsegmap)\n",
-    "coastseg_ui.create_dashboard()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# The Rest of this Notebook is Still Under Active Development\n",
-    "---"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Extract Shorelines From Downloaded Imagery\n",
-    "------\n",
-    "Extracted Shorelines are shoreline vectors in the imagery you downloaded from GEE. The extracted shorelines are created by analyzing each `.tiff` file for each set of satellite imagery downloaded to check if a shoreline classifier can be applied to it. If the `.tiff` file meets the quality standards by being under the maxiumum theshold for cloud cover and other metrics the image is analyzed by a special image segmentation model which classifies the sand, water and surf in the image. Using the labeled images from the image segmentation model a shoreline vector will be detected in the image and saved to a dictionary entry for the ROI it belonged to. The extracted shoreline will appear in purple on the map.\n",
-    "\n",
-    "## Beta Shorelines Do Not Always Work Properly\n",
-    "The classifier to extract shorelines is still under development and fails for complex shorelines sometimes. If you see extracted shorelines that zig and zag this is likely the result of the classifier failing to classifiy your image. You may want to try changing the sand color parameter of the classifier if the sand on your coast is dark or light.\n",
-    "\n",
-    "## Current Bugs Under Development\n",
-    "1. Multiple shoreline vectors will be rendered onto the map at the same location because all the shorelines accross the time period you selected are calculated at the same time. In the next release of CoastSeg these will be rendered one at a time on the map and you will be able to select which dates are rendered.\n",
-    "2. Sometimes shorelines will be not be able to be extracted from any of the imagery downloaded for the ROI on the map due to image quality issues such as excessive cloud cover or too many 'no data'(black) pixels in the downloaded imagery. When this is the case the extracted shoreline will not appear on the map. In a future update of coastseg there will be error messages to tell you when this has occured.\n",
-    "\n",
-    "\n",
-    "## How to Extract Shorelines From Your Iamgery\n",
-    "See the extracted shorelines for the ROIs selected on the map by first selecting your ROIs, downloading their data from GEE and then clicking extract shorelines.\n",
-    "1. Click the ROIs to extract shorelines from on the map\n",
-    "2. Download the ROIs data from GEE or already have it downloaded.\n",
-    "3. Click `extract shorelines`\n",
-    "4. You should see extracted shorelines appear in purple within some of your ROIs.\n",
-    "5. Run the following code block to see the extracted shorelines for each ROI."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "if coastsegmap.rois is None or len(coastsegmap.selected_set) ==0:\n",
-    "    print(\"Please select ROIs on the map first\")\n",
-    "elif coastsegmap.rois is not None:\n",
-    "    if coastsegmap.data_downloaded == False:\n",
-    "        print(\"Download the data first to extract shorelines from it\")\n",
-    "    elif coastsegmap.data_downloaded == True:\n",
-    "        print(coastsegmap.rois.extracted_shorelines)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Compute Transect Cross Distance for Extracted Shorelines\n",
-    "--------\n",
-    "# How Compute Transect Cross Distance for Extracted Shorelines\n",
-    "1. Click `Extract Shorelines`\n",
-    "2.  Click `Compute Transects`"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "## Run Models on Imagery\n",
-    "1. Select if you want to use a GPU or not. By default you won't your computer's GPU\n",
-    "2. Select if you want to use CRF post-processing or not. By default CRF post-processing is not enabled\n",
-    "<br>**WARNING**: There is a bug with CRF post-processing and the models so you will need to enable CRF post-processing until it is fixed.\n",
-    "3. Select whether your data is the type RGB or MNDWI\n",
-    "4. Select the model you want to use\n",
-    "5. Select Ensemble or Best (Ensemble is recommended) \n",
-    "6. \n"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from src.coastseg.UI_models import UI_Models\n",
-    "models_ui = UI_Models()\n",
-    "models_ui.create_dashboard()"
-   ]
-  },
-  {
-   "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.4"
-  },
-  "vscode": {
-   "interpreter": {
-    "hash": "53ab1155a6fba8dac112dd11f6ebbdb1bff1a2fc6fa221ef0a2c21c817842ec3"
-   }
-  },
-  "widgets": {
-   "application/vnd.jupyter.widget-state+json": {
-    "state": {},
-    "version_major": 2,
-    "version_minor": 0
-   }
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 4
-}
diff --git a/src/coastseg/UI_models.py b/src/coastseg/UI_models.py
index 385bbc23..b86c71e7 100644
--- a/src/coastseg/UI_models.py
+++ b/src/coastseg/UI_models.py
@@ -1,7 +1,6 @@
 # standard python imports
 import os
 import glob
-import json
 
 # internal python imports
 from coastseg import common
@@ -10,17 +9,14 @@
 
 # external python imports
 import ipywidgets
-import geopandas as gpd
-from ipyleaflet import GeoJSON
-from IPython.display import display, clear_output
-from tkinter import Tk, filedialog
+from IPython.display import display
+from tkinter import filedialog
 from ipywidgets import Button
 from ipywidgets import HBox
 from ipywidgets import VBox
 from ipywidgets import Layout
 from ipywidgets import HTML
 from ipywidgets import RadioButtons
-from ipywidgets import SelectMultiple
 from ipywidgets import Output
 from ipywidgets import Checkbox
 from tkinter import messagebox
@@ -37,7 +33,6 @@ def __init__(self):
         self.model_dict = {
             "sample_direc": None,
             "use_GPU": False,
-            "use_CRF": False,
             "implementation": "ENSEMBLE",
             "model_type": "landsat_6229071",
         }
@@ -59,7 +54,7 @@ def create_dashboard(self):
         model_choices_box = HBox(
             [self.model_input_dropdown, self.model_dropdown, self.model_implementation]
         )
-        checkboxes = HBox([self.GPU_checkbox, self.CRF_checkbox])
+        checkboxes = HBox([self.GPU_checkbox])
         instr_vbox = VBox(
             [
                 self.instr_header,
@@ -113,14 +108,6 @@ def _create_widgets(self):
         )
         self.GPU_checkbox.observe(self.handle_GPU_checkbox, "value")
 
-        self.CRF_checkbox = Checkbox(
-            value=False,
-            description="Use CRF post-processing",
-            disabled=False,
-            indent=False,
-        )
-        self.CRF_checkbox.observe(self.handle_CRF_checkbox, "value")
-
     def _create_buttons(self):
         # button styles
         load_style = dict(button_color="#69add1")
@@ -197,15 +184,6 @@ def handle_GPU_checkbox(self, change):
             self.model_dict["use_GPU"] = False
             print("Not using the GPU")
 
-    @GPU_view.capture(clear_output=True)
-    def handle_CRF_checkbox(self, change):
-        if change["new"] == True:
-            self.model_dict["use_CRF"] = True
-            print("Using CRF post-processing")
-        else:
-            self.model_dict["use_CRF"] = False
-            print("Not using CRF post-processing")
-
     def handle_model_type(self, change):
         self.model_dict["model_type"] = change["new"]
 
@@ -280,7 +258,6 @@ def run_model_button_clicked(self, button):
                 self.model_dict["sample_direc"],
                 model_list,
                 metadatadict,
-                self.model_dict["use_CRF"],
             )
             # Enable run and open results buttons when model has executed
             self.run_model_button.disabled = False
diff --git a/src/coastseg/bbox.py b/src/coastseg/bbox.py
index a72fba65..450aa728 100644
--- a/src/coastseg/bbox.py
+++ b/src/coastseg/bbox.py
@@ -80,7 +80,7 @@ def check_bbox_size(bbox_area: float):
         """ "Raises an exception if the size of the bounding box is too large or small."""
         # Check if the size is greater than MAX_BBOX_SIZE
         if bbox_area > Bounding_Box.MAX_AREA:
-            raise BboxTooLargeError
+            raise BboxTooLargeError()
         # Check if size smaller than MIN_BBOX_SIZE
         elif bbox_area < Bounding_Box.MIN_AREA:
-            raise BboxTooSmallError
+            raise BboxTooSmallError()
diff --git a/src/coastseg/coastseg_map.py b/src/coastseg/coastseg_map.py
index d07681db..28547718 100644
--- a/src/coastseg/coastseg_map.py
+++ b/src/coastseg/coastseg_map.py
@@ -2,53 +2,63 @@
 import json
 import logging
 import copy
-
 from typing import Union
+
 from coastseg.bbox import Bounding_Box
 from coastseg import common
+from coastseg import factory
 from coastseg.shoreline import Shoreline
 from coastseg.transects import Transects
 from coastseg.roi import ROI
 from coastseg import exceptions
-from coastseg import map_UI
 from coastseg import extracted_shoreline
 from coastseg import exception_handler
 
-import geopandas as gpd
-import numpy as np
-import matplotlib
-
 from coastsat import (
-    SDS_tools,
     SDS_download,
-    SDS_tools,
     SDS_transects,
-    SDS_shoreline,
     SDS_preprocess,
 )
+import geopandas as gpd
 from ipyleaflet import DrawControl, LayersControl, WidgetControl, GeoJSON
 from leafmap import Map
 from ipywidgets import Layout, HTML, Accordion
 from tqdm.auto import tqdm
-from pyproj import Proj, transform
 
 logger = logging.getLogger(__name__)
 
 
 class CoastSeg_Map:
-    def __init__(self, map_settings: dict = None):
+    def __init__(self, settings: dict = None):
+        self.factory = factory.Factory()
         # settings:  used to select data to download and preprocess settings
-        self.settings = {}
+        self.settings = {
+            # general parameters:
+            "cloud_thresh": 0.5,  # threshold on maximum cloud cover
+            "dist_clouds": 300,  # ditance around clouds where shoreline can't be mapped
+            "output_epsg": 4326,  # epsg code of spatial reference system desired for the output
+            # quality control:
+            "check_detection": False,  # if True, shows each shoreline detection to the user for validation
+            "adjust_detection": False,  # if True, allows user to adjust the position of each shoreline by changing the threshold
+            "save_figure": True,  # if True, saves a figure showing the mapped shoreline for each image
+            # [ONLY FOR ADVANCED USERS] shoreline detection parameters:
+            "min_beach_area": 4500,  # minimum area (in metres^2) for an object to be labelled as a beach
+            "min_length_sl": 100,  # minimum length (in metres) of shoreline perimeter to be valid
+            "cloud_mask_issue": False,  # switch this parameter to True if sand pixels are masked (in black) on many images
+            "sand_color": "default",  # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)
+            "pan_off": "False",  # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery
+            "max_dist_ref": 25,
+            "along_dist": 25,
+            "landsat_collection": "C02",
+        }
+        if settings is not None:
+            tmp_settings = {**settings, **self.settings}
+            self.settings = tmp_settings.copy()
         # selected_set set(str): ids of the selected rois
         self.selected_set = set()
         # self.extracted_shoreline_layers : names of extracted shorelines vectors on the map
         self.extracted_shoreline_layers = []
-        # ROI_layer : layer containing all rois
-        self.ROI_layer = None
-        # rois : ROI(Region of Interest)
         self.rois = None
-        # selected_ROI_layer :  layer containing all selected rois
-        self.selected_ROI_layer = None
         # self.transect : transect object containing transects loaded on map
         self.transects = None
         # self.shoreline : shoreline object containing shoreline loaded on map
@@ -58,7 +68,7 @@ def __init__(self, map_settings: dict = None):
         # preprocess_settings : dictionary of settings used by coastsat to download imagery
         self.preprocess_settings = {}
         # create map if map_settings not provided else use default settings
-        self.map = self.create_map(map_settings)
+        self.map = self.create_map()
         # create controls and add to map
         self.draw_control = self.create_DrawControl(DrawControl())
         self.draw_control.on_draw(self.handle_draw)
@@ -68,25 +78,20 @@ def __init__(self, map_settings: dict = None):
         hover_accordion_control = self.create_accordion_widget()
         self.map.add(hover_accordion_control)
 
-    def create_map(self, map_settings: dict):
+    def create_map(self):
         """create an interactive map object using the map_settings
-
-        Args:
-            map_settings (dict): settings to control how map is created
-
         Returns:
            ipyleaflet.Map: ipyleaflet interactive Map object
         """
-        if not map_settings:
-            map_settings = {
-                "center_point": (40.8630302395, -124.166267),
-                "zoom": 13,
-                "draw_control": False,
-                "measure_control": False,
-                "fullscreen_control": False,
-                "attribution_control": True,
-                "Layout": Layout(width="100%", height="100px"),
-            }
+        map_settings = {
+            "center_point": (40.8630302395, -124.166267),
+            "zoom": 13,
+            "draw_control": False,
+            "measure_control": False,
+            "fullscreen_control": False,
+            "attribution_control": True,
+            "Layout": Layout(width="100%", height="100px"),
+        }
         return Map(
             draw_control=map_settings["draw_control"],
             measure_control=map_settings["measure_control"],
@@ -100,15 +105,18 @@ def create_map(self, map_settings: dict):
     def create_accordion_widget(self):
         """creates a accordion style widget controller to hold data of
         a feature that was last hovered over by user on map.
-
         Returns:
            ipyleaflet.WidgetControl: an widget control for an accordion widget
         """
-        html = HTML("Hover over a Feature")
+        html = HTML("Hover over a feature on the map")
+        roi_html = HTML("Hover over a ROI on the map")
+        roi_html.layout.margin = "0px 20px 20px 20px"
         html.layout.margin = "0px 20px 20px 20px"
-        self.accordion = Accordion(children=[html], titles=("Hover Data",))
+        self.accordion = Accordion(
+            children=[html, roi_html], titles=("Features Data", "ROI Data")
+        )
         self.accordion.set_title(0, "Hover Data")
-
+        self.accordion.set_title(1, "ROI Data")
         return WidgetControl(widget=self.accordion, position="topright")
 
     def load_configs(self, filepath: str):
@@ -168,34 +176,28 @@ def load_gdf_config(self, filepath: str):
             logger.info("No Bounding Box was loaded on map")
             print("No Bounding Box was loaded on map")
         else:
-            self.bbox = Bounding_Box(rectangle=bbox_gdf)
-            self.load_bbox_on_map()
+            self.load_feature_on_map("bbox", gdf=bbox_gdf)
         # Create ROI object from roi_gdf
         exception_handler.check_if_gdf_empty(
             roi_gdf, "ROIs", "Cannot load empty ROIs onto map"
         )
         self.rois = ROI(rois_gdf=roi_gdf)
-        self.load_rois_on_map()
+        self.load_feature_on_map("rois", gdf=roi_gdf)
+        # Create Shoreline object from shoreline_gdf
         if shoreline_gdf.empty:
             self.shoreline = None
             logger.info("No shoreline was loaded on map")
             print("No shoreline was loaded on map")
         else:
-            # Create Shoreline object from shoreline_gdf
-            self.shoreline = Shoreline(shoreline=shoreline_gdf)
-            self.load_shoreline_on_map()
-            logger.info("Shoreline was loaded on map")
-            print("Shoreline was loaded on map")
+            self.load_feature_on_map("shoreline", gdf=shoreline_gdf)
+
         # Create Transect object from transect_gdf
         if transect_gdf.empty:
             self.transects = None
             logger.info("No transects were loaded on map")
             print("No transects were loaded on map")
         else:
-            self.transects = Transects(transects=transect_gdf)
-            self.load_transects_on_map()
-            logger.info("Transects were loaded on map")
-            print("Transects were loaded on map")
+            self.load_feature_on_map("transects", gdf=transect_gdf)
 
     def download_imagery(self) -> None:
         """download_imagery  downloads selected rois as jpgs
@@ -211,16 +213,18 @@ def download_imagery(self) -> None:
         subset = set(["dates", "sat_list", "landsat_collection"])
         superset = set(list(self.settings.keys()))
         exception_handler.check_if_subset(subset, superset, "settings")
-        # selected_ROI_layer must contain selected ROI
-        exception_handler.check_empty_roi_layer(self.selected_ROI_layer)
+        # selected_layer must contain selected ROI
+        selected_layer = self.map.find_layer(ROI.SELECTED_LAYER_NAME)
+        exception_handler.check_empty_layer(selected_layer, ROI.SELECTED_LAYER_NAME)
+        exception_handler.check_empty_roi_layer(selected_layer)
         logger.info(f"self.settings: {self.settings}")
-        logger.info(f"self.selected_ROI_layer: {self.selected_ROI_layer}")
+        logger.info(f"selected_layer: {selected_layer}")
         # 1. Create a list of download settings for each ROI.
         # filepath: path to directory where downloaded imagery will be saved. Defaults to data directory in CoastSeg
         filepath = os.path.abspath(os.path.join(os.getcwd(), "data"))
         date_str = common.generate_datestring()
         roi_settings = common.create_roi_settings(
-            self.settings, self.selected_ROI_layer.data, filepath, date_str
+            self.settings, selected_layer.data, filepath, date_str
         )
         # Save download settings dictionary to instance of ROI
         self.rois.set_roi_settings(roi_settings)
@@ -244,8 +248,7 @@ def download_imagery(self) -> None:
         logger.info("Done downloading")
 
     def load_json_config(self, filepath: str) -> None:
-        if self.rois is None:
-            raise Exception("Must load ROIs onto the map first")
+        exception_handler.check_if_None(self.rois)
 
         json_data = common.read_json_file(filepath)
         # replace coastseg_map.settings with settings from config file
@@ -270,7 +273,7 @@ def save_config(self, filepath: str = None) -> None:
             Exception: raised if self.settings is missing
             ValueError: raised if any of "dates", "sat_list", "landsat_collection" is missing from self.settings
             Exception: raised if self.rois is missing
-            Exception: raised if self.selected_ROI_layer is missing
+            Exception: raised if selected_layer is missing
         """
         exception_handler.config_check_if_none(self.settings, "settings")
         # settings must contain keys in subset
@@ -278,14 +281,15 @@ def save_config(self, filepath: str = None) -> None:
         superset = set(list(self.settings.keys()))
         exception_handler.check_if_subset(subset, superset, "settings")
         exception_handler.config_check_if_none(self.rois, "ROIs")
-        # selected_ROI_layer must contain selected ROI
-        exception_handler.check_empty_roi_layer(self.selected_ROI_layer)
+        # selected_layer must contain selected ROI
+        selected_layer = self.map.find_layer(ROI.SELECTED_LAYER_NAME)
+        exception_handler.check_empty_roi_layer(selected_layer)
         logger.info(f"self.rois.roi_settings: {self.rois.roi_settings}")
         if self.rois.roi_settings == {}:
             if filepath is None:
                 filepath = os.path.abspath(os.getcwd())
             roi_settings = common.create_roi_settings(
-                self.settings, self.selected_ROI_layer.data, filepath
+                self.settings, selected_layer.data, filepath
             )
             # Save download settings dictionary to instance of ROI
             self.rois.set_roi_settings(roi_settings)
@@ -342,33 +346,111 @@ def save_config(self, filepath: str = None) -> None:
     def save_settings(self, **kwargs):
         """Saves the settings for downloading data in a dictionary
         Pass in data in the form of
-        save_settings(sat_list=sat_list, landsat_collection='C01',dates=dates,**preprocess_settings)
+        save_settings(sat_list=sat_list, dates=dates,**preprocess_settings)
         *You must use the names sat_list, landsat_collection, and dates
         """
+        tmp_settings = self.settings.copy()
         for key, value in kwargs.items():
-            self.settings[key] = value
+            tmp_settings[key] = value
+
+        self.settings = tmp_settings.copy()
+        del tmp_settings
+        logger.info(f"Settings: {self.settings}")
+
+    def update_transects_html(self, feature, **kwargs):
+        # Modifies html of accordion when transect is hovered over
+        default = "unknown"
+        keys = [
+            "id",
+            "slope",
+        ]
+        # returns a dict with keys in keys and if a key does not exist in feature its value is default str
+        values = common.get_default_dict(
+            default=default, keys=keys, fill_dict=feature["properties"]
+        )
+        self.accordion.children[
+            0
+        ].value = """ 
+        <h2>Transect</h2>
+        <p>Id: {}</p>
+        <p>Slope: {}</p>
+        """.format(
+            values["id"],
+            values["slope"],
+        )
 
     def update_extracted_shoreline_html(self, feature, **kwargs):
-        # Modifies main html body of Shoreline Data Accordion
+        # Modifies html body of accordion when extracted shoreline is hovered over
+        default = "unknown"
+        keys = [
+            "date",
+            "geoaccuracy",
+            "cloud_cover",
+            "satname",
+        ]
+        # returns a dict with keys in keys and if a key does not exist in feature its value is default str
+        logger.info(f"feature: {feature}")
+        values = common.get_default_dict(
+            default=default, keys=keys, fill_dict=feature["properties"]
+        )
         self.accordion.children[
             0
         ].value = """
+        <h2>Extracted Shoreline</h2>
         <p>Date: {}</p>
         <p>Geoaccuracy: {}</p>
         <p>Cloud Cover: {}</p>
-        <p>Satellite name: {}</p>
+        <p>Satellite Name: {}</p>
+        """.format(
+            values["date"],
+            values["geoaccuracy"],
+            values["cloud_cover"],
+            values["satname"],
+        )
+
+    def update_roi_html(self, feature, **kwargs):
+        # Modifies html of accordion when roi is hovered over
+        default = "unknown"
+        keys = [
+            "id",
+        ]
+        # returns a dict with keys in keys and if a key does not exist in feature its value is default str
+        values = common.get_default_dict(
+            default=default, keys=keys, fill_dict=feature["properties"]
+        )
+        logger.info(f"ROI feature: {feature}")
+        roi_area = common.get_area(feature["geometry"])
+        self.accordion.children[
+            1
+        ].value = """ 
+        <h2>ROI</h2>
+        <p>Id: {}</p>
+        <p>Area(m²): {}</p>
         """.format(
-            feature["properties"]["date"],
-            feature["properties"]["geoaccuracy"],
-            feature["properties"]["cloud_cover"],
-            feature["properties"]["satname"],
+            values["id"], roi_area
         )
 
     def update_shoreline_html(self, feature, **kwargs):
-        # Modifies main html body of Shoreline Data Accordion
+        # Modifies html of accordion when shoreline is hovered over
+        default_str = "unknown"
+        keys = [
+            "MEAN_SIG_WAVEHEIGHT",
+            "TIDAL_RANGE",
+            "ERODIBILITY",
+            "river_label",
+            "sinuosity_label",
+            "slope_label",
+            "turbid_label",
+            "CSU_ID",
+        ]
+        # returns a dict with keys in keys and if a key does not exist in feature its value is default str
+        values = common.get_default_dict(
+            default=default_str, keys=keys, fill_dict=feature["properties"]
+        )
         self.accordion.children[
             0
         ].value = """
+        <h2>Shoreline</h2>
         <p>Mean Sig Waveheight: {}</p>
         <p>Tidal Range: {}</p>
         <p>Erodibility: {}</p>
@@ -378,14 +460,14 @@ def update_shoreline_html(self, feature, **kwargs):
         <p>Turbid: {}</p>
         <p>CSU_ID: {}</p>
         """.format(
-            feature["properties"]["MEAN_SIG_WAVEHEIGHT"],
-            feature["properties"]["TIDAL_RANGE"],
-            feature["properties"]["ERODIBILITY"],
-            feature["properties"]["river_label"],
-            feature["properties"]["sinuosity_label"],
-            feature["properties"]["slope_label"],
-            feature["properties"]["turbid_label"],
-            feature["properties"]["CSU_ID"],
+            values["MEAN_SIG_WAVEHEIGHT"],
+            values["TIDAL_RANGE"],
+            values["ERODIBILITY"],
+            values["river_label"],
+            values["sinuosity_label"],
+            values["slope_label"],
+            values["turbid_label"],
+            values["CSU_ID"],
         )
 
     def load_extracted_shorelines_to_map(self, roi_ids: list) -> None:
@@ -405,6 +487,26 @@ def save_extracted_shorelines_to_file(self, roi_ids: list) -> None:
                 filepath = self.rois.roi_settings[roi_id]["filepath"]
                 roi_extract_shoreline.save_to_file(sitename, filepath)
 
+    def get_most_accurate_epsg(self, settings: dict, bbox: gpd.GeoDataFrame) -> int:
+        """Returns most accurate epsg code based on lat and lon if output epsg
+        was 4326 or 4327
+
+        Args:
+            settings (dict): settings for map must contain output_epsg
+            bbox (gpd.GeoDataFrame): geodataframe for bounding box on map
+
+        Returns:
+            int: epsg code that is most accurate or unchanged if crs not 4326 or 4327
+        """
+        output_epsg = settings["output_epsg"]
+        logger.info(f"Old output_epsg:{output_epsg}")
+        # coastsat cannot use 4326 to extract shorelines so modify output_epsg
+        if output_epsg == 4326 or output_epsg == 4327:
+            geometry = bbox.iloc[0]["geometry"]
+            output_epsg = common.get_epsg_from_geometry(geometry)
+            logger.info(f"New output_epsg:{output_epsg}")
+        return output_epsg
+
     def extract_all_shorelines(self) -> None:
         """Use this function when the user interactively downloads rois
         Iterates through all the ROIS downloaded by the user as indicated by the roi_settings generated by
@@ -432,6 +534,14 @@ def extract_all_shorelines(self) -> None:
         logger.info(f"roi_ids: {roi_ids}")
         exception_handler.check_if_rois_downloaded(self.rois.roi_settings, roi_ids)
 
+        exception_handler.check_if_None(self.bbox, "bounding box")
+        # if output_epsg is 4326 or 4327 change output_epsg to most accurate crs
+        self.settings["output_epsg"] = self.get_most_accurate_epsg(
+            self.settings, self.bbox.gdf
+        )
+        # update configs with new output_epsg
+        self.save_config()
+
         # extracted_shoreline_dict: holds extracted shorelines for each ROI
         extracted_shoreline_dict = {}
         # get selected ROIs on map and extract shoreline for each of them
@@ -444,7 +554,10 @@ def extract_all_shorelines(self) -> None:
                 shoreline_in_roi = gpd.clip(self.shoreline.gdf, single_roi)
                 # extract shorelines from ROI
                 extracted_shorelines = extracted_shoreline.Extracted_Shoreline(
-                    roi_id, shoreline_in_roi, roi_settings, self.settings
+                    roi_id,
+                    shoreline_in_roi,
+                    roi_settings,
+                    self.settings,
                 )
                 logger.info(
                     f"extracted_shoreline_dict[{roi_id}]: {extracted_shorelines}"
@@ -481,6 +594,7 @@ def get_selected_rois(self, roi_ids: list) -> gpd.GeoDataFrame:
 
     def compute_transects_from_roi(
         self,
+        roi_id: str,
         extracted_shorelines: list,
         transects_gdf: gpd.GeoDataFrame,
         settings: dict,
@@ -488,6 +602,7 @@ def compute_transects_from_roi(
         """Computes the intersection between the 2D shorelines and the shore-normal.
             transects. It returns time-series of cross-shore distance along each transect.
         Args:
+            roi_id(str): id of roi that transects intersect
             extracted_shorelines (list): contains the extracted shorelines and corresponding metadata
             transects_gdf (gpd.GeoDataFrame): transects in ROI with crs= output_crs in settings
             settings (dict): settings dict with keys
@@ -497,17 +612,13 @@ def compute_transects_from_roi(
             dict:  time-series of cross-shore distance along each of the transects.
                    Not tidally corrected.
         """
-        transects_coords = common.make_coastsat_compatible(transects_gdf)
-        logger.info(f"transects_coords:{transects_coords}")
         # create dict of numpy arrays of transect start and end points
-        # {'NA<index>': array([[start point],[end point]]),...}
-        transects = common.get_transect_points_dict(transects_coords)
+        transects = common.get_transect_points_dict(roi_id, transects_gdf)
         logger.info(f"transects: {transects}")
         # cross_distance: along-shore distance over which to consider shoreline points to compute median intersection (robust to outliers)
         cross_distance = SDS_transects.compute_intersection(
             extracted_shorelines, transects, settings
         )
-        # print(f"transects cross_distance:{cross_distance}")
         return cross_distance
 
     def compute_transects(self) -> dict:
@@ -605,7 +716,10 @@ def compute_transects(self) -> dict:
                     # compute cross distances of transects and extracted shorelines
                     if cross_distance is None:
                         cross_distance = self.compute_transects_from_roi(
-                            extracted_shorelines, transects_in_roi_gdf, self.settings
+                            roi_id,
+                            extracted_shorelines,
+                            transects_in_roi_gdf,
+                            self.settings,
                         )
                 if cross_distance == 0:
                     logger.warning(failure_msg)
@@ -665,7 +779,7 @@ def save_transects_to_csv(self) -> None:
         self.save_cross_distance_df(roi_ids, self.rois)
 
     def save_cross_distance_df(self, roi_ids: list, rois: ROI) -> None:
-        """save_cross_distance_df Saves the cross distances of the transects and
+        """Saves cross distances of transects and
         extracted shorelines in ROI to csv file within each ROI's directory.
         If no shorelines were extracted for an ROI then nothing is saved
         Args:
@@ -676,16 +790,41 @@ def save_cross_distance_df(self, roi_ids: list, rois: ROI) -> None:
                 'cross_distance_transects': cross distance of transects and extracted shoreline from roi
         """
         for roi_id in roi_ids:
-            extracted_shorelines = rois.extracted_shorelines[
-                roi_id
-            ].extracted_shorelines
+            roi_extracted_shorelines = rois.extracted_shorelines[roi_id]
+            # if roi does not have extracted shoreline skip it
+            if roi_extracted_shorelines is None:
+                print(f"ROI: {roi_id} had no extracted shorelines")
+                print(
+                    f"ROI: {roi_id} will not have time-series of shoreline change along transects "
+                )
+                logger.info(f"ROI: {roi_id} had no extracted shorelines ")
+                continue
+            # get extracted_shorelines from extracted shoreline object in rois
+            extracted_shorelines = roi_extracted_shorelines.extracted_shorelines
             cross_distance_transects = rois.cross_distance_transects[roi_id]
+            logger.info(f"ROI: {roi_id} cross distance : {cross_distance_transects}")
+            logger.info(f"ROI: {roi_id} extracted_shorelines : {extracted_shorelines}")
+            # if no cross distance was 0 then skip
+            if cross_distance_transects == 0:
+                print(f"ROI: {roi_id} cross distance is 0")
+                print(
+                    f"ROI: {roi_id} will not have time-series of shoreline change along transects "
+                )
+                logger.info(f"ROI: {roi_id} cross distance is 0")
+                continue
             # if no shorelines were extracted then skip
-            if extracted_shoreline is None:
+            if extracted_shorelines is None:
+                print(f"ROI: {roi_id} had no extracted shorelines")
+                print(
+                    f"ROI: {roi_id} will not have time-series of shoreline change along transects "
+                )
+                logger.info(f"ROI: {roi_id} had no extracted shorelines ")
                 continue
+            # check if cross_distance transects is 0
             cross_distance_df = common.get_cross_distance_df(
                 extracted_shorelines, cross_distance_transects
             )
+            logger.info(f"ROI: {roi_id} cross_distance_df : {cross_distance_df}")
             filepath = rois.roi_settings[roi_id]["filepath"]
             sitename = rois.roi_settings[roi_id]["sitename"]
             fn = os.path.join(filepath, sitename, "transect_time_series.csv")
@@ -693,52 +832,18 @@ def save_cross_distance_df(self, roi_ids: list, rois: ROI) -> None:
                 print(f"Overwriting:{fn}")
                 os.remove(fn)
             cross_distance_df.to_csv(fn, sep=",")
+            print(f"ROI: {roi_id} time-series of shoreline change along transects")
             print(
                 f"Time-series of the shoreline change along the transects saved as:{fn}"
             )
 
-    def load_transects_on_map(self) -> None:
-        """Loads transects within bounding box on map"""
-        # if no transects on map then create new transects
-        if self.transects is None:
-            exception_handler.check_if_None(
-                self.bbox,
-                "bounding box",
-                "Cannot load transects on map because bbox does not exist",
-            )
-            exception_handler.check_if_gdf_empty(
-                self.bbox.gdf,
-                "bounding box",
-                "Cannot load transects on map because bbox does not exist",
-            )
-            # if bounding box exists load transects within it
-            transects = Transects(self.bbox.gdf)
-            exception_handler.check_if_gdf_empty(
-                transects.gdf,
-                "transects",
-                "Transects Not Found in this region. Draw a new bounding box",
-            )
-            # Save transects to coastseg_map
-            self.transects = transects
-        layer_name = "transects"
-        # Replace old transect layer with new transect layer
-        self.remove_layer_by_name(layer_name)
-        # style and add the transect to the map
-        new_layer = self.create_layer(self.transects, layer_name)
-        if new_layer is None:
-            print("Cannot add an empty transects layer to the map.")
-        else:
-            self.map.add_layer(new_layer)
-            print("Loaded transects")
-            logger.info(f"Added layer to map: {new_layer}")
-
     def remove_all(self):
         """Remove the bbox, shoreline, all rois from the map"""
         self.remove_bbox()
         self.remove_shoreline()
         self.remove_transects()
         self.remove_all_rois()
-        self.remove_shoreline_html()
+        self.remove_accordion_html()
         self.remove_layer_by_name("geodataframe")
         self.remove_extracted_shorelines()
 
@@ -759,15 +864,16 @@ def remove_extracted_shorelines(self):
             self.remove_layer_by_name(layer)
         self.extracted_shoreline_layers = []
 
-    def remove_layer_by_name(self, layer_name):
+    def remove_layer_by_name(self, layer_name: str):
         existing_layer = self.map.find_layer(layer_name)
         if existing_layer is not None:
             self.map.remove_layer(existing_layer)
         logger.info(f"Removed layer {layer_name}")
 
-    def remove_shoreline_html(self):
+    def remove_accordion_html(self):
         """Clear the shoreline html accordion"""
-        self.accordion.children[0].value = "Hover over the shoreline."
+        self.accordion.children[0].value = "Hover over a feature on the map."
+        self.accordion.children[1].value = "Hover over a ROI on the map."
 
     def remove_shoreline(self):
         del self.shoreline
@@ -779,26 +885,39 @@ def remove_transects(self):
         self.transects = None
         self.remove_layer_by_name(Transects.LAYER_NAME)
 
-    def remove_all_rois(self, delete_rois: bool = True) -> None:
-        """Removes all the unselected rois from the map
-        delete_rois: bool
-             controls whether rois are deleted from coastseg_map class instance
+    def replace_layer_by_name(
+        self, layer_name: str, new_layer: GeoJSON, on_hover=None, on_click=None
+    ) -> None:
+        """Replaces layer with layer_name with new_layer on map. Adds on_hover and on_click callable functions
+        as handlers for hover and click events on new_layer
+        Args:
+            layer_name (str): name of layer to replace
+            new_layer (GeoJSON): ipyleaflet GeoJSON layer to add to map
+            on_hover (callable, optional): Callback function that will be called on hover event on a feature, this function
+            should take the event and the feature as inputs. Defaults to None.
+            on_click (callable, optional): Callback function that will be called on click event on a feature, this function
+            should take the event and the feature as inputs. Defaults to None.
         """
-        if delete_rois == True:
-            del self.rois
-            self.rois = None
-        # Remove the selected rois
-        self.selected_ROI_layer = None
-        del self.ROI_layer
-        self.ROI_layer = None
-        # remove both roi layers from map
-        existing_layer = self.map.find_layer("Selected ROIs")
-        if existing_layer is not None:
-            self.map.remove_layer(existing_layer)
-        existing_layer = self.map.find_layer(ROI.LAYER_NAME)
-        if existing_layer is not None:
-            # Remove the layer from the map
-            self.map.remove_layer(existing_layer)
+        logger.info(
+            f"layer_name {layer_name} \non_hover {on_hover}\n on_click {on_click}"
+        )
+        self.remove_layer_by_name(layer_name)
+        exception_handler.check_empty_layer(new_layer, layer_name)
+        # when feature is hovered over on_hover function is called
+        if on_hover is not None:
+            new_layer.on_hover(on_hover)
+        if on_click is not None:
+            # when feature is clicked on on_click function is called
+            new_layer.on_click(on_click)
+        self.map.add_layer(new_layer)
+        logger.info(f"Add layer to map: {layer_name}")
+
+    def remove_all_rois(self) -> None:
+        """Removes all the unselected rois from the map"""
+        logger.info("Removing all ROIs from map")
+        # Remove the selected and unselected rois
+        self.remove_layer_by_name(ROI.SELECTED_LAYER_NAME)
+        self.remove_layer_by_name(ROI.LAYER_NAME)
         # clear all the ids from the selected set
         self.selected_set = set()
 
@@ -836,37 +955,26 @@ def handle_draw(
             action (str): name of the most recent action ex. 'created', 'deleted'
             geo_json (dict): geojson dictionary
         """
-        self.action = action
-        self.geo_json = geo_json
-        self.target = target
         if (
             self.draw_control.last_action == "created"
             and self.draw_control.last_draw["geometry"]["type"] == "Polygon"
         ):
             # validate the bbox size
-            bbox_area = common.get_area(self.draw_control.last_draw["geometry"])
+            geometry = self.draw_control.last_draw["geometry"]
+            bbox_area = common.get_area(geometry)
             try:
                 Bounding_Box.check_bbox_size(bbox_area)
-            except exceptions.Invalid_Bbox_Size as bbox_too_big:
-                exception_handler.handle_bbox_error(bbox_too_big)
+            except exceptions.BboxTooLargeError as bbox_too_big:
                 self.remove_bbox()
+                exception_handler.handle_bbox_error(bbox_too_big)
             except exceptions.BboxTooSmallError as bbox_too_small:
-                exception_handler.handle_bbox_error(bbox_too_small)
                 self.remove_bbox()
+                exception_handler.handle_bbox_error(bbox_too_small)
             else:
-                # if a bbox already exists delete its layer from map
-                if self.bbox is not None:
-                    temp_bbox = Bounding_Box(self.draw_control.last_draw["geometry"])
-                    self.remove_bbox()
-                    self.bbox = temp_bbox
-                    new_layer = self.create_layer(self.bbox, self.bbox.LAYER_NAME)
-                    if new_layer is None:
-                        print("Cannot add an empty bbox layer to the map.")
-                    else:
-                        self.map.add_layer(new_layer)
-                else:
-                    # Save new bbox to coastseg_map
-                    self.bbox = Bounding_Box(self.draw_control.last_draw["geometry"])
+                # if no exceptions occur create new bbox, remove old bbox, and load new bbox
+                logger.info(f"Made it with bbox area: {bbox_area}")
+                self.load_feature_on_map("bbox")
+
         if self.draw_control.last_action == "deleted":
             self.remove_bbox()
 
@@ -883,73 +991,45 @@ def load_extracted_shorelines_on_map(self, extracted_shoreline):
             new_layer.on_hover(self.update_extracted_shoreline_html)
             self.map.add_layer(new_layer)
 
-    def load_gdf_on_map(self, layer_name: str, file=None):
-        # assumes geodataframe has a crs
-        self.remove_layer_by_name(layer_name)
-        if file is not None:
-            gdf = gpd.read_file(file)
-            new_gdf = gdf.to_crs("EPSG:4326")
-            new_gdf.drop(
-                new_gdf.columns.difference(["geometry"]), "columns", inplace=True
-            )
-            layer_geojson = json.loads(new_gdf.to_json())
-            new_layer = GeoJSON(
-                data=layer_geojson,
-                name=layer_name,
-                style={
-                    "color": "#9933ff",
-                    "fill_color": "#9933ff",
-                    "opacity": 1,
-                    "fillOpacity": 0.5,
-                    "weight": 2,
-                },
-                hover_style={"color": "white", "fillOpacity": 0.7},
-            )
-            self.map.add_layer(new_layer)
-            logger.info(f"Loaded geodataframe from file:\n{file}")
-
-    def load_bbox_on_map(self, file=None):
-        # remove old bbox layer from map if it exists
-        self.draw_control.clear()
-        existing_layer = self.map.find_layer(Bounding_Box.LAYER_NAME)
-        if existing_layer is not None:
-            self.map.remove_layer(existing_layer)
-        # if geojson file containing a bbox was given
-        if file is not None:
-            gdf = gpd.read_file(file)
-            self.bbox = Bounding_Box(gdf)
-            print(f"Loading bbox from file :\n{file}")
-            logger.info(f"Loading bbox from file :\n{file}")
-        # if bbox not empty create layer and add to map
-        exception_handler.check_if_None(self.bbox, "bounding box")
-        exception_handler.check_if_gdf_empty(self.bbox.gdf, "bounding box")
-        new_layer = self.create_layer(self.bbox, self.bbox.LAYER_NAME)
-        exception_handler.check_empty_layer(new_layer, "bounding box")
-        self.map.add_layer(new_layer)
-        logger.info("Bounding Box was loaded on the map")
-        print("Bounding Box was loaded on the map")
+    def load_feature_on_map(self, feature_name, file="", gdf=None, **kwargs) -> None:
+        # feature name can be ['shoreline','transects','bbox','ROIs']
+        # if feature name given is not one of possible features throw exception
+        # create new feature based on feature passed in and using file
+
+        # if file is passed read gdf from file
+        if file != "":
+            gdf = common.read_gpd_file(file)
+
+        new_feature = self.factory.make_feature(self, feature_name, gdf, **kwargs)
+        on_hover = None
+        on_click = None
+        if "shoreline" in feature_name.lower():
+            on_hover = self.update_shoreline_html
+        if "transects" in feature_name.lower():
+            on_hover = self.update_transects_html
+        if "rois" in feature_name.lower():
+            on_hover = self.update_roi_html
+            on_click = self.geojson_onclick_handler
+        # load new feature on map
+        layer_name = new_feature.LAYER_NAME
+        self.load_on_map(new_feature, layer_name, on_hover, on_click)
+
+    def load_on_map(
+        self, feature, layer_name: str, on_hover=None, on_click=None
+    ) -> None:
+        """Loads feature on map
+
+        Replaces current feature layer on map with given feature
 
-    def load_shoreline_on_map(self) -> None:
-        """Adds shoreline within the drawn bbox onto the map"""
-        # if no shorelines have been loaded onto the map before create a new shoreline
-        if self.shoreline is None:
-            exception_handler.check_if_None(self.bbox, "bounding box")
-            exception_handler.check_if_gdf_empty(self.bbox.gdf, "bounding box")
-            # if bounding box exists create shoreline within it
-            shoreline = Shoreline(self.bbox.gdf)
-            exception_handler.check_if_gdf_empty(shoreline.gdf, "shoreline")
-            # Save shoreline to coastseg_map
-            self.shoreline = shoreline
-        layer_name = "shoreline"
-        # Replace old shoreline layer with new shoreline layer
-        self.remove_layer_by_name(layer_name)
-        # style and add the shoreline to the map
-        new_layer = self.create_layer(self.shoreline, layer_name)
-        exception_handler.check_empty_layer(new_layer, "shoreline")
-        # add on_hover handler to update shoreline widget when user hovers over shoreline
-        new_layer.on_hover(self.update_shoreline_html)
-        self.map.add_layer(new_layer)
-        logger.info(f"Add layer to map: {layer_name}")
+        Raises:
+            Exception: raised if feature layer is empty
+        """
+        # style and add the feature to the map
+        new_layer = self.create_layer(feature, layer_name)
+        # Replace old feature layer with new feature layer
+        self.replace_layer_by_name(
+            layer_name, new_layer, on_hover=on_hover, on_click=on_click
+        )
 
     def create_layer(self, feature, layer_name: str):
         if feature.gdf.empty:
@@ -961,64 +1041,32 @@ def create_layer(self, feature, layer_name: str):
         styled_layer = feature.style_layer(layer_geojson, layer_name)
         return styled_layer
 
-    def load_rois_on_map(self, large_len=7500, small_len=5000, file: str = None):
-        # Remove old ROI_layers
-        if self.rois is not None or file is not None:
-            # removes the old rois from the map
-            if self.rois is not None:
-                self.remove_all_rois(delete_rois=False)
-            # read rois from geojson file and save them to the map
-            if file is not None:
-                self.remove_all_rois()
-                logger.info(f"Loading ROIs from file {file}")
-                gdf = gpd.read_file(file)
-                self.rois = ROI(rois_gdf=gdf)
-            logger.info(f"ROIs: {self.rois}")
-            # Create new ROI layer
-            self.ROI_layer = self.create_layer(self.rois, ROI.LAYER_NAME)
-            exception_handler.check_empty_layer(self.ROI_layer, "ROI")
-            logger.info(f"ROI_layer: {self.ROI_layer}")
-            # add on click handler to add the ROI to the selected geojson layer when its clicked
-            self.ROI_layer.on_click(self.geojson_onclick_handler)
-            self.map.add_layer(self.ROI_layer)
-        else:
-            # if no rois exist on the map then generate ROIs within the bounding box
-            self.remove_all_rois()
-            logger.info(f"No file provided. Generating ROIs")
-            self.generate_ROIS_fishnet(large_len, small_len)
-
-    def generate_ROIS_fishnet(self, large_len: float = 7500, small_len: float = 5000):
+    def generate_ROIS_fishnet(
+        self, large_len: float = 7500, small_len: float = 5000
+    ) -> ROI:
         """Generates series of overlapping ROIS along shoreline on map using fishnet method"""
-        if self.bbox is None:
-            raise exceptions.Object_Not_Found("bounding box")
+        exception_handler.check_if_None(self.bbox, "bounding box")
         logger.info(f"bbox for ROIs: {self.bbox.gdf}")
         # If no shoreline exists on map then load one in
         if self.shoreline is None:
-            self.load_shoreline_on_map()
-        logger.info(f"self.shoreline used for ROIs:{ self.shoreline}")
+            self.load_feature_on_map("shoreline")
+        logger.info(f"self.shoreline used for ROIs:{self.shoreline}")
         # create rois within the bbox that intersect shorelines
-        self.rois = ROI(
+        rois = ROI(
             self.bbox.gdf,
             self.shoreline.gdf,
             square_len_lg=large_len,
             square_len_sm=small_len,
         )
-        # create roi layer to add to map
-        self.ROI_layer = self.create_layer(self.rois, ROI.LAYER_NAME)
-        if self.ROI_layer is None:
-            logger.error("Cannot add an empty ROI layer to the map.")
-            raise Exception("Cannot add an empty ROI layer to the map.")
-        # new rois have been generated so they have no downloaded data associated with them yet
-        self.ROI_layer.on_click(self.geojson_onclick_handler)
-        self.map.add_layer(self.ROI_layer)
+        return rois
 
     def geojson_onclick_handler(
         self, event: str = None, id: "NoneType" = None, properties: dict = None, **args
     ):
         """On click handler for when unselected geojson is clicked.
 
-        Adds the geojson's id to the selected_set. Replaces current selected layer with a new one that includes
-        the recently clicked geojson.
+        Adds geojson's id to selected_set. Replaces current selected layer with a new one that includes
+        recently clicked geojson.
 
         Args:
             event (str, optional): event fired ('click'). Defaults to None.
@@ -1027,24 +1075,28 @@ def geojson_onclick_handler(
         """
         if properties is None:
             return
-        logger.info(f"geojson_onclick_handler: properties : {properties}")
-        logger.info(f"geojson_onclick_handler: ROI_id : {properties['id']}")
+        logger.info(f"properties : {properties}")
+        logger.info(f"ROI_id : {properties['id']}")
 
-        # Add the id of the clicked ROI to selected_set
+        # Add id of clicked ROI to selected_set
         ROI_id = str(properties["id"])
         self.selected_set.add(ROI_id)
         logger.info(f"Added ID to selected set: {self.selected_set}")
-        if self.selected_ROI_layer is not None:
-            self.map.remove_layer(self.selected_ROI_layer)
+        # remove old selected layer
+        self.remove_layer_by_name(ROI.SELECTED_LAYER_NAME)
 
-        self.selected_ROI_layer = GeoJSON(
+        selected_layer = GeoJSON(
             data=self.convert_selected_set_to_geojson(self.selected_set),
-            name="Selected ROIs",
+            name=ROI.SELECTED_LAYER_NAME,
             hover_style={"fillColor": "blue", "fillOpacity": 0.1, "color": "aqua"},
         )
-        logger.info(f"selected_ROI_layer: {self.selected_ROI_layer}")
-        self.selected_ROI_layer.on_click(self.selected_onclick_handler)
-        self.map.add_layer(self.selected_ROI_layer)
+        logger.info(f"selected_layer: {selected_layer}")
+        self.replace_layer_by_name(
+            ROI.SELECTED_LAYER_NAME,
+            selected_layer,
+            on_click=self.selected_onclick_handler,
+            on_hover=self.update_roi_html,
+        )
 
     def selected_onclick_handler(
         self, event: str = None, id: "NoneType" = None, properties: dict = None, **args
@@ -1067,18 +1119,20 @@ def selected_onclick_handler(
         cid = properties["id"]
         self.selected_set.remove(cid)
         logger.info(f"selected set after ID removal: {self.selected_set}")
-        if self.selected_ROI_layer is not None:
-            self.map.remove_layer(self.selected_ROI_layer)
-        # Recreate the selected layers without the layer that was removed
-        self.selected_ROI_layer = GeoJSON(
+        self.remove_layer_by_name(ROI.SELECTED_LAYER_NAME)
+        # Recreate selected layers without layer that was removed
+
+        selected_layer = GeoJSON(
             data=self.convert_selected_set_to_geojson(self.selected_set),
-            name="Selected ROIs",
+            name=ROI.SELECTED_LAYER_NAME,
             hover_style={"fillColor": "blue", "fillOpacity": 0.1, "color": "aqua"},
         )
-        # Recreate the onclick handler for the selected layers
-        self.selected_ROI_layer.on_click(self.selected_onclick_handler)
-        # Add selected layer to the map
-        self.map.add_layer(self.selected_ROI_layer)
+        self.replace_layer_by_name(
+            ROI.SELECTED_LAYER_NAME,
+            selected_layer,
+            on_click=self.selected_onclick_handler,
+            on_hover=self.update_roi_html,
+        )
 
     def save_feature_to_file(
         self,
@@ -1108,10 +1162,14 @@ def convert_selected_set_to_geojson(self, selected_set: set) -> dict:
         """
         # create a new geojson dictionary to hold selected ROIs
         selected_rois = {"type": "FeatureCollection", "features": []}
-        # Copy only selected ROIs based on the ids in selected_set from ROI_geojson
+        roi_layer = self.map.find_layer(ROI.LAYER_NAME)
+        # if ROI layer does not exist throw an error
+        if roi_layer is not None:
+            exception_handler.check_empty_layer(roi_layer, "ROI")
+        # Copy only selected ROIs with id in selected_set
         selected_rois["features"] = [
             feature
-            for feature in self.ROI_layer.data["features"]
+            for feature in roi_layer.data["features"]
             if feature["properties"]["id"] in selected_set
         ]
         # Each selected ROI will be blue and unselected rois will appear black
diff --git a/src/coastseg/common.py b/src/coastseg/common.py
index 5e8e311f..1f34b1da 100644
--- a/src/coastseg/common.py
+++ b/src/coastseg/common.py
@@ -12,7 +12,6 @@
 # Internal dependencies imports
 from coastseg import exceptions
 
-from coastsat import SDS_tools
 from tqdm.auto import tqdm
 import requests
 from area import area
@@ -95,45 +94,21 @@ def get_center_rectangle(coords: list) -> tuple:
     return center_x, center_y
 
 
-# @todo remove this
-def is_shoreline_present(extracted_shorelines: dict, roi_id: int) -> bool:
-    """Returns true if shoreline array exists for roi_id
-    Args:
-        extracted_shorelines (dict): dictionary of extracted shorelines in form of :
-            {   roi_id : {dates: [datetime.datetime,datetime.datetime],
-                shorelines: [array(),array()]}  }
-        roi_id (int): id of the roi
-    Returns:
-        bool: false if shoreline does not exist at roi_id
-    """
-    for shoreline in extracted_shorelines[roi_id]["shorelines"]:
-        if shoreline.size != 0:
-            return True
-    return False
+def get_epsg_from_geometry(geometry: "shapely.geometry.polygon.Polygon") -> int:
+    """Uses geometry of shapely rectangle in crs 4326 to return the most accurate
+    utm code as a string of format 'epsg:utm_code'
+    example: 'espg:32610'
 
-
-def convert_espg(
-    input_epsg: int, output_epsg: int, coastsat_array: np.ndarray
-) -> np.ndarray:
-    """Convert the coastsat_array espg to the output_espg
     Args:
-        input_epsg (int): input espg
-        output_epsg (int): output espg
-        coastsat_array (np.ndarray): array of coordiinates as [[lat,lon],[lat,lon]....]
+        geometry (shapely.geometry.polygon.Polygon): geometry of a rectangle
+
     Returns:
-        np.ndarray: array with output espg in the form [[[lat,lon,0.]]]
+        int: most accurate epsg code based on lat lon coordinates of given geometry
     """
-    if input_epsg is None:
-        input_epsg = 4326
-    inProj = Proj(init="epsg:" + str(input_epsg))
-    outProj = Proj(init="epsg:" + str(output_epsg))
-    s_proj = []
-    logger.info(f"convert_espg: coastsat_array {coastsat_array}")
-    # Convert all the lat,ln coords to new espg (operation takes some time....)
-    for coord in coastsat_array:
-        x2, y2 = transform(inProj, outProj, coord[0], coord[1])
-        s_proj.append([x2, y2, 0.0])
-    return np.array(s_proj)
+    rect_coords = geometry.exterior.coords
+    center_x, center_y = get_center_rectangle(rect_coords)
+    utm_code = convert_wgs_to_utm(center_x, center_y)
+    return int(utm_code)
 
 
 def convert_wgs_to_utm(lon: float, lat: float) -> str:
@@ -187,21 +162,6 @@ def extract_roi_by_id(gdf: gpd.geodataframe, roi_id: int) -> gpd.geodataframe:
     return single_roi
 
 
-def convert_gdf_to_polygon(gdf: gpd.geodataframe, id: int = None) -> geometry.Polygon:
-    """Returns the roi with given id as Shapely.geometry.Polygon
-    Args:
-        gdf (gpd.geodataframe): geodataframe consisting of rois or a bbox
-        id (str): roi_id
-    Returns:
-        geometry.Polygon: roi with the id converted to Shapely.geometry.Polygon
-    """
-    single_roi = extract_roi_by_id(gdf, id)
-    single_roi = single_roi["geometry"].to_json()
-    single_roi = json.loads(single_roi)
-    polygon = geometry.Polygon(single_roi["features"][0]["geometry"]["coordinates"][0])
-    return polygon
-
-
 def get_area(polygon: dict) -> float:
     "Calculates the area of the geojson polygon using the same method as geojson.io"
     return round(area(polygon), 3)
@@ -253,34 +213,63 @@ def config_to_file(config: Union[dict, gpd.GeoDataFrame], file_path: str):
         config.to_file(save_path, driver="GeoJSON")
 
 
-def get_transect_points_dict(transects_coords: list) -> dict:
+def get_default_dict(default, keys: list, fill_dict: dict) -> dict:
+    """returns a dictionary with keys each with
+    default value if the key does not exist in fill_dict. If the key exists
+    in fill_dict then the value replaces the default value.
+
+    Args:
+        default (): default value for each key
+        keys (list): keys for dictionary
+        fill_dict (dict): dictionary used to replace default values
+
+    Returns:
+        dict: dict with given keys and default value for each key that didn't exist in fill_dict
+    """
+    values = {}
+    values = values.fromkeys(keys, default)
+    for key in fill_dict.keys():
+        if key in values:
+            values[key] = fill_dict[key]
+    return values
+
+
+def get_transect_points_dict(roi_id: str, feature: gpd.geodataframe) -> dict:
     """Returns dict of np.arrays of transect start and end points
     Example
     {
-        'NA0': array([[-13820440.53165404,   4995568.65036405],
+        'ROI_1_usa_CA_0289-0055-NA1': array([[-13820440.53165404,   4995568.65036405],
         [-13820940.93156407,   4995745.1518021 ]]),
-        'NA1': array([[-13820394.24579453,   4995700.97802925],
+        'ROI_1_usa_CA_0289-0056-NA1': array([[-13820394.24579453,   4995700.97802925],
         [-13820900.16320004,   4995862.31860808]])
     }
-
     Args:
-        transects_coords (list): list of np.arrays in the form:
-        [([lat,lon],[lat,lon],[lat,lon]),([lat,lon],[lat,lon],[lat,lon])...])
-
+        roi_id(str): id of roi that transects intersect
+        feature (gpd.geodataframe): clipped transects within roi
     Returns:
         dict: dict of np.arrays of transect start and end points
         of form {
-            'NA<index>': array([[start point],
+            'ROI<roi_id>_<transect_id>': array([[start point],
                         [end point]]),}
-
     """
-    # convert to dict of numpy arrays of start and end points
-    transects = {}
-    for counter, i in enumerate(transects_coords):
-        x0, y0 = i[0][0], i[0][1]
-        x1, y1 = i[1][0], i[1][1]
-        transects["NA" + str(counter)] = np.array([[x0, y0], [x1, y1]])
-    return transects
+    features = []
+    # Use explode to break multilinestrings in linestrings
+    feature_exploded = feature.explode()
+    # For each linestring portion of feature convert to lat,lon tuples
+    lat_lng = feature_exploded.apply(
+        lambda row: {
+            "ROI_"
+            + str(roi_id)
+            + "_"
+            + str(row.id): np.array(np.array(row.geometry).tolist())
+        },
+        axis=1,
+    )
+    features = list(lat_lng)
+    new_dict = {}
+    for item in list(features):
+        new_dict = {**new_dict, **item}
+    return new_dict
 
 
 def get_cross_distance_df(
@@ -294,29 +283,23 @@ def get_cross_distance_df(
     return pd.DataFrame(transects_csv)
 
 
-def make_coastsat_compatible(feature: gpd.geodataframe) -> np.ndarray:
+def make_coastsat_compatible(feature: gpd.geodataframe) -> list:
     """Return the feature as an np.array in the form:
         [([lat,lon],[lat,lon],[lat,lon]),([lat,lon],[lat,lon],[lat,lon])...])
     Args:
         feature (gpd.geodataframe): clipped portion of shoreline within a roi
     Returns:
-        np.ndarray: shorelines in form:
+        list: shorelines in form:
             [([lat,lon],[lat,lon],[lat,lon]),([lat,lon],[lat,lon],[lat,lon])...])
     """
-    # Then convert feature to lat,lon tuples for CoastSat
     features = []
     # Use explode to break multilinestrings in linestrings
     feature_exploded = feature.explode()
     # For each linestring portion of feature convert to lat,lon tuples
     lat_lng = feature_exploded.apply(
-        lambda x: tuple(np.array(x.geometry).tolist()), axis=1
+        lambda row: tuple(np.array(row.geometry).tolist()), axis=1
     )
     features = list(lat_lng)
-    # for k in feature_exploded["geometry"].keys():
-    #     # For each linestring portion of feature convert to lat,lon tuples
-    #     features.append(
-    #         tuple(np.array(feature_exploded["geometry"][k]).tolist())
-    #     )
     return features
 
 
@@ -404,10 +387,6 @@ def read_gpd_file(filename: str) -> gpd.GeoDataFrame:
         with open(filename, "r") as f:
             gpd_data = gpd.read_file(f)
     else:
-        logger.error(f"Geodataframe file does not exist \n {filename}")
-        print(
-            "File does not exist. Please download the coastline_vector necessary here: https://geodata.lib.berkeley.edu/catalog/stanford-xv279yj9196 "
-        )
         raise FileNotFoundError
     return gpd_data
 
@@ -640,7 +619,6 @@ def create_roi_settings(
     sat_list = settings["sat_list"]
     landsat_collection = settings["landsat_collection"]
     dates = settings["dates"]
-    # for roi in self.selected_ROI_layer.data["features"]:
     for roi in selected_rois["features"]:
         roi_id = str(roi["properties"]["id"])
         sitename = (
diff --git a/src/coastseg/exception_handler.py b/src/coastseg/exception_handler.py
index 591334d6..6122faa2 100644
--- a/src/coastseg/exception_handler.py
+++ b/src/coastseg/exception_handler.py
@@ -1,14 +1,13 @@
 # standard python imports
-import os
 import logging
 import traceback
-import sys
 from typing import Union
 
 # internal python imports
 from coastseg.tkinter_window_creator import Tkinter_Window_Creator
 from coastseg import exceptions
 from coastseg import common
+from coastseg.roi import ROI
 
 # external python imports
 from google.auth import exceptions as google_auth_exceptions
@@ -88,6 +87,12 @@ def check_empty_dict(feature, feature_type: str = ""):
 
 def check_empty_layer(layer, feature_type: str = ""):
     if layer is None:
+        if feature_type == ROI.LAYER_NAME:
+            logger.error(f"No ROI layer found on map")
+            raise Exception("No ROI layer found on map")
+        if feature_type == ROI.SELECTED_LAYER_NAME:
+            logger.error(f"No selected ROI layer found on map")
+            raise Exception("No selected ROI layer found on map")
         logger.error(f"Cannot add an empty {feature_type} layer to the map.")
         raise Exception(f"Cannot add an empty {feature_type} layer to the map.")
 
diff --git a/src/coastseg/extracted_shoreline.py b/src/coastseg/extracted_shoreline.py
index 342b6f67..b756ab05 100644
--- a/src/coastseg/extracted_shoreline.py
+++ b/src/coastseg/extracted_shoreline.py
@@ -70,29 +70,36 @@ def __init__(
 
         logger.info(f"Extracting shorelines for ROI id{self.roi_id}")
         self.extracted_shorelines = self.extract_shorelines(
-            shoreline, roi_settings, settings
+            shoreline,
+            roi_settings,
+            settings,
         )
         if common.is_list_empty(self.extracted_shorelines["shorelines"]):
             raise exceptions.No_Extracted_Shoreline(self.roi_id)
         map_crs = "EPSG:4326"
-        input_crs = "EPSG:" + str(settings["output_epsg"])
-        self.gdf = self.create_geodataframe(input_crs, output_crs=map_crs)
+        # extracted shorelines have map crs so they can be displayed on the map
+        self.gdf = self.create_geodataframe(
+            self.shoreline_settings["output_epsg"], output_crs=map_crs
+        )
 
     def extract_shorelines(
-        self, shoreline_gdf: gpd.geodataframe, roi_settings: dict, settings: dict
+        self,
+        shoreline_gdf: gpd.geodataframe,
+        roi_settings: dict,
+        settings: dict,
     ) -> dict:
         """Returns a dictionary containing the extracted shorelines for roi specified by rois_gdf"""
         # project shorelines's crs from map's crs to output crs given in settings
         map_crs = 4326
-        output_crs = settings["output_epsg"]
         reference_shoreline = get_reference_shoreline(
-            shoreline_gdf, output_crs, map_crs
+            shoreline_gdf, settings["output_epsg"]
         )
         # Add reference shoreline to shoreline_settings
         self.create_shoreline_settings(settings, roi_settings, reference_shoreline)
 
         # gets metadata used to extract shorelines
         metadata = SDS_download.get_metadata(self.shoreline_settings["inputs"])
+        logger.info(f"metadata: {metadata}")
         # extract shorelines from ROI
         extracted_shorelines = SDS_shoreline.extract_shorelines(
             metadata, self.shoreline_settings
@@ -111,7 +118,10 @@ def extract_shorelines(
         return extracted_shorelines
 
     def create_shoreline_settings(
-        self, settings: dict, roi_settings: dict, reference_shoreline: dict
+        self,
+        settings: dict,
+        roi_settings: dict,
+        reference_shoreline: dict,
     ) -> None:
         """sets self.shoreline_settings to dictionary containing settings, reference_shoreline
         and roi_settings
@@ -139,7 +149,6 @@ def create_shoreline_settings(
         shoreline_settings["adjust_detection"] = False
         # disable adjusting shorelines manually in shoreline_settings
         shoreline_settings["check_detection"] = False
-
         # copy roi_setting for this specific roi
         shoreline_settings["inputs"] = roi_settings
         logger.info(f"shoreline_settings: {shoreline_settings}")
@@ -241,7 +250,7 @@ def get_styled_layers(self) -> list:
 
 
 def get_reference_shoreline(
-    shoreline_gdf: gpd.geodataframe, output_crs: str, input_crs=4326
+    shoreline_gdf: gpd.geodataframe, output_crs: str
 ) -> np.ndarray:
     # project shorelines's espg from map's espg to output espg given in settings
     reprojected_shorlines = shoreline_gdf.to_crs(output_crs)
diff --git a/src/coastseg/factory.py b/src/coastseg/factory.py
new file mode 100644
index 00000000..2046fda7
--- /dev/null
+++ b/src/coastseg/factory.py
@@ -0,0 +1,175 @@
+import logging
+from typing import Union
+
+from coastseg.bbox import Bounding_Box
+from coastseg import common
+from coastseg.shoreline import Shoreline
+from coastseg.transects import Transects
+from coastseg.roi import ROI
+from coastseg import exceptions
+from coastseg import extracted_shoreline
+from coastseg import exception_handler
+
+logger = logging.getLogger(__name__)
+
+
+class Factory:
+    def _get_feature_maker(self, feature_type):
+        if feature_type.lower() == "shoreline":
+            return create_shoreline
+        elif feature_type.lower() == "transects":
+            return create_transects
+        elif feature_type.lower() == "bbox":
+            return create_bbox
+        elif feature_type.lower() == "rois":
+            return create_rois
+
+    def make_feature(self, coastsegmap, feature, gdf=None, **kwargs):
+        # get function to create feature based on feature requested
+        feature_maker = self._get_feature_maker(feature)
+        # create feature from geodataframe given by gdf
+        if gdf is not None:
+            return feature_maker(coastsegmap, gdf)
+        # if geodataframe not provided then create feature from scratch
+        return feature_maker(coastsegmap, **kwargs)
+
+
+def create_shoreline(
+    coastsegmap, gdf: "geopandas.GeoDataFrame" = None, **kwargs
+) -> Shoreline:
+    """Creates shorelines within bounding box on map
+
+    Creates shorelines if no gdf(geodataframe). If a gdf(geodataframe) is
+    provided a shoreline is created from it
+
+    Sets coastsegmap.shoreline to created shoreline
+
+    Raises:
+        exceptions.Object_Not_Found: raised if bounding box is missing or empty
+        exceptions.Object_Not_Found: raised if shoreline is empty
+    """
+    # if shoreline gdf is given make a shoreline from it
+    if gdf is not None:
+        shoreline = Shoreline(shoreline=gdf)
+    else:
+        # ensure valid bbox exists to create shorelines in
+        exception_handler.check_if_None(coastsegmap.bbox, "bounding box")
+        exception_handler.check_if_gdf_empty(coastsegmap.bbox.gdf, "bounding box")
+        # if bounding box exists create shoreline within it
+        shoreline = Shoreline(coastsegmap.bbox.gdf)
+        exception_handler.check_if_gdf_empty(shoreline.gdf, "shoreline")
+
+    # Save shoreline to coastseg_map
+    logger.info("Shoreline were loaded on map")
+    print("Shoreline were loaded on map")
+    coastsegmap.shoreline = shoreline
+    return shoreline
+
+
+def create_transects(
+    coastsegmap, gdf: "geopandas.GeoDataFrame" = None, **kwargs
+) -> Transects:
+    """Creates transects within bounding box on map
+
+    Creates transects if no gdf(geodataframe). If a gdf(geodataframe) is
+    provided a transect is created from it
+
+    Sets coastsegmap.transect to created transect
+
+    Raises:
+        exceptions.Object_Not_Found: raised if bounding box is missing or empty
+        exceptions.Object_Not_Found: raised if transect is empty
+    """
+    # if gdf is given make a transects from it
+    if gdf is not None:
+        transects = Transects(transects=gdf)
+    else:
+        # ensure valid bbox exists to create transects in
+        exception_handler.check_if_None(coastsegmap.bbox, "bounding box")
+        exception_handler.check_if_gdf_empty(coastsegmap.bbox.gdf, "bounding box")
+        # if bounding box exists load transects within it
+        transects = Transects(coastsegmap.bbox.gdf)
+        exception_handler.check_if_gdf_empty(
+            transects.gdf,
+            "transects",
+            "Transects Not Found in this region. Draw a new bounding box",
+        )
+
+    logger.info("Transects were loaded on map")
+    print("Transects were loaded on map")
+    coastsegmap.transects = transects
+    return transects
+
+
+def create_bbox(
+    coastsegmap, gdf: "geopandas.GeoDataFrame" = None, **kwargs
+) -> Bounding_Box:
+    """Creates bounding box
+
+    Creates bounding box if no gdf(geodataframe). If a gdf(geodataframe) is
+    provided a bbox is created from it
+
+    Sets coastsegmap.bbox to created bbox
+    Raises:
+        exceptions.Object_Not_Found: raised if bounding box is missing or empty
+        exceptions.Object_Not_Found: raised if bbox is empty
+    """
+    # if gdf is given make a bounding box from it
+    if gdf is not None:
+        bbox = Bounding_Box(gdf)
+        exception_handler.check_if_gdf_empty(bbox.gdf, "bounding box")
+    else:
+        # get last drawn polygon on map and create bounding box from it
+        geometry = coastsegmap.draw_control.last_draw["geometry"]
+        bbox = Bounding_Box(geometry)
+        # make sure bounding box created is not empty
+        exception_handler.check_if_gdf_empty(bbox.gdf, "bounding box")
+
+    # clean drawn feature from map
+    coastsegmap.remove_bbox()
+    coastsegmap.draw_control.clear()
+    logger.info("Bounding Box was loaded on map")
+    print("Bounding Box was loaded on map")
+    # save bbox to coastseg_map
+    coastsegmap.bbox = bbox
+    return bbox
+
+
+def create_rois(
+    coastsegmap, gdf: "geopandas.GeoDataFrame" = None, **kwargs
+) -> Bounding_Box:
+    """Creates rois (region of interest)
+
+    Creates rois if no gdf(geodataframe). If a gdf(geodataframe) is
+    provided rois are created from it
+
+    Sets coastsegmap.rois to created rois
+    Raises:
+        exceptions.Object_Not_Found: raised if rois are missing or empty
+        exceptions.Object_Not_Found: raised if rois are empty
+    """
+    # if gdf is given make a rois from it
+    if gdf is not None:
+        rois = ROI(rois_gdf=gdf)
+        exception_handler.check_if_gdf_empty(rois.gdf, "rois")
+    else:
+        # create rois within bounding box
+        large_len = None
+        small_len = None
+        if "large_len" in kwargs:
+            large_len = kwargs["large_len"]
+        if "small_len" in kwargs:
+            small_len = kwargs["small_len"]
+        if large_len is None or small_len is None:
+            raise Exception("Must provide ROI lengths")
+        rois = coastsegmap.generate_ROIS_fishnet(large_len, small_len)
+        # make sure rois created is not empty
+        exception_handler.check_if_gdf_empty(rois.gdf, "rois")
+
+    # clean drawn feature from map
+    coastsegmap.remove_all_rois()
+    logger.info("ROIs was loaded on map")
+    print("ROIs was loaded on map")
+    # save rois to coastseg_map
+    coastsegmap.rois = rois
+    return rois
diff --git a/src/coastseg/map_UI.py b/src/coastseg/map_UI.py
index 9a208c08..886d608f 100644
--- a/src/coastseg/map_UI.py
+++ b/src/coastseg/map_UI.py
@@ -1,13 +1,12 @@
 # standard python imports
 import os
+import datetime
 import logging
-import traceback
-import sys
 
 # internal python imports
 from coastseg.tkinter_window_creator import Tkinter_Window_Creator
-from coastseg import exceptions
 from coastseg import exception_handler
+from coastseg import common
 
 # external python imports
 import ipywidgets
@@ -16,6 +15,7 @@
 from google.auth import exceptions as google_auth_exceptions
 from tkinter import filedialog
 from tkinter import messagebox
+from ipywidgets import Accordion
 from ipywidgets import Button
 from ipywidgets import HBox
 from ipywidgets import VBox
@@ -23,6 +23,7 @@
 from ipywidgets import DatePicker
 from ipywidgets import HTML
 from ipywidgets import RadioButtons
+from ipywidgets import Text
 from ipywidgets import SelectMultiple
 from ipywidgets import Output
 from ipywidgets import Checkbox
@@ -53,15 +54,7 @@ def __init__(self, coastseg_map):
         small_roi_size = 3500
         large_roi_size = 4000
         self.fishnet_sizes = {"small": small_roi_size, "large": large_roi_size}
-        # Declare widgets and on click callbacks
-        self.load_gdf_button = Button(
-            description="Load gdf from file", style=self.load_style
-        )
-        self.load_gdf_button.on_click(self.on_load_gdf_clicked)
-        self.load_bbox_button = Button(
-            description="Load bbox from file", style=self.load_style
-        )
-        self.load_bbox_button.on_click(self.on_load_bbox_clicked)
+
         # buttons to load configuration files
         self.load_configs_button = Button(
             description="Load Config", style=self.load_style
@@ -71,32 +64,34 @@ def __init__(self, coastseg_map):
             description="Save Config", style=self.save_style
         )
         self.save_config_button.on_click(self.on_save_config_clicked)
-        # load buttons
-        self.transects_button = Button(
-            description="Load Transects", style=self.load_style
-        )
-        self.transects_button.on_click(self.on_transects_button_clicked)
-        self.shoreline_button = Button(
-            description="Load Shoreline", style=self.load_style
-        )
-        self.shoreline_button.on_click(self.on_shoreline_button_clicked)
-        self.load_rois_button = Button(
-            description="Load rois from file", style=self.load_style
-        )
-        self.load_rois_button.on_click(self.on_load_rois_clicked)
-        # Save buttons
-        self.save_shoreline_button = Button(
-            description="Save shorelines", style=self.save_style
-        )
-        self.save_shoreline_button.on_click(self.save_shoreline_button_clicked)
-        self.save_transects_button = Button(
-            description="Save transects", style=self.save_style
-        )
-        self.save_transects_button.on_click(self.save_transects_button_clicked)
-        self.save_roi_button = Button(description="Save ROI", style=self.save_style)
-        self.save_roi_button.on_click(self.save_roi_button_clicked)
-        self.save_bbox_button = Button(description="Save box", style=self.save_style)
-        self.save_bbox_button.on_click(self.on_save_bbox_button_clicked)
+
+        self.load_file_instr = HTML(
+            value="<h2>Load Feature from File</h2>\
+                 Load a feature onto map from geojson file.\
+                ",
+            layout=Layout(padding="0px"),
+        )
+
+        self.load_file_radio = RadioButtons(
+            options=[
+                "Shoreline",
+                "Transects",
+                "Bbox",
+            ],
+            value="Shoreline",
+            description="",
+            disabled=False,
+        )
+        self.load_file_button = Button(
+            description=f"Load {self.load_file_radio.value} file", style=self.load_style
+        )
+        self.load_file_button.on_click(self.load_feature_from_file)
+
+        def change_load_file_btn_name(change):
+            self.load_file_button.description = f"Load {str(change['new'])} file"
+
+        self.load_file_radio.observe(change_load_file_btn_name, "value")
+
         # Generate buttons
         self.gen_button = Button(description="Generate ROI", style=self.action_style)
         self.gen_button.on_click(self.on_gen_button_clicked)
@@ -123,43 +118,24 @@ def __init__(self, coastseg_map):
             description="Clear TextBox", style=self.clear_stlye
         )
         self.clear_debug_button.on_click(self.clear_debug_view)
-        self.remove_all_button = Button(
-            description="Remove all", style=self.remove_style
-        )
-        self.remove_all_button.on_click(self.remove_all_from_map)
-        self.remove_transects_button = Button(
-            description="Remove transects", style=self.remove_style
-        )
-        self.remove_transects_button.on_click(self.remove_transects)
-        self.remove_bbox_button = Button(
-            description="Remove bbox", style=self.remove_style
-        )
-        self.remove_bbox_button.on_click(self.remove_bbox_from_map)
-        self.remove_shoreline_button = Button(
-            description="Remove shoreline", style=self.remove_style
-        )
-        self.remove_shoreline_button.on_click(self.remove_shoreline_from_map)
-        self.remove_rois_button = Button(
-            description="Remove ROIs", style=self.remove_style
-        )
-        self.remove_rois_button.on_click(self.remove_all_rois_from_map)
 
         # create the HTML widgets containing the instructions
         self._create_HTML_widgets()
+        self.roi_slider_instr = HTML(value="<b>Choose Side Length of ROIs</b>")
         # define slider widgets that control ROI size
-        slider_style = {"description_width": "initial"}
+        self.slider_style = {"description_width": "initial"}
         self.small_fishnet_slider = ipywidgets.IntSlider(
             value=small_roi_size,
             min=0,
             max=10000,
             step=100,
-            description="Small Grid:",
+            description="Small ROI Length(m)",
             disabled=False,
             continuous_update=False,
             orientation="horizontal",
             readout=True,
             readout_format="d",
-            style=slider_style,
+            style={"description_width": "initial"},
         )
 
         self.large_fishnet_slider = ipywidgets.IntSlider(
@@ -167,54 +143,428 @@ def __init__(self, coastseg_map):
             min=1000,
             max=10000,
             step=100,
-            description="Large Grid:",
+            description="Large ROI Length(m)",
             disabled=False,
             continuous_update=False,
             orientation="horizontal",
             readout=True,
             readout_format="d",
-            style=slider_style,
+            style={"description_width": "initial"},
         )
 
         # widget handlers
         self.small_fishnet_slider.observe(self.handle_small_slider_change, "value")
         self.large_fishnet_slider.observe(self.handle_large_slider_change, "value")
 
+    def get_view_settings_accordion(self) -> Accordion:
+        # update settings button
+        update_settings_btn = Button(
+            description="Update Settings", style=self.action_style
+        )
+        update_settings_btn.on_click(self.update_settings_btn_clicked)
+        self.settings_html = HTML()
+        self.settings_html.value = self.get_settings_html(self.coastseg_map.settings)
+        view_settings_vbox = VBox([self.settings_html, update_settings_btn])
+        html_settings_accordion = Accordion(children=[view_settings_vbox])
+        html_settings_accordion.set_title(0, "View Settings")
+        return html_settings_accordion
+
+    def get_settings_accordion(self):
+        # declare settings widgets
+        dates_vbox = self.get_dates_picker()
+        satellite_radio = self.get_satellite_radio()
+        sand_dropbox = self.get_sand_dropbox()
+        min_length_sl_slider = self.get_min_length_sl_slider()
+        beach_area_slider = self.get_beach_area_slider()
+        shoreline_buffer_slider = self.get_shoreline_buffer_slider()
+        cloud_slider = self.get_cloud_slider()
+        alongshore_distance_slider = self.get_alongshore_distance_slider()
+        pansharpen_toggle = self.get_pansharpen_toggle()
+        cloud_theshold_slider = self.get_cloud_threshold_slider()
+
+        settings_button = Button(description="Save Settings", style=self.action_style)
+        settings_button.on_click(self.save_settings_clicked)
+        self.output_epsg_text = Text(value="4326", description="Output epsg:")
+
+        # create settings accordion
+        settings_vbox = VBox(
+            [
+                dates_vbox,
+                satellite_radio,
+                self.output_epsg_text,
+                sand_dropbox,
+                min_length_sl_slider,
+                beach_area_slider,
+                shoreline_buffer_slider,
+                cloud_slider,
+                alongshore_distance_slider,
+                cloud_theshold_slider,
+                pansharpen_toggle,
+                settings_button,
+            ]
+        )
+        settings_accordion = Accordion(children=[settings_vbox])
+        settings_accordion.set_title(0, "Settings")
+        return settings_accordion
+
+    def get_dates_picker(self):
+        # Date Widgets
+        self.start_date = DatePicker(
+            description="Start Date",
+            value=datetime.date(2018, 12, 1),
+            disabled=False,
+        )
+        self.end_date = DatePicker(
+            description="End Date",
+            value=datetime.date(2019, 3, 1),  # 2019, 1, 1
+            disabled=False,
+        )
+        date_instr = HTML(value="<b>Pick a date:</b>", layout=Layout(padding="10px"))
+        dates_box = HBox([self.start_date, self.end_date])
+        dates_vbox = VBox([date_instr, dates_box])
+        return dates_vbox
+
+    def get_cloud_threshold_slider(self):
+        instr = HTML(value="<b>Maximum percetange of cloud pixels allowed</b>")
+        self.cloud_threshold_slider = ipywidgets.FloatSlider(
+            value=0.5,
+            min=0,
+            max=1,
+            step=0.01,
+            description="Cloud Pixel %:",
+            disabled=False,
+            continuous_update=False,
+            orientation="horizontal",
+            readout=True,
+            readout_format=".2f",
+            style={"description_width": "initial"},
+        )
+        return VBox([instr, self.cloud_threshold_slider])
+
+    def get_pansharpen_toggle(self):
+        instr = HTML(value="<b>Switch pansharpening off for Landsat 7/8/9 imagery</b>")
+        self.pansharpen_toggle = ipywidgets.ToggleButtons(
+            options=["Pansharpen Off", "Pansharpen On"],
+            description="",
+            disabled=False,
+            button_style="",
+        )
+        return VBox([instr, self.pansharpen_toggle])
+
+    def get_sand_dropbox(self):
+        sand_color_instr = HTML(
+            value="<b>Sand color on beach for model to detect 'dark' (grey/black) 'bright' (white)</b>"
+        )
+        self.sand_dropdown = ipywidgets.Dropdown(
+            options=["default", "latest", "dark", "bright"],
+            value="default",
+            description="Sand Color:",
+            disabled=False,
+        )
+        return VBox([sand_color_instr, self.sand_dropdown])
+
+    def get_alongshore_distance_slider(self):
+        # returns slider to control beach area slider
+        instr = HTML(
+            value="<b>Along-shore distance over which to consider shoreline points to compute median intersection with transects</b>"
+        )
+        self.alongshore_distance_slider = ipywidgets.IntSlider(
+            value=25,
+            min=10,
+            max=100,
+            step=1,
+            description="Alongshore Distance:",
+            disabled=False,
+            continuous_update=False,
+            orientation="horizontal",
+            readout=True,
+            readout_format="d",
+            style={"description_width": "initial"},
+        )
+        return VBox([instr, self.alongshore_distance_slider])
+
+    def get_cloud_slider(self):
+        # returns slider to control beach area slider
+        cloud_instr = HTML(
+            value="<b>Allowed distance from extracted shoreline to detected clouds</b>\
+        </br>- Any extracted shorelines within this distance to any clouds will be dropped"
+        )
+
+        self.cloud_slider = ipywidgets.IntSlider(
+            value=300,
+            min=100,
+            max=1000,
+            step=1,
+            description="Cloud Distance (m):",
+            disabled=False,
+            continuous_update=False,
+            orientation="horizontal",
+            readout=True,
+            readout_format="d",
+            style={"description_width": "initial"},
+        )
+        return VBox([cloud_instr, self.cloud_slider])
+
+    def get_shoreline_buffer_slider(self):
+        # returns slider to control beach area slider
+        shoreline_buffer_instr = HTML(
+            value="<b>Buffer around reference shorelines in which shorelines can be extracted</b>"
+        )
+
+        self.shoreline_buffer_slider = ipywidgets.IntSlider(
+            value=50,
+            min=100,
+            max=500,
+            step=1,
+            description="Reference Shoreline Buffer (m):",
+            disabled=False,
+            continuous_update=False,
+            orientation="horizontal",
+            readout=True,
+            readout_format="d",
+            style={"description_width": "initial"},
+        )
+        return VBox([shoreline_buffer_instr, self.shoreline_buffer_slider])
+
+    def get_beach_area_slider(self):
+        # returns slider to control beach area slider
+        beach_area_instr = HTML(
+            value="<b>Minimum area (sqm) for object to be labelled as beach</b>"
+        )
+
+        self.beach_area_slider = ipywidgets.IntSlider(
+            value=4500,
+            min=1000,
+            max=10000,
+            step=10,
+            description="Beach Area (sqm):",
+            disabled=False,
+            continuous_update=False,
+            orientation="horizontal",
+            readout=True,
+            readout_format="d",
+            style={"description_width": "initial"},
+        )
+        return VBox([beach_area_instr, self.beach_area_slider])
+
+    def get_min_length_sl_slider(self):
+        # returns slider to control beach area slider
+        min_length_sl_instr = HTML(
+            value="<b>Minimum shoreline perimeter that model will detect</b>"
+        )
+
+        self.min_length_sl_slider = ipywidgets.IntSlider(
+            value=500,
+            min=200,
+            max=1000,
+            step=1,
+            description="Min shoreline length (m):",
+            disabled=False,
+            continuous_update=False,
+            orientation="horizontal",
+            readout=True,
+            readout_format="d",
+            style={"description_width": "initial"},
+        )
+        return VBox([min_length_sl_instr, self.min_length_sl_slider])
+
+    def get_satellite_radio(self):
+        # satellite selection widgets
+        satellite_instr = HTML(
+            value="<b>Pick multiple satellites:</b>\
+                <br> - Pick multiple satellites by holding the control key> \
+                <br> - images after 2022/01/01 will be automatically downloaded from Collection 2 ",
+            layout=Layout(padding="10px"),
+        )
+
+        self.satellite_selection = SelectMultiple(
+            options=["L5", "L7", "L8", "L9", "S2"],
+            value=["L8"],
+            description="Satellites",
+            disabled=False,
+        )
+        satellite_vbox = VBox([satellite_instr, self.satellite_selection])
+        return satellite_vbox
+
+    def save_to_file_buttons(self):
+        # save to file buttons
+        save_instr = HTML(
+            value="<h2>Save to file</h2>\
+                Save feature on the map to a geojson file.\
+                <br>Geojson file will be saved to CoastSeg directory.\
+            ",
+            layout=Layout(padding="0px"),
+        )
+
+        self.save_radio = RadioButtons(
+            options=[
+                "Shoreline",
+                "Transects",
+                "Bbox",
+                "ROIs",
+            ],
+            value="Shoreline",
+            description="",
+            disabled=False,
+        )
+
+        self.save_button = Button(
+            description=f"Save {self.save_radio.value} to file", style=self.save_style
+        )
+        self.save_button.on_click(self.save_to_file_btn_clicked)
+
+        def save_radio_changed(change):
+            self.save_button.description = f"Save {str(change['new'])} to file"
+
+        self.save_radio.observe(save_radio_changed, "value")
+        save_vbox = VBox([save_instr, self.save_radio, self.save_button])
+        return save_vbox
+
+    def load_feature_on_map_buttons(self):
+        load_instr = HTML(
+            value="<h2>Load Feature into Bounding Box</h2>\
+                Loads shoreline or transects into bounding box on map.\
+                </br>If no transects or shorelines exist in this area, then\
+               </br> draw bounding box somewhere else\
+                ",
+            layout=Layout(padding="0px"),
+        )
+        self.load_radio = RadioButtons(
+            options=["Shoreline", "Transects"],
+            value="Transects",
+            description="",
+            disabled=False,
+        )
+        self.load_button = Button(
+            description=f"Load {self.load_radio.value}", style=self.load_style
+        )
+        self.load_button.on_click(self.load_button_clicked)
+
+        def handle_load_radio_change(change):
+            self.load_button.description = f"Load {str(change['new'])}"
+
+        self.load_radio.observe(handle_load_radio_change, "value")
+        load_buttons = VBox([load_instr, self.load_radio, self.load_button])
+        return load_buttons
+
+    def remove_buttons(self):
+        # define remove feature radio box button
+        remove_instr = HTML(
+            value="<h2>Remove Feature from Map</h2>",
+            layout=Layout(padding="0px"),
+        )
+
+        self.remove_radio = RadioButtons(
+            options=["Shoreline", "Transects", "Bbox", "ROIs"],
+            value="Shoreline",
+            description="",
+            disabled=False,
+        )
+        self.remove_button = Button(
+            description=f"Remove {self.remove_radio.value}", style=self.remove_style
+        )
+
+        def handle_remove_radio_change(change):
+            self.remove_button.description = f"Remove {str(change['new'])}"
+
+        self.remove_button.on_click(self.remove_feature_from_map)
+        self.remove_radio.observe(handle_remove_radio_change, "value")
+        # define remove all button
+        self.remove_all_button = Button(
+            description="Remove all", style=self.remove_style
+        )
+        self.remove_all_button.on_click(self.remove_all_from_map)
+
+        remove_buttons = VBox(
+            [
+                remove_instr,
+                self.remove_radio,
+                self.remove_button,
+                self.remove_all_button,
+            ]
+        )
+        return remove_buttons
+
+    def get_settings_html(
+        self,
+        settings: dict,
+    ):
+        # Modifies html of accordion when transect is hovered over
+        default = "unknown"
+        keys = [
+            "cloud_thresh",
+            "dist_clouds",
+            "output_epsg",
+            "check_detection",
+            "adjust_detection",
+            "save_figure",
+            "min_beach_area",
+            "min_length_sl",
+            "cloud_mask_issue",
+            "sand_color",
+            "pan_off",
+            "max_dist_ref",
+            "along_dist",
+            "sat_list",
+            "landsat_collection",
+            "dates",
+        ]
+        # returns a dict with keys in keys and if a key does not exist in feature its value is default str
+        values = common.get_default_dict(default=default, keys=keys, fill_dict=settings)
+        return """ 
+        <h2>Settings</h2>
+        <p>sat_list: {}</p>
+        <p>dates: {}</p>
+        <p>landsat_collection: {}</p>
+        <p>cloud_thresh: {}</p>
+        <p>dist_clouds: {}</p>
+        <p>output_epsg: {}</p>
+        <p>save_figure: {}</p>
+        <p>min_beach_area: {}</p>
+        <p>min_length_sl: {}</p>
+        <p>cloud_mask_issue: {}</p>
+        <p>sand_color: {}</p>
+        <p>pan_off: {}</p>
+        <p>max_dist_ref: {}</p>
+        <p>along_dist: {}</p>
+        """.format(
+            values["sat_list"],
+            values["dates"],
+            values["landsat_collection"],
+            values["cloud_thresh"],
+            values["dist_clouds"],
+            values["output_epsg"],
+            values["save_figure"],
+            values["min_beach_area"],
+            values["min_length_sl"],
+            values["cloud_mask_issue"],
+            values["sand_color"],
+            values["pan_off"],
+            values["max_dist_ref"],
+            values["along_dist"],
+        )
+
     def _create_HTML_widgets(self):
         """create HTML widgets that display the instructions.
         widgets created: instr_create_ro, instr_save_roi, instr_load_btns
          instr_download_roi
         """
         self.instr_create_roi = HTML(
-            value="<h2><b>Generate ROIs</b></h2> \
-                Use the two sliders to control the size of the ROIs generated.\
+            value="<h2><b>Generate ROIs on Map</b></h2> \
+                Use the sliders to control the side length of the ROIs created on the map.\
                 </br><b>No Overlap</b>: Set small slider to 0 and large slider to ROI size.</li>\
                 </br><b>Overlap</b>: Set small slider to a value and large slider to ROI size.</li>\
-                </br><b>ROI units</b>: meters squared.</li>\
-                </br><h3><b><u>How it Works</u></b></br></h3> \
-                <li>Two grids composed of ROIs (squares) and created within\
-                </br>the bounding box.\
-                <li>Each ROI is created along the shoreline.\
-                <li>If there is no shoreline then ROIs cannot be created.\
-                <li>The slider controls the size of the individual ROIs created.\
+                </br><h3><b><u>How ROIs are Made</u></b></br></h3> \
+                <li>Two grids of ROIs (squares) are created within\
+                </br>the bounding box along the shoreline.\
+                <li>If no shoreline is within the bounding box then ROIs cannot be created.\
+                <li>The slider controls the side length of each ROI.\
                 ",
             layout=Layout(margin="0px 5px 0px 0px"),
         )
 
-        self.instr_save_roi = HTML(
-            value="<h2><b>Save Features</b></h2> \
-                Use these buttons to save features on the map to a geojson file.\
-                <br>These geojson files are saved to the CoastSeg directory.\
-                </br><b>Save ROI</b>: Saves ROIs you selected to a file: 'rois.geojson'\
-                </br><b>Save box</b>: Saves bounding box to a file: 'bbox.geojson'</li>\
-                </br><b>Save shorelines</b>: Saves shorelines to a file: 'shoreline.geojson'</li>\
-                </br><b>Save transects</b>: Saves selected ROI to a file: 'transects.geojson'</li>\
-                ",
-            layout=Layout(margin="0px 5px 0px 5px"),
-        )  # top right bottom left
-
         self.instr_download_roi = HTML(
-            value="<h2><b>Download ROIs</b></h2> \
+            value="<h2><b>Download Imagery</b></h2> \
                 <li><b>You must click an ROI on the map before you can download ROIs</b> \
                 <li>Scroll past the map to see the download progress \
                 </br><h3><b><u>Where is my data?</u></b></br></h3> \
@@ -226,46 +576,34 @@ def _create_HTML_widgets(self):
             layout=Layout(margin="0px 0px 0px 5px"),
         )
 
-        self.instr_load_btns = HTML(
-            value="<h2><b>Load Features</b></h2>\
-                You can upload ROIs or Bbox geojson file.\
-                </br><b>Load BBox</b>: Load bounding box from file: 'bbox.geojson'</li>\
-                </br><b>Load ROIs</b>: Load ROIs from file: 'rois.geojson'</li>\
-                </br><b>Load gdf</b>: Load any geodataframe from a geojson file </li>\
-                    ",
-            layout=Layout(margin="0px 5px 0px 5px"),
-        )  # top right bottom left
-
         self.instr_config_btns = HTML(
             value="<h2><b>Load and Save Config Files</b></h2>\
-                <b>Load Config</b>: Load rois, shorelines, transects and bounding box from file: \
-                </br>'config_gdf.geojson'</li>\
-                <li>Make sure 'config.json' is in the same directory as 'config_gdf.geojson'.</li>\
-                <b>Save Config</b>: Saves rois, shorelines, transects and bounding box to file:\
-                </br>'config_gdf.geojson'</li>\
-                <li>If the ROIs have not been downloaded the config file is in main CoastSeg directory in file:\
-                </br>'config_gdf.geojson'</li>\
-                <li>If the ROIs have been downloaded the config file is in each ROI's folder in file:\
-                </br>'config_gdf.geojson'</li>\
-                <li>The 'config.json' will be saved in the same directory as the 'config_gdf.geojson'.</li>\
-                <li>The 'config.json' contains the data for the ROI and map settings.</li>\
+                <b>Load Config</b>: Load rois, shorelines, transects and bounding box from file: 'config_gdf.geojson'\
+                <li>'config.json' must be in the same directory as 'config_gdf.geojson'.</li>\
+                <b>Save Config</b>: Saves rois, shorelines, transects and bounding box to file: 'config_gdf.geojson'\
+                </br><b>ROIs Not Downloaded:</b> config file will be saved to CoastSeg directory in file: 'config_gdf.geojson'\
+                </br><b>ROIs Not Downloaded:</b>config file will be saved to each ROI's directory in file: 'config_gdf.geojson'\
                 ",
             layout=Layout(margin="0px 5px 0px 5px"),
         )  # top right bottom left
 
     def create_dashboard(self):
         """creates a dashboard containing all the buttons, instructions and widgets organized together."""
+        # create settings accordion
+        settings_accordion = self.get_settings_accordion()
+        # Buttons to load shoreline or transects in bbox on map
+        load_buttons = self.load_feature_on_map_buttons()
+        remove_buttons = self.remove_buttons()
+        save_to_file_buttons = self.save_to_file_buttons()
+
+        load_file_vbox = VBox(
+            [self.load_file_instr, self.load_file_radio, self.load_file_button]
+        )
         save_vbox = VBox(
             [
-                self.instr_save_roi,
-                self.save_roi_button,
-                self.save_bbox_button,
-                self.save_shoreline_button,
-                self.save_transects_button,
-                self.instr_load_btns,
-                self.load_rois_button,
-                self.load_bbox_button,
-                self.load_gdf_button,
+                save_to_file_buttons,
+                load_file_vbox,
+                remove_buttons,
             ]
         )
         config_vbox = VBox(
@@ -282,33 +620,36 @@ def create_dashboard(self):
             ]
         )
 
-        slider_v_box = VBox([self.small_fishnet_slider, self.large_fishnet_slider])
+        slider_v_box = VBox(
+            [
+                self.roi_slider_instr,
+                self.small_fishnet_slider,
+                self.large_fishnet_slider,
+            ]
+        )
         slider_btn_box = VBox([slider_v_box, self.gen_button])
         roi_controls_box = VBox(
-            [self.instr_create_roi, slider_btn_box],
+            [self.instr_create_roi, slider_btn_box, load_buttons],
             layout=Layout(margin="0px 5px 5px 0px"),
         )
 
-        load_buttons = HBox([self.transects_button, self.shoreline_button])
-        erase_buttons = HBox(
-            [
-                self.remove_all_button,
-                self.remove_transects_button,
-                self.remove_bbox_button,
-                self.remove_shoreline_button,
-                self.remove_rois_button,
-            ]
-        )
+        # view settings accordion
+        html_settings_accordion = self.get_view_settings_accordion()
 
+        row_0 = HBox([settings_accordion, html_settings_accordion])
         row_1 = HBox([roi_controls_box, save_vbox, download_vbox])
-        row_2 = HBox([load_buttons])
-        row_3 = HBox([erase_buttons])
         # in this row prints are rendered with UI.debug_view
-        row_4 = HBox([self.clear_debug_button, UI.debug_view])
-        row_5 = HBox([self.coastseg_map.map])
-        row_6 = HBox([UI.download_view])
-
-        return display(row_1, row_2, row_3, row_4, row_5, row_6)
+        row_3 = HBox([self.clear_debug_button, UI.debug_view])
+        row_4 = HBox([self.coastseg_map.map])
+        row_5 = HBox([UI.download_view])
+
+        return display(
+            row_0,
+            row_1,
+            row_3,
+            row_4,
+            row_5,
+        )
 
     def handle_small_slider_change(self, change):
         self.fishnet_sizes["small"] = change["new"]
@@ -316,6 +657,17 @@ def handle_small_slider_change(self, change):
     def handle_large_slider_change(self, change):
         self.fishnet_sizes["large"] = change["new"]
 
+    @debug_view.capture(clear_output=True)
+    def update_settings_btn_clicked(self, btn):
+        UI.debug_view.clear_output(wait=True)
+        # Update settings in view settings accordion
+        try:
+            self.settings_html.value = self.get_settings_html(
+                self.coastseg_map.settings
+            )
+        except Exception as error:
+            exception_handler.handle_exception(error)
+
     @debug_view.capture(clear_output=True)
     def on_gen_button_clicked(self, btn):
         UI.debug_view.clear_output(wait=True)
@@ -323,8 +675,11 @@ def on_gen_button_clicked(self, btn):
         self.coastseg_map.map.default_style = {"cursor": "wait"}
         # Generate ROIs along the coastline within the bounding box
         try:
-            self.coastseg_map.load_rois_on_map(
-                self.fishnet_sizes["large"], self.fishnet_sizes["small"]
+            print("Creating ROIs")
+            self.coastseg_map.load_feature_on_map(
+                "rois",
+                large_len=self.fishnet_sizes["large"],
+                small_len=self.fishnet_sizes["small"],
             )
         except Exception as error:
             exception_handler.handle_exception(error)
@@ -332,47 +687,63 @@ def on_gen_button_clicked(self, btn):
         self.coastseg_map.map.default_style = {"cursor": "default"}
 
     @debug_view.capture(clear_output=True)
-    def on_transects_button_clicked(self, btn):
-
+    def load_button_clicked(self, btn):
         UI.debug_view.clear_output(wait=True)
         self.coastseg_map.map.default_style = {"cursor": "wait"}
         try:
-            self.coastseg_map.load_transects_on_map()
+            if "shoreline" in btn.description.lower():
+                print("Finding Shoreline")
+                self.coastseg_map.load_feature_on_map("shoreline")
+            if "transects" in btn.description.lower():
+                print("Finding 'Transects'")
+                self.coastseg_map.load_feature_on_map("transects")
         except Exception as error:
             exception_handler.handle_exception(error)
         self.coastseg_map.map.default_style = {"cursor": "default"}
 
     @debug_view.capture(clear_output=True)
-    def on_load_rois_clicked(self, button):
-        # Prompt the user to select a directory of images
-        with Tkinter_Window_Creator() as tk_root:
-            tk_root.filename = filedialog.askopenfilename(
-                initialdir=os.getcwd(),
-                filetypes=[("geojson", "*.geojson")],
-                title="Select a geojson file containing rois",
+    def save_settings_clicked(self, btn):
+        if self.satellite_selection.value:
+            # Save satellites selected by user
+            sat_list = list(self.satellite_selection.value)
+            # Save dates selected by user
+            dates = [str(self.start_date.value), str(self.end_date.value)]
+            output_epsg = int(self.output_epsg_text.value)
+            max_dist_ref = self.shoreline_buffer_slider.value
+            along_dist = self.alongshore_distance_slider.value
+            dist_clouds = self.cloud_slider.value
+            beach_area = self.beach_area_slider.value
+            min_length_sl = self.min_length_sl_slider.value
+            sand_color = str(self.sand_dropdown.value)
+            pansharpen_enabled = (
+                False if "off" in self.pansharpen_toggle.value.lower() else True
             )
-            # Save the filename as an attribute of the button
-            if tk_root.filename:
-                try:
-                    self.coastseg_map.load_rois_on_map(file=tk_root.filename)
-                except Exception as error:
-                    exception_handler.handle_exception(error)
-            else:
-                messagebox.showerror(
-                    "ROI Selection Error", "You must select a valid geojson file first!"
+            cloud_thresh = self.cloud_threshold_slider.value
+            settings = {
+                "sat_list": sat_list,
+                "dates": dates,
+                "output_epsg": output_epsg,
+                "max_dist_ref": max_dist_ref,
+                "along_dist": along_dist,
+                "dist_clouds": dist_clouds,
+                "min_beach_area": beach_area,
+                "min_length_sl": min_length_sl,
+                "sand_color": sand_color,
+                "pan_off": pansharpen_enabled,
+                "cloud_thresh": cloud_thresh,
+            }
+            try:
+                self.coastseg_map.save_settings(**settings)
+                self.settings_html.value = self.get_settings_html(
+                    self.coastseg_map.settings
                 )
-
-    @debug_view.capture(clear_output=True)
-    def on_shoreline_button_clicked(self, btn):
-        self.coastseg_map.map.default_style = {"cursor": "wait"}
-        UI.debug_view.clear_output(wait=True)
-        print("Loading shoreline please wait...")
-        try:
-            self.coastseg_map.load_shoreline_on_map()
-        except Exception as error:
-            exception_handler.handle_exception(error)
-        print("Shoreline loaded.")
-        self.coastseg_map.map.default_style = {"cursor": "default"}
+            except Exception as error:
+                exception_handler.handle_exception(error)
+        elif not self.satellite_selection.value:
+            try:
+                raise Exception("Must select at least one satellite first")
+            except Exception as error:
+                exception_handler.handle_exception(error)
 
     @debug_view.capture(clear_output=True)
     def extract_shorelines_button_clicked(self, btn):
@@ -420,26 +791,6 @@ def download_button_clicked(self, btn):
         self.download_button.disabled = False
         self.coastseg_map.map.default_style = {"cursor": "default"}
 
-    @debug_view.capture(clear_output=True)
-    def save_transects_button_clicked(self, btn):
-        UI.debug_view.clear_output(wait=True)
-        try:
-            self.coastseg_map.save_feature_to_file(
-                self.coastseg_map.transects, "transects"
-            )
-        except Exception as error:
-            exception_handler.handle_exception(error)
-
-    @debug_view.capture(clear_output=True)
-    def save_shoreline_button_clicked(self, btn):
-        UI.debug_view.clear_output(wait=True)
-        try:
-            self.coastseg_map.save_feature_to_file(
-                self.coastseg_map.shoreline, "shoreline"
-            )
-        except Exception as error:
-            exception_handler.handle_exception(error)
-
     @debug_view.capture(clear_output=True)
     def on_save_cross_distances_button_clicked(self, btn):
         UI.debug_view.clear_output(wait=True)
@@ -448,37 +799,6 @@ def on_save_cross_distances_button_clicked(self, btn):
         except Exception as error:
             exception_handler.handle_exception(error)
 
-    @debug_view.capture(clear_output=True)
-    def on_save_bbox_button_clicked(self, btn):
-        UI.debug_view.clear_output(wait=True)
-        try:
-            self.coastseg_map.save_feature_to_file(
-                self.coastseg_map.bbox, "bounding box"
-            )
-        except Exception as error:
-            exception_handler.handle_exception(error)
-
-    @debug_view.capture(clear_output=True)
-    def on_load_gdf_clicked(self, button):
-        # Prompt the user to select a directory of images
-        with Tkinter_Window_Creator() as tk_root:
-            tk_root.filename = filedialog.askopenfilename(
-                initialdir=os.getcwd(),
-                filetypes=[("geojson", "*.geojson")],
-                title="Select a geojson file",
-            )
-            # Save the filename as an attribute of the button
-            if tk_root.filename:
-                try:
-                    self.coastseg_map.load_gdf_on_map("geodataframe", tk_root.filename)
-                except Exception as error:
-                    exception_handler.handle_exception(error)
-            else:
-                messagebox.showerror(
-                    "File Selection Error",
-                    "You must select a valid geojson file first!",
-                )
-
     @debug_view.capture(clear_output=True)
     def on_load_configs_clicked(self, button):
         # Prompt user to select a directory of images
@@ -492,6 +812,10 @@ def on_load_configs_clicked(self, button):
             if tk_root.filename:
                 try:
                     self.coastseg_map.load_configs(tk_root.filename)
+                    # update setting html with settings loaded in
+                    self.settings_html.value = self.get_settings_html(
+                        self.coastseg_map.settings
+                    )
                 except Exception as error:
                     exception_handler.handle_exception(error)
             else:
@@ -508,20 +832,24 @@ def on_save_config_clicked(self, button):
             exception_handler.handle_exception(error)
 
     @debug_view.capture(clear_output=True)
-    def on_load_bbox_clicked(self, button):
+    def load_feature_from_file(self, btn):
         # Prompt the user to select a directory of images
         with Tkinter_Window_Creator() as tk_root:
-            tk_root.filename = filedialog.askopenfilename(
+            file = filedialog.askopenfilename(
                 initialdir=os.getcwd(),
                 filetypes=[("geojson", "*.geojson")],
                 title="Select a geojson file containing bbox",
             )
-            # Save the filename as an attribute of the button
-            if tk_root.filename:
-                try:
-                    self.coastseg_map.load_bbox_on_map(tk_root.filename)
-                except Exception as error:
-                    exception_handler.handle_exception(error)
+            if file:
+                if "shoreline" in btn.description.lower():
+                    print(f"Loading shoreline from file: {file}")
+                    self.coastseg_map.load_feature_on_map("shoreline", file)
+                if "transects" in btn.description.lower():
+                    print(f"Loading transects from file: {file}")
+                    self.coastseg_map.load_feature_on_map("transects", file)
+                if "bbox" in btn.description.lower():
+                    print(f"Loading bounding box from file: {file}")
+                    self.coastseg_map.load_feature_on_map("bbox", file)
             else:
                 messagebox.showerror(
                     "File Selection Error",
@@ -529,40 +857,54 @@ def on_load_bbox_clicked(self, button):
                 )
 
     @debug_view.capture(clear_output=True)
-    def save_roi_button_clicked(self, btn):
+    def remove_feature_from_map(self, btn):
         UI.debug_view.clear_output(wait=True)
         try:
-            self.coastseg_map.save_feature_to_file(self.coastseg_map.rois, "ROI")
-        except Exception as error:
-            exception_handler.handle_exception(error)
-
-    def remove_all_from_map(self, btn):
-        try:
-            self.coastseg_map.remove_all()
-        except Exception as error:
-            exception_handler.handle_exception(error)
-
-    def remove_transects(self, btn):
-        try:
-            self.coastseg_map.remove_transects()
-        except Exception as error:
-            exception_handler.handle_exception(error)
-
-    def remove_bbox_from_map(self, btn):
-        try:
-            self.coastseg_map.remove_bbox()
+            # Prompt the user to select a directory of images
+            if "shoreline" in btn.description.lower():
+                print(f"Removing shoreline")
+                self.coastseg_map.remove_shoreline()
+            if "transects" in btn.description.lower():
+                print(f"Removing  transects")
+                self.coastseg_map.remove_transects()
+            if "bbox" in btn.description.lower():
+                print(f"Removing bounding box")
+                self.coastseg_map.remove_bbox()
+            if "rois" in btn.description.lower():
+                print(f"Removing ROIs")
+                self.coastseg_map.remove_all_rois()
         except Exception as error:
             exception_handler.handle_exception(error)
 
-    def remove_shoreline_from_map(self, btn):
+    @debug_view.capture(clear_output=True)
+    def save_to_file_btn_clicked(self, btn):
+        UI.debug_view.clear_output(wait=True)
         try:
-            self.coastseg_map.remove_shoreline()
+            if "shoreline" in btn.description.lower():
+                print(f"Saving shoreline to file")
+                self.coastseg_map.save_feature_to_file(
+                    self.coastseg_map.shoreline, "shoreline"
+                )
+            if "transects" in btn.description.lower():
+                print(f"Saving transects to file")
+                self.coastseg_map.save_feature_to_file(
+                    self.coastseg_map.transects, "transects"
+                )
+            if "bbox" in btn.description.lower():
+                print(f"Saving bounding box to file")
+                self.coastseg_map.save_feature_to_file(
+                    self.coastseg_map.bbox, "bounding box"
+                )
+            if "rois" in btn.description.lower():
+                print(f"Saving ROIs to file")
+                self.coastseg_map.save_feature_to_file(self.coastseg_map.rois, "ROI")
         except Exception as error:
             exception_handler.handle_exception(error)
 
-    def remove_all_rois_from_map(self, btn):
+    @debug_view.capture(clear_output=True)
+    def remove_all_from_map(self, btn):
         try:
-            self.coastseg_map.remove_all_rois()
+            self.coastseg_map.remove_all()
         except Exception as error:
             exception_handler.handle_exception(error)
 
diff --git a/src/coastseg/roi.py b/src/coastseg/roi.py
index 31c153e1..0bfc2aad 100644
--- a/src/coastseg/roi.py
+++ b/src/coastseg/roi.py
@@ -20,6 +20,7 @@ class ROI:
     """Roi (Region of Interest) a square where data can be downloaded from"""
 
     LAYER_NAME = "ROIs"
+    SELECTED_LAYER_NAME = "Selected ROIs"
 
     def __init__(
         self,
@@ -174,12 +175,7 @@ def create_rois(
         Returns:
             gpd.geodataframe: geodataframe containing all the ROIs
         """
-        # coords : list[tuple] first & last tuple are equal
-        bbox_coords = list(bbox.iloc[0]["geometry"].exterior.coords)
-        # get the utm_code for center of bbox
-        center_x, center_y = common.get_center_rectangle(bbox_coords)
-        utm_code = common.convert_wgs_to_utm(center_x, center_y)
-        projected_espg = f"epsg:{utm_code}"
+        projected_espg = common.get_epsg_from_geometry(bbox.iloc[0]["geometry"])
         logger.info(f"projected_espg_code: {projected_espg}")
         # project geodataframe to new CRS specified by utm_code
         projected_bbox_gdf = bbox.to_crs(projected_espg)
diff --git a/src/coastseg/shoreline.py b/src/coastseg/shoreline.py
index 089df177..24a9d9d6 100644
--- a/src/coastseg/shoreline.py
+++ b/src/coastseg/shoreline.py
@@ -242,27 +242,3 @@ def load_total_bounds_df(self, location: str = "usa") -> pd.DataFrame:
         if "filename" in total_bounds_df.columns:
             total_bounds_df.drop("filename", axis=1, inplace=True)
         return total_bounds_df
-
-    def make_coastsat_compatible(shoreline_in_roi: gpd.geodataframe) -> np.ndarray:
-        """Return the shoreline_in_roi as an np.array in the form:
-            array([[lat,lon,0],[lat,lon,0],[lat,lon,0]....])
-
-        Args:
-            shoreline_in_roi (gpd.geodataframe): clipped portion of shoreline withinv a roi
-
-        Returns:
-            np.ndarray: shorelines in the form:
-                array([[lat,lon,0],[lat,lon,0],[lat,lon,0]....])
-        """
-        # Then convert the shoreline to lat,lon tuples for CoastSat
-        shorelines = []
-        for k in shoreline_in_roi["geometry"].keys():
-            # For each linestring portion of shoreline convert to lat,lon tuples
-            shorelines.append(tuple(np.array(shoreline_in_roi["geometry"][k]).tolist()))
-        # shorelines = [([lat,lon],[lat,lon],[lat,lon]),([lat,lon],[lat,lon],[lat,lon])...]
-        # Stack all the tuples into a single list of n rows X 2 columns
-        shorelines = np.vstack(shorelines)
-        # Add third column of 0s to represent mean sea level
-        shorelines = np.insert(shorelines, 2, np.zeros(len(shorelines)), axis=1)
-        # shorelines = array([[lat,lon,0],[lat,lon,0],[lat,lon,0]....])
-        return shorelines
diff --git a/src/coastseg/transects.py b/src/coastseg/transects.py
index 4b0902a1..c4e235eb 100644
--- a/src/coastseg/transects.py
+++ b/src/coastseg/transects.py
@@ -61,9 +61,17 @@ def create_geodataframe(
             )
             transects_gdf = common.read_gpd_file(transect_path)
             # Get all the transects that intersect with bbox
-            transects_in_bbox = self.get_intersecting_transects(
-                bbox, transects_gdf, None
+            transects_in_bbox = gpd.sjoin(
+                left_df=transects_gdf,
+                right_df=bbox,
+                how="inner",
+                predicate="intersects",
             )
+            columns_to_drop = list(
+                set(transects_in_bbox.columns) - set(["id", "geometry", "slope"])
+            )
+            transects_in_bbox.drop(columns_to_drop, axis=1, inplace=True)
+
             if transects_in_bbox.empty:
                 print("Skipping ", transects_name)
             elif not transects_in_bbox.empty:
@@ -110,22 +118,6 @@ def style_layer(self, geojson: dict, layer_name: str) -> dict:
             hover_style={"color": "blue", "fillOpacity": 0.7},
         )
 
-    def get_intersecting_transects(
-        self, gdf: gpd.geodataframe, transect_gdf: gpd.geodataframe, id: str = None
-    ) -> gpd.geodataframe:
-        """Returns a transects that intersect with the roi with id provided
-        Args:
-            gdf (gpd.geodataframe): rois with geometry, ids and more
-            transect_gdf (gpd.geodataframe): transects geometry
-            id (str): id of roi
-
-        Returns:
-            gpd.geodataframe: transects that intersected with gdf
-        """
-        polygon = common.convert_gdf_to_polygon(gdf, id)
-        transect_mask = transect_gdf.intersects(polygon, align=False)
-        return transect_gdf[transect_mask]
-
     def get_intersecting_files(self, bbox_gdf: gpd.geodataframe) -> list:
         """Returns a list of filenames that intersect with bbox_gdf
 
diff --git a/src/coastseg/zoo_model.py b/src/coastseg/zoo_model.py
index dede4a3c..46952e73 100644
--- a/src/coastseg/zoo_model.py
+++ b/src/coastseg/zoo_model.py
@@ -46,7 +46,6 @@ def compute_segmentation(
         sample_direc: str,
         model_list: list,
         metadatadict: dict,
-        do_crf: bool = False,
     ):
         # look for TTA config
         if "TESTTIMEAUG" not in locals():
@@ -66,7 +65,6 @@ def compute_segmentation(
                 TARGET_SIZE=self.TARGET_SIZE,
                 TESTTIMEAUG=TESTTIMEAUG,
                 WRITE_MODELMETADATA=WRITE_MODELMETADATA,
-                DO_CRF=do_crf,
                 OTSU_THRESHOLD=False,
             )
 


Test patch:
diff --git a/.github/workflows/test_multiple_os.yml b/.github/workflows/test_multiple_os.yml
index 0ea59696..31022fa2 100644
--- a/.github/workflows/test_multiple_os.yml
+++ b/.github/workflows/test_multiple_os.yml
@@ -1,8 +1,8 @@
 on:
   push:
-    branches: [main, workflows]
+    branches: [main]
   pull_request:
-    branches: [main, workflows]
+    branches: [main]
 
 name: Test CoastSeg with Multiple OS python 3.9
 jobs:
@@ -36,10 +36,10 @@ jobs:
       - run: |
           conda info
           conda list
-      - name: Install coastseg 0.0.1
+      - name: Install coastseg 0.0.3
         run: |
           python -m pip install --upgrade pip
-          pip install coastseg==0.0.1
+          pip install coastseg==0.0.3
       - name: Pip install pytest
         run: |
           pip install pytest
diff --git a/tests/conftest.py b/tests/conftest.py
index 5731adce..f77b52ef 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -34,12 +34,10 @@ def valid_coastseg_map_with_incomplete_settings() -> coastseg_map.CoastSeg_Map:
         "save_figure": True,  # if True, saves a figure showing the mapped shoreline for each image
         # [ONLY FOR ADVANCED USERS] shoreline detection parameters:
         "min_beach_area": 4500,  # minimum area (in metres^2) for an object to be labelled as a beach
-        "buffer_size": 150,  # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection
         "min_length_sl": 200,  # minimum length (in metres) of shoreline perimeter to be valid
         "cloud_mask_issue": False,  # switch this parameter to True if sand pixels are masked (in black) on many images
         "sand_color": "default",  # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)
         "pan_off": "False",  # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery
-        "create_plot": False,  # True create a matplotlib plot of the image with the datetime as the title
         "max_dist_ref": 25,
     }
     coastsegmap.save_settings(**pre_process_settings)
@@ -64,12 +62,10 @@ def valid_coastseg_map_with_settings() -> coastseg_map.CoastSeg_Map:
         "save_figure": True,  # if True, saves a figure showing the mapped shoreline for each image
         # [ONLY FOR ADVANCED USERS] shoreline detection parameters:
         "min_beach_area": 4500,  # minimum area (in metres^2) for an object to be labelled as a beach
-        "buffer_size": 150,  # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection
         "min_length_sl": 200,  # minimum length (in metres) of shoreline perimeter to be valid
         "cloud_mask_issue": False,  # switch this parameter to True if sand pixels are masked (in black) on many images
         "sand_color": "default",  # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)
         "pan_off": "False",  # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery
-        "create_plot": False,  # True create a matplotlib plot of the image with the datetime as the title
         "max_dist_ref": 25,
     }
     coastsegmap.save_settings(
@@ -109,12 +105,10 @@ def coastseg_map_with_rois(valid_rois_filepath) -> coastseg_map.CoastSeg_Map:
         "save_figure": True,  # if True, saves a figure showing the mapped shoreline for each image
         # [ONLY FOR ADVANCED USERS] shoreline detection parameters:
         "min_beach_area": 4500,  # minimum area (in metres^2) for an object to be labelled as a beach
-        "buffer_size": 150,  # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection
         "min_length_sl": 200,  # minimum length (in metres) of shoreline perimeter to be valid
         "cloud_mask_issue": False,  # switch this parameter to True if sand pixels are masked (in black) on many images
         "sand_color": "default",  # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)
         "pan_off": "False",  # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery
-        "create_plot": False,  # True create a matplotlib plot of the image with the datetime as the title
         "max_dist_ref": 25,
     }
     coastsegmap.save_settings(
@@ -124,7 +118,7 @@ def coastseg_map_with_rois(valid_rois_filepath) -> coastseg_map.CoastSeg_Map:
         **pre_process_settings
     )
     # test if rois will added to coastsegmap and added to ROI layer
-    coastsegmap.load_rois_on_map(file=valid_rois_filepath)
+    coastsegmap.load_feature_on_map('rois',file=valid_rois_filepath)
     return coastsegmap
 
 
@@ -158,12 +152,10 @@ def coastseg_map_with_selected_roi_layer(
         "save_figure": True,  # if True, saves a figure showing the mapped shoreline for each image
         # [ONLY FOR ADVANCED USERS] shoreline detection parameters:
         "min_beach_area": 4500,  # minimum area (in metres^2) for an object to be labelled as a beach
-        "buffer_size": 150,  # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection
         "min_length_sl": 200,  # minimum length (in metres) of shoreline perimeter to be valid
         "cloud_mask_issue": False,  # switch this parameter to True if sand pixels are masked (in black) on many images
         "sand_color": "default",  # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)
         "pan_off": "False",  # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery
-        "create_plot": False,  # True create a matplotlib plot of the image with the datetime as the title
         "max_dist_ref": 25,
     }
     coastsegmap.save_settings(
@@ -173,16 +165,22 @@ def coastseg_map_with_selected_roi_layer(
         **pre_process_settings
     )
     # test if rois will added to coastsegmap and added to ROI layer
-    coastsegmap.load_rois_on_map(file=valid_rois_filepath)
+    coastsegmap.load_feature_on_map('rois',file=valid_rois_filepath)
     # simulate an ROI being clicked on map
     ROI_id = "17"
     coastsegmap.selected_set.add(ROI_id)
-    coastsegmap.selected_ROI_layer = GeoJSON(
+    
+    selected_layer = GeoJSON(
         data=coastsegmap.convert_selected_set_to_geojson(coastsegmap.selected_set),
-        name="Selected ROIs",
+        name=roi.ROI.SELECTED_LAYER_NAME,
         hover_style={"fillColor": "blue", "fillOpacity": 0.1, "color": "aqua"},
-    )
-    coastsegmap.map.add_layer(coastsegmap.selected_ROI_layer)
+        )
+    coastsegmap.replace_layer_by_name(
+            roi.ROI.SELECTED_LAYER_NAME,
+            selected_layer,
+            on_click=coastsegmap.selected_onclick_handler,
+            on_hover=coastsegmap.update_roi_html,
+        )
     return coastsegmap
 
 
@@ -286,12 +284,10 @@ def valid_settings() -> dict:
         "adjust_detection": False,
         "save_figure": True,
         "min_beach_area": 4500,
-        "buffer_size": 150,
         "min_length_sl": 200,
         "cloud_mask_issue": False,
         "sand_color": "default",
         "pan_off": "False",
-        "create_plot": False,
         "max_dist_ref": 25,
         "along_dist": 25,
     }
@@ -585,12 +581,10 @@ def valid_config_json() -> dict:
             "adjust_detection": False,
             "save_figure": True,
             "min_beach_area": 4500,
-            "buffer_size": 550,
             "min_length_sl": 100,
             "cloud_mask_issue": False,
             "sand_color": "default",
             "pan_off": "False",
-            "create_plot": False,
             "max_dist_ref": 25,
             "along_dist": 25,
         },
diff --git a/tests/test_coastseg_map.py b/tests/test_coastseg_map.py
index 9e23f3fc..378bd248 100644
--- a/tests/test_coastseg_map.py
+++ b/tests/test_coastseg_map.py
@@ -14,6 +14,17 @@
 
 # def test_set_roi_settings():
 
+def test_imports():
+    """Test that all the internal coastseg packages are imported correctly"""
+    from coastseg import exceptions
+    from coastseg import exception_handler
+    from coastseg import extracted_shoreline
+    from coastseg import factory
+    from coastseg import map_UI
+    from coastseg import UI_models
+    from coastseg import tkinter_window_creator
+    
+    
 
 def test_save_config_invalid_inputs(
     valid_coastseg_map,
@@ -32,7 +43,7 @@ def test_save_config_invalid_inputs(
     with pytest.raises(Exception):
         valid_coastseg_map_with_settings.save_config()
 
-    # test if exception is raised when coastseg_map missing selected_ROI_layer
+    # test if exception is raised when coastseg_map missing selected_layer
     with pytest.raises(Exception):
         coastseg_map_with_rois.save_config()
 
@@ -55,9 +66,10 @@ def test_save_config(coastseg_map_with_selected_roi_layer, tmp_path):
     landsat_collection = actual_coastsegmap.settings["landsat_collection"]
     sat_list = actual_coastsegmap.settings["sat_list"]
     # Add roi_settings to  actual_coastsegmap.rois
+    selected_layer = actual_coastsegmap.map.find_layer(roi.ROI.SELECTED_LAYER_NAME)
     roi_settings = common.create_roi_settings(
         actual_coastsegmap.settings,
-        actual_coastsegmap.selected_ROI_layer.data,
+        selected_layer.data,
         filepath,
         date_str,
     )
@@ -140,7 +152,7 @@ def test_load_json_config_downloaded(
     # tests if load_json_config will load contents into rois.roi_settings
     # create instance of Coastseg_Map with settings and ROIs initially loaded
     actual_coastsegmap = valid_coastseg_map_with_settings
-    actual_coastsegmap.load_rois_on_map(file=valid_rois_filepath)
+    actual_coastsegmap.load_feature_on_map('rois',file=valid_rois_filepath)
     # test if settings are correctly loaded when valid json config loaded with 'filepath' & 'sitename' keys is loaded
     actual_coastsegmap.load_json_config(downloaded_config_json_filepath)
     assert isinstance(actual_coastsegmap.rois.roi_settings, dict)
@@ -155,7 +167,7 @@ def test_load_json_config(
     # tests if load_json_config will load contents into rois.roi_settings when rois have not been downloaded before
     # create instance of Coastseg_Map with settings and ROIs initially loaded
     actual_coastsegmap = valid_coastseg_map_with_settings
-    actual_coastsegmap.load_rois_on_map(file=valid_rois_filepath)
+    actual_coastsegmap.load_feature_on_map('rois',file=valid_rois_filepath)
     # test if settings are correctly loaded when valid json config without 'filepath' & 'sitename' keys is loaded
     actual_coastsegmap.load_json_config(config_json_filepath)
     assert isinstance(actual_coastsegmap.rois.roi_settings, dict)
@@ -222,32 +234,27 @@ def test_coastseg_map_settings():
         valid_bbox_gdf (gpd.GeoDataFrame): alid rois as a gpd.GeoDataFrame
     """
     coastsegmap = coastseg_map.CoastSeg_Map()
-    dates = ["2018-12-01", "2019-03-01"]
-    landsat_collection = "C01"
-    sat_list = ["L8"]
     pre_process_settings = {
         # general parameters:
+        "dates":["2018-12-01", "2019-03-01"],
+        "sat_list" : ["L8"],
         "cloud_thresh": 0.5,  # threshold on maximum cloud cover
         "dist_clouds": 300,  # ditance around clouds where shoreline can't be mapped
         "output_epsg": 3857,  # epsg code of spatial reference system desired for the output
         # quality control:
         "check_detection": True,  # if True, shows each shoreline detection to the user for validation
-        "adjust_detection": False,  # if True, allows user to adjust the postion of each shoreline by changing the threhold
+        "adjust_detection": False,  # if True, allows user to adjust the position of each shoreline by changing the threshold
         "save_figure": True,  # if True, saves a figure showing the mapped shoreline for each image
         # [ONLY FOR ADVANCED USERS] shoreline detection parameters:
         "min_beach_area": 4500,  # minimum area (in metres^2) for an object to be labelled as a beach
-        "buffer_size": 150,  # radius (in metres) of the buffer around sandy pixels considered in the shoreline detection
         "min_length_sl": 200,  # minimum length (in metres) of shoreline perimeter to be valid
         "cloud_mask_issue": False,  # switch this parameter to True if sand pixels are masked (in black) on many images
         "sand_color": "default",  # 'default', 'dark' (for grey/black sand beaches) or 'bright' (for white sand beaches)
         "pan_off": "False",  # if True, no pan-sharpening is performed on Landsat 7,8 and 9 imagery
-        "create_plot": False,  # True create a matplotlib plot of the image with the datetime as the title
         "max_dist_ref": 25,
+        'landsat_collection': 'C02',
     }
     coastsegmap.save_settings(
-        sat_list=sat_list,
-        landsat_collection=landsat_collection,
-        dates=dates,
         **pre_process_settings
     )
     actual_settings = set(list(coastsegmap.settings.keys()))
@@ -270,38 +277,44 @@ def test_select_roi_layer(
     """
     actual_coastsegmap = valid_coastseg_map_with_settings
     # test if rois will added to coastsegmap and added to ROI layer
-    actual_coastsegmap.load_rois_on_map(file=valid_rois_filepath)
+    actual_coastsegmap.load_feature_on_map('rois',file=valid_rois_filepath)
     # test if roi layer was added to map
-    assert actual_coastsegmap.ROI_layer is not None
+    existing_layer = actual_coastsegmap.map.find_layer(roi.ROI.LAYER_NAME)
+    assert existing_layer is not None
     # simulate an ROI being clicked on map
     ROI_id = "17"
     actual_coastsegmap.selected_set.add(ROI_id)
-    actual_coastsegmap.selected_ROI_layer = GeoJSON(
-        data=actual_coastsegmap.convert_selected_set_to_geojson(
-            actual_coastsegmap.selected_set
-        ),
-        name="Selected ROIs",
+    
+    selected_layer = GeoJSON(
+        data=actual_coastsegmap.convert_selected_set_to_geojson(actual_coastsegmap.selected_set),
+        name=roi.ROI.SELECTED_LAYER_NAME,
         hover_style={"fillColor": "blue", "fillOpacity": 0.1, "color": "aqua"},
-    )
-    actual_coastsegmap.map.add_layer(actual_coastsegmap.selected_ROI_layer)
+        )
+    actual_coastsegmap.replace_layer_by_name(
+            roi.ROI.SELECTED_LAYER_NAME,
+            selected_layer,
+            on_click=actual_coastsegmap.selected_onclick_handler,
+            on_hover=actual_coastsegmap.update_roi_html,
+        )    
     # test if roi layer was added to map
-    assert actual_coastsegmap.selected_ROI_layer is not None
+    selected_layer = actual_coastsegmap.map.find_layer(roi.ROI.SELECTED_LAYER_NAME)
+    assert selected_layer is not None
     existing_layer = actual_coastsegmap.map.find_layer("Selected ROIs")
     assert existing_layer is not None
     assert "17" in actual_coastsegmap.selected_set
-    assert isinstance(actual_coastsegmap.selected_ROI_layer.data, dict)
-    assert isinstance(actual_coastsegmap.selected_ROI_layer.data["features"], list)
-    assert isinstance(actual_coastsegmap.selected_ROI_layer.data["features"][0], dict)
-    assert len(actual_coastsegmap.selected_ROI_layer.data["features"]) == 1
+    assert isinstance(selected_layer.data, dict)
+    assert isinstance(selected_layer.data["features"], list)
+    assert isinstance(selected_layer.data["features"][0], dict)
+    assert len(selected_layer.data["features"]) == 1
     roi_json = actual_coastsegmap.rois.gdf[
         actual_coastsegmap.rois.gdf["id"] == ROI_id
     ].to_json()
     roi_geojson = json.loads(roi_json)
     assert isinstance(roi_geojson, dict)
-    # test if geojson in selected layer matches geojson in coastsegmap.rois.gdf
+    # test if geojson in selected layer matches geojson in coastsegmap.rois.gdf 
     assert (
         roi_geojson["features"][0]["geometry"]
-        == actual_coastsegmap.selected_ROI_layer.data["features"][0]["geometry"]
+        == selected_layer.data["features"][0]["geometry"]
     )
 
 
@@ -315,13 +328,12 @@ def test_load_rois_on_map_with_file(
     """
     actual_coastsegmap = valid_coastseg_map_with_settings
     # test if rois will be correctly loaded onto map
-    actual_coastsegmap.load_rois_on_map(file=valid_rois_filepath)
+    actual_coastsegmap.load_feature_on_map('rois',file=valid_rois_filepath)
     assert actual_coastsegmap.rois is not None
     assert isinstance(actual_coastsegmap.rois, roi.ROI)
     # test if rois geodataframe was created correctly
     assert isinstance(actual_coastsegmap.rois.gdf, gpd.GeoDataFrame)
     assert actual_coastsegmap.rois.gdf.equals(valid_rois_gdf)
     # test if roi layer was added to map
-    assert actual_coastsegmap.ROI_layer is not None
     existing_layer = actual_coastsegmap.map.find_layer(roi.ROI.LAYER_NAME)
     assert existing_layer is not None
diff --git a/tests/test_common.py b/tests/test_common.py
index 6c98aca7..08fd86db 100644
--- a/tests/test_common.py
+++ b/tests/test_common.py
@@ -9,6 +9,47 @@
 from shapely import geometry
 import pytest
 
+def test_get_transect_points_dict(valid_transects_gdf):
+    """Tests get_transect_points_dict to see if it returns a valid dictionary when given 
+    transects geodataframe and an id
+    Args:
+        valid_transects_gdf (geodataframe): transects geodataframe with ids:[17,30,35]
+    """
+    roi_id = '17'
+    transects_dict = common.get_transect_points_dict(roi_id,valid_transects_gdf)
+    # simulate how roi transect ids would be created 
+    transect_ids=valid_transects_gdf['id'].to_list()
+    roi_transect_ids = ["ROI_"+roi_id +"_"+tid for tid in transect_ids]
+    
+    assert isinstance(transects_dict,dict)
+    assert set(transects_dict.keys()).issubset(set(roi_transect_ids))
+    assert isinstance( transects_dict[roi_transect_ids[0]],np.ndarray)
+    
+    
+
+def test_get_default_dict():
+    # should return dict with fill dict values since keys exist in fill_dict
+    default_str = "unknown"
+    fill_dict = {'1':4,'2':5}
+    keys = ['1','2']
+    actual = common.get_default_dict(default=default_str,
+                                     keys=keys,
+                                     fill_dict=fill_dict)
+    assert actual == fill_dict
+    #should return dict with default values if no keys exist in fill_dict
+    fill_dict = {'4':4,'5':5}
+    expected = {'1':default_str,'2':default_str}
+    actual = common.get_default_dict(default=default_str,
+                                     keys=keys,
+                                     fill_dict=fill_dict)
+    assert actual == expected
+    # should return dict with some default values if some keys exist in fill_dict
+    fill_dict = {'1':4,'5':5}
+    expected = {'1':4,'2':default_str}
+    actual = common.get_default_dict(default=default_str,
+                                     keys=keys,
+                                     fill_dict=fill_dict)
+    assert actual == expected
 
 def test_is_list_empty():
     # empty list to test if it detects it as empty
@@ -284,26 +325,3 @@ def test_extract_roi_by_id(valid_rois_gdf):
     expected_roi = valid_rois_gdf[valid_rois_gdf["id"].astype(int) == roi_id]
     assert actual_roi["geometry"][0] == expected_roi["geometry"][0]
     assert actual_roi["id"][0] == expected_roi["id"][0]
-
-
-def test_gdf_to_polygon(valid_bbox_gdf, valid_rois_gdf):
-    # test if it returns a shapely.geometry.Polygon() given geodataframe and no id given
-    polygon = common.convert_gdf_to_polygon(valid_bbox_gdf, None)
-    bbox_dict = json.loads((valid_bbox_gdf["geometry"].to_json()))
-    assert isinstance(polygon, geometry.Polygon)
-    assert geometry.Polygon(bbox_dict["features"][0]["geometry"]["coordinates"][0])
-    # test if it returns the correct shapely.geometry.Polygon() given a specific id in the geodataframe
-    id = "17"
-    polygon = common.convert_gdf_to_polygon(valid_rois_gdf, id)
-    roi_dict = json.loads(
-        valid_rois_gdf[valid_rois_gdf["id"] == id]["geometry"].to_json()
-    )
-    assert isinstance(polygon, geometry.Polygon)
-    assert geometry.Polygon(roi_dict["features"][0]["geometry"]["coordinates"][0])
-
-
-def test_gdf_to_polygon_invalid(valid_rois_gdf):
-    # should raise exception if id is not in the geodataframe
-    with pytest.raises(Exception):
-        id = 18
-        polygon = common.convert_gdf_to_polygon(valid_rois_gdf, id)
diff --git a/tests/test_data/config.json b/tests/test_data/config.json
index f6a74be5..13ff6f5a 100644
--- a/tests/test_data/config.json
+++ b/tests/test_data/config.json
@@ -64,12 +64,10 @@
     "adjust_detection": false,
     "save_figure": true,
     "min_beach_area": 4500,
-    "buffer_size": 550,
     "min_length_sl": 100,
     "cloud_mask_issue": false,
     "sand_color": "default",
     "pan_off": "False",
-    "create_plot": false,
     "max_dist_ref": 25,
     "along_dist": 25
   }
diff --git a/tests/test_data/config_id_2.json b/tests/test_data/config_id_2.json
index 7d8d9f39..19c0a36d 100644
--- a/tests/test_data/config_id_2.json
+++ b/tests/test_data/config_id_2.json
@@ -64,12 +64,10 @@
     "adjust_detection": false,
     "save_figure": true,
     "min_beach_area": 4500,
-    "buffer_size": 550,
     "min_length_sl": 100,
     "cloud_mask_issue": false,
     "sand_color": "default",
     "pan_off": "False",
-    "create_plot": false,
     "max_dist_ref": 25,
     "along_dist": 25
   }
diff --git a/tests/test_extracted_shoreline.py b/tests/test_extracted_shoreline.py
new file mode 100644
index 00000000..7dd19ba6
--- /dev/null
+++ b/tests/test_extracted_shoreline.py
@@ -0,0 +1,316 @@
+import pytest
+from coastseg import extracted_shoreline
+from coastseg import exceptions
+import geopandas as gpd
+import pyproj
+
+def test_init_invalid_inputs(valid_roi_settings,valid_shoreline_gdf,valid_settings):
+    # Test initialize Extracted_Shoreline with invalid ROI id
+    invalid_roi_id=4
+    roi_id='2'
+    invalid_shoreline=None
+    empty_shoreline=gpd.GeoDataFrame()
+    shoreline = valid_shoreline_gdf
+    invalid_roi_settings=None
+    roi_settings=valid_roi_settings
+    invalid_settings=None
+    settings=valid_settings
+    
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(invalid_roi_id,shoreline,roi_settings,settings)
+    
+    # Test initialize Extracted_Shoreline with invalid shoreline
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(roi_id,invalid_shoreline,roi_settings,settings)
+    # Test initialize Extracted_Shoreline with empty shoreline
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(roi_id,empty_shoreline,roi_settings,settings)
+        
+    # Test initialize Extracted_Shoreline with invalid roi_settings
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(roi_id,shoreline,invalid_roi_settings,settings)
+    # Test initialize Extracted_Shoreline with empty roi_settings
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(roi_id,shoreline,{},settings)
+        
+    # Test initialize Extracted_Shoreline with invalid settings
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(roi_id,shoreline,roi_settings,invalid_settings)
+    # Test initialize Extracted_Shoreline with empty settings
+    with pytest.raises(ValueError):
+        extracted_shoreline.Extracted_Shoreline(roi_id,shoreline,roi_settings,{})
+
+
+# def test_init(valid_roi_settings,valid_shoreline_gdf,valid_settings):
+#     # Test initialize Extracted_Shoreline with invalid ROI id
+#     roi_id='2'
+#     shoreline = valid_shoreline_gdf
+#     roi_settings=valid_roi_settings
+#     settings=valid_settings
+    
+#     actual = extracted_shoreline.Extracted_Shoreline(roi_id,shoreline,roi_settings,settings)
+#     assert isinstance(actual,extracted_shoreline.Extracted_Shoreline)
+#     assert actual.roi_id==roi_id
+
+# def test_create_geodataframe(
+#     valid_bbox_gdf: gpd.GeoDataFrame,
+#     valid_shoreline_gdf: gpd.GeoDataFrame,
+# ):
+#     large_len = 1000
+#     small_len = 750
+#     input_espg = "epsg:4326"
+
+#     actual_gdf = valid_ROI.create_geodataframe(
+#         bbox=valid_bbox_gdf,
+#         shoreline=valid_shoreline_gdf,
+#         large_length=large_len,
+#         small_length=small_len,
+#         crs=input_espg,
+#     )
+
+#     assert isinstance(actual_gdf, gpd.GeoDataFrame)
+#     assert set(actual_gdf.columns) == set(["geometry", "id"])
+#     assert actual_gdf.dtypes["geometry"] == "geometry"
+#     assert actual_gdf.dtypes["id"] == "object"
+#     # drop unneeded columns before checking
+#     columns_to_drop = list(valid_shoreline_gdf.columns.difference(["geometry"]))
+#     valid_shoreline_gdf = valid_shoreline_gdf.drop(columns=columns_to_drop)
+#     # Validate any shorelines intersect any squares in actual_gdf
+#     intersection_gdf = gpd.sjoin(
+#         valid_shoreline_gdf, right_df=actual_gdf, how="inner", predicate="intersects"
+#     )
+#     assert intersection_gdf.empty == False
+
+
+# def test_fishnet_intersection(
+#     valid_bbox_gdf: gpd.GeoDataFrame,
+#     valid_shoreline_gdf: gpd.GeoDataFrame,
+#     valid_ROI: roi.ROI,
+# ):
+#     # tests if a valid fishnet geodataframe intersects with given shoreline geodataframe
+#     square_size = 1000
+#     output_espg = "epsg:4326"
+#     fishnet_gdf = valid_ROI.create_rois(valid_bbox_gdf, square_size)
+#     # check if fishnet intersects the shoreline
+#     fishnet_gdf = valid_ROI.fishnet_intersection(fishnet_gdf, valid_shoreline_gdf)
+#     assert isinstance(fishnet_gdf, gpd.GeoDataFrame)
+#     assert fishnet_gdf.empty == False
+#     assert isinstance(fishnet_gdf.crs, pyproj.CRS)
+#     assert fishnet_gdf.crs == output_espg
+#     assert set(fishnet_gdf.columns) == set(["geometry"])
+#     # drop unneeded columns before checking
+#     columns_to_drop = list(valid_shoreline_gdf.columns.difference(["geometry"]))
+#     valid_shoreline_gdf = valid_shoreline_gdf.drop(columns=columns_to_drop)
+#     # check if any shorelines intersect any squares in fishnet_gdf
+#     intersection_gdf = valid_shoreline_gdf.sjoin(
+#         fishnet_gdf, how="inner", predicate="intersects"
+#     )
+#     assert intersection_gdf.empty == False
+
+
+# def test_get_fishnet(
+#     valid_bbox_gdf: gpd.GeoDataFrame,
+#     valid_shoreline_gdf: gpd.GeoDataFrame,
+#     valid_ROI: roi.ROI,
+# ):
+#     # tests if a valid fishnet geodataframe intersects with given shoreline geodataframe
+#     square_size = 1000
+#     output_espg = "epsg:4326"
+#     # check if fishnet intersects the shoreline
+#     fishnet_gdf = valid_ROI.get_fishnet_gdf(
+#         bbox_gdf=valid_bbox_gdf,
+#         shoreline_gdf=valid_shoreline_gdf,
+#         square_length=square_size,
+#     )
+#     assert isinstance(fishnet_gdf, gpd.GeoDataFrame)
+#     assert isinstance(fishnet_gdf.crs, pyproj.CRS)
+#     assert fishnet_gdf.crs == output_espg
+#     assert set(fishnet_gdf.columns) == set(["geometry"])
+#     # drop unneeded columns before checking
+#     columns_to_drop = list(valid_shoreline_gdf.columns.difference(["geometry"]))
+#     valid_shoreline_gdf = valid_shoreline_gdf.drop(columns=columns_to_drop)
+#     # check if any shorelines intersect any squares in fishnet_gdf
+#     intersection_gdf = valid_shoreline_gdf.sjoin(
+#         fishnet_gdf, how="inner", predicate="intersects"
+#     )
+#     assert intersection_gdf.empty == False
+
+
+# def test_roi_missing_lengths(valid_bbox_gdf, valid_shoreline_gdf):
+#     # test with missing square lengths
+#     with pytest.raises(Exception):
+#         roi.ROI(bbox=valid_bbox_gdf, shoreline=valid_shoreline_gdf)
+
+
+# def test_bad_roi_initialization(valid_bbox_gdf):
+#     empty_gdf = gpd.GeoDataFrame()
+#     # test with missing shoreline
+#     with pytest.raises(exceptions.Object_Not_Found):
+#         roi.ROI(bbox=valid_bbox_gdf)
+#     # test with missing bbox and shoreline
+#     with pytest.raises(exceptions.Object_Not_Found):
+#         roi.ROI()
+#     # test with empty bbox
+#     with pytest.raises(exceptions.Object_Not_Found):
+#         roi.ROI(bbox=empty_gdf)
+#     # test with empty shoreline
+#     with pytest.raises(exceptions.Object_Not_Found):
+#         roi.ROI(bbox=valid_bbox_gdf, shoreline=empty_gdf)
+
+
+# def test_roi_from_bbox_and_shorelines(valid_bbox_gdf, valid_shoreline_gdf):
+#     large_len = 1000
+#     small_len = 750
+#     actual_roi = roi.ROI(
+#         bbox=valid_bbox_gdf,
+#         shoreline=valid_shoreline_gdf,
+#         square_len_lg=large_len,
+#         square_len_sm=small_len,
+#     )
+
+#     assert isinstance(actual_roi, roi.ROI)
+#     assert isinstance(actual_roi.gdf, gpd.GeoDataFrame)
+#     assert set(actual_roi.gdf.columns) == set(["geometry", "id"])
+#     assert actual_roi.filename == "rois.geojson"
+#     assert hasattr(actual_roi, "extracted_shorelines")
+#     assert hasattr(actual_roi, "cross_distance_transects")
+#     assert hasattr(actual_roi, "roi_settings")
+
+
+# def test_create_fishnet(valid_bbox_gdf: gpd.GeoDataFrame, valid_ROI: roi.ROI):
+#     # tests if a valid geodataframe is created with square sizes approx. equal to given square_size
+#     square_size = 1000
+#     input_espg = "epsg:32610"
+#     output_espg = "epsg:4326"
+
+#     # convert bbox to input_espg to most accurate espg to create fishnet with
+#     valid_bbox_gdf = valid_bbox_gdf.to_crs(input_espg)
+#     assert valid_bbox_gdf.crs == "epsg:32610"
+
+#     actual_fishnet = valid_ROI.create_fishnet(
+#         valid_bbox_gdf,
+#         input_espg=input_espg,
+#         output_espg=output_espg,
+#         square_size=square_size,
+#     )
+#     assert isinstance(actual_fishnet, gpd.GeoDataFrame)
+#     assert set(actual_fishnet.columns) == set(["geometry"])
+#     assert isinstance(actual_fishnet.crs, pyproj.CRS)
+#     assert actual_fishnet.crs == output_espg
+#     # reproject back to input_espg to check if square sizes are correct
+#     actual_fishnet = actual_fishnet.to_crs(input_espg)
+#     # pick a square out of the fishnet ensure is approx. equal to square size
+#     actual_lengths = tuple(map(lambda x: x.length / 4, actual_fishnet["geometry"]))
+#     # check if actual lengths are close to square_size length
+#     is_actual_length_correct = all(
+#         tuple(
+#             map(lambda x: math.isclose(x, square_size, rel_tol=1e-04), actual_lengths)
+#         )
+#     )
+#     # assert all actual lengths are close to square_size length
+#     assert is_actual_length_correct == True
+
+
+# def test_create_rois(valid_ROI: roi.ROI, valid_bbox_gdf: gpd.GeoDataFrame):
+#     square_size = 1000
+#     # espg code of the valid_bbox_gdf
+#     input_espg = "epsg:4326"
+#     output_espg = "epsg:4326"
+#     actual_roi_gdf = valid_ROI.create_rois(
+#         bbox=valid_bbox_gdf,
+#         input_espg=input_espg,
+#         output_espg=output_espg,
+#         square_size=square_size,
+#     )
+#     assert isinstance(actual_roi_gdf, gpd.GeoDataFrame)
+#     assert isinstance(actual_roi_gdf.crs, pyproj.CRS)
+#     assert actual_roi_gdf.crs == output_espg
+#     assert set(actual_roi_gdf.columns) == set(["geometry"])
+
+
+# def test_transect_compatible_roi(transect_compatible_roi: gpd.GeoDataFrame):
+#     """tests if a ROI will be created from valid rois of type gpd.GeoDataFrame
+#     Args:
+#         valid_bbox_gdf (gpd.GeoDataFrame): alid rois as a gpd.GeoDataFrame
+#     """
+#     actual_roi = roi.ROI(rois_gdf=transect_compatible_roi)
+#     assert isinstance(actual_roi, roi.ROI)
+#     assert isinstance(actual_roi.gdf, gpd.GeoDataFrame)
+#     assert set(actual_roi.gdf.columns) == set(["geometry", "id"])
+#     assert actual_roi.gdf.dtypes["geometry"] == "geometry"
+#     assert actual_roi.gdf.dtypes["id"] == "object"
+#     assert actual_roi.filename == "rois.geojson"
+#     assert hasattr(actual_roi, "extracted_shorelines")
+#     assert hasattr(actual_roi, "cross_distance_transects")
+#     assert hasattr(actual_roi, "roi_settings")
+
+
+# def test_update_extracted_shorelines(valid_ROI: roi.ROI):
+#     """tests if a ROI will be created from valid rois of type gpd.GeoDataFrame
+#     Args:
+#        transect_compatible_roi (gpd.GeoDataFrame): valid rois as a gpd.GeoDataFrame
+#     """
+#     expected_dict = {
+#         23: {
+#             "filename": ["ms.tif", "2019.tif"],
+#             "cloud_cover": [0.14, 0.0],
+#             "geoaccuracy": [7.9, 9.72],
+#             "idx": [4, 6],
+#             "MNDWI_threshold": [-0.231, -0.3],
+#             "satname": ["L8", "L8"],
+#         }
+#     }
+#     valid_ROI.update_extracted_shorelines(expected_dict)
+#     assert valid_ROI.extracted_shorelines != {}
+#     assert valid_ROI.extracted_shorelines == expected_dict
+
+
+# def test_set_roi_settings(valid_ROI: roi.ROI):
+#     """tests if a ROI will be created from valid rois thats a gpd.GeoDataFrame
+#     Args:
+#         transect_compatible_roi (gpd.GeoDataFrame): valid rois as a gpd.GeoDataFrame
+#     """
+#     expected_dict = {
+#         23: {
+#             "dates": ["2018-12-01", "2019-03-01"],
+#             "sat_list": ["L8"],
+#             "sitename": "ID02022-10-07__09_hr_38_min37sec",
+#             "filepath": "C:\\1_USGS\\CoastSeg\\repos\\2_CoastSeg\\CoastSeg_fork\\Seg2Map\\data",
+#             "roi_id": 23,
+#             "polygon": [
+#                 [
+#                     [-124.1662679688807, 40.863030239542944],
+#                     [-124.16690059058178, 40.89905645671534],
+#                     [-124.11942071317034, 40.89952713781644],
+#                     [-124.11881381876809, 40.863500326870245],
+#                     [-124.1662679688807, 40.863030239542944],
+#                 ]
+#             ],
+#             "landsat_collection": "C01",
+#         },
+#         39: {
+#             "dates": ["2018-12-01", "2019-03-01"],
+#             "sat_list": ["L8"],
+#             "sitename": "ID12022-10-07__09_hr_38_min37sec",
+#             "filepath": "C:\\1_USGS\\CoastSeg\\repos\\2_CoastSeg\\CoastSeg_fork\\Seg2Map\\data",
+#             "roi_id": 39,
+#             "polygon": [
+#                 [
+#                     [-124.16690059058178, 40.89905645671534],
+#                     [-124.1675343590045, 40.93508244001033],
+#                     [-124.12002870768146, 40.9355537155221],
+#                     [-124.11942071317034, 40.89952713781644],
+#                     [-124.16690059058178, 40.89905645671534],
+#                 ]
+#             ],
+#             "landsat_collection": "C01",
+#         },
+#     }
+#     valid_ROI.set_roi_settings(expected_dict)
+#     assert valid_ROI.roi_settings != {}
+#     assert valid_ROI.roi_settings == expected_dict
+
+
+# def test_style_layer(valid_ROI: roi.ROI):
+#     with pytest.raises(AssertionError):
+#         valid_ROI.style_layer({}, layer_name="Nope")