diff --git a/.github/workflows/aws_doc_deploy.yml b/.github/workflows/aws_doc_deploy.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d714755b71fb41a3f273a2ffb07e0637096e623f
--- /dev/null
+++ b/.github/workflows/aws_doc_deploy.yml
@@ -0,0 +1,37 @@
+name: aws-doc-deploy
+
+on:
+  workflow_run:
+    workflows: ["Build-Docs"]
+    branches: [main]
+    types: 
+      - completed
+
+jobs:
+  deploy:
+    if: ${{ github.event.workflow_run.conclusion == 'success' }}
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash -el {0}
+    steps:
+      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
+        with:
+          submodules: true
+          fetch-depth: 0
+      - uses: actions/download-artifact@v9bc31d5ccc31df68ecc42ccf4149144866c47d8a
+        with:
+          name: docs_folder
+            
+      - name: Set AWS credentials for upload
+        uses: aws-actions/configure-aws-credentials@0e613a0980cbf65ed5b322eb7a1e075d28913a83
+        with:
+          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+          aws-region: us-west-2
+      
+      - name: Upload to S3
+        working-directory: docs/public
+        run: |
+          cd docs
+          aws s3 sync --delete public s3://asc-docs/usgscsm
\ No newline at end of file
diff --git a/.github/workflows/ci_testing.yml b/.github/workflows/ci_testing.yml
index b0c9aa8eae5be1993007d321930238b26d0833ac..e276902d347e3b47e062ca19f942bf2cfdfc2b89 100644
--- a/.github/workflows/ci_testing.yml
+++ b/.github/workflows/ci_testing.yml
@@ -20,10 +20,10 @@ jobs:
       run:
         shell: bash -l {0}
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
         with:
           submodules: true
-      - uses: conda-incubator/setup-miniconda@v2
+      - uses: conda-incubator/setup-miniconda@3b0f2504dd76ef23b6d31f291f4913fb60ab5ff3
         with:
           miniconda-version: "latest"
           activate-environment: usgscsm
@@ -38,9 +38,42 @@ jobs:
         run: |
           mkdir -p build
           cd build
-          cmake -DCMAKE_BUILD_TYPE=RELEASE -DUSGSCSM_EXTERNAL_DEPS=ON ..
+          cmake -DCMAKE_BUILD_TYPE=RELEASE -DUSGSCSM_EXTERNAL_DEPS=ON -DUSGSCSM_BUILD_DOCS=OFF ..
           cmake --build .
       - name: Test Package
         run: |
           cd build
           ctest -VV
+  
+  Build-Docs:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        shell: bash -el {0}
+    steps:
+      - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9
+        with:
+          submodules: true
+          fetch-depth: 0
+      - uses: conda-incubator/setup-miniconda@3b0f2504dd76ef23b6d31f291f4913fb60ab5ff3
+        with:
+          miniconda-version: "latest"
+          activate-environment: usgscsm
+          environment-file: doc_environment.yml
+          auto-activate-base: false
+          auto-update-conda: true
+
+      - name: Check doc build environment
+        run: |
+          conda list
+
+      - name: Build Docs
+        run: |
+          cd docs
+          doxygen Doxyfile.in
+          sphinx-build -b html source public
+
+      - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce
+        with:
+          name: docs_folder
+          path: docs/public/
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e98361a6600e7dc1a7cd52d28b8e0e1617925f8..a67a7f029235585c06542684aabf3bcfaca0f793 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,10 @@
 cmake_minimum_required(VERSION 3.10)
 project(usgscsm VERSION 1.7.0 DESCRIPTION "usgscsm library")
 
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+message(STATUS "CMake Module Path: " ${CMAKE_MODULE_PATH})
+
 include(GNUInstallDirs)
 
 set(CMAKE_CXX_STANDARD 11)
@@ -129,3 +133,11 @@ if(USGSCSM_BUILD_TESTS)
   add_subdirectory(tests)
 
 endif()
+
+option (USGSCSM_BUILD_DOCS "Build the USGSCSM Docs" ON)
+if(USGSCSM_BUILD_DOCS)
+  add_subdirectory ("docs")
+else()
+  message(STATUS "Skipping Docs")
+endif()
+
diff --git a/doc_environment.yml b/doc_environment.yml
index 8396893eda667b8260a397d2f588856a9cd67458..abda4debb3eade82692fefeeec9997e32274136b 100644
--- a/doc_environment.yml
+++ b/doc_environment.yml
@@ -4,8 +4,8 @@ channels:
   - default
 
 dependencies:
+  - breathe
   - cmake>=3.12
-  - ale
   - csm
+  - doxygen
   - nlohmann_json
-  - breathe
diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..170ad58b85a954e0fbd49fa8436314820e03e2f5
--- /dev/null
+++ b/docs/CMakeLists.txt
@@ -0,0 +1,65 @@
+message(STATUS "Setting Up Docs")
+
+find_package(Doxygen REQUIRED)
+find_package(Sphinx REQUIRED)
+
+# Find all the public headers
+set(USGSCSM_BUILD_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/include/usgscsm")
+file(GLOB_RECURSE USGSCSM_PUBLIC_HEADERS ${USGSCSM_BUILD_INCLUDE_DIR}/*.h)
+
+set(DOXYGEN_INPUT_DIR ${PROJECT_SOURCE_DIR}/usgscsm/include/)
+set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR})
+set(DOXYGEN_INDEX_FILE ${DOXYGEN_OUTPUT_DIR}/xml/index.xml)
+set(DOXYFILE_IN ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.in)
+set(DOXYFILE_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
+
+configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)
+
+message(STATUS "DOXYGEN_INPUT_DIR: " ${DOXYGEN_INPUT_DIR})
+message(STATUS "DOXYGEN_OUTPUT_DIR: " ${DOXYGEN_OUTPUT_DIR})
+message(STATUS "DOXYGEN_INDEX_FILE: " ${DOXYGEN_INDEX_FILE})
+message(STATUS "DOXYFILE_IN: " ${DOXYFILE_IN})
+message(STATUS "DOXYFILE_OUT: " ${DOXYFILE_OUT})
+
+file(MAKE_DIRECTORY ${DOXYGEN_OUTPUT_DIR})
+
+add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE}
+                   DEPENDS ${USGSCSM_PUBLIC_HEADERS}
+                   COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} 
+                   WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+                   MAIN_DEPENDENCY Doxyfile
+                   COMMENT "Generating docs"
+                   VERBATIM)
+
+add_custom_target(Doxygen ALL DEPENDS ${DOXYGEN_INDEX_FILE})
+
+set(SPHINX_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/source)
+set(SPHINX_BUILD ${CMAKE_CURRENT_BINARY_DIR}/sphinx)
+set(SPHINX_INDEX_FILE ${SPHINX_BUILD}/index.html)
+
+set(SPHINX_RST_FILES ${CMAKE_CURRENT_SOURCE_DIR}/source/index.rst)
+
+add_custom_command(OUTPUT ${SPHINX_INDEX_FILE}
+                   COMMAND
+                     ${SPHINX_EXECUTABLE} -b html
+                     # Tell Breathe where to find the Doxygen output
+                     -Dbreathe_projects.SpiceQL=${DOXYGEN_OUTPUT_DIR}/xml
+                   ${SPHINX_SOURCE} ${SPHINX_BUILD}
+                   WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
+                   DEPENDS ${USGSCSM_PUBLIC_HEADERS}
+                   # Other docs files you want to track should go here (or in some variable)
+                   ${SPHINX_RST_FILES}
+                   ${CMAKE_CURRENT_SOURCE_DIR}/../README.md # Docs insert the readme, so it's a dep
+                   ${DOXYGEN_INDEX_FILE}
+                   # MAIN_DEPENDENCY ${SPHINX_SOURCE}/conf.py
+                   COMMENT "Generating documentation with Sphinx")
+
+
+# Nice named target so we can run the job easily
+add_custom_target(Sphinx ALL DEPENDS ${SPHINX_INDEX_FILE})
+
+# Add an install target to install the docs
+include(GNUInstallDirs)
+install(DIRECTORY ${SPHINX_BUILD}
+DESTINATION ${CMAKE_INSTALL_DOCDIR})
+
diff --git a/docs/Doxyfile b/docs/Doxyfile.in
similarity index 100%
rename from docs/Doxyfile
rename to docs/Doxyfile.in
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index d0c3cbf1020d5c292abdedf27627c6abe25e2293..0000000000000000000000000000000000000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,20 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line, and also
-# from the environment for the first two.
-SPHINXOPTS    ?=
-SPHINXBUILD   ?= sphinx-build
-SOURCEDIR     = source
-BUILDDIR      = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
-	@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option.  $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
-	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/source/conf.py b/docs/source/conf.py
index a2debacf28953d684e8451d51fe38283256d66a9..6adc7abbe3806b03d45e38d61cd4fe3d8d891a67 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -17,7 +17,7 @@
 
 # -- Project information -----------------------------------------------------
 
-project = 'usgscsm'
+project = 'USGSCSM'
 copyright = '2021, USGS Astrogeology Software Team'
 author = 'USGS Astrogeology Software Team'
 
@@ -51,8 +51,8 @@ html_theme = 'alabaster'
 html_static_path = ['_static']
 
 # breathe (doxygen -> sphinx package) settings
-breathe_projects = { "usgscsm": "../xml/" }
-breathe_default_project = "usgscsm"
+breathe_projects = { "USGSCSM": "../xml/" }
+breathe_default_project = "USGSCSM"
 
 import subprocess, os
 
diff --git a/docs/source/index.rst b/docs/source/index.rst
index f7aecf31030eaec99b55d94fec337fe86babe789..0e6195714f8e6c94788a3008f2fbbcbb2988edb2 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,4 +1,4 @@
-Welcome to usgscsm's documentation!
+Welcome to USGSCSM's documentation!
 ===================================
 
 :Release: |version|
@@ -14,6 +14,10 @@ This project is hosted on `GitHub`_. If you run into a problem, please open an `
    :maxdepth: 2
 
    library/index
+   sensors/index
+   user/creating_an_environment
+   user/isd_generation
+   tools/usgscsm_cam_test
 
 .. _GitHub: http://github.com/USGS-Astrogeology/usgscsm
 .. _issue: https://github.com/USGS-Astrogeology/usgscsm/issues?state=open
diff --git a/docs/source/library/distortion.rst b/docs/source/library/distortion.rst
index 25e46506bf0c36f427450128711cfd70101717d6..461990128ebe89d05e64dc48188d9aaa00b33576 100644
--- a/docs/source/library/distortion.rst
+++ b/docs/source/library/distortion.rst
@@ -2,6 +2,6 @@ USGSCSM Distortion Class
 ==============================================
 
 .. doxygenfile:: Distortion.h
-   :project: usgscsm
+   :project: USGSCSM
 
 
diff --git a/docs/source/library/framer.rst b/docs/source/library/framer.rst
index f18d32e81fbd5c32d323d7940d0eff709d7dd3a3..17bce139f40080a53c544fccaf080a308f244430 100644
--- a/docs/source/library/framer.rst
+++ b/docs/source/library/framer.rst
@@ -2,5 +2,5 @@ USGSCSM Astro Frame Sensor Model Class
 =======================================================
 
 .. doxygenfile:: UsgsAstroFrameSensorModel.h
-   :project: usgscsm
+   :project: USGSCSM
 
diff --git a/docs/source/library/linescanner.rst b/docs/source/library/linescanner.rst
index 76c1de2996b8d63acfa23ddd41c85f90fe12ada4..41383422fc20a4b59918bde7a1b9f029184005cf 100644
--- a/docs/source/library/linescanner.rst
+++ b/docs/source/library/linescanner.rst
@@ -2,5 +2,5 @@ USGSCSM Astro Linescan Sensor Model Class
 =============================================================
 
 .. doxygenfile:: UsgsAstroLsSensorModel.h
-   :project: usgscsm
+   :project: USGSCSM
 
diff --git a/docs/source/library/plugin.rst b/docs/source/library/plugin.rst
index 349a08505f75f98d80156ee53e2125279687e369..5d04588c39ed58f9718064ac6d07fab84a6fe3af 100644
--- a/docs/source/library/plugin.rst
+++ b/docs/source/library/plugin.rst
@@ -2,5 +2,5 @@ USGSCSM Astro Plugin Class
 =====================================================
 
 .. doxygenfile:: UsgsAstroPlugin.h
-   :project: usgscsm
+   :project: USGSCSM
 
diff --git a/docs/source/library/sar.rst b/docs/source/library/sar.rst
index fa52db2d85cfe77cc88d5a133bd3c4c4c0f9b4d1..afa8da0ca02ae39c15d87471b47a26cd4f46fe7c 100644
--- a/docs/source/library/sar.rst
+++ b/docs/source/library/sar.rst
@@ -2,5 +2,5 @@ USGSCSM Astro SAR Sensor Model Class
 =============================================================
 
 .. doxygenfile:: UsgsAstroSarSensorModel.h
-   :project: usgscsm
+   :project: USGSCSM
 
diff --git a/docs/source/library/utilities.rst b/docs/source/library/utilities.rst
index f9502ad64eedd8d1f3e2000d1772243f90837d61..27c3c305a2790288334ee4f9ec6c149a688604fd 100644
--- a/docs/source/library/utilities.rst
+++ b/docs/source/library/utilities.rst
@@ -2,5 +2,5 @@ USGSCSM Utilities Class
 ==============================================
 
 .. doxygenfile:: Utilities.h
-   :project: usgscsm
+   :project: USGSCSM
 
diff --git a/docs/source/sensors/Cassini.rst b/docs/source/sensors/Cassini.rst
index a673b498f859c070da282a7a1b8dbe9235ff4de7..a8f586cde15659535d677151e2b605a0def0b4b8 100644
--- a/docs/source/sensors/Cassini.rst
+++ b/docs/source/sensors/Cassini.rst
@@ -6,34 +6,36 @@ wide angle camera contains 18 filters.
 
 Processing Cassini ISS Images
 -----------------------------
-    import ale, json, os
-    from ale.drivers.cassini_drivers import CassiniIssPds3LabelNaifSpiceDriver
-    from ale.drivers import JsonEncoder
+.. code-block:: python
 
-    # Use the images to generate ISDs and create CSM cameras
-    # Assume images are in current directory
+  import ale, json, os
+  from ale.drivers.cassini_drivers import CassiniIssPds3LabelNaifSpiceDriver
+  from ale.drivers import JsonEncoder
 
-    nac_stereo_1 = 'N1702360370_1.LBL'
-    nac_stereo_2 = 'N1702360308_1.LBL'
+  # Use the images to generate ISDs and create CSM cameras
+  # Assume images are in current directory
 
-    def generate_isd(filename):
-        driver = CassiniIssPds3LabelNaifSpiceDriver(filename)
+  nac_stereo_1 = 'N1702360370_1.LBL'
+  nac_stereo_2 = 'N1702360308_1.LBL'
 
-        # SPICE kernels are furnished inside this with
-        with driver as d:
-            # this is the information for the ISD in a python dict
-            aledict = d.to_dict()
+  def generate_isd(filename):
+      driver = CassiniIssPds3LabelNaifSpiceDriver(filename)
 
-            # Export python dictionary ISD to external json file to be used by CSM
-            alelabel = os.path.splitext(filename)[0]+".json"
-            with open (alelabel, "w") as file:
-              json.dump(aledict, file, cls=JsonEncoder)
-            return aledict
+      # SPICE kernels are furnished inside this with
+      with driver as d:
+          # this is the information for the ISD in a python dict
+          aledict = d.to_dict()
 
-    # Generate ISD and export to a json file
-    nac1_dict = generate_isd(nac_stereo_1)
-    nac2_dict = generate_isd(nac_stereo_2)
+          # Export python dictionary ISD to external json file to be used by CSM
+          alelabel = os.path.splitext(filename)[0]+".json"
+          with open (alelabel, "w") as file:
+            json.dump(aledict, file, cls=JsonEncoder)
+          return aledict
 
-    # Construct a camera
-    camera1 = csm.create_csm(nac_stereo_1)
-    camera2 = csm.create_csm(nac_stereo_2)
+  # Generate ISD and export to a json file
+  nac1_dict = generate_isd(nac_stereo_1)
+  nac2_dict = generate_isd(nac_stereo_2)
+
+  # Construct a camera
+  camera1 = csm.create_csm(nac_stereo_1)
+  camera2 = csm.create_csm(nac_stereo_2)
diff --git a/docs/source/sensors/Selene.rst b/docs/source/sensors/Selene.rst
index 9b58192a0c58c6f23f4adb178bf7187bd979c206..64de671f46c3cd055d4c1e5d94b7771160b3aff8 100644
--- a/docs/source/sensors/Selene.rst
+++ b/docs/source/sensors/Selene.rst
@@ -9,6 +9,8 @@ Processing Kaguya TC Images
 ---------------------------
 Begin by generating an ISD as described in the section on :ref:`isd_generation`.
 
+.. code-block:: python
+
     import json
     import os
     import csmapi
diff --git a/docs/source/sensors/index.rst b/docs/source/sensors/index.rst
new file mode 100644
index 0000000000000000000000000000000000000000..5355a133991b96ce0efccda3eaab2fcca57bb6f5
--- /dev/null
+++ b/docs/source/sensors/index.rst
@@ -0,0 +1,20 @@
+#####################################
+Cameras that can be used with USGSCSM
+#####################################
+
+.. toctree::
+  Cassini
+  Dawn
+  Galileo
+  Hayabusa
+  Juno
+  LunarReconnaissanceOrbiter
+  MarsExpress
+  MarsReconnaissanceOrbiter
+  Messenger
+  NewHorizons
+  Odyssey
+  Selene
+  TraceGasOrbiter
+  Viking
+  Voyager
diff --git a/docs/source/user/isd_generation.rst b/docs/source/user/isd_generation.rst
index 55f8b75c06871dc9953d57edbd8339c529839134..e5771c7b78c2a5b4297afcb704ab7a2c7244ade0 100644
--- a/docs/source/user/isd_generation.rst
+++ b/docs/source/user/isd_generation.rst
@@ -1,19 +1,21 @@
 .. _isd_generation:
 
 Creating and testing an ISD
-===
+===========================
 
 This tutorial assumes that the user has created a conda environment with ale, usgscsm,
 and csmapi installed.
 
 Generating an ISD with ALE
------------------
+--------------------------
 ISD generation is the first step in processing an image with the models provided
 by USGSCSM.  ISDs can be easily generated with the use of
 `ALE <https://github.com/USGS-Astrogeology/ale>`_, which generates ISDs through
 the use of the "load" and "loads" functions  After installing the library
 (as described on the repository), one can generate an ISD using the image's label
-in the following fashion::
+in the following fashion:
+
+.. code-block:: python
 
     import ale
 
@@ -29,30 +31,19 @@ Under these circumstances, the user must explicitly pass the desired kernel set
 to the loads function.  This kernel set can be generated from a spiceinit'd cub
 and passed to ale as follows:
 
+.. code-block:: python
+
     kernels = ale.util.generate_kernels_from_cube(spice_initted_cub, expand=True)
     isd = ale.loads(img_file, props={'kernels': kernels})
 
 After generating the ISD in Python, it is necessary to save it to disc. This is
 accomplished via Python builtin functions such as:
 
+.. code-block:: python
+
     with open('out.json', 'w') as f:
         f.write(isd)
 
-Generating an ISD with Pfeffernusse
--------------
-Alternatively, ISDs can be generated via web service through the API exposed by
-`Pfeffernusse <https://app.swaggerhub.com/apis/USGS-Astro/pfeffernusse2/0.1.4-oas3>`_.
-While the web service is not yet publicly hosted, a public version of the service
-is under development.  Individuals or organizations may choose to host their own
-service, and it can be accessed as follows::
-
-    curl -X POST "http://<servername>:<port>/v1/pds/" -H \
-    "accept: application/json" -H "Content-Type: application/json" \
-    -d @EN1040199536M_tmp.json
-
-where EN1040199536M_tmp.json is a file containing a JSON representation of an
-image label.
-
 Testing the ISD
 ---------------
 USGSCSM includes a test utility that can be used to verify the integrity of an