diff --git a/CHANGELOG.md b/CHANGELOG.md
index 813750f4ec0136c705b858aeeced2dd6c3b75774..fd0d61ea57237a113b622cff13a58c34190df091 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@ release.
 - Added adjusted XYZ point coordinate sigmas to the points.csv jigsaw output file. Modified
 ctest FunctionalTestJigsawApollo to validate this output. [#5710](https://github.com/DOI-USGS/ISIS3/issues/5710)
 - Added OFFBODY and OFFBODYTRIM parameters to cam2cam. Added tests and updated documentation. [#3602] (https://github.com/DOI-USGS/ISIS3/issues/3602)
+- Added support for reading, writing, and viewing GeoTIFFs in ISIS. [#5618](https://github.com/DOI-USGS/ISIS3/pull/5618)
 
 ### Changed
 
diff --git a/environment.yml b/environment.yml
index d0441833c774349f01e723a48d13696443579a39..bdbf5cc5fd3867e56f8e64c54e5d665eb80a753d 100644
--- a/environment.yml
+++ b/environment.yml
@@ -5,8 +5,8 @@ dependencies:
   - ale =0.10.0,<1
   - aom
   - armadillo
-  - boost >=1.78.0,<1.79
-  - boost-cpp >=1.78.0,<1.79
+  - boost >=1.76.0,<1.82
+  - boost-cpp >=1.76.0,<1.82
   - blas
   - usgs-astrogeology::bullet
   - bz2file
@@ -22,6 +22,7 @@ dependencies:
   - ffmpeg
   - geos >=3.12, <3.13
   - geotiff
+  - gdal >=3.5 ,<3.9.0a0
   - gmp
   - graphviz
   - conda-forge::gsl >=2.6, <2.7
@@ -34,10 +35,10 @@ dependencies:
   - kalasiris
   - krb5
   - libgdal >=3.7,<3.8
-  - libopencv >=4.7,<4.8
+  - libopencv >=4.5,<4.8
   - libpng >=1.6.34,<1.7
   - libprotobuf
-  - libtiff >=4.5,<4.6
+  - libtiff >=4.5,<4.8
   - libxml2
   - make
   - mesalib
@@ -47,17 +48,17 @@ dependencies:
   - nlohmann_json=3.11.2
   - ninja >=1.7.2,<1.8
   - nn
-  - opencv >=4.7,<4.8
+  - opencv >=4.5,<4.8
   - openssl >=3.1.2,<3.2
   - pcl >=1.13,<1.14
   - plio
   - protobuf
-  - python >=3.10
-  - python_abi >=3.10
+  - python >=3.9
+  - python_abi >=3.9
   - pytest
   - rclone
   - qhull
-  - qt-main>=5.15.8, <5.16
+  - qt-main>=5.15, <5.16
   - qwt <6.3.0
   - sqlite >=3.46.0,<3.47
   - suitesparse <7.7.0
diff --git a/gtest b/gtest
index b10fad38c4026a29ea6561ab15fc4818170d1c10..f8d7d77c06936315286eb55f8de22cd23c188571 160000
--- a/gtest
+++ b/gtest
@@ -1 +1 @@
-Subproject commit b10fad38c4026a29ea6561ab15fc4818170d1c10
+Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571
diff --git a/isis/CMakeLists.txt b/isis/CMakeLists.txt
index 8584042726be66daa8768e9d10e717504240f606..8bdf20b3015dfef06b97019e180c95557bd83724 100644
--- a/isis/CMakeLists.txt
+++ b/isis/CMakeLists.txt
@@ -267,6 +267,7 @@ find_package(CSM       3.0.3.3 REQUIRED)
 find_package(CSPICE    65      REQUIRED)
 find_package(Eigen             REQUIRED)
 find_package(Embree    3.13.0  REQUIRED)
+find_package(GDAL              REQUIRED CONFIG)
 find_package(GeoTIFF   2       REQUIRED)
 SET(GSL_ROOT_DIR $ENV{CONDA_PREFIX})
 find_package(GSL       2.2.1   REQUIRED)
@@ -353,7 +354,7 @@ foreach (_variableName ${_variableNames})
 endforeach()
 
 # add target based linkages to ALLLIBS variable
-list(APPEND ALLLIBS pantor::inja sensorutilities protobuf::libprotobuf embree)
+list(APPEND ALLLIBS pantor::inja sensorutilities protobuf::libprotobuf embree GDAL::GDAL)
 
 # Sometimes we add the same lib more than once (especially with LIBDIRS)
 list(REMOVE_DUPLICATES ALLLIBDIRS)
diff --git a/isis/src/apollo/apps/apollopanstitcher/apollopanstitcher.cpp b/isis/src/apollo/apps/apollopanstitcher/apollopanstitcher.cpp
index 4a4ad3a4147b600a1377a792c4a2ddbfc5dcb3f7..de48692a9a8f14d05b71e90689f8838b15696df5 100644
--- a/isis/src/apollo/apps/apollopanstitcher/apollopanstitcher.cpp
+++ b/isis/src/apollo/apps/apollopanstitcher/apollopanstitcher.cpp
@@ -715,11 +715,11 @@ namespace Isis {
     att.setFileFormat( panC[0]->format() );
     att.setByteOrder(  panC[0]->byteOrder() );
     att.setPixelType(  panC[0]->pixelType() );
-    if (panC[0]->labelsAttached()) {
-      att.setLabelAttachment(AttachedLabel);
+    if (panC[0]->labelsAttached() == Cube::AttachedLabel) {
+      att.setLabelAttachment(Cube::AttachedLabel);
     }
     else {
-      att.setLabelAttachment(DetachedLabel);
+      att.setLabelAttachment(Cube::DetachedLabel);
     }
 
     //define an output cube
diff --git a/isis/src/base/apps/cathist/main.cpp b/isis/src/base/apps/cathist/main.cpp
index 4280d3ad27944938010082de83e76ef71f7ade74..ccdd311f9d08d413916c1cc17738ce63fe063045 100644
--- a/isis/src/base/apps/cathist/main.cpp
+++ b/isis/src/base/apps/cathist/main.cpp
@@ -26,9 +26,17 @@ void IsisMain() {
   }
 
   // Extract history from file
-  Blob historyBlob("IsisCube", "History", fromfile.expanded());
-  History hist(historyBlob);
-  Pvl pvl = hist.ReturnHist();
+  Pvl pvl;
+  try {
+    Blob historyBlob("IsisCube", "History", fromfile.expanded());
+    History hist(historyBlob);
+    pvl = hist.ReturnHist();
+  }
+  catch (...) {
+    Cube cube(fromfile);
+    History hist = cube.readHistory();
+    pvl = hist.ReturnHist();
+  }
 
   // Print full history
   if(mode == "FULL") {
diff --git a/isis/src/base/apps/catlab/main.cpp b/isis/src/base/apps/catlab/main.cpp
index f48d3a0525c1ff5e9703b423b4e5a64b59c40a3c..a9595e30545b4227c1a7745696cb5bf82cecc8ef 100644
--- a/isis/src/base/apps/catlab/main.cpp
+++ b/isis/src/base/apps/catlab/main.cpp
@@ -17,7 +17,14 @@ void IsisMain() {
   QString file = ui.GetCubeName("FROM");
   
   // Extract label from file
-  Pvl label(file);
+  Pvl label;
+  try {
+    label = Pvl(file);
+  }
+  catch (...) {
+    Cube cube(file);
+    label = *cube.label();
+  }
 
   // Output to file if entered
   if(ui.WasEntered("TO")) {
diff --git a/isis/src/base/apps/catoriglab/main.cpp b/isis/src/base/apps/catoriglab/main.cpp
index 6702a19274a0ebe2161b323d326c897c348ec3cb..2d14832746fcc832d9a3091088bbf80957de5bc2 100644
--- a/isis/src/base/apps/catoriglab/main.cpp
+++ b/isis/src/base/apps/catoriglab/main.cpp
@@ -22,9 +22,10 @@ void IsisMain() {
   UserInterface &ui = Application::GetUserInterface();
   QString file = ui.GetCubeName("FROM");
 
-  Pvl fromLabel(file);
+  Cube fromCube(file);
+  Pvl fromLabel = *(fromCube.label());
   if ( fromLabel.hasObject("OriginalLabel") ) {
-    OriginalLabel origLab(file);
+    OriginalLabel origLab = fromCube.readOriginalLabel();
     Pvl pvl = origLab.ReturnLabels();
     if (ui.IsInteractive()) {
       Application::GuiLog(pvl);
diff --git a/isis/src/base/apps/crop/crop.cpp b/isis/src/base/apps/crop/crop.cpp
index 6943e20c3a30e9ea5551197d810dc117dba361f6..b7d0f3a9703e60e6019c3386ed40a6c5b148e00f 100644
--- a/isis/src/base/apps/crop/crop.cpp
+++ b/isis/src/base/apps/crop/crop.cpp
@@ -140,7 +140,7 @@ namespace Isis {
       }
 
       // Read the table into a table object
-      Table table(obj["Name"], from);
+      Table table = cube->readTable(obj["Name"]);
 
       // We are not going to bother with line/sample associations; they apply
       //   only to the alpha cube at this time. I'm leaving this code here for the
diff --git a/isis/src/base/apps/noproj/noproj.cpp b/isis/src/base/apps/noproj/noproj.cpp
index 021792ec28beed901127f06cab09e52774d3b9a4..76a44d56abc6d6a8f90dcc5da5c027cbeb262139 100644
--- a/isis/src/base/apps/noproj/noproj.cpp
+++ b/isis/src/base/apps/noproj/noproj.cpp
@@ -191,7 +191,7 @@ namespace Isis {
     CubeAttributeOutput cao;
 
     // Can we do a regular label? Didn't work on 12-15-2006
-    cao.setLabelAttachment(Isis::DetachedLabel);
+    cao.setLabelAttachment(Cube::DetachedLabel);
     FileName matchCubeFile = FileName::createTempFile("$Temporary/match.cub");
     QString matchCubeFileNoExt = matchCubeFile.path() + "/" + matchCubeFile.baseName();
 
diff --git a/isis/src/base/apps/shadow/ShadowFunctor.cpp b/isis/src/base/apps/shadow/ShadowFunctor.cpp
index 02447dbfae635bd6275a908e09614823e7cc3c2e..8bdf79e031a36f1c669541e2227e8dd678ee7a69 100644
--- a/isis/src/base/apps/shadow/ShadowFunctor.cpp
+++ b/isis/src/base/apps/shadow/ShadowFunctor.cpp
@@ -394,7 +394,7 @@ namespace Isis {
 
 
           Portal portal(3, 3, m_inputDem->pixelType(), -0.5, -0.5);
-          portal.SetPosition(sample, line, input.Band());
+          portal.SetPosition(sample + 1, line + 1, input.Band());
 
           if (!portal.CopyOverlapFrom(input)) {
             m_inputDem->read(portal);
diff --git a/isis/src/base/apps/spiceinit/spiceinit.cpp b/isis/src/base/apps/spiceinit/spiceinit.cpp
index cd9f23d493df7dbfc75ed8b7990e8521b1dffd93..611a0b96cc9d97ccb27588463619408231c33e34 100644
--- a/isis/src/base/apps/spiceinit/spiceinit.cpp
+++ b/isis/src/base/apps/spiceinit/spiceinit.cpp
@@ -263,7 +263,6 @@ namespace Isis {
       }
     }
     icube->deleteGroup("CsmInfo");
-
     p.WriteHistory(*icube);
     p.EndProcess();
   }
@@ -592,7 +591,6 @@ namespace Isis {
       icube->putGroup(originalKernels);
       return false;
     }
-
     return true;
   }
 
diff --git a/isis/src/base/apps/tabledump/main.cpp b/isis/src/base/apps/tabledump/main.cpp
index 1880572f589fb4641d7db7f0c1322a3da2ce9424..898fc4ada18f9d1c6c8d3993e4392d76cfb18c95 100644
--- a/isis/src/base/apps/tabledump/main.cpp
+++ b/isis/src/base/apps/tabledump/main.cpp
@@ -27,7 +27,14 @@ void IsisMain() {
   UserInterface &ui = Application::GetUserInterface();
   FileName file = ui.GetCubeName("FROM");
   QString tableName = ui.GetString("NAME");
-  Table table(tableName, file.expanded());
+  Table table(tableName);
+  try {
+    table = Table(tableName, file.expanded());
+  }
+  catch (...) {
+    Cube fromCube(file);
+    table = fromCube.readTable(tableName);
+  }
 
   // Set the character to separate the entries
   QString delimit;
@@ -147,7 +154,14 @@ void helperButtonGetTableList() {
 
   UserInterface &ui = Application::GetUserInterface();
   QString currentFile = ui.GetCubeName("FROM");
-  const Pvl label(FileName(currentFile).expanded());
+  Pvl label;
+  try {
+    label = Pvl(FileName(currentFile).expanded());
+  }
+  catch (...) {
+    Cube cube(FileName(currentFile).expanded());
+    label = *cube.label();
+  }
 
   // Check to see if the "FILE" parameter has changed since last press
   if (currentFile != g_previousFile) {
diff --git a/isis/src/base/objs/Blob/Blob.cpp b/isis/src/base/objs/Blob/Blob.cpp
index 42791b03d9bae8f23aaf49cd0ed1070aafb9fe29..5dfd0d38c02653259490fb4edd13cfbe9106fa72 100644
--- a/isis/src/base/objs/Blob/Blob.cpp
+++ b/isis/src/base/objs/Blob/Blob.cpp
@@ -9,6 +9,8 @@ find files of those names at the top level of this repository. **/
 #include <cstring>
 #include <fstream>
 #include <sstream>
+#include <filesystem>
+#include <gdal_priv.h>
 
 #include <QDebug>
 
@@ -17,7 +19,11 @@ find files of those names at the top level of this repository. **/
 #include "Message.h"
 #include "Pvl.h"
 
+namespace fs = std::filesystem;
+using json = nlohmann::json;
+using ordered_json = nlohmann::ordered_json;
 using namespace std;
+
 namespace Isis {
   /**
    * Constructs a Blob object using a name and type.
@@ -244,6 +250,33 @@ namespace Isis {
     }
   }
 
+  void Blob::ReadGdal(GDALDataset *dataset) {
+    try {
+      std::string key = QString(p_type + "_" + p_blobName).toStdString();
+      const char *jsonblobStr = dataset->GetMetadataItem(key.c_str(), "USGS");
+      if (jsonblobStr == nullptr) {
+        QString msg = "The key [" + QString::fromStdString(key) + "] does not exist on the geodata set.";
+        throw IException( IException::Io, msg, _FILEINFO_);
+      }
+      ordered_json jsonblob = ordered_json::parse(jsonblobStr);
+      std::string blobData = jsonblob[key]["Data"];
+      jsonblob[key].erase("Data");
+
+      Pvl pvl;
+      Pvl::readObject(pvl, jsonblob);
+
+      p_blobName = QString::fromStdString(jsonblob[key]["Name"]);
+      p_type = QString::fromStdString(jsonblob[key]["_container_name"]);
+
+      Find(pvl);
+      ReadData(blobData);
+    }
+    catch(exception &e) { 
+      QString msg = "Failed to read blob [" + p_blobName + "]: " + e.what();
+      throw IException(IException::Io, msg, _FILEINFO_);
+    }
+  }
+
   /**
    * This method reads Pvl values from a specified file.
    *
@@ -255,7 +288,7 @@ namespace Isis {
   void Blob::Read(const QString &file, const std::vector<PvlKeyword> keywords) {
     // Expand the filename
     QString temp(FileName(file).expanded());
-
+    
     // Get the pvl
     Pvl pvl;
     try {
@@ -351,7 +384,8 @@ namespace Isis {
   void Blob::ReadData(std::istream &stream) {
     // Read the binary data
     if (p_buffer != NULL) delete [] p_buffer;
-    p_buffer = new char[p_nbytes];
+    p_buffer = new char[p_nbytes + 1];
+    p_buffer[p_nbytes] = '\0';
 
     streampos sbyte = p_startByte - 1;
     stream.seekg(sbyte, std::ios::beg);
@@ -380,8 +414,16 @@ namespace Isis {
    * @param nbytes The amount of data in the buffer
    */
   void Blob::setData(const char *buffer, int nbytes) {
-    char *buf = new char[nbytes];
-    memcpy(buf, buffer, nbytes);
+    char *buf;
+    if (buffer[nbytes - 1] != '\0') {
+      buf = new char[nbytes + 1];
+      memcpy(buf, buffer, nbytes);
+      buf[nbytes] = '\0';
+    }
+    else {
+      buf = new char[nbytes];
+      memcpy(buf, buffer, nbytes);
+    }
     takeData(buf, nbytes);
   }
 
@@ -405,6 +447,46 @@ namespace Isis {
     p_buffer = buffer;
   }
 
+  void Blob::WriteGdal(GDALDataset *dataset) {
+    try {
+      WriteInit();
+      Pvl pvl;
+      pvl.addObject(p_blobPvl);
+      ostringstream os;
+      os << pvl << endl;
+      os.seekp(0, std::ios::end);
+
+      stringstream stream;
+      WriteData(stream);
+      PvlObject &blobObj = pvl.findObject(p_type);
+
+      blobObj["Bytes"] = toString(p_nbytes);
+      blobObj["StartByte"] = toString(1);
+      if(blobObj.hasKeyword("Data")) { 
+        blobObj["Data"] = QString::fromStdString(stream.str()); 
+      }
+      else { 
+        blobObj += PvlKeyword("Data", QString::fromStdString(stream.str()));
+      }
+
+      if(blobObj.hasKeyword("Name")) { 
+        blobObj["Name"] = p_blobName; 
+      }
+      else { 
+        blobObj += PvlKeyword("Name", p_blobName);
+      }
+
+      // update metadata
+      string jsonblobstr = pvl.toJson().dump();
+      string key = this->Type().toStdString() + "_" + this->Name().toStdString();
+      dataset->SetMetadataItem(key.c_str(), jsonblobstr.c_str(), "USGS");
+    }
+    catch(exception &e) {
+      cout << "Failed to write blob [" + p_blobName + "]: " << e.what() << endl;
+    }
+  }
+
+
   /**
    * Write the blob data out to a file.
    *
@@ -415,7 +497,7 @@ namespace Isis {
    * @throws IException::Io - Error creating file
    */
   void Blob::Write(const QString &file) {
-    // Determine the size of the label and write it out
+    // Determine the size of the label and write it out   
     try {
       WriteInit();
       Pvl pvl;
@@ -456,6 +538,7 @@ namespace Isis {
     }
   }
 
+
   /**
    * Write the blob data out to a Pvl object.
    * @param pvl The pvl object to update
@@ -464,10 +547,10 @@ namespace Isis {
    * the name of the file
    */
   void Blob::Write(Pvl &pvl, std::fstream &stm,
-                   const QString &detachedFileName, bool overwrite) {
+                   const QString &detachedFileName, bool overwrite, bool inline_data) {
+                    
     // Handle 64-bit I/O
     WriteInit();
-
     // Find out where they wanted to write the blob
     streampos sbyte = stm.tellp();
     sbyte += 1;
@@ -482,11 +565,9 @@ namespace Isis {
       p_blobPvl += PvlKeyword("^" + p_type, detachedFileName);
     }
 
-
     p_blobPvl["StartByte"] = toString((BigInt)sbyte);
     p_blobPvl["Bytes"] = toString(p_nbytes);
 
-
     // See if the blob is already in the file
     bool found = false;
     if (overwrite) {
@@ -570,6 +651,55 @@ namespace Isis {
   }
 
 
+  /**
+   * Writes blob data to a stream
+   *
+   * @param stream Output stream blob data will be written to
+   *
+   * @throws IException::Io - Error writing data to stream
+   */
+  void Blob::WriteData(std::stringstream &stream) {
+    stream << std::hex << std::setfill('0');
+    int copy = 0;
+    for (int i = 0; i < p_nbytes; ++i) {
+      memcpy(&copy, &(p_buffer[i]), 1);
+      stream << std::setw(2) << copy;
+    }
+
+    if (!stream.good()) {
+      QString msg = "Error writing data to " + p_type + " [" + p_blobName + "]";
+      throw IException(IException::Io, msg, _FILEINFO_);
+    }
+  }
+
+
+  /**
+   * Writes blob data to a stream
+   *
+   * @param stream Output steam blob data will be written to
+   *
+   * @throws IException::Io - Error writing data to stream
+   */
+  void Blob::ReadData(string &hexdata) {
+    // Read the binary data
+    if (p_buffer != NULL) delete [] p_buffer;
+    p_buffer = new char[p_nbytes + 1];
+    p_buffer[p_nbytes] = '\0';
+
+    // Loop through the hex string and bytes, hex is two characters at a time 
+    for (size_t i=0,j=0; i < p_nbytes; i++,j+=2) {
+        string byteString = hexdata.substr(j, 2);
+  
+        char byteValue = static_cast<char>( 
+            stoi(byteString, nullptr, 16));
+  
+        // Add the byte to the byte array 
+        p_buffer[i] = byteValue;
+    }
+  }
+
+
+
   /**
    * Checks pvl object and returns whether or not it is a Blob
    *
diff --git a/isis/src/base/objs/Blob/Blob.h b/isis/src/base/objs/Blob/Blob.h
index 1abf7828e4fada4476670b54a980ee41e5fbc846..f431171d0327dffc880aa5d9e662e09e3e25b1c4 100644
--- a/isis/src/base/objs/Blob/Blob.h
+++ b/isis/src/base/objs/Blob/Blob.h
@@ -11,6 +11,8 @@ find files of those names at the top level of this repository. **/
 #include <QList>
 #include <QPair>
 
+#include "gdal_priv.h"
+
 #include "PvlObject.h"
 
 namespace Isis {
@@ -70,11 +72,13 @@ namespace Isis {
                 const std::vector<PvlKeyword> keywords = std::vector<PvlKeyword>());
       virtual void Read(const Pvl &pvl, std::istream &is,
                         const std::vector<PvlKeyword> keywords = std::vector<PvlKeyword>());
+      void ReadData(std::string &hexdata);
 
       void Write(const QString &file);
       void Write(Pvl &pvl, std::fstream &stm,
-                 const QString &detachedFileName = "", bool overwrite=true);
-
+                 const QString &detachedFileName = "", bool overwrite=true, bool inline_data=true);
+      void WriteGdal(GDALDataset *dataset);
+      void ReadGdal(GDALDataset *dataset);
 
       char *getBuffer();
       void setData(const char *buffer, int nbytes);
@@ -86,6 +90,7 @@ namespace Isis {
       virtual void ReadData(std::istream &is);
       virtual void WriteInit();
       virtual void WriteData(std::fstream &os);
+      virtual void WriteData(std::stringstream &os);
 
       PvlObject p_blobPvl;     //!< Pvl Blob object
       QString p_blobName;  //!< Name of the Blob object
diff --git a/isis/src/base/objs/Blob/Blob.truth b/isis/src/base/objs/Blob/Blob.truth
deleted file mode 100644
index 0079631fb369a650f9284205b243f457be6bc6d1..0000000000000000000000000000000000000000
--- a/isis/src/base/objs/Blob/Blob.truth
+++ /dev/null
@@ -1,27 +0,0 @@
-Testing Blob(name, type) constructor and Write(filename) method...
-Name = UnitTest
-Number of Bytes = 4
-StartByte = 150
-Type = Blob
-
-Testing Blob(name, type, file) constructor...
-Name = UNITtest
-Number of Bytes = 4
-StartByte = 150
-Type = Blob
-ABCD
-
-Testing writing into existing space...
-Testing Write(pvl, stream)...
-Name = UNITtest
-Number of Bytes = 3
-StartByte = 150
-Type = Blob
-
-Testing writing over existing space at the end of file...
-Testing Write(pvl, stream)...
-Name = UNITtest
-Number of Bytes = 4
-StartByte = 150
-Type = Blob
-
diff --git a/isis/src/base/objs/Blob/unitTest.cpp b/isis/src/base/objs/Blob/unitTest.cpp
deleted file mode 100644
index 56113ace38951da7f56ee740e5f85c13caca3b81..0000000000000000000000000000000000000000
--- a/isis/src/base/objs/Blob/unitTest.cpp
+++ /dev/null
@@ -1,100 +0,0 @@
-/** This is free and unencumbered software released into the public domain.
-The authors of ISIS do not claim copyright on the contents of this file.
-For more details about the LICENSE terms and the AUTHORS, you will
-find files of those names at the top level of this repository. **/
-
-/* SPDX-License-Identifier: CC0-1.0 */
-#include <iostream>
-#include "Blob.h"
-#include "IException.h"
-#include "IString.h"
-#include "Preference.h"
-
-using namespace Isis;
-using namespace std;
-
-class MyBlob : public Isis::Blob {
-  public:
-    MyBlob(const QString &name) : Isis::Blob(name, "Blob") {};
-    MyBlob(const QString &name, const QString &file) : Isis::Blob(name, "Blob", file) {};
-    void MyBuf(char *buf, int size) {
-      p_buffer = new char[size];
-      p_nbytes = size;
-      memcpy(p_buffer, buf, size);
-    };
-    int StartByte() {
-      return p_startByte;
-    };
-    void GetBuf(char *buf) {
-      memcpy(buf, p_buffer, p_nbytes);
-    }
-};
-
-int main(int argc, char *argv[]) {
-  Isis::Preference::Preferences(true);
-  try {
-    cout << "Testing Blob(name, type) constructor and Write(filename) method..." << endl;
-    MyBlob b("UnitTest");
-    char buf[] = {"ABCD"};
-    b.MyBuf(buf, 4);
-    b.Write("junk");
-    cout << "Name = " << b.Name() << endl;
-    cout << "Number of Bytes = " << b.Size() << endl;
-    cout << "StartByte = " << b.StartByte() << endl;
-    cout << "Type = " << b.Type() << endl;
-    cout << endl;
-
-    cout << "Testing Blob(name, type, file) constructor..." << endl;
-    MyBlob c("UNITtest", "junk");
-    char buf2[5];
-    c.GetBuf(buf2);
-    buf2[4] = 0;
-    cout << "Name = " << c.Name() << endl;
-    cout << "Number of Bytes = " << c.Size() << endl;
-    cout << "StartByte = " << c.StartByte() << endl;
-    cout << "Type = " << c.Type() << endl;
-    cout << buf2 << endl;
-    cout << endl;
-
-    // Test writing into existing space
-    cout << "Testing writing into existing space..." << endl;
-    cout << "Testing Write(pvl, stream)..." << endl;
-    Isis::Pvl pvl("junk");
-    fstream strm;
-    strm.open("junk", std::ios::binary | std::ios::out);
-    c.MyBuf(buf, 3);
-    c.Write(pvl, strm);
-    strm.seekp(0, std::ios::beg);
-    strm << pvl;
-    cout << "Name = " << c.Name() << endl;
-    cout << "Number of Bytes = " << c.Size() << endl;
-    cout << "StartByte = " << c.StartByte() << endl;
-    cout << "Type = " << c.Type() << endl;
-    cout << endl;
-    strm.close();
-
-    // Test writing over existing space at the end of file
-    cout << "Testing writing over existing space at the end of file..." << endl;
-    cout << "Testing Write(pvl, stream)..." << endl;
-    Isis::Pvl pvl2("junk");
-    fstream strm2;
-    strm2.open("junk", std::ios::binary | std::ios::out);
-    c.MyBuf(buf, 4);
-    c.Write(pvl2, strm2);
-    strm2.seekp(0, std::ios::beg);
-    strm2 << pvl2;
-    cout << "Name = " << c.Name() << endl;
-    cout << "Number of Bytes = " << c.Size() << endl;
-    cout << "StartByte = " << c.StartByte() << endl;
-    cout << "Type = " << c.Type() << endl;
-    cout << endl;
-    strm2.close();
-
-    remove("junk");
-
-  }
-  catch(Isis::IException &e) {
-    e.print();
-  }
-}
-
diff --git a/isis/src/base/objs/Brick/Brick.cpp b/isis/src/base/objs/Brick/Brick.cpp
index 630458f0d0a3f04cb681ac6b0b5f360abf419604..c0bb72a675087be5d5b9a0883682a1ce7e64d6e8 100644
--- a/isis/src/base/objs/Brick/Brick.cpp
+++ b/isis/src/base/objs/Brick/Brick.cpp
@@ -22,7 +22,27 @@ namespace Isis {
     p_nsamps = nsamps;
     p_nlines = nlines;
     p_nbands = nbands;
-    p_npixels = p_nsamps * p_nlines * p_nbands;
+    if(p_nsamps <= 0) {
+      std::string message = "Invalid value for sample dimensions (nsamps)";
+      throw IException(IException::Programmer, message, _FILEINFO_);
+    }
+    if(p_nlines <= 0) {
+      std::string message = "Invalid value for line dimensions (nlines)";
+      throw IException(IException::Programmer, message, _FILEINFO_);
+    }
+    if(p_nbands <= 0) {
+      std::string message = "Invalid value for band dimensions (nbands)";
+      throw IException(IException::Programmer, message, _FILEINFO_);
+    }
+    if (int(p_nsamps * p_scale) <= 0) {
+      std::string message = "Scaleing [" + std::to_string(p_nsamps) + "] by [" + std::to_string(p_scale) + "] resulted in 0 samples in the buffer";
+      throw IException(IException::Programmer, message, _FILEINFO_);
+    }
+    if (int(p_nlines * p_scale) <= 0) {
+      std::string message = "Scaleing [" + std::to_string(p_nlines) + "] by [" + std::to_string(p_scale) + "] resulted in 0 lines in the buffer";
+      throw IException(IException::Programmer, message, _FILEINFO_);
+    }
+    p_npixels = (int(p_nsamps * p_scale) * int(p_nlines * p_scale)) * p_nbands;
     Allocate();
   }
 
diff --git a/isis/src/base/objs/Brick/Brick.h b/isis/src/base/objs/Brick/Brick.h
index 29ce6c54ccdee36dd7952b6beed6078bbf6b9092..25494657e436fb9b6bd5690ca0e05d7e41416509 100644
--- a/isis/src/base/objs/Brick/Brick.h
+++ b/isis/src/base/objs/Brick/Brick.h
@@ -58,9 +58,9 @@ namespace Isis {
        *             bands first, then lines, then samples.
        */
       Brick(const int nsamps, const int nlines, const int nbands,
-            const Isis::PixelType type, bool reverse=false) :
+            const Isis::PixelType type, bool reverse=false, double scale=1.0) :
         Isis::BufferManager(nsamps, nlines, nbands,
-                            nsamps, nlines, nbands, type, reverse) {
+                            nsamps, nlines, nbands, type, reverse, scale) {
       };
 
       /**
@@ -78,10 +78,10 @@ namespace Isis {
        */
       Brick(const Isis::Cube &cube, const int &bufNumSamples,
             const int &bufNumLines, const int &bufNumBands,
-            bool reverse=false) :
+            bool reverse=false, double scale=1.0) :
         Isis::BufferManager(cube.sampleCount(), cube.lineCount(),
                             cube.bandCount(), bufNumSamples, bufNumLines,
-                            bufNumBands, cube.pixelType(), reverse) {
+                            bufNumBands, cube.pixelType(), reverse, scale) {
       };
 
       /**
@@ -103,9 +103,9 @@ namespace Isis {
        */
       Brick(int maxSamples, int maxLines, int maxBands, int bufNumSamples,
             int bufNumLines, int bufNumBands, Isis::PixelType type,
-            bool reverse=false) :
+            bool reverse=false, double scale=1.0) :
         Isis::BufferManager(maxSamples, maxLines, maxBands, bufNumSamples,
-                            bufNumLines, bufNumBands, type, reverse) {
+                            bufNumLines, bufNumBands, type, reverse, scale) {
       };
 
     public:
diff --git a/isis/src/base/objs/Buffer/Buffer.cpp b/isis/src/base/objs/Buffer/Buffer.cpp
index 0757c99bc306afba8fe0ecc3ef814b9df679a877..cf00d406e7ec638bd3d0de6180b1aa5ea61a532e 100644
--- a/isis/src/base/objs/Buffer/Buffer.cpp
+++ b/isis/src/base/objs/Buffer/Buffer.cpp
@@ -5,10 +5,11 @@ find files of those names at the top level of this repository. **/
 
 /* SPDX-License-Identifier: CC0-1.0 */
 
-#include "PixelType.h"
 #include "Buffer.h"
 #include "IException.h"
 #include "Message.h"
+#include "PixelType.h"
+#include "SpecialPixel.h"
 
 #include <iostream>
 
@@ -24,7 +25,7 @@ namespace Isis {
    */
   Buffer::Buffer() : p_sample(0), p_nsamps(0), p_line(0), p_nlines(0),
     p_band(0), p_nbands(0), p_npixels(0), p_buf(0),
-    p_pixelType(None), p_rawbuf(0) { }
+    p_pixelType(None), p_rawbuf(0), p_scale(1) { }
 
 
   /**
@@ -39,9 +40,11 @@ namespace Isis {
    * @throws Isis::iException::Programmer - Invalid value for a dimension
    */
   Buffer::Buffer(const int nsamps, const int nlines,
-                 const int nbands, const Isis::PixelType type) :
+                 const int nbands, const Isis::PixelType type, 
+                 const double scale) :
     p_nsamps(nsamps), p_nlines(nlines),
-    p_nbands(nbands), p_pixelType(type) {
+    p_nbands(nbands), p_pixelType(type),
+    p_scale(scale) {
 
     p_sample = p_line = p_band = 0;
 
@@ -57,8 +60,20 @@ namespace Isis {
       string message = "Invalid value for band dimensions (nbands)";
       throw IException(IException::Programmer, message, _FILEINFO_);
     }
+    if(p_scale <= 0 || scale > 1) {
+      string message = "Invalid value for scale (scale)";
+      throw IException(IException::Programmer, message, _FILEINFO_);
+    }
+    p_nsampsScaled = int(p_nsamps * p_scale);
+    if (p_nsampsScaled <= 0) {
+      p_nsampsScaled = 1;
+    }
+    p_nlinesScaled = int(p_nlines * p_scale);
+    if (p_nlinesScaled <= 0) {
+      p_nlinesScaled = 1;
+    }
 
-    p_npixels = p_nsamps * p_nlines * p_nbands;
+    p_npixels = (p_nsampsScaled * p_nlinesScaled) * p_nbands;
 
     Allocate();
   }
@@ -111,6 +126,29 @@ namespace Isis {
     SetBaseBand(start_band);
   }
 
+  /**
+   * This method is used to print out a buffer
+   *
+   * @param os Output stream to write to
+   * @param buffer The buffer to write to the output stream
+   *
+   * @return The updated output stream
+   */
+  std::ostream &operator<<(std::ostream &os, Buffer &buffer) {
+    for(int i = 1; i < buffer.size() + 1; i++) {
+      os << buffer[i - 1];
+
+      if ((i % buffer.SampleDimensionScaled()) == 0) {
+        os << std::endl;
+      }
+      if ((i % (buffer.SampleDimensionScaled() * buffer.LineDimensionScaled())) == 0) {
+        os << std::endl;
+      }
+    }
+
+    return os;
+  }
+
 
   /**
    * Returns the sample position associated with a shape buffer index.  The
@@ -212,9 +250,10 @@ namespace Isis {
     }
 
     //  Got a valid reference location so compute the index and return
-    int index = (i_band - p_band) * (p_nlines * p_nsamps) +
-                (i_line - p_line) * (p_nsamps) +
-                (i_samp - p_sample);
+    int bandOffset = (i_band - p_band) * (p_nsampsScaled * p_nlinesScaled);
+    int lineOffset = (int((i_line - p_line) * p_scale) % p_nlinesScaled) * p_nsampsScaled;
+    int sampleOffset = (int((i_samp - p_sample) * p_scale));
+    int index = bandOffset + lineOffset + sampleOffset;
     return (index);
   }
 
@@ -283,7 +322,45 @@ namespace Isis {
    * @return The operation was successful (the buffers overlapped)
    */
   bool Buffer::CopyOverlapFrom(const Buffer &in) {
-    bool isSubareaOfIn = (p_npixels <= in.size());
+    bool isSubareaOfIn = true;
+
+    // If one rectangle is on left side of other
+    // if (l1.x > r2.x || l2.x > r1.x)
+    if (p_line > in.p_line + in.p_nlines ||
+        in.p_line > p_line + p_nlines)
+      isSubareaOfIn = false;
+
+    // If one rectangle is above other
+    // if (r1.y > l2.y || r2.y > l1.y)
+    if (p_sample + p_nsamps < in.p_sample ||
+        in.p_sample + in.p_nsamps < p_sample)
+      isSubareaOfIn = false;
+
+    if (isSubareaOfIn) {
+      int topLine = max(in.p_line, p_line);
+      int topSamp = max(in.p_sample, p_sample);
+
+      int bottomLine = min(in.p_line + in.p_nlines, p_line + p_nlines);
+      int bottomSamp = min(in.p_sample + in.p_nsamps, p_sample + p_nsamps);
+
+      int firstBand = max(in.p_band, p_band);
+      int lastBand = min(in.p_band + in.p_nbands, p_band + p_nbands);
+
+      for (int b = firstBand; b < lastBand; b++) {
+        for (int i = topLine; i < bottomLine; i++) {
+          for (int j = topSamp; j < bottomSamp; j++) {
+            try {
+              (*this)[Index(j, i, b)] = in[in.Index(j, i, b)];
+            }
+            catch(...) {
+              (*this)[Index(j, i, b)] = NULL8;
+            }
+          }
+        }
+      }
+    }
+
+    isSubareaOfIn = (p_npixels <= in.size());
     isSubareaOfIn &= (p_sample >= in.p_sample);
     isSubareaOfIn &= (p_line >= in.p_line);
     isSubareaOfIn &= (p_band >= in.p_band);
@@ -301,12 +378,6 @@ namespace Isis {
     isSubareaOfIn &= (endLine <= otherEndLine);
     isSubareaOfIn &= (endBand <= otherEndBand);
 
-    if (isSubareaOfIn) {
-      for (int i = 0; i < size(); i++) {
-        (*this)[i] = in[in.Index(Sample(i), Line(i), Band(i))];
-      }
-    }
-
     return isSubareaOfIn;
   }
 
@@ -322,10 +393,13 @@ namespace Isis {
     p_nbands(rhs.p_nbands), p_pixelType(rhs.p_pixelType) {
 
     p_sample = rhs.p_sample;
+    p_nsampsScaled = rhs.p_nsampsScaled;
     p_line = rhs.p_line;
+    p_nlinesScaled = rhs.p_nlinesScaled;
     p_band = rhs.p_band;
 
     p_npixels = rhs.p_npixels;
+    p_scale = rhs.p_scale;
 
     Allocate();
     Copy(rhs);
@@ -364,5 +438,6 @@ namespace Isis {
       QString message = Message::MemoryAllocationFailed();
       throw IException(IException::Unknown, message, _FILEINFO_);
     }
+    (*this) = NULL8;
   }
 }
diff --git a/isis/src/base/objs/Buffer/Buffer.h b/isis/src/base/objs/Buffer/Buffer.h
index f0e52986024ee31fec29a9d3837bdfe49232fc7c..90f2d0918cf855b3b6f7a1d7ac775baa08d09a95 100644
--- a/isis/src/base/objs/Buffer/Buffer.h
+++ b/isis/src/base/objs/Buffer/Buffer.h
@@ -54,7 +54,7 @@ namespace Isis {
     public:
       Buffer();
       Buffer(const int nsamps, const int nlines, const int nbands,
-             const Isis::PixelType type);
+             const Isis::PixelType type, const double scale = 1);
 
       ~Buffer();
 
@@ -80,6 +80,24 @@ namespace Isis {
         return (p_nlines);
       }
 
+      /**
+       * Returns the number of samples in the shape buffer
+       *
+       * @return int
+       */
+      inline int SampleDimensionScaled() const {
+        return (p_nsampsScaled);
+      }
+
+      /**
+       * Returns the number of lines in the shape buffer
+       *
+       * @return int
+       */
+      inline int LineDimensionScaled() const {
+        return (p_nlinesScaled);
+      }
+
       /**
        * Returns the number of bands in the shape buffer
        *
@@ -89,6 +107,15 @@ namespace Isis {
         return (p_nbands);
       }
 
+      /**
+       * Returns the scale of the shape buffer
+       *
+       * @return double
+       */
+      inline double scale() const {
+        return (p_scale);
+      }
+
       /**
        * Returns the total number of pixels in the shape buffer
        *
@@ -161,10 +188,12 @@ namespace Isis {
         return p_pixelType;
       };
 
-    protected:
       void SetBasePosition(const int start_sample, const int start_line,
                            const int start_band);
 
+      friend std::ostream &operator<<(std::ostream &os, Buffer &buffer);
+
+    protected:
       /**
        * This method is used to set the base sample position of the shape buffer.
        *
@@ -197,20 +226,25 @@ namespace Isis {
 
       int p_sample;   //!< Starting sample to read/write
       int p_nsamps;   //!< Number of samples to read/write
+      int p_nsampsScaled;
 
       int p_line;     //!< Starting line to read/write
       int p_nlines;   //!< Number of lines to read/write
+      int p_nlinesScaled;
 
       int p_band;     //!< Starting band to read/write
       int p_nbands;   //!< Number of bands to read/write
 
-      int p_npixels;  //!< Number of pixels (nsamps * nlines * nbands)
+      int p_npixels;  //!< Number of pixels (nsamps * nlines * scale ** 2) * bands
       double *p_buf;  /**< Shape buffer allocated to the size of npixels for
                            handling reads/writes*/
 
       const Isis::PixelType p_pixelType;  //!< The pixel type of the raw buffer
       void *p_rawbuf;                     //!< The raw dm read from the disk
 
+      // Might need x and y scale
+      double p_scale; //!< Amount to scale the buffers lines and samples, defaults to 1
+
       void Allocate();
 
       /**
diff --git a/isis/src/base/objs/Buffer/Buffer.truth b/isis/src/base/objs/Buffer/Buffer.truth
deleted file mode 100644
index 29652d23140ebd0440fe021a25947b139a14fba6..0000000000000000000000000000000000000000
--- a/isis/src/base/objs/Buffer/Buffer.truth
+++ /dev/null
@@ -1,53 +0,0 @@
-Isis::Buffer Unit Test
-
-SampleDimension:  4
-LineDimension:    3
-BandDimension:    2
-Size:             24
-
-Sample():         0
-Line():           0
-Band():           0
-Position:         0 0 0
-Index:            0
-
-at(0):            0
-at(10):           10
-at(23):           23
-b[0]:             0
-b[10]:            10
-b[23]:            23
-
-Sample():         3
-Line():           2
-Band():           1
-Position:         3 2 1
-Index:            0
-
-Sample(16):       3
-Line(16):         3
-Band(16):         2
-Position:         3 3 2
-Index:            16
-
-Copy constructor: Worked
-SampleDimension:  4
-LineDimension:    3
-BandDimension:    2
-Size:             24
-a[0]:             0
-a[23]:            23
-
-PixelType =       SignedInteger
-
-Null Buffer size: 0
-
-Test assignment operator for a constant...
-d.size():         8
-d[0]:             999
-d[2]:             999
-d[n]:             999
-
-**PROGRAMMER ERROR** Array subscript [-1] is out of array bounds.
-**PROGRAMMER ERROR** Array subscript [24] is out of array bounds.
-**PROGRAMMER ERROR** Input and output buffers are not the same pixel type.
diff --git a/isis/src/base/objs/Buffer/unitTest.cpp b/isis/src/base/objs/Buffer/unitTest.cpp
deleted file mode 100644
index 2babf6da8e240686cd49b5221e465bc631650d23..0000000000000000000000000000000000000000
--- a/isis/src/base/objs/Buffer/unitTest.cpp
+++ /dev/null
@@ -1,134 +0,0 @@
-/** This is free and unencumbered software released into the public domain.
-The authors of ISIS do not claim copyright on the contents of this file.
-For more details about the LICENSE terms and the AUTHORS, you will
-find files of those names at the top level of this repository. **/
-
-/* SPDX-License-Identifier: CC0-1.0 */
-#include <iostream>
-#include "Buffer.h"
-#include "IException.h"
-#include "Preference.h"
-
-using namespace std;
-using namespace Isis;
-
-class Test : public Buffer {
-  public:
-    Test(int s, int l, int b) : Isis::Buffer(s, l, b, Isis::SignedInteger) {};
-    ~Test() {};
-    void print();
-};
-
-void Test::print() {
-  SetBasePosition(3, 2, 1);
-  cout << "Sample():         " << Sample() << endl;
-  cout << "Line():           " << Line() << endl;
-  cout << "Band():           " << Band() << endl;
-
-  int samp, line, band;
-  Position(0, samp, line, band);
-  cout << "Position:         " << samp << " " << line << " " << band
-       << endl;
-  cout << "Index:            " << Index(samp, line, band) << endl << endl;
-
-  cout << "Sample(16):       " << Sample(16) << endl;
-  cout << "Line(16):         " << Line(16) << endl;
-  cout << "Band(16):         " << Band(16) << endl;
-
-  Position(16, samp, line, band);
-  cout << "Position:         " << samp << " " << line << " " << band
-       << endl;
-  cout << "Index:            " << Index(samp, line, band) << endl << endl;
-}
-
-int main(int argc, char *argv[]) {
-  Isis::Preference::Preferences(true);
-  cout << "Isis::Buffer Unit Test" << endl << endl;
-
-  Test b(4, 3, 2);
-  for(int i = 0; i < b.size(); i++) {
-    b[i] = i;
-  }
-
-  cout << "SampleDimension:  " << b.SampleDimension() << endl;
-  cout << "LineDimension:    " << b.LineDimension() << endl;
-  cout << "BandDimension:    " << b.BandDimension() << endl;
-  cout << "Size:             " << b.size() << endl << endl;
-
-  cout << "Sample():         " << b.Sample() << endl;
-  cout << "Line():           " << b.Line() << endl;
-  cout << "Band():           " << b.Band() << endl;
-
-  int samp, line, band;
-  b.Position(0, samp, line, band);
-  cout << "Position:         " << samp << " " << line << " " << band
-       << endl;
-  cout << "Index:            " << b.Index(samp, line, band) << endl << endl;
-
-  cout << "at(0):            " << b.at(0) << endl;
-  cout << "at(10):           " << b.at(10) << endl;
-  cout << "at(23):           " << b.at(23) << endl;
-  cout << "b[0]:             " << b[0] << endl;
-  cout << "b[10]:            " << b[10] << endl;
-  cout << "b[23]:            " << b[23] << endl << endl;
-
-  b.print();
-
-  Test a = b;
-  if(a.DoubleBuffer() != b.DoubleBuffer()) {
-    cout << "Copy constructor: Worked" << endl;
-  }
-  else {
-    cout << "Copy constructor: Failed" << endl;
-  }
-  cout << "SampleDimension:  " << a.SampleDimension() << endl;
-  cout << "LineDimension:    " << a.LineDimension() << endl;
-  cout << "BandDimension:    " << a.BandDimension() << endl;
-  cout << "Size:             " << a.size() << endl ;
-  cout << "a[0]:             " << a[0] << endl;
-  cout << "a[23]:            " << a[23] << endl << endl;
-
-  cout << "PixelType =       " << Isis::PixelTypeName(a.PixelType()) << endl;
-  cout << endl;
-
-  //  Test new default constructor.   Enclose in braces so destructor is tested
-  {
-    Buffer nullbuf;
-    cout << "Null Buffer size: " << nullbuf.size() << endl << endl;
-  }
-
-  // Test assignment operator
-  cout << "Test assignment operator for a constant...\n";
-  Buffer d(2, 2, 2, Double);
-  d = 999.0;
-  cout << "d.size():         " << d.size() << endl;
-  cout << "d[0]:             " << d[0] << endl;
-  cout << "d[2]:             " << d[2] << endl;
-  cout << "d[n]:             " << d[d.size()-1] << endl << endl;
-
-  try {
-    a.at(-1);
-  }
-  catch(Isis::IException &e) {
-    e.print();
-  }
-
-  try {
-    a.at(24);
-  }
-  catch(Isis::IException &e) {
-    e.print();
-  }
-
-  try {
-    Buffer a2(2, 2, 2, Isis::SignedWord);
-    Buffer b2(2, 2, 2, Isis::SignedByte);
-
-    a2.Copy(b2);
-  }
-  catch(Isis::IException &e) {
-    e.print();
-  }
-
-  return 0;
-}
diff --git a/isis/src/base/objs/BufferManager/BufferManager.cpp b/isis/src/base/objs/BufferManager/BufferManager.cpp
index 7b2ff2fb24cfa3e48035f8943312c6d5f816bddc..3da87df37f6d79e877b4b49f7b970148746ff7f3 100644
--- a/isis/src/base/objs/BufferManager/BufferManager.cpp
+++ b/isis/src/base/objs/BufferManager/BufferManager.cpp
@@ -44,8 +44,9 @@ namespace Isis {
   BufferManager::BufferManager(int maxsamps, int maxlines,
                                int maxbands, int bufsamps,
                                int buflines, int bufbands,
-                               Isis::PixelType type, bool reverse) :
-                                   Isis::Buffer(bufsamps, buflines, bufbands, type),
+                               Isis::PixelType type, bool reverse, 
+                               double scale) :
+                                   Isis::Buffer(bufsamps, buflines, bufbands, type, scale),
                                    p_maxSamps(maxsamps), p_maxLines(maxlines),
                                    p_maxBands(maxbands) {
     SetIncrements(bufsamps, buflines, bufbands);
diff --git a/isis/src/base/objs/BufferManager/BufferManager.h b/isis/src/base/objs/BufferManager/BufferManager.h
index b034f146e2e0a1f03fd4712106a10aaeb9268e2e..746599704c463efddb59bf339ab94bbf28a58f96 100644
--- a/isis/src/base/objs/BufferManager/BufferManager.h
+++ b/isis/src/base/objs/BufferManager/BufferManager.h
@@ -56,7 +56,8 @@ namespace Isis {
       BufferManager();
       BufferManager(int maxsamps, int maxlines, int maxbands,
                     int bufsamps, int buflines, int bufbands,
-                    Isis::PixelType type, bool reverse = false);
+                    Isis::PixelType type, bool reverse = false,
+                    double scale = 1.0);
       BufferManager(const BufferManager &other);
 
       //! Destroys the BufferManager object
diff --git a/isis/src/base/objs/Cube/Cube.cpp b/isis/src/base/objs/Cube/Cube.cpp
index c4206bf0c15ca4b9c2ff1223e98a6e5980a23088..9c842ba8773cf5e77b9eb19bd2af555d136766b0 100644
--- a/isis/src/base/objs/Cube/Cube.cpp
+++ b/isis/src/base/objs/Cube/Cube.cpp
@@ -25,6 +25,7 @@ find files of those names at the top level of this repository. **/
 #include "CubeStretch.h"
 #include "Endian.h"
 #include "FileName.h"
+#include "GdalIoHandler.h"
 #include "History.h"
 #include "ImageHistogram.h"
 #include "ImagePolygon.h"
@@ -42,12 +43,13 @@ find files of those names at the top level of this repository. **/
 #include "TProjection.h"
 #include "Longitude.h"
 
-
 using namespace std;
 
 namespace Isis {
   //! Constructs a Cube object.
   Cube::Cube() {
+    GDALAllRegister();
+    CPLSetErrorHandler(CPLQuietErrorHandler);
     construct();
   }
 
@@ -60,6 +62,8 @@ namespace Isis {
    *     "r" or read-write "rw".
    */
   Cube::Cube(const FileName &fileName, QString access) {
+    GDALAllRegister();
+    CPLSetErrorHandler(CPLQuietErrorHandler);
     construct();
     open(fileName.toString(), access);
   }
@@ -74,6 +78,8 @@ namespace Isis {
    *     "r" or read-write "rw".
    */
   void Cube::fromLabel(const FileName &fileName, Pvl &label, QString access) {
+    GDALAllRegister();
+    CPLSetErrorHandler(CPLQuietErrorHandler);
     initCoreFromLabel(label);
     create(fileName.expanded());
 
@@ -97,6 +103,8 @@ namespace Isis {
    *     "r" or read-write "rw".
    */
   void Cube::fromIsd(const FileName &fileName, Pvl &label, nlohmann::json &isd, QString access) {
+    GDALAllRegister();
+    CPLSetErrorHandler(CPLQuietErrorHandler);
     fromLabel(fileName, label, access);
 
     PvlGroup &instGrp = label.findGroup("Instrument", Pvl::Traverse);
@@ -220,8 +228,18 @@ namespace Isis {
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
-    if ((m_labelFile->openMode() & QIODevice::ReadWrite) != QIODevice::ReadWrite)
-      readOnly = true;
+    if (m_labelFile) {
+      if ((m_labelFile->openMode() & QIODevice::ReadWrite) != QIODevice::ReadWrite)
+        readOnly = true;
+    }
+    else if (m_geodataSet) {
+      if (m_geodataSet->GetAccess() == GA_ReadOnly)
+        readOnly = true;
+    }
+    else {
+      QString msg = "No source files open";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
 
     return readOnly;
   }
@@ -247,7 +265,7 @@ namespace Isis {
    *
    * @returns True for attached labels, false for detached
    */
-  bool Cube::labelsAttached() const {
+  Cube::LabelAttachment Cube::labelsAttached() const {
     return m_attached;
   }
 
@@ -284,15 +302,15 @@ namespace Isis {
 
 
     Cube *result = new Cube;
+    result->setLabelsAttached(newFileAttributes.labelAttachment());
 
-    if (newFileAttributes.labelAttachment() != ExternalLabel) {
+    if (result->labelsAttached() != ExternalLabel) {
       result->setDimensions(sampleCount(), lineCount(), bandCount());
       result->setByteOrder(newFileAttributes.byteOrder());
       result->setFormat(newFileAttributes.fileFormat());
 
-      if (newFileAttributes.labelAttachment() == DetachedLabel) {
-        result->setLabelsAttached(false);
-      }
+      // if (newFileAttributes.labelAttachment() == DetachedLabel) {
+      // }
 
       if (newFileAttributes.propagatePixelType()) {
         result->setPixelType(pixelType());
@@ -413,7 +431,7 @@ namespace Isis {
    *      is forced). Environment variables in the filename will be
    *      automatically expanded as well.
    */
-  void Cube::create(const QString &cubeFileName) {
+  void Cube::create(const QString &imageFileName) {
     // Already opened?
     if (isOpen()) {
       string msg = "You already have a cube opened";
@@ -430,11 +448,11 @@ namespace Isis {
     if (m_pixelType == None) {
       throw IException(IException::Unknown,
           QString("Cannot create the cube [%1] with a pixel type set to None")
-            .arg(cubeFileName),
+            .arg(imageFileName),
           _FILEINFO_);
     }
 
-    if (m_storesDnData) {
+    if (labelsAttached() == LabelAttachment::AttachedLabel) {
       // Make sure the cube is not going to exceed the maximum size preference
       BigInt size = (BigInt)m_samples * m_lines *
                     (BigInt)m_bands * (BigInt)SizeOf(m_pixelType);
@@ -450,7 +468,7 @@ namespace Isis {
 
       if (size > maxSizePreference) {
         QString msg;
-        msg += "The cube you are attempting to create [" + cubeFileName + "] is ["
+        msg += "The cube you are attempting to create [" + imageFileName + "] is ["
                + toString(size) + "GB]. This is larger than the current allowed "
                "size of [" + toString(maxSizePreference) + "GB]. The cube "
                "dimensions were (S,L,B) [" + toString(m_samples) + ", " +
@@ -465,59 +483,106 @@ namespace Isis {
     }
 
     // Expand output name
-    FileName cubFile(cubeFileName);
+    FileName imageFile(imageFileName);
     PvlObject isiscube("IsisCube");
     PvlObject core("Core");
 
-    if (m_storesDnData) {
-      cubFile = cubFile.addExtension("cub");
+    // Create the size of the core
+    PvlGroup dims("Dimensions");
+    dims += PvlKeyword("Samples", toString(m_samples));
+    dims += PvlKeyword("Lines", toString(m_lines));
+    dims += PvlKeyword("Bands", toString(m_bands));
+
+    // Create the pixel type
+    PvlGroup ptype("Pixels");
+    ptype += PvlKeyword("Type", PixelTypeName(m_pixelType));
+
+    // And the byte ordering
+    ptype += PvlKeyword("ByteOrder", ByteOrderName(m_byteOrder));
+    ptype += PvlKeyword("Base", toString(m_base));
+    ptype += PvlKeyword("Multiplier", toString(m_multiplier));
+
+    if (labelsAttached() != LabelAttachment::ExternalLabel) {
+      if (format() !=  Format::GTiff) {
+        imageFile = imageFile.addExtension("cub");
+      }
+      else if (format() == Format::GTiff) {
+        imageFile = imageFile.addExtension("tiff");
+      }
+      else {
+        QString msg = "Unknown format type [" + toString(format()) + "]";
+        throw IException(IException::Programmer, msg, _FILEINFO_);
+      }
+
+      m_dataFileName = new FileName(imageFile);
 
       // See if we have attached or detached labels
-      if (m_attached) {
+      if (labelsAttached() == LabelAttachment::AttachedLabel) {
         // StartByte is 1-based (why!!) so we need to do + 1
         core += PvlKeyword("StartByte", toString(m_labelBytes + 1));
-        m_labelFileName = new FileName(cubFile);
-        m_dataFileName = new FileName(cubFile);
+        m_labelFileName = new FileName(imageFile);
         m_labelFile = new QFile(m_labelFileName->expanded());
       }
-      else {
+      else if (labelsAttached() == LabelAttachment::DetachedLabel) {
         core += PvlKeyword("StartByte", toString(1));
-        core += PvlKeyword("^Core", cubFile.name());
-        m_dataFileName = new FileName(cubFile);
+        core += PvlKeyword("^Core", imageFile.name());
         m_dataFile = new QFile(realDataFileName().expanded());
 
-        FileName labelFileName(cubFile);
+        FileName labelFileName(imageFile);
         labelFileName = labelFileName.setExtension("lbl");
         m_labelFileName = new FileName(labelFileName);
         m_labelFile = new QFile(m_labelFileName->expanded());
       }
-
-      // Create the size of the core
-      PvlGroup dims("Dimensions");
-      dims += PvlKeyword("Samples", toString(m_samples));
-      dims += PvlKeyword("Lines",   toString(m_lines));
-      dims += PvlKeyword("Bands",   toString(m_bands));
+      else {
+        QString msg = "Label type [" + LabelAttachmentName(labelsAttached()) + "] not supported";
+        throw IException(IException::Programmer, msg, _FILEINFO_);
+      }
       core.addGroup(dims);
-
-      // Create the pixel type
-      PvlGroup ptype("Pixels");
-      ptype += PvlKeyword("Type", PixelTypeName(m_pixelType));
-
-      // And the byte ordering
-      ptype += PvlKeyword("ByteOrder", ByteOrderName(m_byteOrder));
-      ptype += PvlKeyword("Base", toString(m_base));
-      ptype += PvlKeyword("Multiplier", toString(m_multiplier));
       core.addGroup(ptype);
     }
-    else {
-      cubFile = cubFile.addExtension("ecub");
+    else if (labelsAttached() == LabelAttachment::ExternalLabel) {
+      if (!m_dataFileName) {
+        if (format() == Bsq || format() == Tile) {
+          imageFile = imageFile.addExtension("cub");
+
+          Pvl dnLabel;
+          PvlObject isiscube("IsisCube");
+          PvlObject dnCore(core);
+          PvlKeyword fileFormat("Format", toString(format()));
+
+          dnCore.addKeyword(fileFormat);
+          dnCore.addGroup(dims);
+          dnCore.addGroup(ptype);
 
-      core += PvlKeyword("^DnFile", m_dataFileName->original());
-//       m_dataFileName = new FileName(cubFile);
-      m_dataFile = new QFile(realDataFileName().expanded());
+          isiscube.addObject(dnCore);
+          dnLabel.addObject(isiscube);
 
-      m_labelFileName = new FileName(cubFile);
-      m_labelFile = new QFile(cubFile.expanded());
+          Cube dnCube;
+          dnCube.fromLabel(imageFile, dnLabel, "rw");
+          dnCube.close();
+
+          m_dataFileName = new FileName(imageFile);
+        }
+        else if (format() ==  Format::GTiff) {
+          imageFile = imageFile.setExtension("tiff");
+          m_dataFileName = new FileName(imageFile);
+        }
+        
+        imageFile = imageFile.setExtension("ecub");
+        FileName labelFileName(imageFile);
+        m_labelFileName = new FileName(labelFileName);
+        m_labelFile = new QFile(m_labelFileName->expanded());
+      }
+
+      core += PvlKeyword("^DnFile", m_dataFileName->name());
+      m_dataFile = new QFile(m_dataFileName->expanded());
+
+      m_labelFileName = new FileName(imageFile);
+      m_labelFile = new QFile(m_labelFileName->expanded());
+    }
+    else {
+      QString msg = "Label type [" + LabelAttachmentName(labelsAttached()) + "] not supported";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
     isiscube.addObject(core);
@@ -545,15 +610,15 @@ namespace Isis {
       cleanUp(false);
       throw IException(IException::Io, msg, _FILEINFO_);
     }
-
+    
     if (m_dataFile) {
-      if (m_storesDnData && !m_dataFile->open(QIODevice::Truncate | QIODevice::ReadWrite)) {
+      if (labelsAttached() != ExternalLabel && !m_dataFile->open(QIODevice::Truncate | QIODevice::ReadWrite)) {
         QString msg = "Failed to create [" + m_dataFile->fileName() + "]. ";
         msg += "Verify the output path exists and you have permission to write to the path.";
         cleanUp(false);
         throw IException(IException::Io, msg, _FILEINFO_);
       }
-      else if (!m_storesDnData && !m_dataFile->open(QIODevice::ReadOnly)) {
+      else if (labelsAttached() == ExternalLabel && !m_dataFile->open(QIODevice::ReadWrite)) {
         QString msg = "Failed to open [" + m_dataFile->fileName() + "] for reading. ";
         msg += "Verify the output path exists and you have permission to read from the path.";
         cleanUp(false);
@@ -561,24 +626,103 @@ namespace Isis {
       }
     }
 
-    bool dataAlreadyOnDisk = m_storesDnData ? false : true;
+    bool dataAlreadyOnDisk = (labelsAttached() == ExternalLabel);
 
-    if (m_format == Bsq) {
-      m_ioHandler = new CubeBsqHandler(dataFile(), m_virtualBandList, realDataFileLabel(),
-                                       dataAlreadyOnDisk);
-    }
-    else {
-      m_ioHandler = new CubeTileHandler(dataFile(), m_virtualBandList, realDataFileLabel(),
+    if (format() != GTiff) {
+      if (format() == Bsq) {
+        m_ioHandler = new CubeBsqHandler(dataFile(), m_virtualBandList, realDataFileLabel(),
                                         dataAlreadyOnDisk);
+      }
+      else if(format() == Tile) {
+        m_ioHandler = new CubeTileHandler(dataFile(), m_virtualBandList, realDataFileLabel(),
+                                          dataAlreadyOnDisk);
+      }
+
+      if (!dataAlreadyOnDisk) {
+        m_ioHandler->updateLabels(*m_label);
+      }
     }
+    else if(format() ==  Format::GTiff) {
+      char **papszOptions = NULL;
+      papszOptions = CSLSetNameValue(papszOptions, "COMPRESS", "DEFLATE");
+      papszOptions = CSLSetNameValue(papszOptions, "PREDICTOR", "2");
+      QString datafile = m_dataFileName->expanded();
+      QString format = "GTiff";
+      createGdal(datafile, format, papszOptions);
+
+      m_geodataSet = GDALDataset::FromHandle(GDALOpen(datafile.toStdString().c_str(), GA_Update));
+      if (!m_geodataSet) {
+        QString msg = "Failed opening GDALDataset from [" + m_dataFileName->name() + "]";
+        cleanUp(false);
+        throw IException(IException::Programmer, msg, _FILEINFO_);
+      }
 
-    if (m_storesDnData)
+      m_ioHandler = new GdalIoHandler(gdalDataset(), m_virtualBandList, IsisPixelToGdal(pixelType()));
       m_ioHandler->updateLabels(*m_label);
+    }
+    else {
+      QString msg = "Format [" + toString(format()) + "] not supported";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
 
     // Write the labels
     writeLabels();
   }
 
+  void Cube::createGdal(QString &dataFileName, QString &driverName, char **papszOptions) {
+    GDALAllRegister();
+    CPLSetErrorHandler(CPLQuietErrorHandler);
+    GDALDriver* driver = GetGDALDriverManager()->GetDriverByName(driverName.toStdString().c_str());
+    if (driver) {
+      double noDataValue;
+      switch (pixelType()) {
+        case UnsignedByte:
+          noDataValue = (double) NULL1;
+          break;
+        case SignedByte:
+          noDataValue = (double) NULLS1;
+          break;
+        case UnsignedWord:
+          noDataValue = (double) NULLU2;
+          break;
+        case SignedWord:
+          noDataValue = (double) NULL2;
+          break;
+        case UnsignedInteger:
+          noDataValue = (double) NULLUI4;
+          break;
+        case SignedInteger:
+          noDataValue = (double) NULLI4;
+          break;
+        case Real:
+          noDataValue = (double) NULL4;
+          break;
+
+        default:
+          noDataValue = NULL8;
+          break;
+      }
+      GDALDataset *dataset = driver->Create(dataFileName.toStdString().c_str(), sampleCount(), lineCount(), bandCount(), IsisPixelToGdal(pixelType()), papszOptions);
+      if (!dataset) {
+        QString msg = "Could not create file [" + dataFileName + "] from [" + driverName + "] Driver.";
+        throw IException(IException::Io, msg, _FILEINFO_);
+      }
+      for (int i = 1; i <= bandCount(); i++) {
+        GDALRasterBand *band = dataset->GetRasterBand(i);
+        band->SetScale(multiplier());
+        band->SetOffset(base());
+        band->SetNoDataValue(noDataValue);
+        band->Fill(noDataValue);
+      }
+      GDALClose(dataset);
+      CSLDestroy( papszOptions );
+    } 
+    else {
+      QString msg = "No Driver found for name [" + driverName + "]";
+      throw IException(IException::Io, msg, _FILEINFO_);
+    }
+  }
+
 
   /**
    * This method will create an isis cube for writing.   The programmer should
@@ -606,7 +750,7 @@ namespace Isis {
 
     setByteOrder(att.byteOrder());
     setFormat(att.fileFormat());
-    setLabelsAttached(att.labelAttachment() == AttachedLabel);
+    setLabelsAttached(att.labelAttachment());
     if (!att.propagatePixelType())
       setPixelType(att.pixelType());
     setMinMax(att.minimum(), att.maximum());
@@ -615,11 +759,20 @@ namespace Isis {
     create(cubeFileName);
   }
 
+  void Cube::checkAccess(QString access) {
+    if (!(access == "r" || access == "rw")) {
+      QString msg = "Unknown value for access [" + access + "]. Expected 'r' "
+                    " or 'rw'";
+      cleanUp(false);
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
+  }
+
 
   /**
-   * This method will open an existing isis cube for reading or 
-   * reading/writing. Any input cube attributes following the file
-   * name will be applied.
+   * This method will try to open a file as either a cube or 
+   * geotiff in either read or read/write. Any input cube 
+   * attributes following the file name will be applied.
    *
    * @param[in] cubeFileName Name of the cube file to open. Environment
    *     variables in the filename will be automatically expanded.
@@ -634,7 +787,7 @@ namespace Isis {
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
-    initLabelFromFile(cubeFileName, (access == "rw"));
+    checkAccess(access);
 
     try {
       Isis::CubeAttributeInput att(cubeFileName);
@@ -647,6 +800,49 @@ namespace Isis {
       // there is an exception parsing and adding an attribute
     }
 
+    QString msg = "Failed to open [" + cubeFileName + "]";
+    IException exceptions(IException::Io, msg, _FILEINFO_);
+    if (!isOpen()) {
+      try {
+        openCube(cubeFileName, access);
+      }
+      catch (IException &e) {
+        cleanUp(false);
+        exceptions.append(e);
+      }
+    }
+
+    if (!isOpen()) {
+      try {
+        openGdal(cubeFileName, access);
+      }
+      catch(IException &e) {
+        cleanUp(false);
+        exceptions.append(e);
+      }
+    }
+
+    if (!isOpen()) {
+      throw exceptions;
+    }
+
+    applyVirtualBandsToLabel();
+  }
+
+  /**
+   * This method will open an existing isis cube for reading or 
+   * reading/writing.
+   *
+   * @param[in] cubeFileName Name of the cube file to open. Environment
+   *     variables in the filename will be automatically expanded.
+   * @param[in] access (Default value of "r") Defines how the cube will be
+   *     accessed. Either read-only "r" or read-write "rw".
+   */
+  void Cube::openCube(const QString &cubeFileName, QString access) {
+    checkAccess(access);
+
+    initLabelFromFile(cubeFileName, (access == "rw"));
+
     // Figure out the name of the data file
     try {
       PvlObject &core = m_label->findObject("IsisCube").findObject("Core");
@@ -660,11 +856,13 @@ namespace Isis {
         else {
           m_dataFileName = new FileName(temp);
         }
+        setLabelsAttached(LabelAttachment::DetachedLabel);
 
-        m_attached = false;
-        m_storesDnData = true;
+        FileName realDataFile = realDataFileName();
+        delete m_dataFileName;
+        m_dataFileName = new FileName(realDataFile.expanded());
 
-        m_dataFile = new QFile(realDataFileName().expanded());
+        m_dataFile = new QFile(m_dataFileName->expanded());
       }
       // External cube files (ecub), ecub contains all labels and SPICE blobs, history
       else if (core.hasKeyword("^DnFile")) {
@@ -676,103 +874,242 @@ namespace Isis {
         else {
           m_dataFileName = new FileName(dataFileName);
         }
+        setLabelsAttached(LabelAttachment::ExternalLabel);
 
-        m_attached = true;
-        m_storesDnData = false;
-        *m_dataFileName = FileName(realDataFileName().expanded());
-        m_dataFile = new QFile(realDataFileName().expanded());
+        FileName realDataFile = realDataFileName();
+        delete m_dataFileName;
+        m_dataFileName = new FileName(realDataFile.expanded());
+
+        m_dataFile = new QFile(m_dataFileName->expanded());
       }
       // Typical cube containing labels, SPICE, history and dn data
       else {
+        setLabelsAttached(LabelAttachment::AttachedLabel);
         m_dataFileName = new FileName(*m_labelFileName);
-        m_attached = true;
-        m_storesDnData = true;
       }
     }
     catch (IException &e) {
       cleanUp(false);
-      throw;
+      throw e;
     }
 
-    if (access == "r") {
-      if (!m_labelFile->open(QIODevice::ReadOnly)) {
-        QString msg = "Failed to open [" + m_labelFile->fileName() + "] with "
-            "read only access";
-        cleanUp(false);
-        throw IException(IException::Io, msg, _FILEINFO_);
-      }
-
-      if (m_dataFile) {
-        if (!m_dataFile->open(QIODevice::ReadOnly)) {
-          QString msg = "Failed to open [" + m_dataFile->fileName() + "] with "
-              "read only access";
-          cleanUp(false);
-          throw IException(IException::Io, msg, _FILEINFO_);
-        }
-      }
+    QIODevice::OpenModeFlag qtAccess = QIODevice::ReadOnly;
+    GDALAccess eAccess = GA_ReadOnly;
+    if (access == "rw") {
+      qtAccess = QIODevice::ReadWrite;
+      eAccess = GA_Update;
     }
 
-    else if (access == "rw") {
-      if (!m_labelFile->open(QIODevice::ReadWrite)) {
+    if (!m_labelFile->open(qtAccess)) {
         QString msg = "Failed to open [" + m_labelFile->fileName() + "] with "
-            "read/write access";
+                    "access [" + access + "]";
         cleanUp(false);
         throw IException(IException::Io, msg, _FILEINFO_);
       }
 
       if (m_dataFile) {
-        if (m_storesDnData && !m_dataFile->open(QIODevice::ReadWrite)) {
-          QString msg = "Failed to open [" + m_dataFile->fileName() + "] with "
-              "read/write access";
-          cleanUp(false);
-          throw IException(IException::Io, msg, _FILEINFO_);
-        }
-        else if (!m_storesDnData && !m_dataFile->open(QIODevice::ReadOnly)) {
-          QString msg = "Failed to open [" + m_dataFile->fileName() + "] with "
-              "read access";
-          cleanUp(false);
-          throw IException(IException::Io, msg, _FILEINFO_);
+        if (!m_dataFile->open(qtAccess)) {
+            QString msg = "Failed to open [" + m_dataFile->fileName() + "] with "
+                      "access [" + access + "]";
+            cleanUp(false);
+            throw IException(IException::Io, msg, _FILEINFO_);
         }
-      }
-    }
-    else {
-      QString msg = "Unknown value for access [" + access + "]. Expected 'r' "
-                    " or 'rw'";
-      cleanUp(false);
-      throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
     initCoreFromLabel(*m_label);
 
     // Determine the number of bytes in the label
-    if (m_attached) {
+    if (labelsAttached() != LabelAttachment::DetachedLabel) {
       m_labelBytes = m_label->findObject("Label")["Bytes"];
     }
     else {
       m_labelBytes = labelSize(true);
     }
 
-    QPair<bool, Pvl *> dataLabel = qMakePair(false, m_label);
-    if (!m_storesDnData) {
-      dataLabel = qMakePair(true, new Pvl(m_dataFileName->expanded()));
-    }
-
     // Now examine the format to see which type of handler to create
     if (m_format == Bsq) {
       m_ioHandler = new CubeBsqHandler(dataFile(), m_virtualBandList,
           realDataFileLabel(), true);
     }
-    else {
+    else if (m_format == Tile) {
       m_ioHandler = new CubeTileHandler(dataFile(), m_virtualBandList,
           realDataFileLabel(), true);
     }
+    else if (m_format == GTiff) {
+      m_dataFile->close();
+      m_geodataSet = GDALDataset::FromHandle(GDALOpen(m_dataFileName->expanded().toStdString().c_str(), eAccess));
+      if (!m_geodataSet) {
+        QString msg = "Opening GDALDataset from [" + m_dataFileName->name() + "] failed with access [" + eAccess +"]";
+        cleanUp(false);
+        throw IException(IException::Io, msg, _FILEINFO_);
+      }
+      m_ioHandler = new GdalIoHandler(gdalDataset(), m_virtualBandList, IsisPixelToGdal(pixelType()));
+    }
+    else {
+      QString msg = "Unknown format [" + toString(m_format) + "]";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
+  }
+
+  /**
+   * This method will open an existing geotiff for reading or 
+   * reading/writing.
+   *
+   * @param[in] cubeFileName Name of the file to open. Environment
+   *     variables in the filename will be automatically expanded.
+   * @param[in] access (Default value of "r") Defines how the cube will be
+   *     accessed. Either read-only "r" or read-write "rw".
+   */
+  void Cube::openGdal(const QString &cubeFileName, QString access) {
+    checkAccess(access);
+
+    m_labelFileName = new FileName(cubeFileName);
+
+    setLabelsAttached(LabelAttachment::AttachedLabel);
+    m_dataFileName = new FileName(*m_labelFileName);
+
+    initCoreFromGdal(m_labelFileName->expanded());
+    GDALDataset *dataset = GDALDataset::FromHandle(GDALOpen(m_dataFileName->expanded().toStdString().c_str(), GA_ReadOnly));
+    if (!dataset) {
+      QString msg = "Failed opening GDALDataset from [" + m_dataFileName->name() + "]";
+      cleanUp(false);
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
 
-    if (dataLabel.first) {
-      delete dataLabel.second;
-      dataLabel.second = NULL;
+    CPLStringList metadata = CPLStringList(dataset->GetMetadata("USGS"), false);
+
+    m_label = new Pvl();
+    if (metadata) {
+      for (int i = 0; i < metadata.size(); i++) {
+        const char *metadataItem = CPLParseNameValue(metadata[i], nullptr);
+        nlohmann::ordered_json metadataAsJson = nlohmann::ordered_json::parse(metadataItem);
+        Pvl pvl;
+        Pvl::readObject(pvl, metadataAsJson);
+        for (int i = 0; i < pvl.objects(); i++) {
+          m_label->addObject(pvl.object(i));
+        }
+        for (int i = 0; i < pvl.groups(); i++) {
+          m_label->addGroup(pvl.group(i));
+        }
+      }
     }
+    else {
+      // Setup the PVL
+      PvlObject isiscube("IsisCube");
+      PvlObject core("Core");
 
-    applyVirtualBandsToLabel();
+      // Create the size of the core
+      PvlGroup dims("Dimensions");
+      dims += PvlKeyword("Samples", toString(m_samples));
+      dims += PvlKeyword("Lines", toString(m_lines));
+      dims += PvlKeyword("Bands", toString(m_bands));
+
+      // Create the pixel type
+      PvlGroup ptype("Pixels");
+      ptype += PvlKeyword("Type", PixelTypeName(m_pixelType));
+
+      // And the byte ordering
+      ptype += PvlKeyword("ByteOrder", ByteOrderName(m_byteOrder));
+      ptype += PvlKeyword("Base", toString(m_base));
+      ptype += PvlKeyword("Multiplier", toString(m_multiplier));
+      
+      core += PvlKeyword("StartByte", toString(m_labelBytes + 1));
+
+      core.addGroup(dims);
+      core.addGroup(ptype);
+
+      isiscube.addObject(core);
+
+      if (dataset->GetSpatialRef()) {
+        char ** projStr = new char*[1];
+        const OGRSpatialReference &oSRS = *dataset->GetSpatialRef();
+        oSRS.exportToProj4(projStr);
+        QString qProjStr = QString::fromStdString(std::string(projStr[0]) + " +type=crs");
+        delete[] projStr[0];
+        delete[] projStr;
+
+        char ** projJsonStr = new char*[1];
+        oSRS.exportToPROJJSON(projJsonStr, nullptr);
+        nlohmann::json projJson = nlohmann::json::parse(projJsonStr[0]);
+        CPLFree(projJsonStr);
+
+        PvlGroup mappingGroup("Mapping");
+        mappingGroup.addKeyword(PvlKeyword("ProjectionName", "IProj"));
+        mappingGroup.addKeyword(PvlKeyword("EquatorialRadius", toString(oSRS.GetSemiMajor()), "meters"));
+        mappingGroup.addKeyword(PvlKeyword("PolarRadius", toString(oSRS.GetSemiMinor()), "meters"));
+
+        if (projJson.contains("base_crs")) {
+          projJson = projJson["base_crs"];
+        }
+
+        std::string direction = projJson["coordinate_system"]["axis"][1]["direction"];
+        if (direction == "east") {
+          mappingGroup.addKeyword(PvlKeyword("LongitudeDirection", "PositiveEast"));
+        }
+        else if (direction == "west") {
+          mappingGroup.addKeyword(PvlKeyword("LongitudeDirection", "PositiveWest"));
+        }
+        else {
+          QString msg = "Unknown direction [" + QString::fromStdString(direction) + "]";
+          throw IException(IException::Programmer, msg, _FILEINFO_);
+        }
+        mappingGroup.addKeyword(PvlKeyword("LongitudeDomain", "180"));
+        mappingGroup.addKeyword(PvlKeyword("LatitudeType", "Planetocentric"));
+        mappingGroup.addKeyword(PvlKeyword("ProjStr", qProjStr));
+        
+        // Read the GeoTransform and get the elements we care about
+        double *padfTransform = new double[6];
+        dataset->GetGeoTransform(padfTransform);
+        if (abs(padfTransform[1]) != abs(padfTransform[5])) {
+          delete[] padfTransform;
+          QString msg = "Vertical and horizontal resolution do not match";
+          throw IException(IException::Io, msg, _FILEINFO_);
+        }
+
+        double dfScale;
+        double dfRes;
+        double upperLeftX;
+        double upperLeftY;
+        dfRes = padfTransform[1] * oSRS.GetLinearUnits();
+        upperLeftX = padfTransform[0];
+        upperLeftY = padfTransform[3];
+        if (oSRS.IsProjected()) {
+          const double dfDegToMeter = oSRS.GetSemiMajor() * M_PI / 180.0;
+          dfScale = dfDegToMeter / dfRes;
+          mappingGroup.addKeyword(PvlKeyword("PixelResolution", toString(dfRes), "meters/pixel"));
+        }
+        else if (oSRS.IsGeographic()) {
+          dfScale = 1.0 / dfRes;
+          mappingGroup.addKeyword(PvlKeyword("PixelResolution", toString(dfRes), "degrees/pixel"));
+        }
+        else {
+          QString msg = "Gdal spatial reference is not Geographic or Projected";
+          throw IException(IException::Io, msg, _FILEINFO_);
+        }
+        mappingGroup.addKeyword(PvlKeyword("Scale", toString(dfScale), "pixels/degree"));
+        mappingGroup.addKeyword(PvlKeyword("UpperLeftCornerX", toString(upperLeftX)));
+        mappingGroup.addKeyword(PvlKeyword("UpperLeftCornerY", toString(upperLeftY)));
+        delete[] padfTransform;
+
+        isiscube.addGroup(mappingGroup);
+      }
+      m_label->addObject(isiscube);
+    }
+    GDALClose(dataset);
+    
+    GDALAccess eAccess = GA_ReadOnly;
+    if (access == "rw") {
+      eAccess = GA_Update;
+    }
+    m_geodataSet = GDALDataset::FromHandle(GDALOpen(m_dataFileName->expanded().toStdString().c_str(), eAccess));
+    if (!m_geodataSet) {
+      QString msg = "Opening GDALDataset from [" + m_dataFileName->name() + "] failed with access [" + eAccess +"]";
+      cleanUp(false);
+      throw IException(IException::Io, msg, _FILEINFO_);
+    }
+
+    m_ioHandler = new GdalIoHandler(gdalDataset(), m_virtualBandList, IsisPixelToGdal(pixelType()));
+    m_ioHandler->updateLabels(*m_label);
   }
 
 
@@ -827,9 +1164,14 @@ namespace Isis {
     if (m_tempCube)
       cubeFile = *m_tempCube;
 
-    QMutexLocker locker(m_mutex);
-    QMutexLocker locker2(m_ioHandler->dataFileMutex());
-    blob.Read(cubeFile.toString(), *label(), keywords);
+    if (format() == Format::GTiff && labelsAttached() == LabelAttachment::AttachedLabel) {
+      blob.ReadGdal(gdalDataset());
+    }
+    else {
+      QMutexLocker locker(m_mutex);
+      QMutexLocker locker2(m_ioHandler->dataFileMutex());
+      blob.Read(cubeFile.toString(), *label(), keywords);
+    }
   }
 
 
@@ -987,13 +1329,19 @@ namespace Isis {
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
+    if (m_format == Format::GTiff && labelsAttached() == LabelAttachment::AttachedLabel) {  
+      // write new type of blob
+      blob.WriteGdal(gdalDataset());
+      return; // nothing else to do
+    }
+
     if (!m_labelFile->isWritable()) {
       string msg = "The cube must be opened in read/write mode, not readOnly";
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
     // Write an attached blob
-    if (m_attached) {
+    if (labelsAttached() != LabelAttachment::DetachedLabel) {
       QMutexLocker locker(m_mutex);
       QMutexLocker locker2(m_ioHandler->dataFileMutex());
 
@@ -1008,9 +1356,7 @@ namespace Isis {
       // maxbyte = position after the cube DN data and labels
       streampos maxbyte = (streampos) m_labelBytes;
 
-      if (m_storesDnData) {
-        maxbyte += (streampos) m_ioHandler->getDataSize();
-      }
+      maxbyte += (streampos) m_ioHandler->getDataSize();
 
       // If EOF is too early, allocate space up to where we want the blob
       if (endByte < maxbyte) {
@@ -1021,7 +1367,7 @@ namespace Isis {
       blob.Write(*m_label, stream, "", overwrite);
     }
 
-    // Write a detached blob
+    // Write a detached
     else {
       FileName blobFileName = fileName();
       blobFileName = blobFileName.removeExtension();
@@ -1140,11 +1486,11 @@ namespace Isis {
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
-    if (!m_storesDnData) {
-      QString msg = "The cube [" + QFileInfo(fileName()).fileName() +
-          "] does not support storing DN data because it is using an external file for DNs";
-      throw IException(IException::Unknown, msg, _FILEINFO_);
-    }
+    // if (labelsAttached() == ExternalLabel) {
+    //   QString msg = "The cube [" + QFileInfo(fileName()).fileName() +
+    //       "] does not support storing DN data because it is using an external file for DNs";
+    //   throw IException(IException::Unknown, msg, _FILEINFO_);
+    // }
 
     QMutexLocker locker(m_mutex);
     m_ioHandler->write(bufferToWrite);
@@ -1184,7 +1530,7 @@ namespace Isis {
     m_base = 0.0;
     m_multiplier = 1.0;
 
-    double x1, x2;
+    double x1 = 0.0, x2 = 0.0;
     if (m_pixelType == UnsignedByte) {
       x1 = VALID_MIN1;
       x2 = VALID_MAX1;
@@ -1252,14 +1598,17 @@ namespace Isis {
       delete m_label;
       m_label = NULL;
     }
-    catch (IException &) {
+    catch (IException &e) {
       delete m_label;
       m_label = NULL;
-      throw;
+      throw e;
     }
 
-    m_storesDnData = false;
+    setLabelsAttached(Cube::ExternalLabel);
     m_dataFileName = new FileName(cubeFileWithDnData);
+    FileName realDataFile = realDataFileName();
+    delete m_dataFileName;
+    m_dataFileName = new FileName(realDataFile.expanded());
 
     delete m_labelFile;
     m_labelFile = NULL;
@@ -1288,7 +1637,7 @@ namespace Isis {
    *
    * @param attach If false, the labels and data will be in separate files.
    */
-  void Cube::setLabelsAttached(bool attach) {
+  void Cube::setLabelsAttached(LabelAttachment attach) {
     openCheck();
     m_attached = attach;
   }
@@ -1386,7 +1735,7 @@ namespace Isis {
     }
 
 
-    if (m_storesDnData) {
+    if (labelsAttached() != ExternalLabel) {
       throw IException(IException::Unknown,
                        QString("The cube [%1] stores DN data. It cannot be relocated to [%2] - "
                                "this is only supported for external cube label files.")
@@ -1573,7 +1922,7 @@ namespace Isis {
                        _FILEINFO_);
     }
 
-    if (storesDnData()) {
+    if (labelsAttached() == ExternalLabel) {
       throw IException(IException::Unknown,
                        "Cube::getExternalCubeFileName can only be called on an external cube label "
                          "file",
@@ -1581,8 +1930,7 @@ namespace Isis {
     }
 
 
-   PvlObject &core = m_label->findObject("IsisCube").findObject("Core");
-
+    PvlObject &core = m_label->findObject("IsisCube").findObject("Core");
     return core["^DnFile"][0];
   }
 
@@ -1929,16 +2277,6 @@ namespace Isis {
   }
 
 
-  /**
-   * This method returns a boolean value
-   *
-   * @return bool
-   */
-  bool Cube::storesDnData() const {
-    return m_storesDnData;
-  }
-
-
   /**
    * This will add the given caching algorithm to the list of attempted caching
    *   algorithms. The algorithms are tried in the opposite order that they
@@ -2164,6 +2502,11 @@ namespace Isis {
     delete m_dataFile;
     m_dataFile = NULL;
 
+    if (m_geodataSet) {
+      GDALClose(m_geodataSet);
+    }
+    m_geodataSet = NULL;
+
     delete m_labelFileName;
     m_labelFileName = NULL;
 
@@ -2223,6 +2566,14 @@ namespace Isis {
   }
 
 
+  GDALDataset *Cube::gdalDataset() const {
+    if (!m_geodataSet) {
+      QString msg = "No GDALDataset has been constructed";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
+    return m_geodataSet;
+  }
+
   /**
    * This gets the file name of the file which actually contains the DN data. With ecub's, our
    *    data file name could be another ecub or a detached label, so using m_dataFileName is
@@ -2234,15 +2585,15 @@ namespace Isis {
     FileName result;
 
     // Attached, stores DN data - normal cube
-    if (m_attached && m_storesDnData) {
+    if (labelsAttached() == LabelAttachment::AttachedLabel) {
       result = *m_labelFileName;
     }
     // Detached, stores DN data - standard detached cube
-    else if (!m_attached && m_storesDnData) {
+    else if (labelsAttached() == LabelAttachment::DetachedLabel) {
       result = *m_dataFileName;
     }
     // External cube - go look at our external file
-    else if (!m_storesDnData) {
+    else if (labelsAttached() == LabelAttachment::ExternalLabel) {
       FileName guess = *m_dataFileName;
       QDir dir(guess.toString());
 
@@ -2255,20 +2606,26 @@ namespace Isis {
         guess = dir2.absolutePath() + "/" + guess.name();
       }
       do {
-        Pvl guessLabel(guess.expanded());
+        Cube dataCube(guess.expanded());
+        Pvl guessLabel = *(dataCube.label());
 
-        PvlObject &core = guessLabel.findObject("IsisCube").findObject("Core");
+        if (guessLabel.hasObject("IsisCube")) {
+          PvlObject &core = guessLabel.findObject("IsisCube").findObject("Core");
 
-        if (core.hasKeyword("^DnFile")) {
-          FileName currentGuess = guess;
-          guess = core["^DnFile"][0];
+          if (core.hasKeyword("^DnFile")) {
+            FileName currentGuess = guess;
+            guess = core["^DnFile"][0];
 
-          if (!guess.path().startsWith("/")) {
-            guess = currentGuess.path() + "/" + guess.original();
+            if (!guess.path().startsWith("/")) {
+              guess = currentGuess.path() + "/" + guess.original();
+            }
+          }
+          else if (core.hasKeyword("^Core")) {
+            result = core["^Core"][0];
+          }
+          else {
+            result = guess;
           }
-        }
-        else if (core.hasKeyword("^Core")) {
-          result = core["^Core"][0];
         }
         else {
           result = guess;
@@ -2299,8 +2656,7 @@ namespace Isis {
     m_format = Tile;
     m_pixelType = Real;
 
-    m_attached = true;
-    m_storesDnData = true;
+    m_attached = LabelAttachment::AttachedLabel;
     m_labelBytes = 65536;
 
     m_samples = 0;
@@ -2318,9 +2674,11 @@ namespace Isis {
    * @param label Pvl label to initialize from
    */
   void Cube::initCoreFromLabel(const Pvl &label) {
+    initialize();
+    initLabelState(label);
     const PvlObject &core = label.findObject("IsisCube").findObject("Core");
 
-    if (!core.hasKeyword("^DnFile")) {
+    if (labelsAttached() != ExternalLabel) {
       // Dimensions
       const PvlGroup &dims = core.findGroup("Dimensions");
       m_samples = dims["Samples"];
@@ -2336,10 +2694,17 @@ namespace Isis {
 
       // Now examine the format to see which type of handler to create
       if ((QString) core["Format"] == "BandSequential") {
-        m_format = Bsq;
+        m_format = Format::Bsq;
+      }
+      else if ((QString) core["Format"] == "Tile") {
+        m_format = Format::Tile;
+      }
+      else if ((QString) core["Format"] == "GTiff") {
+        m_format = Format::GTiff;
       }
       else {
-        m_format = Tile;
+        QString msg = "Unknown format [" + (QString)core["Format"] + "]";
+        throw IException(IException::Io, msg, _FILEINFO_);
       }
     }
     else {
@@ -2347,12 +2712,34 @@ namespace Isis {
       if (!temp.expanded().startsWith("/")) {
         temp = FileName(m_labelFileName->path() + "/" + temp.original());
       }
-
-      initCoreFromLabel(Pvl(temp.toString()));
+      try {
+        initCoreFromLabel(Pvl(temp.toString()));
+      }
+      catch (IException &e) {
+        initCoreFromGdal(temp.toString());
+      }
+      // Reset the label state as we recurse back out from initCoreFromLabel calls
+      initLabelState(label);
     }
   }
 
 
+  void Cube::initCoreFromGdal(const QString &labelFile) {
+    GDALDataset *geodataSet = GDALDataset::FromHandle(GDALOpen(labelFile.toStdString().c_str(), GA_ReadOnly));
+    if (!geodataSet) {
+      QString msg = "Gdal failed to open [" + labelFile + "]";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
+
+    setFormat(GTiff);
+    setDimensions(geodataSet->GetRasterXSize(), geodataSet->GetRasterYSize(), geodataSet->GetRasterCount());
+
+    GDALRasterBand *band = geodataSet->GetRasterBand(1);
+    setPixelType(GdalPixelToIsis(band->GetRasterDataType()));
+    setBaseMultiplier(band->GetOffset(), band->GetScale());
+    GDALClose(geodataSet);
+  }
+
   /**
    * This function initializes the Cube label from a file passed as a parameter
    *
@@ -2632,19 +3019,35 @@ namespace Isis {
       string msg = "Cube must be opened first before writing labels";
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
+    
+    if (m_format == Format::GTiff && labelsAttached() == LabelAttachment::AttachedLabel) { 
+      // update metadata
+      nlohmann::ordered_json jsonblob = this->label()->toJson()["Root"];
+      nlohmann::ordered_json jsonOut;
+      // std::cout << jsonblob << std::endl;
+      for (auto& [key, val] : jsonblob.items()) {
+        if (!val.contains("Bytes") || key == "Label") {
+          jsonOut[key] = val;
+        }
+      }
+      std::string jsonblobstr = jsonOut.dump();
+      std::string name = "CubeLabel";
+      gdalDataset()->SetMetadataItem(name.c_str(), jsonblobstr.c_str(), "USGS");
+      return;
+    }
 
     // Set the pvl's format template
     m_label->setFormatTemplate(m_formatTemplateFile->original());
 
     // Write them with attached data
-    if (m_attached) {
+    if (labelsAttached() != LabelAttachment::DetachedLabel) {
       QMutexLocker locker(m_mutex);
       QMutexLocker locker2(m_ioHandler->dataFileMutex());
 
       ostringstream temp;
       temp << *m_label << endl;
       string tempstr = temp.str();
-      if ((int) tempstr.length() < m_labelBytes) {
+      if ((int) tempstr.length() <= m_labelBytes) {
         QByteArray labelArea(m_labelBytes, '\0');
         QByteArray labelUnpaddedContents(tempstr.c_str(), tempstr.length());
         labelArea.replace(0, labelUnpaddedContents.size(), labelUnpaddedContents);
@@ -2667,4 +3070,17 @@ namespace Isis {
       m_label->write(m_labelFileName->expanded());
     }
   }
+
+  void Cube::initLabelState(const Pvl &label) {
+    const PvlObject &core = label.findObject("IsisCube").findObject("Core");
+    LabelAttachment attachmentStyle = AttachedLabel;
+    if (core.hasKeyword("^DnFile")) {
+      attachmentStyle = ExternalLabel;
+    }
+    else if (core.hasKeyword("^Core")) {
+      attachmentStyle = DetachedLabel;
+    }
+
+    setLabelsAttached(attachmentStyle);
+  }
 }
diff --git a/isis/src/base/objs/Cube/Cube.h b/isis/src/base/objs/Cube/Cube.h
index 32d234367c1ceb7dac2c7909d11a4ed8eb0b33dd..6e28110aa48c659a3d81c6393ade6a323959473b 100644
--- a/isis/src/base/objs/Cube/Cube.h
+++ b/isis/src/base/objs/Cube/Cube.h
@@ -30,9 +30,9 @@ namespace Isis {
   class Camera;
   class CubeAttributeOutput;
   class CubeCachingAlgorithm;
-  class CubeIoHandler;
   class CubeStretch;
   class FileName;
+  class ImageIoHandler;
   class Projection;
   class Pvl;
   class PvlGroup;
@@ -230,7 +230,29 @@ namespace Isis {
          * The symbol '*' denotes tile boundaries.
          * The symbols '-' and '|' denote cube boundaries.
          */
-        Tile
+        Tile,
+        GTiff
+      };
+
+      /**
+       * @brief Input cube label type tracker
+       *
+       * This enumeration and its functions are for the label
+       * type of an input cube. The enum defines the type of labels (i.e.,
+       * Both the label and cube are in the same file and the label is in a
+       * separate file from the cube.
+       */
+      enum LabelAttachment {
+        AttachedLabel,  //!< The input label is embedded in the image file
+        DetachedLabel,  //!< The input label is in a separate data file from the image
+        /**
+         * The label is pointing to an external DN file - the label is also external to the data.
+         *
+         * This format implies that the output is a cube that contains everything except DN data
+         *   (more similar to attached than detached).
+         */
+        ExternalLabel,
+        GdalLabel
       };
 
       void fromIsd(const FileName &fileName, Pvl &label, nlohmann::json &isd, QString access);
@@ -242,7 +264,7 @@ namespace Isis {
       bool isProjected() const;
       bool isReadOnly() const;
       bool isReadWrite() const;
-      bool labelsAttached() const;
+      LabelAttachment labelsAttached() const;
 
       void attachSpiceFromIsd(nlohmann::json Isd);
       void attachLineScanTableFromIsd(nlohmann::json isd);
@@ -251,7 +273,11 @@ namespace Isis {
       Cube *copy(FileName newFile, const CubeAttributeOutput &newFileAttributes);
       void create(const QString &cfile);
       void create(const QString &cfile, const CubeAttributeOutput &att);
+      void createGdal(QString &dataFileName, QString &, char **papszOptions);
+      void checkAccess(QString access);
       void open(const QString &cfile, QString access = "r");
+      void openCube(const QString &cubeFileName, QString access);
+      void openGdal(const QString &cubeFileName, QString access);
       void reopen(QString access = "r");
 
       void read(Blob &blob,
@@ -279,7 +305,8 @@ namespace Isis {
       void setDimensions(int ns, int nl, int nb);
       void setExternalDnData(FileName cubeFileWithDnData);
       void setFormat(Format format);
-      void setLabelsAttached(bool attached);
+      void setLabelsAttached(LabelAttachment attached);
+      // void setLabelsExternal(bool external);
       void setLabelSize(int labelBytes);
       void setPixelType(PixelType pixelType);
       void setVirtualBands(const QList<QString> &vbands);
@@ -313,7 +340,6 @@ namespace Isis {
       Statistics *statistics(const int &band, const double &validMin,
                              const double &validMax,
                              QString msg = "Gathering statistics");
-      bool storesDnData() const;
 
       void addCachingAlgorithm(CubeCachingAlgorithm *);
       void clearIoCache();
@@ -334,9 +360,12 @@ namespace Isis {
 
       void construct();
       QFile *dataFile() const;
+      GDALDataset *gdalDataset() const;
 
       void initialize();
       void initCoreFromLabel(const Pvl &label);
+      void initCoreFromGdal(const QString &labelFile);
+      void initLabelState(const Pvl &label);
       void initLabelFromFile(FileName labelFileName, bool readWrite);
       void openCheck();
       Pvl realDataFileLabel() const;
@@ -353,6 +382,7 @@ namespace Isis {
        * If isOpen() is true, then this is allocated.
        */
       QFile *m_labelFile;
+
       /**
        * This is only sometimes allocated. This is used for when IsOpen is true
        *   and labels are detached so the QFile for the labels does not give
@@ -360,11 +390,16 @@ namespace Isis {
        */
       QFile *m_dataFile;
 
+      /**
+       * TODO: Write description
+       */
+      GDALDataset *m_geodataSet = nullptr;
+
       /**
        * This does the heavy lifting for cube DN IO and is always allocated
        *   when isOpen() is true.
        */
-      CubeIoHandler *m_ioHandler;
+      ImageIoHandler *m_ioHandler;
 
       /**
        * The byte order of the opened cube; if there is no open cube then
@@ -413,14 +448,7 @@ namespace Isis {
       FileName *m_formatTemplateFile;
 
       //! True if labels are attached
-      bool m_attached;
-
-      /**
-       * True (most common case) when the cube DN data is inside the file we're writing to. False
-       *   means we're referencing another cube's internal DN data for reading, and writing buffers
-       *   is disallowed.
-       */
-      bool m_storesDnData;
+      LabelAttachment m_attached;
 
       //! The label if IsOpen(), otherwise NULL
       Pvl *m_label;
diff --git a/isis/src/base/objs/Cube/CubeIoHandler.cpp b/isis/src/base/objs/Cube/CubeIoHandler.cpp
index f94d0755539e25fa853bb45a619ed11fd8e903b1..de8c20c97c1441233b48a8a5a4361c7d3a5e365d 100644
--- a/isis/src/base/objs/Cube/CubeIoHandler.cpp
+++ b/isis/src/base/objs/Cube/CubeIoHandler.cpp
@@ -59,17 +59,16 @@ namespace Isis {
    *          initialized into the file before this object is destructed.
    */
   CubeIoHandler::CubeIoHandler(QFile * dataFile,
-      const QList<int> *virtualBandList, const Pvl &label, bool alreadyOnDisk) {
+      const QList<int> *virtualBandList, const Pvl &label, bool alreadyOnDisk) :
+                 ImageIoHandler(virtualBandList) {
     m_byteSwapper = NULL;
     m_cachingAlgorithms = NULL;
     m_dataIsOnDiskMap = NULL;
     m_rawData = NULL;
-    m_virtualBands = NULL;
     m_nullChunkData = NULL;
     m_lastProcessByLineChunks = NULL;
     m_writeCache = NULL;
     m_ioThreadPool = NULL;
-    m_writeThreadMutex = NULL;
 
     try {
       if (!dataFile) {
@@ -134,8 +133,6 @@ namespace Isis {
       if(!alreadyOnDisk) {
         m_dataIsOnDiskMap = new QMap<int, bool>;
       }
-
-      setVirtualBands(virtualBandList);
     }
     catch(IException &e) {
       IString msg = "Constructing CubeIoHandler failed";
@@ -201,17 +198,11 @@ namespace Isis {
     delete m_byteSwapper;
     m_byteSwapper = NULL;
 
-    delete m_virtualBands;
-    m_virtualBands = NULL;
-
     delete m_nullChunkData;
     m_nullChunkData = NULL;
 
     delete m_lastProcessByLineChunks;
     m_lastProcessByLineChunks = NULL;
-
-    delete m_writeThreadMutex;
-    m_writeThreadMutex = NULL;
   }
 
 
@@ -425,37 +416,6 @@ namespace Isis {
            (BigInt)getBytesPerChunk();
   }
 
-
-  /**
-   * This changes the virtual band list.
-   *
-   * @param virtualBandList A list where the indices are the vbands and the
-   *          values are the physical bands. The values are 1-based. This can
-   *          be specified as NULL, in which case the vbands are the physical
-   *          bands. The virtual band list is copied
-   *          (the pointer provided isn't remembered).
-   */
-  void CubeIoHandler::setVirtualBands(const QList<int> *virtualBandList) {
-    if(m_virtualBands) {
-      delete m_virtualBands;
-      m_virtualBands = NULL;
-    }
-
-    if(virtualBandList && !virtualBandList->empty())
-      m_virtualBands = new QList<int>(*virtualBandList);
-  }
-
-  /**
-   * Get the mutex that this IO handler is using around I/Os on the given
-   *   data file. A lock should be acquired before doing any reads/writes on
-   *   the data file externally.
-   *
-   * @return A mutex that can guarantee exclusive access to the data file
-   */
-  QMutex *CubeIoHandler::dataFileMutex() {
-    return m_writeThreadMutex;
-  }
-
   /**
    * @return the number of physical bands in the cube.
    */
@@ -920,10 +880,10 @@ namespace Isis {
     startX = max(cube1.getStartSample(), cube2.Sample());
     startY = max(cube1.getStartLine(), cube2.Line());
     startZ = max(cube1.getStartBand(), startPhysicalBand);
-    endX = min(cube1.getStartSample() + cube1.sampleCount() - 1,
-               cube2.Sample() + cube2.SampleDimension() - 1);
-    endY = min(cube1.getStartLine() + cube1.lineCount() - 1,
-               cube2.Line() + cube2.LineDimension() - 1);
+    endX = min(cube1.getStartSample() + cube1.sampleCount(),
+               cube2.Sample() + cube2.SampleDimension());
+    endY = min(cube1.getStartLine() + cube1.lineCount(),
+               cube2.Line() + cube2.LineDimension());
     endZ = min(cube1.getStartBand() + cube1.bandCount() - 1,
                endPhysicalBand);
   }
@@ -1379,6 +1339,11 @@ namespace Isis {
     const char *chunkBuf = chunk.getRawData().data();
     char *buffersRawBuf = (char *)output.RawBuffer();
 
+
+    // Scale the sampleand line increment, only necessary in read for Q apps
+    int lineIncrement = (double)output.LineDimension() / (double)output.LineDimensionScaled();
+    int sampleIncrement = (double)output.SampleDimension() / (double)output.SampleDimensionScaled();
+
     for(int z = startZ; z <= endZ; z++) {
       const int &bandIntoChunk = z - chunkStartBand;
       int virtualBand = z;
@@ -1388,12 +1353,16 @@ namespace Isis {
       if(virtualBand != 0 && virtualBand >= bufferBand &&
          virtualBand <= bufferBand + bufferBands - 1) {
 
-        for(int y = startY; y <= endY; y++) {
+        for(int y = startY; y < endY; y = y + lineIncrement) {
           const int &lineIntoChunk = y - chunkStartLine;
-          int bufferIndex = output.Index(startX, y, virtualBand);
 
-          for(int x = startX; x <= endX; x++) {
+          for(int x = startX; x < endX; x = x + sampleIncrement) {
             const int &sampleIntoChunk = x - chunkStartSample;
+            int bufferIndex = output.Index(x, y, virtualBand);
+            // Avoid rolling back onto your buffer
+            if (bufferIndex >= output.size()) {
+              bufferIndex = output.size() - 1;
+            }
 
             const int &chunkIndex = sampleIntoChunk +
                 (chunkLineSize * lineIntoChunk) +
@@ -1534,7 +1503,7 @@ namespace Isis {
               ((unsigned char *)buffersRawBuf)[bufferIndex] = raw;
             }
 
-            bufferIndex ++;
+            bufferIndex++;
           }
         }
       }
@@ -1590,11 +1559,11 @@ namespace Isis {
       if(virtualBand != 0 && virtualBand >= bufferBand &&
          virtualBand <= bufferBand + bufferBands - 1) {
 
-        for(int y = startY; y <= endY; y++) {
+        for(int y = startY; y < endY; y++) {
           const int &lineIntoChunk = y - outputStartLine;
           int bufferIndex = buffer.Index(startX, y, virtualBand);
 
-          for(int x = startX; x <= endX; x++) {
+          for(int x = startX; x < endX; x++) {
             const int &sampleIntoChunk = x - outputStartSample;
 
             const int &chunkIndex = sampleIntoChunk +
diff --git a/isis/src/base/objs/Cube/CubeIoHandler.h b/isis/src/base/objs/Cube/CubeIoHandler.h
index a5114ec4fea3cd065c1734be1ae24480bf2944ae..69ce20435e3c04fc9e8a3363379517e56d26c0ac 100644
--- a/isis/src/base/objs/Cube/CubeIoHandler.h
+++ b/isis/src/base/objs/Cube/CubeIoHandler.h
@@ -13,6 +13,7 @@ find files of those names at the top level of this repository. **/
 
 #include "Constants.h"
 #include "Endian.h"
+#include "ImageIoHandler.h"
 #include "PixelType.h"
 
 class QFile;
@@ -106,19 +107,18 @@ namespace Isis {
    *   @history 2018-08-13 Summer Stapleton - Fixed incoming buffer comparison values for 
    *                            unsigned int type in writeIntoRaw(...). 
    */
-  class CubeIoHandler {
+  class CubeIoHandler : public ImageIoHandler {
     public:
       CubeIoHandler(QFile * dataFile, const QList<int> *virtualBandList,
           const Pvl &label, bool alreadyOnDisk);
       virtual ~CubeIoHandler();
 
-      void read(Buffer &bufferToFill) const;
-      void write(const Buffer &bufferToWrite);
+      virtual void read(Buffer &bufferToFill) const;
+      virtual void write(const Buffer &bufferToWrite);
 
-      void addCachingAlgorithm(CubeCachingAlgorithm *algorithm);
-      void clearCache(bool blockForWriteCache = true) const;
-      BigInt getDataSize() const;
-      void setVirtualBands(const QList<int> *virtualBandList);
+      virtual void addCachingAlgorithm(CubeCachingAlgorithm *algorithm);
+      virtual void clearCache(bool blockForWriteCache = true) const;
+      virtual BigInt getDataSize() const;
       /**
        * Function to update the labels with a Pvl object
        *
@@ -126,7 +126,6 @@ namespace Isis {
        */
       virtual void updateLabels(Pvl &labels) = 0;
 
-      QMutex *dataFileMutex();
       int bandCount() const;
       int getBandCountInChunk() const;
       BigInt getBytesPerChunk() const;
@@ -310,8 +309,6 @@ namespace Isis {
       //! The map from chunk index to on-disk status, all true if not allocated.
       mutable QMap<int, bool> * m_dataIsOnDiskMap;
 
-      //! Converts from virtual band to physical band.
-      QList<int> * m_virtualBands;
 
       //! The number of samples in a cube chunk.
       int m_samplesInChunk;
@@ -352,9 +349,6 @@ namespace Isis {
        */
       bool m_useOptimizedCubeWrite;
 
-      //! This enables us to block while the write thread is working
-      QMutex *m_writeThreadMutex;
-
       //! Ideal write cache flush size
       mutable volatile int m_idealFlushSize;
 
diff --git a/isis/src/base/objs/CubeAttribute/CubeAttribute.cpp b/isis/src/base/objs/CubeAttribute/CubeAttribute.cpp
index 1402990f611d940a2a8ed8e5c236fd8c11e26757..f1d8b400db4e19449cf625e61db25850263265b2 100644
--- a/isis/src/base/objs/CubeAttribute/CubeAttribute.cpp
+++ b/isis/src/base/objs/CubeAttribute/CubeAttribute.cpp
@@ -274,6 +274,9 @@ namespace Isis {
 
       if (formatString == "BSQ" || formatString == "BANDSEQUENTIAL")
         result = Cube::Bsq;
+      else if(formatString == "GTIFF") {
+        result = Cube::GTiff;
+      }
     }
 
     return result;
@@ -286,8 +289,19 @@ namespace Isis {
 
 
   void CubeAttributeOutput::setFileFormat(Cube::Format fmt) {
-    setAttribute((fmt == Cube::Tile)? "Tile" : "BandSequential",
-                 &CubeAttributeOutput::isFileFormat);
+    if (fmt == Cube::Tile) {
+      setAttribute("Tile", &CubeAttributeOutput::isFileFormat);
+    }
+    else if (fmt == Cube::Bsq) {
+      setAttribute("BandSequential", &CubeAttributeOutput::isFileFormat);
+    }
+    else if (fmt == Cube::GTiff) {
+      setAttribute("GTiff", &CubeAttributeOutput::isFileFormat);
+    }
+    else {
+      QString msg = "Unsupported format [" + toString(fmt) + "]";
+      IException(IException::Programmer, msg, _FILEINFO_);
+    }
   }
 
 
@@ -392,22 +406,22 @@ namespace Isis {
   }
 
 
-  void CubeAttributeOutput::setLabelAttachment(LabelAttachment attachment) {
+  void CubeAttributeOutput::setLabelAttachment(Cube::LabelAttachment attachment) {
     setAttribute(LabelAttachmentName(attachment), &CubeAttributeOutput::isLabelAttachment);
   }
 
 
-  LabelAttachment CubeAttributeOutput::labelAttachment() const {
-    LabelAttachment result = AttachedLabel;
+  Cube::LabelAttachment CubeAttributeOutput::labelAttachment() const {
+    Cube::LabelAttachment result = Cube::AttachedLabel;
 
     QStringList labelAttachmentAtts = attributeList(&CubeAttributeOutput::isLabelAttachment);
     if (!labelAttachmentAtts.isEmpty()) {
       QString labelAttachmentAtt = labelAttachmentAtts.last();
 
       if (labelAttachmentAtt == "DETACHED")
-        result = DetachedLabel;
+        result = Cube::DetachedLabel;
       else if (labelAttachmentAtt == "EXTERNAL")
-        result = ExternalLabel;
+        result = Cube::ExternalLabel;
     }
 
     return result;
@@ -420,7 +434,7 @@ namespace Isis {
 
 
   bool CubeAttributeOutput::isFileFormat(QString attribute) const {
-    return QRegExp("(BANDSEQUENTIAL|BSQ|TILE)").exactMatch(attribute);
+    return QRegExp("(BANDSEQUENTIAL|BSQ|TILE|GTIFF)").exactMatch(attribute);
   }
 
 
@@ -443,10 +457,21 @@ namespace Isis {
 
 
   QString CubeAttributeOutput::toString(Cube::Format format) {
-    QString result = "Tile";
+    QString result;
 
-    if (format == Cube::Bsq)
+    if (format == Cube::Tile) {
+      result = "Tile";
+    }
+    else if (format == Cube::Bsq) {
       result = "BandSequential";
+    }
+    else if (format == Cube::GTiff) {
+      result = "GTiff";
+    }
+    else {
+      QString msg = "Format [" + QString::number(format) + "] cannot be translated to string";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
 
     return result;
   }
diff --git a/isis/src/base/objs/CubeAttribute/CubeAttribute.h b/isis/src/base/objs/CubeAttribute/CubeAttribute.h
index 28684c4a5a7729ef21908d8bf2104e179b5b5c60..5736d1ac21651cbc31400bde5382303e40410412 100644
--- a/isis/src/base/objs/CubeAttribute/CubeAttribute.h
+++ b/isis/src/base/objs/CubeAttribute/CubeAttribute.h
@@ -20,27 +20,6 @@ find files of those names at the top level of this repository. **/
 
 
 namespace Isis {
-  /**
-   * @brief Input cube label type tracker
-   *
-   * This enumeration and its functions are for the label
-   * type of an input cube. The enum defines the type of labels (i.e.,
-   * Both the label and cube are in the same file and the label is in a
-   * separate file from the cube.
-   */
-  enum LabelAttachment {
-    AttachedLabel,  //!< The input label is embedded in the image file
-    DetachedLabel,  //!< The input label is in a separate data file from the image
-    /**
-     * The label is pointing to an external DN file - the label is also external to the data.
-     *
-     * This format implies that the output is a cube that contains everything except DN data
-     *   (more similar to attached than detached).
-     */
-    ExternalLabel
-  };
-
-
   /**
    * Return the string representation of the contents of a
    * variable of type LabelAttachment
@@ -49,10 +28,11 @@ namespace Isis {
    *
    * @return A string representation of the parameter
    */
-  inline QString LabelAttachmentName(LabelAttachment labelType) {
-    if(labelType == AttachedLabel) return "Attached";
-    if(labelType == DetachedLabel) return "Detached";
-    if(labelType == ExternalLabel) return "External";
+  inline QString LabelAttachmentName(Cube::LabelAttachment labelType) {
+    if(labelType == Cube::AttachedLabel) return "Attached";
+    if(labelType == Cube::DetachedLabel) return "Detached";
+    if(labelType == Cube::ExternalLabel) return "External";
+    if(labelType == Cube::GdalLabel) return "Gdal";
 
     QString msg = "Invalid label attachment type [" + QString::number(labelType) + "]";
     throw IException(IException::Programmer, msg, _FILEINFO_);
@@ -67,18 +47,17 @@ namespace Isis {
    *
    * @return The RangeType enum corresponding to the string parameter
    */
-  inline LabelAttachment LabelAttachmentEnumeration(const QString &labelType) {
+  inline Cube::LabelAttachment LabelAttachmentEnumeration(const QString &labelType) {
     QString temp = labelType.toUpper();
-    if(temp == "ATTACHED") return AttachedLabel;
-    if(temp == "DETACHED") return DetachedLabel;
-    if(temp == "External") return ExternalLabel;
+    if(temp == "ATTACHED") return Cube::AttachedLabel;
+    if(temp == "DETACHED") return Cube::DetachedLabel;
+    if(temp == "EXTERNAL") return Cube::ExternalLabel;
+    if(temp == "GDAL") return Cube::GdalLabel;
 
     QString msg = "Invalid label attachment type string [" + labelType + "]";
     throw IException(IException::Unknown, msg, _FILEINFO_);
   }
 
-
-
   /**
    * @brief Parent class for CubeAttributeInput and CubeAttributeOutput.
    *
@@ -539,9 +518,9 @@ namespace Isis {
       void setPixelType(PixelType type);
 
       //! Set the label attachment type to the parameter value
-      void setLabelAttachment(LabelAttachment attachment);
+      void setLabelAttachment(Cube::LabelAttachment attachment);
 
-      LabelAttachment labelAttachment() const;
+      Cube::LabelAttachment labelAttachment() const;
 
       using CubeAttribute<CubeAttributeOutput>::toString;
 
diff --git a/isis/src/base/objs/CubeAttribute/unitTest.cpp b/isis/src/base/objs/CubeAttribute/unitTest.cpp
index 1892e65209b2e7cdf2585023dae63b4a9c9ac6dc..d4832be86986dea6cee638843ceb7680f848899f 100644
--- a/isis/src/base/objs/CubeAttribute/unitTest.cpp
+++ b/isis/src/base/objs/CubeAttribute/unitTest.cpp
@@ -205,10 +205,10 @@ int main(int argc, char *argv[]) {
     att.setPixelType(Real);
     cout << att.toString() << endl;
 
-    att.setLabelAttachment(DetachedLabel);
+    att.setLabelAttachment(Cube::DetachedLabel);
     cout << att.toString() << endl;
 
-    att.setLabelAttachment(ExternalLabel);
+    att.setLabelAttachment(Cube::ExternalLabel);
     cout << att.toString() << endl;
   }
   catch (IException &e) {
@@ -300,8 +300,8 @@ void reportOutput(const CubeAttributeOutput &att, QString orderHint) {
   }
 
   cout << "Label attachment     = ";
-  if(att.labelAttachment() == AttachedLabel)  cout << LabelAttachmentName(AttachedLabel) << endl;
-  if(att.labelAttachment() == DetachedLabel) cout << LabelAttachmentName(DetachedLabel) << endl;
+  if(att.labelAttachment() == Cube::AttachedLabel)  cout << LabelAttachmentName(Cube::AttachedLabel) << endl;
+  if(att.labelAttachment() == Cube::DetachedLabel) cout << LabelAttachmentName(Cube::DetachedLabel) << endl;
 
 #if 0
   fstream stream("CubeAttribute.truth", ios::in | ios::out);
diff --git a/isis/src/base/objs/CubeDataThread/CubeDataThread.cpp b/isis/src/base/objs/CubeDataThread/CubeDataThread.cpp
index d566ea8a4a1c839901c0d0d8fe5421d4b8d1d786..ed835e4283cb65ed2f975092a86d2ae50c8a09c2 100644
--- a/isis/src/base/objs/CubeDataThread/CubeDataThread.cpp
+++ b/isis/src/base/objs/CubeDataThread/CubeDataThread.cpp
@@ -255,13 +255,13 @@ namespace Isis {
    * @param sharedLock True if read-only, false if read-write
    */
   void CubeDataThread::GetCubeData(int cubeId, int ss, int sl, int es, int el,
-                                   int band, void *caller, bool sharedLock) {
+                                   int band, void *caller, bool sharedLock, double scale) {
 
     Brick *requestedBrick = NULL;
 
     p_threadSafeMutex->lock();
     requestedBrick = new Brick(*p_managedCubes->value(cubeId).second, es
-                                     - ss + 1, el - sl + 1, 1);
+                                     - ss + 1, (el - sl + 1) / scale, 1, false, scale);
     requestedBrick->SetBasePosition(ss, sl, band);
     p_threadSafeMutex->unlock();
 
@@ -425,7 +425,7 @@ namespace Isis {
    */
   void CubeDataThread::ReadCube(int cubeId, int startSample, int startLine,
                                 int endSample, int endLine, int band,
-                                void *caller) {
+                                void *caller, double scale) {
 
     if(!p_managedCubes->contains(cubeId)) {
       IString msg = "cube ID [";
@@ -434,8 +434,12 @@ namespace Isis {
       throw IException(IException::Programmer, msg, _FILEINFO_);
     }
 
+    if (scale > 1) {
+      scale = 1.0;
+    }
+
     GetCubeData(cubeId, startSample, startLine, endSample, endLine, band,
-                caller, true);
+                caller, true, scale);
   }
 
   /**
@@ -469,7 +473,7 @@ namespace Isis {
     }
 
     GetCubeData(cubeId, startSample, startLine, endSample, endLine, band,
-                caller, false);
+                caller, false, 1);
   }
 
   /**
diff --git a/isis/src/base/objs/CubeDataThread/CubeDataThread.h b/isis/src/base/objs/CubeDataThread/CubeDataThread.h
index 4c3af01b7145364089a69016593eb760c17bbb19..94f2a1530e30a053c321d23c2abd0bf958d459f3 100644
--- a/isis/src/base/objs/CubeDataThread/CubeDataThread.h
+++ b/isis/src/base/objs/CubeDataThread/CubeDataThread.h
@@ -81,7 +81,7 @@ namespace Isis {
 
     public slots:
       void ReadCube(int cubeId, int startSample, int startLine,
-                    int endSample, int endLine, int band, void *caller);
+                    int endSample, int endLine, int band, void *caller, double scale);
       void ReadWriteCube(int cubeId, int startSample, int startLine,
                          int endSample, int endLine, int band, void *caller);
 
@@ -155,7 +155,7 @@ namespace Isis {
                        int instanceNum, bool &exact);
 
       void GetCubeData(int cubeId, int ss, int sl, int es, int el, int band,
-                       void *caller, bool sharedLock);
+                       void *caller, bool sharedLock, double scale=1);
 
       void AcquireLock(QReadWriteLock *lockObject, bool readLock);
 
diff --git a/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.cpp b/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.cpp
index 8399b3bcef12decb1f7040af7ebf2bcb427776f8..50276bf5239e906bfd218dd0d7454dcd6a09b841 100644
--- a/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.cpp
+++ b/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.cpp
@@ -51,9 +51,9 @@ namespace Isis {
    */
   void CubeDataThreadTester::Connect() {
     connect(this,
-            SIGNAL(RequestReadCube(int, int, int, int, int, int, void *)),
+            SIGNAL(RequestReadCube(int, int, int, int, int, int, void *, double)),
             p_cubeDataThread,
-            SLOT(ReadCube(int, int, int, int, int, int, void *)));
+            SLOT(ReadCube(int, int, int, int, int, int, void *, double)));
 
     connect(this,
             SIGNAL(RequestReadWriteCube(int, int, int, int, int, int, void *)),
@@ -98,7 +98,7 @@ namespace Isis {
    */
   void CubeDataThreadTester::ReadCubeTest(int cubeId) {
     cout << "=============== Testing Basic Read ===============" << endl;
-    emit RequestReadCube(cubeId, 1, 1, 2, 2, 1, this);
+    emit RequestReadCube(cubeId, 1, 1, 2, 2, 1, this, 1.0);
   }
 
 
@@ -113,8 +113,8 @@ namespace Isis {
          "===============" << endl;
 
     p_notifyDone = false;
-    emit RequestReadCube(cubeId1, 1, 1, 3, 2, 1, this);
-    emit RequestReadCube(cubeId2, 1, 2, 3, 2, 1, this);
+    emit RequestReadCube(cubeId1, 1, 1, 3, 2, 1, this, 1.0);
+    emit RequestReadCube(cubeId2, 1, 2, 3, 2, 1, this, 1.0);
   }
 
 
@@ -128,8 +128,8 @@ namespace Isis {
          << endl << endl;
 
     p_notifyDone = false;
-    emit RequestReadCube(cubeId, 1, 2, 2, 2, 1, this);
-    emit RequestReadCube(cubeId, 1, 2, 2, 2, 1, this);
+    emit RequestReadCube(cubeId, 1, 2, 2, 2, 1, this, 1.0);
+    emit RequestReadCube(cubeId, 1, 2, 2, 2, 1, this, 1.0);
   }
 
 
@@ -141,7 +141,7 @@ namespace Isis {
   void CubeDataThreadTester::WriteCubeTest(int cubeId) {
     cout << "=============== Testing Basic R/W ===============" << endl << endl;
     emit RequestReadWriteCube(cubeId, 1, 1, 2, 2, 1, this);
-    emit RequestReadCube(cubeId, 1, 1, 2, 2, 1, this);
+    emit RequestReadCube(cubeId, 1, 1, 2, 2, 1, this, 1.0);
   }
 
 
diff --git a/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.h b/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.h
index 8134df48d7919991bb52be105279ce6a4e93f77c..04c6d85334989e12b8f06050b8ed7d1ef5478029 100644
--- a/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.h
+++ b/isis/src/base/objs/CubeDataThread/CubeDataThreadTester.h
@@ -67,7 +67,7 @@ namespace Isis {
        * @param caller A this pointer
        */
       void RequestReadCube(int cubeId, int startSample, int startLine,
-                           int endSample, int endLine, int band, void *caller);
+                           int endSample, int endLine, int band, void *caller, double scale);
 
 
       /**
diff --git a/isis/src/base/objs/CubeManager/CubeManager.truth b/isis/src/base/objs/CubeManager/CubeManager.truth
index 064d543ea9667cfd50b0fa59e0e228d116185e1f..0465b4a2f5f975f04ae65eee8da6f2f67273c584 100644
--- a/isis/src/base/objs/CubeManager/CubeManager.truth
+++ b/isis/src/base/objs/CubeManager/CubeManager.truth
@@ -49,4 +49,6 @@ Verify isisTruth2.cub is now managed and limit of 2 is enforced:
 Setting number of open cubes > 60 percent of system open file limit
 
 Attempting to open a file that does not exist:
-  **I/O ERROR** Unable to open [dne.cub].
+  **I/O ERROR** Failed to open [dne.cub].
+**PROGRAMMER ERROR** Gdal failed to open [dne.cub].
+**I/O ERROR** Unable to open [dne.cub].
diff --git a/isis/src/base/objs/EquatorialCylindricalShape/EquatorialCylindricalShape.cpp b/isis/src/base/objs/EquatorialCylindricalShape/EquatorialCylindricalShape.cpp
index ba969599db46b231beaf5b1a58ee31cfa3c9b679..777c5ee2d9858a0ae3c3abf204ac1fdb50ffa211 100644
--- a/isis/src/base/objs/EquatorialCylindricalShape/EquatorialCylindricalShape.cpp
+++ b/isis/src/base/objs/EquatorialCylindricalShape/EquatorialCylindricalShape.cpp
@@ -32,8 +32,6 @@ find files of those names at the top level of this repository. **/
 
 using namespace std;
 
-#define MAX(x,y) (((x) > (y)) ? (x) : (y))
-
 namespace Isis {
   /**
    * Initialize the ISIS Equatorial Cylindrical shape model.
@@ -222,7 +220,7 @@ namespace Isis {
       // Set dalpha to be half the grid spacing for nyquist sampling
       //double dalpha = (PI/180.0)/(2.0*p_demScale);
       double cmin = cos((90.0 - 1.0 / (2.0 * demScale())) * DEG2RAD);
-      double dalpha = MAX(cos(g1lat * DEG2RAD), cmin) / (2.0 * demScale() * RAD2DEG);
+      double dalpha = std::max(cos(g1lat * DEG2RAD), cmin) / (2.0 * demScale() * RAD2DEG);
 
       // Previous Sensor version used local version of this method with lat and lon doubles.  Steven said
       // it didn't make a significant difference in speed.
@@ -390,7 +388,7 @@ namespace Isis {
         // put in a test (above) for dd being smaller than the pixel
         // convergence tolerance.  If so the loop exits without an
         // intersection
-        dalpha = MAX(cos(g2lat * DEG2RAD), cmin) / (2.0 * demScale() * RAD2DEG);
+        dalpha = std::max(cos(g2lat * DEG2RAD), cmin) / (2.0 * demScale() * RAD2DEG);
       } // end while
 
       SpiceDouble intersectionPoint[3];
diff --git a/isis/src/base/objs/FileName/FileName.cpp b/isis/src/base/objs/FileName/FileName.cpp
index 425e0801a5af08f81bd93a3e6e4e6c10e21632f4..0ef719ced72f6b2edcfa22fada8efd3bfc6ecd9e 100644
--- a/isis/src/base/objs/FileName/FileName.cpp
+++ b/isis/src/base/objs/FileName/FileName.cpp
@@ -447,6 +447,9 @@ namespace Isis {
    * @return Boolean
    */
   bool FileName::fileExists() const {
+    if (toString().contains((QString)"https://")) { 
+      return true; 
+    }
     return QFileInfo(expanded()).exists();
   }
 
diff --git a/isis/src/base/objs/Gui/GuiOutputAttribute.cpp b/isis/src/base/objs/Gui/GuiOutputAttribute.cpp
index cff2ed9119b7e9dd7b4964080ba741b614eff2cc..04579559bb8d43fac8450928b53428be9a208cd7 100644
--- a/isis/src/base/objs/Gui/GuiOutputAttribute.cpp
+++ b/isis/src/base/objs/Gui/GuiOutputAttribute.cpp
@@ -244,7 +244,7 @@ namespace Isis {
       p_msb->setChecked(true);
     }
 
-    if(att.labelAttachment() == AttachedLabel) {
+    if(att.labelAttachment() == Cube::AttachedLabel) {
       p_attached->setChecked(true);
     }
     else {
diff --git a/isis/src/base/objs/IException/IException.cpp b/isis/src/base/objs/IException/IException.cpp
index cd42a87e53b18b1ac4f9860edbad56a63c225300..aa79d36efb16766f722eebaffd7865649bc297df 100644
--- a/isis/src/base/objs/IException/IException.cpp
+++ b/isis/src/base/objs/IException/IException.cpp
@@ -639,6 +639,10 @@ namespace Isis {
     return *this;
   }
 
+  int IException::length() {
+    return m_previousExceptions->length();
+  }
+
 
   /**
    * Returns the source of the error in string format for the given ErrorType.
diff --git a/isis/src/base/objs/IException/IException.h b/isis/src/base/objs/IException/IException.h
index 17c0c84fac20db572b8a62ed53fec208687f104f..f78f00f4913f99ff48939e9bed57a7af17d8b5f5 100644
--- a/isis/src/base/objs/IException/IException.h
+++ b/isis/src/base/objs/IException/IException.h
@@ -197,6 +197,8 @@ namespace Isis {
       void swap(IException &other);
       IException &operator=(const IException &rhs);
 
+      int length();
+
     private:
       static IException createStackTrace();
       static ErrorType stringToErrorType(const QString &s);
diff --git a/isis/src/base/objs/ImageIoHandler/GdalIoHandler.cpp b/isis/src/base/objs/ImageIoHandler/GdalIoHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a5af442c9d6c914654d6a7408d9257e8396524d5
--- /dev/null
+++ b/isis/src/base/objs/ImageIoHandler/GdalIoHandler.cpp
@@ -0,0 +1,873 @@
+#include "GdalIoHandler.h"
+
+#include "Brick.h"
+#include "Buffer.h"
+#include "CubeCachingAlgorithm.h"
+#include "IException.h"
+#include "Pvl.h"
+#include "PvlObject.h"
+#include "SpecialPixel.h"
+
+#include <QList>
+#include <QMutex>
+#include <QString>
+
+#include <iostream>
+
+using namespace std;
+
+namespace Isis {
+  GdalIoHandler::GdalIoHandler(QString &dataFilePath, const QList<int> *virtualBandList, GDALDataType pixelType, GDALAccess eAccess) : 
+                 ImageIoHandler(virtualBandList) {
+    GDALAllRegister();
+    m_geodataSetPath = dataFilePath.toUtf8().constData();
+    m_geodataSet = GDALDataset::FromHandle(GDALOpen(m_geodataSetPath.c_str(), eAccess));
+    m_datasetOwner = true;
+    m_pixelType = pixelType;
+    init();
+  }
+
+  GdalIoHandler::GdalIoHandler(GDALDataset *geodataSet, const QList<int> *virtualBandList, GDALDataType pixelType) : 
+                 ImageIoHandler(virtualBandList) {
+    m_geodataSet = geodataSet;
+    if (!m_geodataSet) {
+      QString msg = "Constructing GdalIoHandler failed. Null geodataSet pointer passed";
+      throw IException(IException::Programmer, msg, _FILEINFO_);
+    }
+    m_datasetOwner = false;
+    m_pixelType = pixelType;
+    init();
+  }
+
+  void GdalIoHandler::init() {
+    m_samples = m_geodataSet->GetRasterXSize();
+    m_lines = m_geodataSet->GetRasterYSize();
+    m_bands = m_geodataSet->GetRasterCount();
+
+    m_driverName = std::string(m_geodataSet->GetDriverName());
+
+    // Check if we need to create the mask band
+    if ((m_geodataSet->GetAccess() == GA_Update) && (m_driverName != "ISIS3")) {
+      m_geodataSet->CreateMaskBand(8);
+      m_geodataSet->GetRasterBand(1)->GetMaskBand()->Fill(255);
+    }
+
+    GDALRasterBand *band = m_geodataSet->GetRasterBand(1);
+    m_offset = band->GetOffset();
+    m_scale = band->GetScale();
+    int *pbSuccess = new int;
+    m_gdalNoDataValue = band->GetNoDataValue(pbSuccess);
+    if (!pbSuccess) {
+      m_gdalNoDataValue = NULL8;
+    }
+    delete pbSuccess;
+  }
+
+  GdalIoHandler::~GdalIoHandler() {
+    clearCache();
+    if (m_maskBuff) {
+      delete m_maskBuff;
+    }
+    if (m_geodataSet && m_datasetOwner) {
+      m_geodataSet->Close();
+    }
+  }
+
+  void GdalIoHandler::read(Buffer &bufferToFill) const {
+    GDALRasterBand  *poBand;
+
+    bufferToFill = NULL8;
+    int maxBand = bufferToFill.BandDimension() + bufferToFill.Band() - 1;
+    for (int band = bufferToFill.Band(); band <= maxBand; band++) {
+      int vband = band;
+      if (m_virtualBands)
+        vband = m_virtualBands->at(band - 1);
+
+      if (vband > m_bands) {
+        return;
+      }
+
+      poBand = m_geodataSet->GetRasterBand(vband);
+      
+      // Account for 1 based line and sample reading from ISIS process classes
+      // as gdal reads 0 based lines and samples
+      int lineStart = bufferToFill.Line() - 1;
+      int sampleStart = bufferToFill.Sample() - 1;
+      int lineSize = bufferToFill.LineDimension();
+      int sampleSize = bufferToFill.SampleDimension();
+
+      // Handle reading out of bound pixels as cube IO handler would
+      bool outOfBounds = false;
+      if (lineStart < 0) {
+        lineStart = 0;
+        outOfBounds = true;
+      }
+      if (lineStart + lineSize > m_lines) {
+        lineSize = m_lines - lineStart;
+        outOfBounds = true;
+      }
+      if (sampleStart < 0) {
+        sampleStart = 0;
+        outOfBounds = true;
+      }
+      if (sampleStart + sampleSize > m_samples) {
+        sampleSize = m_samples - sampleStart;
+        outOfBounds = true;
+      }
+
+      if (sampleSize > 0 && lineSize > 0) {
+        if (outOfBounds) {
+          Brick boundedBrick(sampleSize, lineSize, bufferToFill.BandDimension(), GdalPixelToIsis(m_pixelType), false, bufferToFill.scale());
+          boundedBrick.SetBasePosition(sampleStart + 1, lineStart + 1, bufferToFill.Band());
+          int bufferSize = boundedBrick.SampleDimensionScaled() * boundedBrick.LineDimensionScaled();
+          int currentBandIdx = bufferSize * (vband - boundedBrick.Band());
+          char *buffersRawBuf = &(((char *)boundedBrick.RawBuffer())[(int)(currentBandIdx * SizeOf(boundedBrick.PixelType()))]);
+          double *buffersDoubleBuf = &(boundedBrick.DoubleBuffer()[currentBandIdx]);
+          CPLErr err = poBand->RasterIO(GF_Read, sampleStart, lineStart,
+                                        sampleSize, lineSize,
+                                        buffersRawBuf,
+                                        boundedBrick.SampleDimensionScaled(), boundedBrick.LineDimensionScaled(),
+                                        m_pixelType,
+                                        0, 0);
+
+          if (err >= CE_Failure) {
+            QString msg = "Failure when trying to read image";
+            throw IException(IException::Unknown, msg, _FILEINFO_);
+          }
+
+          // Handle pixel type conversion
+          for (int bufferIdx = 0; bufferIdx < bufferSize; bufferIdx++) {
+            readPixelType(buffersDoubleBuf, buffersRawBuf, bufferIdx);
+          }
+          bufferToFill.CopyOverlapFrom(boundedBrick);
+        }
+        else {
+          int bufferSize = bufferToFill.SampleDimensionScaled() * bufferToFill.LineDimensionScaled();;
+          int currentBandIdx = bufferSize * (vband - bufferToFill.Band());
+          // silence warnings
+          char *buffersRawBuf = &(((char *)bufferToFill.RawBuffer())[(int)(currentBandIdx * SizeOf(bufferToFill.PixelType()))]);
+          double *buffersDoubleBuf = &(bufferToFill.DoubleBuffer()[currentBandIdx]);
+          CPLErr err = poBand->RasterIO(GF_Read, sampleStart, lineStart,
+                                        sampleSize, lineSize,
+                                        buffersRawBuf,
+                                        bufferToFill.SampleDimensionScaled(), bufferToFill.LineDimensionScaled(),
+                                        m_pixelType,
+                                        0, 0);
+          if (err >= CE_Failure) {
+            QString msg = "Failure when trying to read image";
+            throw IException(IException::Unknown, msg, _FILEINFO_);
+          }
+
+          // Handle pixel type conversion
+          for (int bufferIdx = 0; bufferIdx < bufferSize; bufferIdx++) {
+            readPixelType(buffersDoubleBuf, buffersRawBuf, bufferIdx);
+          }
+        }
+      }
+    }
+  }
+
+  void GdalIoHandler::write(const Buffer &bufferToWrite) {
+    CPLErr gdalerr;
+    GDALRasterBand  *poBand;
+
+    int maxBand = bufferToWrite.BandDimension() + bufferToWrite.Band() - 1;
+    for (int band = bufferToWrite.Band(); band <= maxBand; band++) {
+      int vband = band;
+      if(m_virtualBands) 
+        vband = m_virtualBands->indexOf(band) + 1;
+      
+      if (band > m_bands) {
+        return;
+      }
+
+      poBand = m_geodataSet->GetRasterBand(vband);
+
+      int lineStart = bufferToWrite.Line() - 1;
+      int sampleStart = bufferToWrite.Sample() - 1;
+      int lineSize = bufferToWrite.LineDimension();
+      int sampleSize = bufferToWrite.SampleDimension();
+
+      if (lineStart < 0) {
+        lineStart = 0;
+      }
+      if (lineStart + lineSize > m_lines) {
+        lineSize = m_lines - lineStart;
+      }
+      if (sampleStart < 0) {
+        sampleStart = 0;
+      }
+      if (sampleStart + sampleSize > m_samples) {
+        sampleSize = m_samples - sampleStart;
+      }
+
+      int bufferSize = sampleSize * lineSize;
+      int currentBandIdx = bufferSize * (vband - bufferToWrite.Band());
+      m_maskBuff = (unsigned char *) CPLMalloc(sizeof(unsigned char) * bufferSize);
+      for (int i = 0; i < bufferSize; i++) {
+        m_maskBuff[i] = 255;
+      }
+
+
+      // Handle pixel type conversion
+      char *buffersRawBuf = &(((char *)bufferToWrite.RawBuffer())[(int)(currentBandIdx * SizeOf(bufferToWrite.PixelType()))]);
+      double *buffersDoubleBuf = &(bufferToWrite.DoubleBuffer()[currentBandIdx]);
+      // std::cout << buffersDoubleBuf[0] << std::endl;
+      for (int bufferIdx = 0; bufferIdx < bufferSize; bufferIdx++) {
+        if (writePixelType(buffersDoubleBuf, buffersRawBuf, bufferIdx)) {
+          m_maskBuff[bufferIdx] = 0;
+        };
+      }
+
+      // silence warning
+      gdalerr = poBand->RasterIO(GF_Write, sampleStart, lineStart,
+                                sampleSize, lineSize,
+                                (void *)buffersRawBuf,
+                                sampleSize, lineSize,
+                                m_pixelType,
+                                0, 0);
+      if (gdalerr >= CE_Failure) {
+        QString msg = "Failure when trying to write image";
+        throw IException(IException::Unknown, msg, _FILEINFO_);
+      }
+
+      if (m_driverName != "ISIS3") { 
+        poBand = m_geodataSet->GetRasterBand(band)->GetMaskBand();
+        gdalerr = poBand->RasterIO(GF_Write, sampleStart, lineStart,
+                                  sampleSize, lineSize,
+                                  m_maskBuff,
+                                  sampleSize, lineSize,
+                                  GDT_Byte,
+                                  0, 0);
+        if (gdalerr >= CE_Failure) {
+          QString msg = "Failure when trying to write msk file";
+          throw IException(IException::Unknown, msg, _FILEINFO_);
+        }
+      }
+    }
+
+    delete m_maskBuff;
+    m_maskBuff = NULL;
+  }
+
+  BigInt GdalIoHandler::getDataSize() const {
+    return 0;
+  }
+  /**
+   * Function to update the labels with a Pvl object
+   *
+   * @param labels Pvl object to update with
+   */
+  void GdalIoHandler::updateLabels(Pvl &labels) {
+    PvlObject &core = labels.findObject("IsisCube").findObject("Core");
+    core.addKeyword(PvlKeyword("Format", "GTiff"), PvlContainer::Replace);
+  }
+
+  void GdalIoHandler::readPixelType(double *doubleBuff, void *rawBuff, int idx) const {
+    double &bufferVal = doubleBuff[idx];
+    
+    if (m_pixelType == GDT_Float64) {
+      bufferVal = ((double *)rawBuff)[idx];
+      if (bufferVal == m_gdalNoDataValue) {
+        bufferVal = NULL8;
+      }
+    }
+    
+    else if(m_pixelType == GDT_Float32) {
+      float raw = ((float *)rawBuff)[idx];
+      // if(m_byteSwapper)
+      //   raw = m_byteSwapper->Float(&raw);
+      if (raw == (float) m_gdalNoDataValue) {
+        raw = NULL4;
+      }
+
+      if(raw >= VALID_MIN4) {
+        bufferVal = (double) raw;
+      }
+      else {
+        if(raw == NULL4)
+          bufferVal = NULL8;
+        else if(raw == LOW_INSTR_SAT4)
+          bufferVal = LOW_INSTR_SAT8;
+        else if(raw == LOW_REPR_SAT4)
+          bufferVal = LOW_REPR_SAT8;
+        else if(raw == HIGH_INSTR_SAT4)
+          bufferVal = HIGH_INSTR_SAT8;
+        else if(raw == HIGH_REPR_SAT4)
+          bufferVal = HIGH_REPR_SAT8;
+        else
+          bufferVal = LOW_REPR_SAT8;
+      }
+
+      ((float *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Int32) {
+      int raw = ((int *)rawBuff)[idx];
+      // if(m_byteSwapper)
+      //   raw = m_byteSwapper->Uint32_t(&raw);
+      if (raw == (int) m_gdalNoDataValue) {
+        raw = NULLI4;
+      }
+
+      if(raw >= VALID_MINI4) {
+        bufferVal = (double) raw * m_scale + m_offset;
+      }
+      else {
+        if (raw == NULLI4)
+          bufferVal = NULL8;
+        else if (raw == LOW_INSTR_SATI4)
+          bufferVal = LOW_INSTR_SAT8;
+        else if (raw == LOW_REPR_SATI4)
+          bufferVal = LOW_REPR_SAT8;
+        else if (raw == HIGH_INSTR_SATI4)
+          bufferVal = HIGH_INSTR_SAT8;
+        else if (raw == HIGH_REPR_SATI4)
+          bufferVal = HIGH_REPR_SAT8;
+        else
+          bufferVal = LOW_REPR_SAT8;
+      }
+      ((int *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_UInt32) {
+      unsigned int raw = ((unsigned int *)rawBuff)[idx];
+      // if(m_byteSwapper)
+      //   raw = m_byteSwapper->Uint32_t(&raw);
+      if (raw == (unsigned int) m_gdalNoDataValue) {
+        raw = NULLUI4;
+      }
+
+      if(raw >= VALID_MINUI4) {
+        bufferVal = (double) raw * m_scale + m_offset;
+        if (raw >= VALID_MAXUI4) {
+          if(raw == HIGH_INSTR_SATUI4)
+            bufferVal = HIGH_INSTR_SAT8;
+          else if(raw == HIGH_REPR_SATUI4)
+            bufferVal = HIGH_REPR_SAT8;
+          else
+            bufferVal = HIGH_REPR_SAT8;
+        }
+      }
+      else {
+        if(raw == NULLUI4)
+          bufferVal = NULL8;
+        else if(raw == LOW_INSTR_SATUI4)
+          bufferVal = LOW_INSTR_SAT8;
+        else if(raw == LOW_REPR_SATUI4)
+          bufferVal = LOW_REPR_SAT8;
+        else
+          bufferVal = LOW_REPR_SAT8;
+      }
+      ((unsigned int *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Int16) {
+      short raw = ((short *)rawBuff)[idx];
+      // if(m_byteSwapper)
+      //   raw = m_byteSwapper->ShortInt(&raw);
+      if (raw == (short) m_gdalNoDataValue) {
+        raw = NULL2;
+      }
+
+      if(raw >= VALID_MIN2) {
+        bufferVal = (double) raw * m_scale + m_offset;
+      }
+      else {
+        if(raw == NULL2)
+          bufferVal = NULL8;
+        else if(raw == LOW_INSTR_SAT2)
+          bufferVal = LOW_INSTR_SAT8;
+        else if(raw == LOW_REPR_SAT2)
+          bufferVal = LOW_REPR_SAT8;
+        else if(raw == HIGH_INSTR_SAT2)
+          bufferVal = HIGH_INSTR_SAT8;
+        else if(raw == HIGH_REPR_SAT2)
+          bufferVal = HIGH_REPR_SAT8;
+        else
+          bufferVal = LOW_REPR_SAT8;
+      }
+
+      ((short *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_UInt16) {
+      unsigned short raw = ((unsigned short *)rawBuff)[idx];
+      // if(m_byteSwapper)
+      //   raw = m_byteSwapper->UnsignedShortInt(&raw);
+      if (raw == (unsigned short) m_gdalNoDataValue) {
+        raw = NULLU2;
+      }
+
+      if(raw >= VALID_MINU2) {
+        bufferVal = (double) raw * m_scale + m_offset;
+        if (raw > VALID_MAXU2) {
+          if(raw == HIGH_INSTR_SATU2)
+            bufferVal = HIGH_INSTR_SAT8;
+          else if(raw == HIGH_REPR_SATU2)
+            bufferVal = HIGH_REPR_SAT8;
+          else
+            bufferVal = HIGH_REPR_SAT8;
+        }
+      }
+      else {
+        if(raw == NULLU2)
+          bufferVal = NULL8;
+        else if(raw == LOW_INSTR_SATU2)
+          bufferVal = LOW_INSTR_SAT8;
+        else if(raw == LOW_REPR_SATU2)
+          bufferVal = LOW_REPR_SAT8;
+        else
+          bufferVal = LOW_REPR_SAT8;
+      }
+      ((unsigned short *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Int8) {
+      char raw = ((char *)rawBuff)[idx];
+      if (raw == (char) m_gdalNoDataValue) {
+        raw = NULLS1;
+      }
+
+      if(raw == NULLS1) {
+        bufferVal = NULL8;
+      }
+      else if(raw == HIGH_REPR_SATS1) {
+        bufferVal = HIGH_REPR_SAT8;
+      }
+      else {
+        bufferVal = (double) raw * m_scale + m_offset;
+      }
+
+      ((char *)rawBuff)[idx] = raw;
+    }
+    
+    else if(m_pixelType == GDT_Byte) {
+      unsigned char raw = ((unsigned char *)rawBuff)[idx];
+      if (raw == (char) m_gdalNoDataValue) {
+        raw = NULL1;
+      }
+
+      if(raw == NULL1) {
+        bufferVal = NULL8;
+      }
+      else if(raw == HIGH_REPR_SAT1) {
+        bufferVal = HIGH_REPR_SAT8;
+      }
+      else {
+        bufferVal = (double) raw * m_scale + m_offset;
+      }
+
+      ((unsigned char *)rawBuff)[idx] = raw;
+    }
+  }
+
+  bool GdalIoHandler::writePixelType(double *doubleBuff, void *rawBuff, int idx) const {
+    double bufferVal = doubleBuff[idx];
+    bool isSpecial = false;
+    if (m_pixelType == GDT_Float64) {
+      ((double *)rawBuff)[idx] = bufferVal;
+    }
+    
+    else if(m_pixelType == GDT_Float32) {
+      float raw = 0;
+
+      if(bufferVal >= VALID_MIN8) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+
+        if(filePixelValueDbl < (double) VALID_MIN4) {
+          raw = LOW_REPR_SAT4;
+          isSpecial = true;
+        }
+        else if(filePixelValueDbl > (double) VALID_MAX4) {
+          raw = HIGH_REPR_SAT4;
+          isSpecial = true;
+        }
+        else {
+          raw = (float) filePixelValueDbl;
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULL4;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SAT4;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SAT4;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SAT4;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SAT4;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SAT4;
+          isSpecial = true;
+        }
+      }
+      ((float *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Int32) {
+      int raw;
+
+      if(bufferVal >= VALID_MINI4) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+        if(filePixelValueDbl < VALID_MINI4 - 0.5) {
+          raw = LOW_REPR_SATI4;
+          isSpecial = true;
+        }
+        if(filePixelValueDbl > VALID_MAXI4) {
+          raw = HIGH_REPR_SATI4;
+          isSpecial = true;
+        }
+        else {
+          int filePixelValue = (int)round(filePixelValueDbl);
+
+          if(filePixelValue < VALID_MINI4) {
+            raw = LOW_REPR_SATI4;
+            isSpecial = true;
+          }
+          else if(filePixelValue > VALID_MAXI4) {
+            raw = HIGH_REPR_SATI4;
+            isSpecial = true;
+          }
+          else {
+            raw = filePixelValue;
+          }
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULLI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SATI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SATI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SATI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SATI4;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SATI4;
+          isSpecial = true;
+        }
+      }
+      ((int *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_UInt32) {
+      unsigned int raw;
+
+      if(bufferVal >= VALID_MINUI4) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+        if(filePixelValueDbl < VALID_MINUI4 - 0.5) {
+          raw = LOW_REPR_SATUI4;
+          isSpecial = true;
+        }
+        if(filePixelValueDbl > VALID_MAXUI4) {
+          raw = HIGH_REPR_SATUI4;
+          isSpecial = true;
+        }
+        else {
+          unsigned int filePixelValue = (unsigned int)round(filePixelValueDbl);
+
+          if(filePixelValue < VALID_MINUI4) {
+            raw = LOW_REPR_SATUI4;
+            isSpecial = true;
+          }
+          else if(filePixelValue > VALID_MAXUI4) {
+            raw = HIGH_REPR_SATUI4;
+            isSpecial = true;
+          }
+          else {
+            raw = filePixelValue;
+          }
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULLUI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SATUI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SATUI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SATUI4;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SATUI4;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SATUI4;
+          isSpecial = true;
+        }
+      }
+      ((unsigned int *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Int16) {
+      short raw;
+
+      if(bufferVal >= VALID_MIN8) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+        if(filePixelValueDbl < VALID_MIN2 - 0.5) {
+          raw = LOW_REPR_SAT2;
+          isSpecial = true;
+        }
+        if(filePixelValueDbl > VALID_MAX2 + 0.5) {
+          raw = HIGH_REPR_SAT2;
+          isSpecial = true;
+        }
+        else {
+          int filePixelValue = (int)round(filePixelValueDbl);
+
+          if(filePixelValue < VALID_MIN2) {
+            raw = LOW_REPR_SAT2;
+            isSpecial = true;
+          }
+          else if(filePixelValue > VALID_MAX2) {
+            raw = HIGH_REPR_SAT2;
+            isSpecial = true;
+          }
+          else {
+            raw = filePixelValue;
+          }
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULL2;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SAT2;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SAT2;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SAT2;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SAT2;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SAT2;
+          isSpecial = true;
+        }
+      }
+      ((short *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_UInt16) {
+      unsigned short raw;
+
+      if(bufferVal >= VALID_MIN8) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+        if(filePixelValueDbl < VALID_MINU2 - 0.5) {
+          raw = LOW_REPR_SATU2;
+          isSpecial = true;
+        }
+        if(filePixelValueDbl > VALID_MAXU2 + 0.5) {
+          raw = HIGH_REPR_SATU2;
+          isSpecial = true;
+        }
+        else {
+          int filePixelValue = (int)round(filePixelValueDbl);
+
+          if(filePixelValue < VALID_MINU2) {
+            raw = LOW_REPR_SATU2;
+            isSpecial = true;
+          }
+          else if(filePixelValue > VALID_MAXU2) {
+            raw = HIGH_REPR_SATU2;
+            isSpecial = true;
+          }
+          else {
+            raw = filePixelValue;
+          }
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULLU2;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SATU2;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SATU2;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SATU2;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SATU2;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SATU2;
+          isSpecial = true;
+        }
+      }
+      ((unsigned short *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Int8) {
+     char raw;
+
+      if(bufferVal >= VALID_MIN8) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+        if(filePixelValueDbl < VALID_MINS1 - 0.5) {
+          raw = LOW_REPR_SATS1;
+          isSpecial = true;
+        }
+        else if(filePixelValueDbl > VALID_MAXS1 + 0.5) {
+          raw = HIGH_REPR_SATS1;
+          isSpecial = true;
+        }
+        else {
+          int filePixelValue = (int)(filePixelValueDbl + 0.5);
+          if(filePixelValue < VALID_MINS1) {
+            raw = LOW_REPR_SATS1;
+            isSpecial = true;
+          }
+          else if(filePixelValue > VALID_MAXS1) {
+            raw = HIGH_REPR_SATS1;
+            isSpecial = true;
+          }
+          else {
+            raw = (char)(filePixelValue);
+          }
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULLS1;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SATS1;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SATS1;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SATS1;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SATS1;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SATS1;
+          isSpecial = true;
+        }
+      }
+      ((char *)rawBuff)[idx] = raw;
+    }
+
+    else if(m_pixelType == GDT_Byte) {
+      unsigned char raw;
+
+      if(bufferVal >= VALID_MIN8) {
+        double filePixelValueDbl = (bufferVal - m_offset) /
+            m_scale;
+        if(filePixelValueDbl < VALID_MIN1 - 0.5) {
+          raw = LOW_REPR_SAT1;
+          isSpecial = true;
+        }
+        else if(filePixelValueDbl > VALID_MAX1 + 0.5) {
+          raw = HIGH_REPR_SAT1;
+          isSpecial = true;
+        }
+        else {
+          int filePixelValue = (int)(filePixelValueDbl + 0.5);
+          if(filePixelValue < VALID_MIN1) {
+            raw = LOW_REPR_SAT1;
+            isSpecial = true;
+          }
+          else if(filePixelValue > VALID_MAX1) {
+            raw = HIGH_REPR_SAT1;
+            isSpecial = true;
+          }
+          else {
+            raw = (unsigned char)(filePixelValue);
+          }
+        }
+      }
+      else {
+        if(bufferVal == NULL8) {
+          raw = NULL1;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_INSTR_SAT8) {
+          raw = LOW_INSTR_SAT1;
+          isSpecial = true;
+        }
+        else if(bufferVal == LOW_REPR_SAT8) {
+          raw = LOW_REPR_SAT1;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_INSTR_SAT8) {
+          raw = HIGH_INSTR_SAT1;
+          isSpecial = true;
+        }
+        else if(bufferVal == HIGH_REPR_SAT8) {
+          raw = HIGH_REPR_SAT1;
+          isSpecial = true;
+        }
+        else {
+          raw = LOW_REPR_SAT1;
+          isSpecial = true;
+        }
+      }
+      ((unsigned char *)rawBuff)[idx] = raw;
+    }
+    return isSpecial;
+  }
+}
\ No newline at end of file
diff --git a/isis/src/base/objs/ImageIoHandler/GdalIoHandler.h b/isis/src/base/objs/ImageIoHandler/GdalIoHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..bdc5079d815e5b3ef296bc4b26d2526e5a6ee3a5
--- /dev/null
+++ b/isis/src/base/objs/ImageIoHandler/GdalIoHandler.h
@@ -0,0 +1,85 @@
+/** This is free and unencumbered software released into the public domain.
+The authors of ISIS do not claim copyright on the contents of this file.
+For more details about the LICENSE terms and the AUTHORS, you will
+find files of those names at the top level of this repository. **/
+
+/* SPDX-License-Identifier: CC0-1.0 */
+
+#ifndef GdalIoHandler_h
+#define GdalIoHandler_h
+
+#include "Constants.h"
+#include "ImageIoHandler.h"
+#include "SpecialPixel.h"
+
+#include "gdal_priv.h"
+
+class QFile;
+class QMutex;
+class QString;
+template <typename A> class QList;
+
+namespace Isis {
+  class Buffer;
+  class CubeCachingAlgorithm;
+  class Pvl;
+
+  /**
+   * @ingroup Low Level Image IO
+   *
+   * @brief Handles converting buffers to and from disk.
+   *
+   * This class handles converting buffers to and from disk. This class holds
+   *   the cube chunks in memory and is capable of reading and writing them. It
+   *   asks the caching algorithms to recommend cube chunks to not keep in
+   *   memory. Children need to call setChunkSizes() in their constructor.
+   *
+   * This class handles all of the virtual band conversions. This class also
+   *   guarantees that unwritten cube data ends up read and written as NULLs.
+   *   The default caching algorithm is a RegionalCachingAlgorithm.
+   *
+   * @author 2024-04-30 Adam Paquette
+   *
+   */
+  class GdalIoHandler : public ImageIoHandler {
+    public:
+      GdalIoHandler(QString &dataFilePath, const QList<int> *virtualBandList, GDALDataType pixelType = GDT_Float64, GDALAccess eAccess=GA_ReadOnly);
+      GdalIoHandler(GDALDataset *geodataSet, const QList<int> *virtualBandList, GDALDataType pixelType = GDT_Float64);
+      void init();
+      virtual ~GdalIoHandler();
+
+      virtual void read(Buffer &bufferToFill) const;
+      virtual void write(const Buffer &bufferToWrite);
+
+      virtual BigInt getDataSize() const;
+      /**
+       * Function to update the labels with a Pvl object
+       *
+       * @param labels Pvl object to update with
+       */
+      virtual void updateLabels(Pvl &labels);
+
+      virtual void clearCache(bool blockForWriteCache=false) {
+        m_geodataSet->FlushCache(blockForWriteCache);
+      }
+
+    private:
+      void readPixelType(double *doubleBuff, void *rawBuff, int idx) const;
+      bool writePixelType(double *doubleBuff, void *rawBuff, int idx) const;
+
+      GDALDataset *m_geodataSet = nullptr;
+      std::string m_geodataSetPath = "";
+      GDALDataType m_pixelType;
+      int m_lines;
+      int m_samples;
+      int m_bands;
+      double m_offset;
+      double m_scale;
+      unsigned char *m_maskBuff = nullptr;
+      bool m_datasetOwner = false;
+      double m_gdalNoDataValue = NULL8;
+      std::string m_driverName;
+  };
+}
+
+#endif
diff --git a/isis/src/base/objs/ImageIoHandler/ImageIoHandler.cpp b/isis/src/base/objs/ImageIoHandler/ImageIoHandler.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..7965c69e2b82b76acd0a4a43d401870cc27c98fc
--- /dev/null
+++ b/isis/src/base/objs/ImageIoHandler/ImageIoHandler.cpp
@@ -0,0 +1,61 @@
+#include "ImageIoHandler.h"
+
+#include "Buffer.h"
+#include "CubeCachingAlgorithm.h"
+
+#include <QList>
+#include <QMutex>
+
+using namespace std;
+
+namespace Isis {
+  ImageIoHandler::ImageIoHandler(const QList<int> *virtualBandList) {
+    m_writeThreadMutex = NULL;
+    m_writeThreadMutex = new QMutex;
+    m_virtualBands = NULL;
+
+    setVirtualBands(virtualBandList);
+  }
+
+  ImageIoHandler::~ImageIoHandler() {
+    delete m_virtualBands;
+    m_virtualBands = NULL;
+
+    delete m_writeThreadMutex;
+    m_writeThreadMutex = NULL;
+  }
+
+  void ImageIoHandler::addCachingAlgorithm(CubeCachingAlgorithm *algorithm) {}
+
+  void ImageIoHandler::clearCache(bool blockForWriteCache) const {}
+
+  /**
+   * This changes the virtual band list.
+   *
+   * @param virtualBandList A list where the indices are the vbands and the
+   *          values are the physical bands. The values are 1-based. This can
+   *          be specified as NULL, in which case the vbands are the physical
+   *          bands. The virtual band list is copied
+   *          (the pointer provided isn't remembered).
+   */
+  void ImageIoHandler::setVirtualBands(const QList<int> *virtualBandList) {
+    if(m_virtualBands) {
+      delete m_virtualBands;
+      m_virtualBands = NULL;
+    }
+
+    if(virtualBandList && !virtualBandList->empty())
+      m_virtualBands = new QList<int>(*virtualBandList);
+  }
+
+  /**
+   * Get the mutex that this IO handler is using around I/Os on the given
+   *   data file. A lock should be acquired before doing any reads/writes on
+   *   the data file externally.
+   *
+   * @return A mutex that can guarantee exclusive access to the data file
+   */
+  QMutex *ImageIoHandler::dataFileMutex() {
+    return m_writeThreadMutex;
+  }
+}
\ No newline at end of file
diff --git a/isis/src/base/objs/ImageIoHandler/ImageIoHandler.h b/isis/src/base/objs/ImageIoHandler/ImageIoHandler.h
new file mode 100644
index 0000000000000000000000000000000000000000..baab584cd7a709116151238a7a0a94a946dc1a2d
--- /dev/null
+++ b/isis/src/base/objs/ImageIoHandler/ImageIoHandler.h
@@ -0,0 +1,128 @@
+/** This is free and unencumbered software released into the public domain.
+The authors of ISIS do not claim copyright on the contents of this file.
+For more details about the LICENSE terms and the AUTHORS, you will
+find files of those names at the top level of this repository. **/
+
+/* SPDX-License-Identifier: CC0-1.0 */
+
+#ifndef ImageIoHandler_h
+#define ImageIoHandler_h
+
+#include "Constants.h"
+
+class QFile;
+class QMutex;
+template <typename A> class QList;
+
+namespace Isis {
+  class Buffer;
+  class CubeCachingAlgorithm;
+  class Pvl;
+
+  /**
+   * @ingroup Low Level Image IO
+   *
+   * @brief Handles converting buffers to and from disk.
+   *
+   * This class handles converting buffers to and from disk. This class holds
+   *   the cube chunks in memory and is capable of reading and writing them. It
+   *   asks the caching algorithms to recommend cube chunks to not keep in
+   *   memory. Children need to call setChunkSizes() in their constructor.
+   *
+   * This class handles all of the virtual band conversions. This class also
+   *   guarantees that unwritten cube data ends up read and written as NULLs.
+   *   The default caching algorithm is a RegionalCachingAlgorithm.
+   *
+   * @author 2011-??-?? Jai Rideout and Steven Lambright
+   *
+   * @internal
+   *   @history 2011-06-27 Steven Lambright - Added addCachingAlgorithm()
+   *   @history 2011-07-18 Jai Rideout and Steven Lambright - Added
+   *                           unimplemented copy constructor and assignment
+   *                           operator.
+   *   @history 2011-07-18 Jai Rideout and Steven Lambright - getDataFile() is
+   *                           no longer const, along with readRaw() and
+   *                           writeRaw().
+   *   @history 2011-11-23 Jai Rideout - Added fix to findCubeChunks() and
+   *                           findIntersection() so that requested areas that
+   *                           are outside of the virtual bands of the cube will
+   *                           no longer fail, but instead will fill the buffer
+   *                           with nulls.
+   *   @history 2012-02-17 Steven Lambright - The read() method is now const -
+   *                           the caching should be transparent to the API. It
+   *                           is still important to note that you cannot call
+   *                           read() and write() simultaneously - Cube is
+   *                           guaranteeing this. Backgrounded writes are now
+   *                           implemented and fully supported. Timing tests
+   *                           show results like this:
+   *                           Before:
+   *                           User: 358.71 Kernel: 38.29 Total Elapsed: 6:49.83
+   *                           After:
+   *                           User: 380.01 Kernel: 44.97 Total Elapsed: 6:19.96
+   *                             User- user-space CPU time consumed.
+   *                             Kernel- kernel-space CPU time consumed.
+   *                           The CPU consumption and kernel time consumption
+   *                           goes up, but the overall run time is lower. This
+   *                           makes the option perfectly viable for typical
+   *                           multi-core desktop solutions. Turning off the
+   *                           cube write optimization results in equivalent run
+   *                           times as before implementation. These time tests
+   *                           were run before the adaptive cache flush size was
+   *                           added, which improved performance further.
+   *                           References #727.
+   *   @history 2012-06-06 Jeff Anderson - The read() method was modified to
+   *                           improve the speed for small buffer sizes as seen
+   *                           in ProcessByBoxcar and ProcessRubbersheet.  The
+   *                           internal cache was always checked to be minimized
+   *                           which is slow for every read.  Now the cache is
+   *                           only mimimized if it changed in size.
+   *                           Reference #894.
+   *   @history 2014-04-07 Kimberly Oyama and Stuart Sides - Modified the findCubeChunks,
+   *                            writeIntoDouble, and writeIntoRaw methods to handle
+   *                            repeating virtual bands. Fixes #1927.
+   *   @history 2014-09-30 Ian Humphrey - Modified read method to correctly access virtual bands
+   *                           when cube dimensions and buffer shape are same size. Fixes #1689.
+   *   @history 2015-01-30 Ian Humphrey - Modified destructor to free m_writThreadMutex to
+   *                           prevent memory leaks upon destruction. Fixes #2082.
+   *   @history 2016-04-21 Makayla Shepherd - Added UnsignedWord pixel type handling.
+   *   @history 2016-06-21 Kris Becker - Properly forward declare QPair as struct not class
+   *   @history 2016-08-28 Kelvin Rodriguez - updateLabels now a pure virtual, it had no
+   *                           implementation causing warnings in clang. Part of OS X 10.11 porting.
+   *                           QPair forward declaration now properly claims it as a struct.
+   *   @history 2017-09-22 Cole Neubauer - Fixed documentation. References #4807
+   *   @history 2018-07-20 Tyler Wilson - Added support for unsigned integer special pixel values.
+   *                            in functions writeIntoRaw(...) and writeIntoDouble(...)
+   *                            References #971.
+   *   @history 2018-08-13 Summer Stapleton - Fixed incoming buffer comparison values for 
+   *                            unsigned int type in writeIntoRaw(...). 
+   */
+  class ImageIoHandler {
+    public:
+      ImageIoHandler(const QList<int> *virtualBandList);
+      virtual ~ImageIoHandler();
+
+      virtual void read(Buffer &bufferToFill) const = 0;
+      virtual void write(const Buffer &bufferToWrite) = 0;
+
+      virtual void addCachingAlgorithm(CubeCachingAlgorithm *algorithm);
+      virtual void clearCache(bool blockForWriteCache = true) const;
+      virtual BigInt getDataSize() const = 0;
+      void setVirtualBands(const QList<int> *virtualBandList);
+      /**
+       * Function to update the labels with a Pvl object
+       *
+       * @param labels Pvl object to update with
+       */
+      virtual void updateLabels(Pvl &labels) = 0;
+      QMutex *dataFileMutex();
+
+    protected:
+      //! This enables us to block while the write thread is working
+      QMutex *m_writeThreadMutex;
+
+      //! Converts from virtual band to physical band.
+      QList<int> *m_virtualBands;
+  };
+}
+
+#endif
diff --git a/isis/src/base/objs/PixelType/PixelType.h b/isis/src/base/objs/PixelType/PixelType.h
index c9f5868fe88850d988a55c5620f2e63068f938a1..8f2de66d2c8bd13f680c40f9657aeb9af6c38d58 100644
--- a/isis/src/base/objs/PixelType/PixelType.h
+++ b/isis/src/base/objs/PixelType/PixelType.h
@@ -8,6 +8,8 @@ find files of those names at the top level of this repository. **/
 /* SPDX-License-Identifier: CC0-1.0 */
 #include <QString>
 
+#include "gdal_priv.h"
+
 namespace Isis {
   /**
    * @brief Enumerations for Isis Pixel Types
@@ -98,6 +100,31 @@ namespace Isis {
     if(temp == "DOUBLE") return Isis::Double;
     return Isis::None;
   }
+
+  inline Isis::PixelType GdalPixelToIsis(GDALDataType type) {
+    if (type == GDT_Byte) return Isis::UnsignedByte;
+    if (type == GDT_Int8) return Isis::SignedByte;
+    if (type == GDT_UInt16) return Isis::UnsignedWord;
+    if (type == GDT_Int16) return Isis::SignedWord;
+    if (type == GDT_UInt32) return Isis::UnsignedInteger;
+    if (type == GDT_Int32) return Isis::SignedInteger;
+    if (type == GDT_Float32) return Isis::Real;
+    if (type == GDT_Float64) return Isis::Double;
+    return Isis::None;
+  }
+
+  inline GDALDataType IsisPixelToGdal(Isis::PixelType type) {
+    if(type == Isis::None) return GDT_Unknown;
+    if(type == Isis::UnsignedByte) return GDT_Byte;
+    if(type == Isis::SignedByte) return GDT_Int8;
+    if(type == Isis::UnsignedWord) return GDT_UInt16;
+    if(type == Isis::SignedWord) return GDT_Int16;
+    if(type == Isis::UnsignedInteger) return GDT_UInt32;
+    if(type == Isis::SignedInteger) return GDT_Int32;
+    if(type == Isis::Real) return GDT_Float32;
+    if(type == Isis::Double) return GDT_Float64;
+    return GDT_Unknown;
+  }
 }
 
 #endif
diff --git a/isis/src/base/objs/Process/Process.cpp b/isis/src/base/objs/Process/Process.cpp
index b0046635298415d142b27003c9bc5b5efb39d4f7..0d00aafe736d37bde495f171957f3388fbc14f63 100644
--- a/isis/src/base/objs/Process/Process.cpp
+++ b/isis/src/base/objs/Process/Process.cpp
@@ -330,7 +330,7 @@ Isis::Cube *Process::SetOutputCubeStretch(const QString &parameter, const int ns
       cube->setDimensions(ns, nl, nb);
       cube->setByteOrder(att.byteOrder());
       cube->setFormat(att.fileFormat());
-      cube->setLabelsAttached(att.labelAttachment() == AttachedLabel);
+      cube->setLabelsAttached(att.labelAttachment());
       if(att.propagatePixelType()) {
         if(InputCubes.size() > 0) {
           cube->setPixelType(InputCubes[0]->pixelType());
diff --git a/isis/src/base/objs/ProcessMapMosaic/ProcessMapMosaic.cpp b/isis/src/base/objs/ProcessMapMosaic/ProcessMapMosaic.cpp
index 90368f87ce5b1d126f56a45bcc0f133d01ad74a0..48d9292d49efead3769693bcb3f113b412380e75 100644
--- a/isis/src/base/objs/ProcessMapMosaic/ProcessMapMosaic.cpp
+++ b/isis/src/base/objs/ProcessMapMosaic/ProcessMapMosaic.cpp
@@ -508,7 +508,8 @@ namespace Isis {
       double xmin, double xmax, double ymin, double ymax,
       double slat, double elat, double slon, double elon, int nbands,
       CubeAttributeOutput &oAtt, const QString &mosaicFile, bool latlonflag) {
-    Pvl fileLab(inputFile);
+    Cube cube(inputFile);
+    Pvl fileLab = *cube.label();
     PvlGroup &mapping = fileLab.findGroup("Mapping", Pvl::Traverse);
 
     // All mosaicking programs use only the upper left x and y to determine where to
diff --git a/isis/src/base/objs/Pvl/Pvl.cpp b/isis/src/base/objs/Pvl/Pvl.cpp
index 1c6240509303098d37e4bb79bcd0c0c3050731fe..318913bca1b12a016ce52572d71c5782542d5c0b 100644
--- a/isis/src/base/objs/Pvl/Pvl.cpp
+++ b/isis/src/base/objs/Pvl/Pvl.cpp
@@ -11,6 +11,11 @@ find files of those names at the top level of this repository. **/
 #include <locale>
 #include <fstream>
 #include <sstream>
+#include <cctype>
+
+#include <filesystem>
+#include <random>
+#include <nlohmann/json.hpp>
 
 #include "FileName.h"
 #include "IException.h"
@@ -18,6 +23,10 @@ find files of those names at the top level of this repository. **/
 #include "PvlTokenizer.h"
 #include "PvlFormat.h"
 
+namespace fs = std::filesystem;
+using json = nlohmann::json;
+using ordered_json = nlohmann::ordered_json;
+
 using namespace std;
 namespace Isis {
   //! Constructs an empty Pvl object.
@@ -36,7 +45,6 @@ namespace Isis {
     read(file);
   }
 
-
   //! Copy constructor
   Pvl::Pvl(const Pvl &other) : PvlObject::PvlObject(other) {
     m_internalTemplate = false;
@@ -51,6 +59,109 @@ namespace Isis {
     m_internalTemplate = false;
   }
 
+  // This function specifically reads from GDAL-style JSON metadata.  
+  Isis::PvlObject &Pvl::readObject(Isis::PvlObject &pvlobj, nlohmann::ordered_json jdata) {
+    for(auto &[key, value] : jdata.items()) {
+      string name = key; 
+      if(value.contains("_type")) { 
+        string type = value.at("_type"); 
+        value.erase("_type"); 
+        if (value.contains("_container_name")) { 
+          name = value["_container_name"];
+          value.erase("_container_name");
+        }
+
+        if(type == "object") { 
+          PvlObject nestedObj(QString::fromStdString(name));
+          pvlobj.addObject(readObject(nestedObj, value)); 
+        }
+        if(type == "group") { 
+          // parse keys 
+          PvlGroup group(QString::fromStdString(name)); 
+          for(auto &[pvlkeyword, pvlvalue] : value.items())  { 
+            PvlKeyword keyword;
+            keyword.setName(QString::fromStdString(pvlkeyword));
+            if (pvlvalue.is_array())
+                keyword.addJsonArrayValue(pvlvalue);
+            else 
+              keyword.setJsonValue(pvlvalue);
+            group.addKeyword(keyword);
+          }
+          pvlobj.addGroup(group);
+        } // end of group
+      } // end of _type search
+
+      // not a group or object, must be a keyword
+      else if (key != "_type" && key != "_filename" && key != "Data") { 
+        PvlKeyword keyword;
+        keyword.setName(QString::fromStdString(key));
+        if (value.is_array()) 
+          keyword.setJsonArrayValue(value);
+        else 
+          keyword.setJsonValue(value);
+
+        pvlobj.addKeyword(keyword);
+      }
+    }
+    return pvlobj;
+  }
+
+
+  ordered_json Pvl::toJson() { 
+    // needs to be a function because recursion 
+    function<ordered_json(PvlObject&)> pvlobject_to_json = [&](PvlObject &pvlobj) -> ordered_json { 
+      ordered_json jsonobj;
+      string okey = (pvlobj.name()).toStdString();
+      if (pvlobj.hasKeyword("Name")) {
+        okey = (pvlobj.name()+"_"+(QString)pvlobj.findKeyword("Name")).toStdString();
+      }
+      jsonobj[okey] = {};
+      if (pvlobj.hasKeyword("Name")) {
+        jsonobj[okey]["_container_name"] = pvlobj.name().toStdString();
+      }
+
+      if (okey != "Root") {
+        jsonobj[okey]["_type"] = "object";
+      }
+      
+      for (int i=0; i < pvlobj.objects(); i++) { 
+        jsonobj[okey].merge_patch(pvlobject_to_json(pvlobj.object(i)));
+      }
+      
+      for (int i=0; i<pvlobj.groups(); i++) { 
+        PvlGroup g = pvlobj.group(i);
+        string gkey = (g.name()).toStdString();
+        if (g.hasKeyword("Name")) {
+          gkey = (g.name()+"_"+(QString)g.findKeyword("Name")).toStdString();
+        }
+        jsonobj[okey][gkey] = {};
+        if (g.hasKeyword("Name")) {
+          jsonobj[okey][gkey]["_container_name"] = g.name().toStdString();
+        }
+        jsonobj[okey][gkey]["_type"] = "group"; 
+
+        // go through keywords
+        for(int j =0; j<g.keywords();j++) { 
+          PvlKeyword k=g[j];
+          jsonobj[okey][gkey][k.name().toStdString()] = k.toJson();         
+        }        
+      }
+
+      // get left over keywords
+      for(int i =0; i<pvlobj.keywords();i++) { 
+        PvlKeyword k=pvlobj[i]; 
+        jsonobj[okey][k.name().toStdString()] = k.toJson();         
+      }
+      return jsonobj;
+    };
+
+    if (this->name() == "Root" && this->objects() == 1) { 
+      return pvlobject_to_json(this->object(0));
+    }
+    else { 
+      return pvlobject_to_json(*this);
+    }
+  }
 
   /**
    * Load PVL information from a string 
diff --git a/isis/src/base/objs/Pvl/Pvl.h b/isis/src/base/objs/Pvl/Pvl.h
index d634ac5a6dcd5b6e37a424372133585303804e37..df89e8db6ca3f757fccfdf0b439ec12686f0e3b4 100644
--- a/isis/src/base/objs/Pvl/Pvl.h
+++ b/isis/src/base/objs/Pvl/Pvl.h
@@ -8,8 +8,11 @@ find files of those names at the top level of this repository. **/
 
 /* SPDX-License-Identifier: CC0-1.0 */
 #include <fstream>
+
 #include "PvlObject.h"
 
+#include <gdal_priv.h>
+
 namespace Isis {
   /**
    * @brief Container for cube-like labels
@@ -126,12 +129,15 @@ namespace Isis {
       friend std::ostream &operator<<(std::ostream &os, Isis::Pvl &pvl);
       void fromString(const std::string &str);
 
+      static Isis::PvlObject &readObject(Isis::PvlObject &pvlobj, nlohmann::ordered_json jdata);
+      nlohmann::ordered_json toJson();
+
       ~Pvl() {
         if(m_internalTemplate) delete m_formatTemplate;
       };
 
       void read(const QString &file);
-
+      
       void write(const QString &file);
       void append(const QString &file);
 
diff --git a/isis/src/base/objs/PvlKeyword/PvlKeyword.cpp b/isis/src/base/objs/PvlKeyword/PvlKeyword.cpp
index 0fa0aca35bf96376bd7c480f309f65c98a2f72f3..4fa5c779d20fcda30bde1c4c197d0fb533aa6ba2 100644
--- a/isis/src/base/objs/PvlKeyword/PvlKeyword.cpp
+++ b/isis/src/base/objs/PvlKeyword/PvlKeyword.cpp
@@ -19,6 +19,7 @@ find files of those names at the top level of this repository. **/
 
 using namespace std;
 using json = nlohmann::json;
+
 namespace Isis {
   //! Constructs a blank PvlKeyword object.
   PvlKeyword::PvlKeyword() {
@@ -172,10 +173,9 @@ namespace Isis {
    *
    * @see addJsonValue()
    */
-  void PvlKeyword::setJsonValue(json jsonobj, QString unit)
-  {
+  void PvlKeyword::setJsonValue(json jsonobj) {
     clear();
-    addJsonValue(jsonobj, unit);
+    addJsonValue(jsonobj);
   }
 
   /**
@@ -301,31 +301,108 @@ namespace Isis {
    *
    * @throws Isis::iException::Unknown - jsonobj cannot be an array of values
    */
-  void PvlKeyword::addJsonValue(json jsonobj, QString unit) {
+  void PvlKeyword::addJsonValue(json jsonobj) {
     QString value;
-    if (jsonobj.is_array()) {
+    QString unit = "";
+    json jvalue = jsonobj; 
+    if (jsonobj.contains("unit")) {
+       unit = QString::fromStdString(jsonobj["unit"].get<string>());
+       jvalue = jsonobj["value"];
+    }
+
+    if (jvalue.is_array()) {
       QString msg = "Unable to convert " + name() + " with nested json array value into PvlKeyword";
       throw IException(IException::Unknown, msg, _FILEINFO_);
     }
-    else if (jsonobj.is_number())
-    {
-      value = QString::number(jsonobj.get<double>(), 'g', 16);
+    else if (jvalue.is_number()) {
+      value = QString::number(jvalue.get<double>(), 'g', 16);
     }
-    else if (jsonobj.is_boolean())
-    {
-      value = QString(jsonobj.get<bool>() ? "true" : "false");
+    else if (jvalue.is_boolean()) {
+      value = QString(jvalue.get<bool>() ? "true" : "false");
     }
-    else if (jsonobj.is_null())
-    {
+    else if (jvalue.is_null()) {
       value = QString("Null");
     }
-    else
-    {
-      value = QString::fromStdString(jsonobj);
+    else {
+      value = QString::fromStdString(jvalue);
     }
     addValue(value, unit);
   }
 
+  /**
+   * Adds multiple items from a json array.
+   *
+   * If no current value exists, this method sets the given json value.
+   * Otherwise, it retains any current values and adds the json value
+   * given to the array of values for this PvlKeyword object using addValue.
+   * Defaults to unit = "" (empty QString).
+   *
+   * @param jsonobj New jsonobj to be parsed and assigned.
+   * @param unit Units of measurement corresponding to the value.
+   *
+   * @see setJsonValue()
+   * @see addValue()
+   *
+   * @throws Isis::iException::Unknown - jsonobj must be a json array
+   */
+  void PvlKeyword::addJsonArrayValue(json jsonobj) { 
+    if(!jsonobj.is_array()) { 
+      QString msg = "Unable to convert to a json array:\n" + QString::fromStdString(jsonobj.dump());
+      throw IException(IException::Unknown, msg, _FILEINFO_); 
+    } 
+
+    for(auto ar = jsonobj.begin(); ar!=jsonobj.end(); ar++) {
+      try {
+        addJsonValue(*ar);
+      }
+      catch (IException &e) {
+        QString msg = "While attempting to parse " + name() + " the following occured";
+        throw IException(e, IException::Unknown, msg, _FILEINFO_);
+      }
+    }
+  }
+
+
+  json PvlKeyword::toJson() { 
+    if(size() == 1) { 
+      if(unit() == "") {
+        return this->operator[](0).toStdString();
+      } else { 
+        return {{"unit", unit().toStdString()}, {"value", this->operator[](0).toStdString()}};
+      }
+    } else { 
+      // it's an array 
+      json jsonarr = json::array();
+      for(int i = 0; i < size(); i++) { 
+        jsonarr += this->operator[](i).toStdString();
+      }
+      return jsonarr;
+    } 
+  }
+
+
+  /**
+   * sets multiple items from a json array.
+   *
+   * If no current value exists, this method sets the given json value.
+   * Otherwise, it retains any current values and adds the json value
+   * given to the array of values for this PvlKeyword object using addValue.
+   * Defaults to unit = "" (empty QString).
+   *
+   * @param jsonobj New jsonobj to be parsed and assigned.
+   * @param unit Units of measurement corresponding to the value.
+   *
+   * @see setJsonValue()
+   * @see addValue()
+   *
+   * @throws Isis::iException::Unknown - jsonobj must be a json array
+   */
+  void PvlKeyword::setJsonArrayValue(json jsonobj) { 
+    clear(); 
+    addJsonArrayValue(jsonobj);
+  }
+
+
   /**
    * Adds a value.
    *
diff --git a/isis/src/base/objs/PvlKeyword/PvlKeyword.h b/isis/src/base/objs/PvlKeyword/PvlKeyword.h
index ba9800c6a166f6011a2ffbf7ccfea956f64b500d..fff827b1995e5d680ef00299a5882a47cc654b28 100644
--- a/isis/src/base/objs/PvlKeyword/PvlKeyword.h
+++ b/isis/src/base/objs/PvlKeyword/PvlKeyword.h
@@ -117,7 +117,9 @@ namespace Isis {
       };
 
       void setValue(QString value, QString unit = "");
-      void setJsonValue(nlohmann::json jsonobj, QString unit = "");
+      void setJsonValue(nlohmann::json jsonobj);
+      void setJsonArrayValue(nlohmann::json jsonobj);
+
 
       void setUnits(QString units);
       void setUnits(QString value, QString units);
@@ -125,7 +127,8 @@ namespace Isis {
       PvlKeyword &operator=(QString value);
 
       void addValue(QString value, QString unit = "");
-      void addJsonValue(nlohmann::json jsonobj, QString unit = "");
+      void addJsonValue(nlohmann::json jsonobj);
+      void addJsonArrayValue(nlohmann::json jsonobj);
 
       PvlKeyword &operator+=(QString value);
 
@@ -155,6 +158,8 @@ namespace Isis {
 
       operator QString() const;
 
+      nlohmann::json toJson();
+
       const QString &operator[](int index) const;
       QString &operator[](int index);
       QString unit(const int index = 0) const;
diff --git a/isis/src/base/objs/ShapeModelFactory/ShapeModelFactory.truth b/isis/src/base/objs/ShapeModelFactory/ShapeModelFactory.truth
index 2750ff4e6556d5a9e169bf94617f5c705cd0fd00..0ea1a8227ae49ad2f78e95158f5cd25d2f6fa63a 100644
--- a/isis/src/base/objs/ShapeModelFactory/ShapeModelFactory.truth
+++ b/isis/src/base/objs/ShapeModelFactory/ShapeModelFactory.truth
@@ -39,6 +39,8 @@ Unit test for Isis::ShapeModel
 **PROGRAMMER ERROR** Unable to create a shape model from given target and pvl.
 **I/O ERROR** Invalid shape model file [NotAFile] in Kernels group.
 **ERROR** The given shape model file is not a valid ISIS DEM. Unable to open as an ISIS cube.
+**I/O ERROR** Failed to open [NotAFile].
+**PROGRAMMER ERROR** Gdal failed to open [NotAFile].
 **I/O ERROR** Unable to open [NotAFile].
 **ERROR** The given shape model file is not a valid NAIF DSK file. Unable to construct a NAIF DSK shape model.
 **USER ERROR** NAIF DSK file [NotAFile] does not exist.
diff --git a/isis/src/base/objs/SpecialPixel/SpecialPixel.h b/isis/src/base/objs/SpecialPixel/SpecialPixel.h
index 2d1c56ed98fbd77c9f8ad2ef0192efb6059f155e..277c7e877bbfeb49b92b3edd5e3757c7da35d6d1 100644
--- a/isis/src/base/objs/SpecialPixel/SpecialPixel.h
+++ b/isis/src/base/objs/SpecialPixel/SpecialPixel.h
@@ -150,10 +150,10 @@ namespace Isis {
   const int IVALID_MAX4  = (*((const int *) &VALID_MAX4));
 
   // 2-byte signed special pixel values
-  const short VALID_MIN2     = ((short)(-32752));
-  const short NULL2          = ((short)(-32768));
-  const short LOW_REPR_SAT2  = ((short)(-32767));
-  const short LOW_INSTR_SAT2 = ((short)(-32766));
+  const short VALID_MIN2      = ((short)(-32752));
+  const short NULL2           = ((short)(-32768));
+  const short LOW_REPR_SAT2   = ((short)(-32767));
+  const short LOW_INSTR_SAT2  = ((short)(-32766));
   const short HIGH_INSTR_SAT2 = ((short)(-32765));
   const short HIGH_REPR_SAT2  = ((short)(-32764));
   const short VALID_MAX2      = ((short)   32767);
@@ -167,6 +167,15 @@ namespace Isis {
   const unsigned short HIGH_REPR_SATU2  = ((unsigned short)   65535);
   const unsigned short VALID_MAXU2      = ((unsigned short)   65522);
 
+  // 4-byte signed special pixel values
+  const int VALID_MINI4      = ((int)(-2147483632));
+  const int NULLI4           = ((int)(-2147483648));
+  const int LOW_REPR_SATI4   = ((int)(-2147483647));
+  const int LOW_INSTR_SATI4  = ((int)(-2147483646));
+  const int HIGH_INSTR_SATI4 = ((int)(-2147483645));
+  const int HIGH_REPR_SATI4  = ((int)(-2147483644));
+  const int VALID_MAXI4      = ((int)  2147483647 );
+
   // 4-byte unsigned special pixel values
   const unsigned int VALID_MINUI4      = ((unsigned int)       3);
   const unsigned int NULLUI4           = ((unsigned int)       0);
@@ -176,8 +185,16 @@ namespace Isis {
   const unsigned int HIGH_REPR_SATUI4  = ((unsigned int)   4294967295);
   const unsigned int VALID_MAXUI4      = ((unsigned int)   4294967282);
 
+  // 1-byte signed special pixel values
+  const char VALID_MINS1      = ((char) -126);
+  const char NULLS1           = ((char) -128);
+  const char LOW_REPR_SATS1   = ((char) -128);
+  const char LOW_INSTR_SATS1  = ((char) -128);
+  const char HIGH_INSTR_SATS1 = ((char) -127);
+  const char HIGH_REPR_SATS1  = ((char) -127);
+  const char VALID_MAXS1      = ((char) 127);
 
-  // 1-byte special pixel values
+  // 1-byte unsigned special pixel values
   const unsigned char VALID_MIN1      = ((unsigned char) 1);
   const unsigned char NULL1           = ((unsigned char) 0);
   const unsigned char LOW_REPR_SAT1   = ((unsigned char) 0);
diff --git a/isis/src/base/objs/SpectralDefinitionFactory/SpectralDefinitionFactory.truth b/isis/src/base/objs/SpectralDefinitionFactory/SpectralDefinitionFactory.truth
index 4c5ff858d38dba84df0d9c9fe6a537cc9a9ab651..de5d45ce46ae401e349a9d100b7c1d8e2066fbfa 100644
--- a/isis/src/base/objs/SpectralDefinitionFactory/SpectralDefinitionFactory.truth
+++ b/isis/src/base/objs/SpectralDefinitionFactory/SpectralDefinitionFactory.truth
@@ -1,5 +1,9 @@
 ----- Testing SpectralDefinitionFactory -----
 **ERROR** Unable to open input file [assets/test.csv]. Is it a valid CSV?.
 **USER ERROR** Unable to open file [assets/test.csv].
+**I/O ERROR** Failed to open [assets/test.txt].
+**PROGRAMMER ERROR** Gdal failed to open [assets/test.txt].
 **I/O ERROR** Unable to open [assets/test.txt].
+**I/O ERROR** Failed to open [assets/cube.cub].
+**PROGRAMMER ERROR** Gdal failed to open [assets/cube.cub].
 **I/O ERROR** Unable to open [assets/cube.cub].
diff --git a/isis/src/base/objs/Spice/Spice.cpp b/isis/src/base/objs/Spice/Spice.cpp
index 1e357c77d7d6d802379863617e36665c856cea2b..b940718b806c01a9e717e6628f95bd7ae8207137 100644
--- a/isis/src/base/objs/Spice/Spice.cpp
+++ b/isis/src/base/objs/Spice/Spice.cpp
@@ -68,9 +68,37 @@ namespace Isis {
     }
     else {
       PvlGroup kernels = lab.findGroup("Kernels", Pvl::Traverse);
-      bool hasTables = (kernels["TargetPosition"][0] == "Table");
+      
       // BONUS TODO: update to pull out separate init methods
-      init(lab, !hasTables);
+      // try using ALE
+      bool hasTables = (kernels["TargetPosition"][0] == "Table");
+      m_usingNaif = !lab.hasObject("NaifKeywords") || !hasTables;
+      m_usingAle = false;
+
+      if (m_usingNaif) {
+        try {
+          std::ostringstream kernel_pvl;
+          kernel_pvl << kernels;
+
+          json props;
+          if (kernels["InstrumentPointing"][0].toUpper() == "NADIR") {
+            props["nadir"] = true;
+          }
+
+          props["kernels"] = kernel_pvl.str();
+
+          json isd = ale::load(lab.fileName().toStdString(), props.dump(), "ale", false, false, true);
+          m_usingAle = true;
+
+          isdInit(lab, isd);
+        }
+        catch(...) {
+          init(cube, lab, !hasTables);
+        }
+      }
+      else {
+        init(cube, lab, !hasTables);
+      }
     }
   }
 
@@ -82,7 +110,209 @@ namespace Isis {
    * @param isd ALE Json ISD
    */
   Spice::Spice(Pvl &lab, json isd) {
-    init(lab, true, isd);
+    // Set the expected member states for other parts of the Spice
+    // class
+    m_usingNaif = true;
+    m_usingAle = true;
+
+    isdInit(lab, isd);
+  }
+
+  void Spice::isdInit(Pvl &lab, json isd) {
+    NaifStatus::CheckErrors();
+    // Initialize members
+    defaultInit();
+
+    m_spkCode = new SpiceInt;
+    m_ckCode = new SpiceInt;
+    m_ikCode = new SpiceInt;
+    m_sclkCode = new SpiceInt;
+    m_spkBodyCode = new SpiceInt;
+    m_bodyFrameCode = new SpiceInt;
+
+    m_naifKeywords = new PvlObject("NaifKeywords");
+
+    // Get the kernel group and load main kernels
+    PvlGroup kernels = lab.findGroup("Kernels", Pvl::Traverse);
+
+    // Get the time padding first
+    if (kernels.hasKeyword("StartPadding")) {
+      *m_startTimePadding = toDouble(kernels["StartPadding"][0]);
+    }
+    else {
+      *m_startTimePadding = 0.0;
+    }
+
+    if (kernels.hasKeyword("EndPadding")) {
+      *m_endTimePadding  = toDouble(kernels["EndPadding"][0]);
+    }
+    else {
+      *m_endTimePadding = 0.0;
+    }
+
+    try {
+      json aleNaifKeywords = isd["naif_keywords"];
+      m_naifKeywords = new PvlObject("NaifKeywords", aleNaifKeywords);
+
+      // Still need to load clock kernels for now
+      load(kernels["LeapSecond"], true);
+      if ( kernels.hasKeyword("SpacecraftClock")) {
+        load(kernels["SpacecraftClock"], true);
+      }
+    }
+    catch(IException &e) {
+      QString msg = "Failed to read naif_keywords from ISD";
+      throw IException(e, IException::Programmer, msg, _FILEINFO_);
+    }
+
+    // Moved the construction of the Target after the NAIF kenels have been loaded or the
+    // NAIF keywords have been pulled from the cube labels, so we can find target body codes
+    // that are defined in kernels and not just body codes build into spicelib
+    // TODO: Move this below the else once the rings code below has been refactored
+    m_target = new Target(this, lab);
+
+    // This should not be here. Consider having spiceinit add the necessary rings kernels to the
+    // Extra parameter if the user has set the shape model to RingPlane.
+    // If Target is Saturn and ShapeModel is RingPlane, load the extra rings pck file
+    //  which changes the prime meridian values to report longitudes with respect to
+    // the ascending node of the ringplane.
+    if (m_target->name().toUpper() == "SATURN" && m_target->shape()->name().toUpper() == "PLANE") {
+      PvlKeyword ringPck = PvlKeyword("RingPCK","$cassini/kernels/pck/saturnRings_v001.tpc");
+      load(ringPck, true);
+    }
+
+    // Get NAIF ik, spk, sclk, and ck codes
+    //
+    //    Use ikcode to get parameters from instrument kernel such as focal
+    //    length, distortions, focal plane maps, etc
+    //
+    //    Use spkcode to get spacecraft position from spk file
+    //
+    //    Use sclkcode to transform times from et to tics
+    //
+    //    Use ckcode to transform between frames
+    //
+    //    Use bodycode to obtain radii and attitude (pole position/omega0)
+    //
+    //    Use spkbodycode to read body position from spk
+
+    QString trykey = "NaifIkCode";
+    if (kernels.hasKeyword("NaifFrameCode")) trykey = "NaifFrameCode";
+    *m_ikCode = toInt(kernels[trykey][0]);
+
+    *m_spkCode  = *m_ikCode / 1000;
+    *m_sclkCode = *m_spkCode;
+    *m_ckCode   = *m_ikCode;
+
+    if (!m_target->isSky()) {
+      // Get target body code and radii and store them in the Naif group
+      // DAC modified to look for and store body code so that the radii keyword name
+      // will be able to be constructed even for new bodies not in the standard PCK yet.
+      QString radiiKey = "BODY" + Isis::toString(m_target->naifBodyCode()) + "_RADII";
+      QVariant result = m_target->naifBodyCode();
+      storeValue("BODY_CODE", 0, SpiceIntType, result);
+      std::vector<Distance> radii(3,Distance());
+      radii[0] = Distance(getDouble(radiiKey, 0), Distance::Kilometers);
+      radii[1] = Distance(getDouble(radiiKey, 1), Distance::Kilometers);
+      radii[2] = Distance(getDouble(radiiKey, 2), Distance::Kilometers);
+      // m_target doesn't have the getDouble method so Spice gets the radii for it
+      m_target->setRadii(radii);
+    }
+
+    *m_spkBodyCode = m_target->naifBodyCode();
+
+    // Override them if they exist in the labels
+    if (kernels.hasKeyword("NaifSpkCode")) {
+      *m_spkCode = (int) kernels["NaifSpkCode"];
+    }
+
+    if (kernels.hasKeyword("NaifCkCode")) {
+      *m_ckCode = (int) kernels["NaifCkCode"];
+    }
+
+    if (kernels.hasKeyword("NaifSclkCode")) {
+      *m_sclkCode = (int) kernels["NaifSclkCode"];
+    }
+
+    if (!m_target->isSky()) {
+      if (kernels.hasKeyword("NaifSpkBodyCode")) {
+        *m_spkBodyCode = (int) kernels["NaifSpkBodyCode"];
+      }
+    }
+
+    if (m_target->isSky()) {
+      // Create the identity rotation for sky targets
+      // Everything in bodyfixed will really be J2000
+      m_bodyRotation = new SpiceRotation(1);
+    }
+    else {
+      SpiceInt frameCode;
+      try {
+        frameCode = getInteger("BODY_FRAME_CODE", 0);
+      }
+      catch(IException &e) {
+        QString msg = "Unable to read BODY_FRAME_CODE from naifkeywords group";
+        throw IException(IException::Io, msg, _FILEINFO_);
+      }
+
+      m_bodyRotation = new SpiceRotation(frameCode);
+      *m_bodyFrameCode = frameCode;
+    }
+
+    m_instrumentRotation = new SpiceRotation(*m_ckCode);
+
+    //  Set up for observer/target and light time correction to between s/c
+    // and target body.
+    LightTimeCorrectionState ltState(*m_ikCode, this);
+    ltState.checkSpkKernelsForAberrationCorrection();
+
+    vector<Distance> radius = m_target->radii();
+    Distance targetRadius((radius[0] + radius[2])/2.0);
+    m_instrumentPosition = new SpacecraftPosition(*m_spkCode, *m_spkBodyCode,
+                                                  ltState, targetRadius);
+
+    m_sunPosition = new SpicePosition(10, m_target->naifBodyCode());
+
+
+    // Check to see if we have nadir pointing that needs to be computed &
+    // See if we have table blobs to load
+    m_sunPosition->LoadCache(isd["sun_position"]);
+    if (m_sunPosition->cacheSize() > 3) {
+      m_sunPosition->Memcache2HermiteCache(0.01);
+    }
+    m_bodyRotation->LoadCache(isd["body_rotation"]);
+    m_bodyRotation->MinimizeCache(SpiceRotation::DownsizeStatus::Yes);
+    if (m_bodyRotation->cacheSize() > 5) {
+      m_bodyRotation->LoadTimeCache();
+    }
+    solarLongitude();
+
+    //  We can't assume InstrumentPointing & InstrumentPosition exist, old
+    //  files may be around with the old keywords, SpacecraftPointing &
+    //  SpacecraftPosition.  The old keywords were in existance before the
+    //  Table option, so we don't need to check for Table under the old
+    //  keywords.
+    if (kernels["InstrumentPointing"].size() == 0) {
+      throw IException(IException::Unknown,
+                       "No camera pointing available",
+                       _FILEINFO_);
+    }
+
+    m_instrumentRotation->LoadCache(isd["instrument_pointing"]);
+    m_instrumentRotation->MinimizeCache(SpiceRotation::DownsizeStatus::Yes);
+    if (m_instrumentRotation->cacheSize() > 5) {
+      m_instrumentRotation->LoadTimeCache();
+    }
+
+
+    if (kernels["InstrumentPosition"].size() == 0) {
+      throw IException(IException::Unknown,
+                       "No instrument position available",
+                       _FILEINFO_);
+    }
+
+    m_instrumentPosition->LoadCache(isd["instrument_position"]);
+    NaifStatus::CheckErrors();
   }
 
 
@@ -151,7 +381,7 @@ namespace Isis {
    * @internal
    *   @history 2011-02-08 Jeannie Walldren - Initialize pointers to null.
    */
-  void Spice::init(Pvl &lab, bool noTables, json isd) {
+  void Spice::init(Cube &cube, Pvl &lab, bool noTables) {
     NaifStatus::CheckErrors();
     // Initialize members
     defaultInit();
@@ -184,73 +414,40 @@ namespace Isis {
       *m_endTimePadding = 0.0;
     }
 
-    // We should remove this completely in the near future
-    m_usingNaif = !lab.hasObject("NaifKeywords") || noTables;
-    m_usingAle = false;
-
-
     //  Modified  to load planetary ephemeris SPKs before s/c SPKs since some
     //  missions (e.g., MESSENGER) may augment the s/c SPK with new planet
     //  ephemerides. (2008-02-27 (KJB))
     if (m_usingNaif) {
-      try {
-        if (isd == NULL) {
-          // try using ALE
-          std::ostringstream kernel_pvl;
-          kernel_pvl << kernels;
-
-          json props;
-          if (kernels["InstrumentPointing"][0].toUpper() == "NADIR") {
-            props["nadir"] = true;
-          }
-
-          props["kernels"] = kernel_pvl.str();
-
-          isd = ale::load(lab.fileName().toStdString(), props.dump(), "ale", false, false, true);
-        }
-
-        json aleNaifKeywords = isd["naif_keywords"];
-        m_naifKeywords = new PvlObject("NaifKeywords", aleNaifKeywords);
-
-        // Still need to load clock kernels for now
-        load(kernels["LeapSecond"], noTables);
-        if ( kernels.hasKeyword("SpacecraftClock")) {
-          load(kernels["SpacecraftClock"], noTables);
-        }
-        m_usingAle = true;
+      // Backup to standard ISIS implementation
+      if (noTables) {
+        load(kernels["TargetPosition"], noTables);
+        load(kernels["InstrumentPosition"], noTables);
+        load(kernels["InstrumentPointing"], noTables);
       }
-      catch(...) {
-        // Backup to standard ISIS implementation
-        if (noTables) {
-          load(kernels["TargetPosition"], noTables);
-          load(kernels["InstrumentPosition"], noTables);
-          load(kernels["InstrumentPointing"], noTables);
-        }
 
-        if (kernels.hasKeyword("Frame")) {
-          load(kernels["Frame"], noTables);
-        }
+      if (kernels.hasKeyword("Frame")) {
+        load(kernels["Frame"], noTables);
+      }
 
-        load(kernels["TargetAttitudeShape"], noTables);
-        if (kernels.hasKeyword("Instrument")) {
-          load(kernels["Instrument"], noTables);
-        }
-        // Always load after instrument
-        if (kernels.hasKeyword("InstrumentAddendum")) {
-          load(kernels["InstrumentAddendum"], noTables);
-        }
+      load(kernels["TargetAttitudeShape"], noTables);
+      if (kernels.hasKeyword("Instrument")) {
+        load(kernels["Instrument"], noTables);
+      }
+      // Always load after instrument
+      if (kernels.hasKeyword("InstrumentAddendum")) {
+        load(kernels["InstrumentAddendum"], noTables);
+      }
 
-        // Still need to load clock kernels for now
-        load(kernels["LeapSecond"], noTables);
-        if ( kernels.hasKeyword("SpacecraftClock")) {
-          load(kernels["SpacecraftClock"], noTables);
-        }
+      // Still need to load clock kernels for now
+      load(kernels["LeapSecond"], noTables);
+      if ( kernels.hasKeyword("SpacecraftClock")) {
+        load(kernels["SpacecraftClock"], noTables);
+      }
 
-        // Modified to load extra kernels last to allow overriding default values
-        // (2010-04-07) (DAC)
-        if (kernels.hasKeyword("Extra")) {
-          load(kernels["Extra"], noTables);
-        }
+      // Modified to load extra kernels last to allow overriding default values
+      // (2010-04-07) (DAC)
+      if (kernels.hasKeyword("Extra")) {
+        load(kernels["Extra"], noTables);
       }
 
       // Moved the construction of the Target after the NAIF kenels have been loaded or the
@@ -347,7 +544,7 @@ namespace Isis {
     else {
       // JAA - Modified to store and look for the frame body code in the cube labels
       SpiceInt frameCode;
-      if (((m_usingNaif) || (!m_naifKeywords->hasKeyword("BODY_FRAME_CODE"))) && !isUsingAle()) {
+      if ((m_usingNaif) || (!m_naifKeywords->hasKeyword("BODY_FRAME_CODE"))) {
         char frameName[32];
         SpiceBoolean found;
         cidfrm_c(*m_spkBodyCode, sizeof(frameName), &frameCode, frameName, &found);
@@ -396,23 +593,11 @@ namespace Isis {
 
     // Check to see if we have nadir pointing that needs to be computed &
     // See if we have table blobs to load
-    if (m_usingAle) {
-      m_sunPosition->LoadCache(isd["sun_position"]);
-      if (m_sunPosition->cacheSize() > 3) {
-        m_sunPosition->Memcache2HermiteCache(0.01);
-      }
-      m_bodyRotation->LoadCache(isd["body_rotation"]);
-      m_bodyRotation->MinimizeCache(SpiceRotation::DownsizeStatus::Yes);
-      if (m_bodyRotation->cacheSize() > 5) {
-        m_bodyRotation->LoadTimeCache();
-      }
-      solarLongitude();
-    }
-    else if (kernels["TargetPosition"][0].toUpper() == "TABLE") {
-      Table t("SunPosition", lab.fileName(), lab);
+    if (kernels["TargetPosition"][0].toUpper() == "TABLE") {
+      Table t = cube.readTable("SunPosition");
       m_sunPosition->LoadCache(t);
 
-      Table t2("BodyRotation", lab.fileName(), lab);
+      Table t2 = cube.readTable("BodyRotation");
       m_bodyRotation->LoadCache(t2);
       if (t2.Label().hasKeyword("SolarLongitude")) {
         *m_solarLongitude = Longitude(t2.Label()["SolarLongitude"],
@@ -437,7 +622,7 @@ namespace Isis {
 
     //  2009-03-18  Tracie Sucharski - Removed test for old keywords, any files
     // with the old keywords should be re-run through spiceinit.
-    if (kernels["InstrumentPointing"][0].toUpper() == "NADIR" && !isUsingAle()) {
+    if (kernels["InstrumentPointing"][0].toUpper() == "NADIR") {
       if (m_instrumentRotation) {
         delete m_instrumentRotation;
         m_instrumentRotation = NULL;
@@ -445,15 +630,8 @@ namespace Isis {
 
       m_instrumentRotation = new SpiceRotation(*m_ikCode, *m_spkBodyCode);
     }
-    else if (m_usingAle) {
-     m_instrumentRotation->LoadCache(isd["instrument_pointing"]);
-     m_instrumentRotation->MinimizeCache(SpiceRotation::DownsizeStatus::Yes);
-     if (m_instrumentRotation->cacheSize() > 5) {
-       m_instrumentRotation->LoadTimeCache();
-     }
-    }
     else if (kernels["InstrumentPointing"][0].toUpper() == "TABLE") {
-      Table t("InstrumentPointing", lab.fileName(), lab);
+      Table t = cube.readTable("InstrumentPointing");
       m_instrumentRotation->LoadCache(t);
     }
 
@@ -464,13 +642,11 @@ namespace Isis {
                        _FILEINFO_);
     }
 
-    if (m_usingAle) {
-      m_instrumentPosition->LoadCache(isd["instrument_position"]);
-    }
-    else if (kernels["InstrumentPosition"][0].toUpper() == "TABLE") {
-      Table t("InstrumentPosition", lab.fileName(), lab);
+    if (kernels["InstrumentPosition"][0].toUpper() == "TABLE") {
+      Table t = cube.readTable("InstrumentPosition");
       m_instrumentPosition->LoadCache(t);
     }
+
     NaifStatus::CheckErrors();
   }
 
diff --git a/isis/src/base/objs/Spice/Spice.h b/isis/src/base/objs/Spice/Spice.h
index f95b73cec2501e82686b45f143bd1e1dedf3b21d..4ffa4097cb30744970bec2f060f31cdbd62ff6f2 100644
--- a/isis/src/base/objs/Spice/Spice.h
+++ b/isis/src/base/objs/Spice/Spice.h
@@ -284,7 +284,7 @@ namespace Isis {
     public:
       // constructors
       Spice(Cube &cube);
-      Spice(Pvl &lab, nlohmann::json);
+      Spice(Pvl &lab, nlohmann::json isd);
 
       // destructor
       virtual ~Spice();
@@ -387,7 +387,8 @@ namespace Isis {
       Spice(const Spice &other);
       Spice &operator=(const Spice &other);
 
-      void init(Pvl &pvl, bool noTables, nlohmann::json isd = NULL);
+      void init(Cube &cube, Pvl &pvl, bool noTables);
+      void isdInit(Pvl &pvl, nlohmann::json isd);
       void csmInit(Cube &cube, Pvl label);
       void defaultInit();
 
diff --git a/isis/src/lro/apps/lronac2isis/lronac2isis.cpp b/isis/src/lro/apps/lronac2isis/lronac2isis.cpp
index d24d3ba0a8c183bc05abf51bab63dfb6df58b5fa..addabeb0e544d9dfeff9333367d7473b7219b392 100644
--- a/isis/src/lro/apps/lronac2isis/lronac2isis.cpp
+++ b/isis/src/lro/apps/lronac2isis/lronac2isis.cpp
@@ -107,7 +107,7 @@ namespace Isis {
     g_ocube->setByteOrder(outAtt.byteOrder());
     g_ocube->setFormat(outAtt.fileFormat());
     g_ocube->setMinMax((double) VALID_MIN2, (double) VALID_MAX2);
-    g_ocube->setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+    g_ocube->setLabelsAttached(outAtt.labelAttachment());
     g_ocube->setDimensions(p.Samples(), p.Lines(), p.Bands());
     g_ocube->setPixelType(Isis::Real);
     g_ocube->create(ui.GetCubeName("TO"));
diff --git a/isis/src/mex/apps/hrsc2isis/hrsc2isis.cpp b/isis/src/mex/apps/hrsc2isis/hrsc2isis.cpp
index 7f0a2b9b16bb1eda4cdd97ed9055ffcf8d90dca7..24272da3562db6b66443d5afcd0d11d8a279967d 100644
--- a/isis/src/mex/apps/hrsc2isis/hrsc2isis.cpp
+++ b/isis/src/mex/apps/hrsc2isis/hrsc2isis.cpp
@@ -149,7 +149,7 @@ namespace Isis{
 
     outCube->setByteOrder(outAtt.byteOrder());
     outCube->setFormat(outAtt.fileFormat());
-    outCube->setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+    outCube->setLabelsAttached(outAtt.labelAttachment());
 
     TableField ephTimeField("EphemerisTime", TableField::Double);
     TableField expTimeField("ExposureTime", TableField::Double);
diff --git a/isis/src/mro/apps/ctxcal/ctxcal.cpp b/isis/src/mro/apps/ctxcal/ctxcal.cpp
index 6969f691cc86ce21704fc06126f8df662a91efc2..04d9289a725d3fca36a1fce0a3005a6c9fedd1d1 100644
--- a/isis/src/mro/apps/ctxcal/ctxcal.cpp
+++ b/isis/src/mro/apps/ctxcal/ctxcal.cpp
@@ -45,7 +45,7 @@ namespace Isis {
       // We will be processing by line
       ProcessByLine p;
 
-      Isis::Pvl lab(icube->fileName());
+      Isis::Pvl lab = *(icube->label());
       Isis::PvlGroup &inst =
           lab.findGroup("Instrument", Pvl::Traverse);
 
diff --git a/isis/src/mro/apps/marcical/marcical.cpp b/isis/src/mro/apps/marcical/marcical.cpp
index f5592320a28851351def72b940dcbc5ca9a154d4..f2b203f262cbff89c1f9604e0a3f5588816a4146 100644
--- a/isis/src/mro/apps/marcical/marcical.cpp
+++ b/isis/src/mro/apps/marcical/marcical.cpp
@@ -297,7 +297,7 @@ namespace Isis {
     ocube.setDimensions(icube.sampleCount(), icube.lineCount(), icube.bandCount());
     ocube.setByteOrder(outAtt.byteOrder());
     ocube.setFormat(outAtt.fileFormat());
-    ocube.setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+    ocube.setLabelsAttached(outAtt.labelAttachment());
     ocube.setPixelType(outAtt.pixelType());
 
     ocube.create(FileName(ui.GetCubeName("TO")).expanded());
diff --git a/isis/src/odyssey/apps/thmvisflat/main.cpp b/isis/src/odyssey/apps/thmvisflat/main.cpp
index 64982a03bc1bb56a82543044e7a0b5ee1be3f063..afaf6bedc84347f5ea420aa85916f88c64df6621 100644
--- a/isis/src/odyssey/apps/thmvisflat/main.cpp
+++ b/isis/src/odyssey/apps/thmvisflat/main.cpp
@@ -68,7 +68,7 @@ void IsisMain() {
   ocube.setDimensions(icube.sampleCount(), icube.lineCount(), icube.bandCount());
   ocube.setByteOrder(outAtt.byteOrder());
   ocube.setFormat(outAtt.fileFormat());
-  ocube.setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+  ocube.setLabelsAttached(outAtt.labelAttachment());
   ocube.setPixelType(outAtt.pixelType());
 
   ocube.create(FileName(ui.GetCubeName("TO")).expanded());
diff --git a/isis/src/qisis/apps/qview/main.cpp b/isis/src/qisis/apps/qview/main.cpp
index 69f06fe013e070d47fb29fee2c50d51db97fd28f..eb8a1198e25641dbdd40771384d1c1120dceeb57 100644
--- a/isis/src/qisis/apps/qview/main.cpp
+++ b/isis/src/qisis/apps/qview/main.cpp
@@ -104,7 +104,7 @@ int main(int argc, char *argv[]) {
            */
           QChar escape(27);
           for (int i = 1; i < argc; i++) {
-            temp += QFileInfo(FileName(argv[i]).expanded()).absoluteFilePath();
+            temp += FileName(argv[i]).expanded();
             temp += QString(escape);
           }
           temp += "raise";
diff --git a/isis/src/qisis/objs/CubeViewport/ViewportBuffer.cpp b/isis/src/qisis/objs/CubeViewport/ViewportBuffer.cpp
index 6dbecb5d681a5a5f8efebd6458367a09e61132a8..4d2579a8dedff9225636f470c81bb7d3ca458e9b 100644
--- a/isis/src/qisis/objs/CubeViewport/ViewportBuffer.cpp
+++ b/isis/src/qisis/objs/CubeViewport/ViewportBuffer.cpp
@@ -61,8 +61,8 @@ namespace Isis {
     p_requestedFillArea = 0.0;
     p_bricksOrdered = true;
 
-    connect(this, SIGNAL(ReadCube(int, int, int, int, int, int, void *)),
-            p_dataThread, SLOT(ReadCube(int, int, int, int, int, int, void *)));
+    connect(this, SIGNAL(ReadCube(int, int, int, int, int, int, void *, double)),
+            p_dataThread, SLOT(ReadCube(int, int, int, int, int, int, void *, double)));
 
     connect(p_dataThread, SIGNAL(ReadReady(void *, int, const Isis::Brick *)),
             this, SLOT(DataReady(void *, int, const Isis::Brick *)));
@@ -76,8 +76,8 @@ namespace Isis {
    *
    */
   ViewportBuffer::~ViewportBuffer() {
-    disconnect(this, SIGNAL(ReadCube(int, int, int, int, int, int, void *)),
-               p_dataThread, SLOT(ReadCube(int, int, int, int, int, int, void *)));
+    disconnect(this, SIGNAL(ReadCube(int, int, int, int, int, int, void *, double)),
+               p_dataThread, SLOT(ReadCube(int, int, int, int, int, int, void *, double)));
 
     disconnect(p_dataThread, SIGNAL(ReadReady(void *, int, const Isis::Brick *)),
                this, SLOT(DataReady(void *, int, const Isis::Brick *)));
@@ -238,24 +238,22 @@ namespace Isis {
       p_bricksOrdered = true;
     }
 
-    double samp;
-
     // Loop through x values of rect on screen that we want to fill
+    int brickIndex = 0;
     for(int x = rect->left(); x <= rect->right(); x++) {
       // Index into internal buffer is minus leftmost/topmost pixel
       int xIndex = x - fill->getLeftmostPixelPosition();
       int yIndex = y - fill->getTopmostPixelPosition();
 
-      samp = fill->viewportToSample(x);
-
-      // Index into buffer is current sample - start sample
-      //   *Brick indices are in units of cube pixels, not screen pixels
-      int brickIndex = (int)(samp + 0.5) - brick->Sample();
+      int scaledBufferIndex = brickIndex;
+      if (fill->scale() > 1.0) {
+        scaledBufferIndex /= fill->scale();
+      }
 
-      if(brickIndex < 0) {
+      if(scaledBufferIndex < 0) {
         p_buffer.at(yIndex).at(xIndex) = brick->at(0);
       }
-      else if(brickIndex  >= brick->size()) {
+      else if(scaledBufferIndex >= brick->size()) {
         p_buffer.at(yIndex).at(xIndex) = brick->at(brick->size() - 1);
       }
       else {
@@ -277,7 +275,8 @@ namespace Isis {
           throw IException(IException::Programmer, msg, _FILEINFO_);
         }
         else {
-          p_buffer.at(yIndex).at(xIndex) = brick->at(brickIndex);
+          p_buffer.at(yIndex).at(xIndex) = brick->at(scaledBufferIndex);
+          brickIndex++;
         }
       }
     }
@@ -547,17 +546,18 @@ namespace Isis {
 
     double esamp = fill->viewportToSample(rect.right());
 
-    int brickWidth = (int)(ceil(esamp) - floor(ssamp)) + 1;
+    int brickWidth = (int)(ceil(esamp) - floor(ssamp));
 
     if(brickWidth <= 0)
       return;
 
     double line = fill->viewportToLine(fill->getRequestPosition());
+    line -= (1 / fill->scale()) / 2.0;
     int roundedSamp = (int)(ssamp + 0.5);
     int roundedLine = (int)(line + 0.5);
 
-    emit ReadCube(p_cubeId, roundedSamp, roundedLine, roundedSamp + brickWidth,
-                  roundedLine, p_band, this);
+    emit ReadCube(p_cubeId, roundedSamp, roundedLine, roundedSamp + brickWidth - 1,
+                  roundedLine, p_band, this, fill->scale());
 
     fill->incRequestPosition();
   }
diff --git a/isis/src/qisis/objs/CubeViewport/ViewportBuffer.h b/isis/src/qisis/objs/CubeViewport/ViewportBuffer.h
index 747dbbe15b1b1d5f72b7d069fccf56ce5764a1c6..bf9b0b00c1490d8a3d8721c966e3fa7cf64176c1 100644
--- a/isis/src/qisis/objs/CubeViewport/ViewportBuffer.h
+++ b/isis/src/qisis/objs/CubeViewport/ViewportBuffer.h
@@ -125,7 +125,7 @@ namespace Isis {
        * @param caller
        */
       void ReadCube(int cubeId, int startSample, int startLine,
-                    int endSample, int endLine, int band, void *caller);
+                    int endSample, int endLine, int band, void *caller, double scale);
 
       //! Tell cube data thread we're done with a brick
       void DoneWithData(int, const Isis::Brick *);
diff --git a/isis/src/qisis/objs/CubeViewport/ViewportBufferFill.h b/isis/src/qisis/objs/CubeViewport/ViewportBufferFill.h
index 55428fc197120c6b634c7f1f25341b3683db8620..71224c09e1f2ad344d708e36787a6d215d0635fc 100644
--- a/isis/src/qisis/objs/CubeViewport/ViewportBufferFill.h
+++ b/isis/src/qisis/objs/CubeViewport/ViewportBufferFill.h
@@ -111,6 +111,9 @@ namespace Isis {
 
       void stop();
 
+      double scale() {
+        return p_xScale;
+      }
 
     private:
       //! Position of the cube reads
diff --git a/isis/src/qisis/objs/FileDialog/FileDialog.cpp b/isis/src/qisis/objs/FileDialog/FileDialog.cpp
index 0f4b95ff595c708f45f22c149f08b16839d684cb..30446bfaf3474ba1332e213050f76c6297ac1ef4 100644
--- a/isis/src/qisis/objs/FileDialog/FileDialog.cpp
+++ b/isis/src/qisis/objs/FileDialog/FileDialog.cpp
@@ -20,7 +20,7 @@ namespace Isis {
     this->setOptions(QFileDialog::DontUseNativeDialog);
 
     this->setWindowTitle(title);
-    this->setFileMode(QFileDialog::ExistingFiles);
+    this->setFileMode(QFileDialog::AnyFile);
     if(parent != 0) {
       parent->installEventFilter(this);
       p_appName = parent->windowTitle();
diff --git a/isis/src/qisis/objs/FileTool/FileTool.cpp b/isis/src/qisis/objs/FileTool/FileTool.cpp
index 214013ed26ba716c5c2251696b3a2bd24c66342b..d56fb76284c727c2b837339fea25c0094b414ae7 100644
--- a/isis/src/qisis/objs/FileTool/FileTool.cpp
+++ b/isis/src/qisis/objs/FileTool/FileTool.cpp
@@ -559,7 +559,7 @@ namespace Isis {
       ocube->setDimensions(piNumSamples, piNumLines, piNumBands);
       ocube->setByteOrder(outAtt.byteOrder());
       ocube->setFormat(outAtt.fileFormat());
-      ocube->setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
+      ocube->setLabelsAttached(outAtt.labelAttachment());
 
       if (outAtt.propagatePixelType()) {
         ocube->setPixelType(icube->pixelType());
diff --git a/isis/src/qisis/objs/Image/Image.cpp b/isis/src/qisis/objs/Image/Image.cpp
index 576f8fc9a72381f3e0932c58abd7a1382238dc1f..9a19d368ac54e97c9325c23cbf4b63f68bc37a52 100644
--- a/isis/src/qisis/objs/Image/Image.cpp
+++ b/isis/src/qisis/objs/Image/Image.cpp
@@ -525,7 +525,7 @@ namespace Isis {
 
         // If this is an ecub (it should be) and is pointing to a relative file name,
         //   then we want to copy the DN cube also.
-        if (!origImage.storesDnData() ) {
+        if ( origImage.labelsAttached() == Cube::ExternalLabel) {
           if (origImage.externalCubeFileName().path() == ".") {
             Cube dnFile(
                 FileName(m_fileName).path() + "/" + origImage.externalCubeFileName().name());
diff --git a/isis/src/qisis/objs/Shape/Shape.cpp b/isis/src/qisis/objs/Shape/Shape.cpp
index 0061a412fe7d98340683fd2a65a57825260b2645..405fabdb8f0b25b97251a49bd842eb9688818d56 100644
--- a/isis/src/qisis/objs/Shape/Shape.cpp
+++ b/isis/src/qisis/objs/Shape/Shape.cpp
@@ -548,7 +548,7 @@ namespace Isis {
 
       // If this is an ecub (it should be) and is pointing to a relative file name,
       //   then we want to copy the DN cube also.
-      if (!origShape.storesDnData()) {
+      if ( origShape.labelsAttached() == Cube::ExternalLabel ) {
         if (origShape.externalCubeFileName().path() == ".") {
           Cube dnFile(
               FileName(m_fileName).path() + "/" + origShape.externalCubeFileName().name());
diff --git a/isis/src/system/apps/blobdump/main.cpp b/isis/src/system/apps/blobdump/main.cpp
index 22c57e817ccfc3b31d5df9483190666b14ed6750..55c1d5056a3c78fac3883ed67886925a5bfc780d 100644
--- a/isis/src/system/apps/blobdump/main.cpp
+++ b/isis/src/system/apps/blobdump/main.cpp
@@ -37,7 +37,14 @@ void IsisMain() {
   FileName file = ui.GetCubeName("FROM");
   QString blobname = ui.GetString("NAME");
   QString blobtype = ui.GetString("TYPE");
-  Blob blob(blobname, blobtype, file.expanded());
+  Blob blob(blobname, blobtype);
+  try {
+    blob.Read(file.expanded());
+  }
+  catch (...) {
+    Cube cube(file);
+    cube.read(blob);
+  }
   FileName outfname = ui.GetFileName("TO");
   blob.Write(outfname.expanded());
 }
@@ -49,7 +56,14 @@ void helperButtonGetBlobList() {
 
   UserInterface &ui = Application::GetUserInterface();
   QString currentFile = ui.GetCubeName("FROM");
-  const Pvl label(FileName(currentFile).expanded());
+  Pvl label;
+  try {
+    label = Pvl(FileName(currentFile).expanded());
+  }
+  catch (...) {
+    Cube cube(FileName(currentFile).expanded());
+    label = *cube.label();
+  }
 
   // Check to see if the "FILE" parameter has changed since last press
   if(currentFile != previousFile) {
diff --git a/isis/tests/BlobTests.cpp b/isis/tests/BlobTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9f9afeb88ad5c3cdfd0225679b0e1c28e3ae6ede
--- /dev/null
+++ b/isis/tests/BlobTests.cpp
@@ -0,0 +1,125 @@
+#include <QTemporaryFile>
+#include <QString>
+#include <iostream>
+
+#include "Blob.h"
+
+#include "TempFixtures.h"
+#include "TiffFixtures.h"
+#include "TestUtilities.h"
+
+#include "gmock/gmock.h"
+
+using namespace Isis;
+
+class DefaultBlob : public TempTestingFiles {
+
+  protected:
+    Blob *testBlob;
+    QString testBlobPath = tempDir.path() + "/junk_blob.pvl";
+
+    void SetUp() override {
+      TempTestingFiles::SetUp();
+
+      testBlob = new Blob("UnitTest", "Blob");
+      char buf[] = {"ABCD"};
+      testBlob->setData(buf, 4);
+      testBlob->Write(testBlobPath);
+    }
+
+    void TearDown() override {
+      delete testBlob;
+    }
+  
+};
+
+TEST_F(TempTestingFiles, TestBlobDefault) {
+  Blob b("UnitTest", "Blob");
+  char buf[] = {"ABCD"};
+  b.setData(buf, 4);
+  b.Write("junk");
+
+  EXPECT_EQ("UnitTest", b.Name());
+  EXPECT_EQ(4, b.Size());
+  EXPECT_EQ("Blob", b.Type());
+}
+
+TEST_F(DefaultBlob, TestBlobRead) {
+  Blob b("UnitTest", "Blob", testBlobPath);
+  
+  EXPECT_EQ("UnitTest", b.Name());
+  EXPECT_EQ(4, b.Size());
+  EXPECT_EQ("Blob", b.Type());
+
+  std::string buffStr(b.getBuffer());
+  EXPECT_EQ("ABCD", buffStr);
+}
+
+TEST_F(DefaultBlob, TestBlobWriteExisting) {
+  Isis::Pvl pvl(testBlobPath);
+  std::fstream strm;
+  strm.open(testBlobPath.toStdString(), std::ios::binary | std::ios::out);
+  char buf[] = {"ABCD"};
+  testBlob->setData(buf, 3);
+  testBlob->Write(pvl, strm);
+  strm.seekp(0, std::ios::beg);
+  strm << pvl;
+  EXPECT_EQ("UnitTest", testBlob->Name());
+  EXPECT_EQ(3, testBlob->Size());
+  EXPECT_EQ("Blob", testBlob->Type());
+  strm.close();
+
+  Blob b("UnitTest", "Blob", testBlobPath);
+
+  EXPECT_EQ("UnitTest", b.Name());
+  EXPECT_EQ(3, b.Size());
+  EXPECT_EQ("Blob", b.Type());
+}
+
+TEST_F(DefaultBlob, TestBlobWriteExistingEOF) {
+  Isis::Pvl pvl("junk");
+  std::fstream strm;
+  strm.open(testBlobPath.toStdString(), std::ios::binary | std::ios::out);
+  char buf[] = {"ABCD"};
+  testBlob->setData(buf, 4);
+  testBlob->Write(pvl, strm);
+  strm.seekp(0, std::ios::beg);
+  strm << pvl;
+  EXPECT_EQ("UnitTest", testBlob->Name());
+  EXPECT_EQ(4, testBlob->Size());
+  EXPECT_EQ("Blob", testBlob->Type());
+  strm.close();
+
+  Blob b("UnitTest", "Blob", testBlobPath);
+
+  EXPECT_EQ("UnitTest", b.Name());
+  EXPECT_EQ(4, b.Size());
+  EXPECT_EQ("Blob", b.Type());
+}
+
+TEST_F(ReadWriteTiff, TestBlobWriteReadGdal) {
+  createTiff(UnsignedByte, false);
+  GDALDataset *dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_Update));
+
+  Blob writeBlob("UnitTest", "Blob");
+  char buf[] = {"ABCD"};
+  writeBlob.setData(buf, 4);
+  writeBlob.WriteGdal(dataset);
+
+  GDALClose(dataset);
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_Update));
+
+  Blob readBlob("UnitTest", "Blob");
+  readBlob.ReadGdal(dataset);
+
+  EXPECT_EQ(writeBlob.Name(), readBlob.Name());
+  EXPECT_EQ(writeBlob.Size(), readBlob.Size());
+  EXPECT_EQ(writeBlob.Type(), readBlob.Type());
+
+  std::string writeBuff(writeBlob.getBuffer());
+  std::string readBuff(readBlob.getBuffer());
+
+  EXPECT_EQ(writeBuff, readBuff);
+
+  GDALClose(dataset);
+}
\ No newline at end of file
diff --git a/isis/tests/BufferTests.cpp b/isis/tests/BufferTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..49126fc251d4021f53a03830bb33d19a89906873
--- /dev/null
+++ b/isis/tests/BufferTests.cpp
@@ -0,0 +1,157 @@
+#include <QTemporaryFile>
+#include <QString>
+#include <iostream>
+
+#include "Buffer.h"
+
+#include "TempFixtures.h"
+#include "TestUtilities.h"
+
+#include "gmock/gmock.h"
+
+using namespace Isis;
+
+TEST(BufferTest, TestBufferDefault) {
+  Buffer b(4, 3, 2, Isis::SignedInteger);
+
+  for(int i = 0; i < b.size(); i++) {
+    b[i] = i;
+  }
+
+  EXPECT_EQ(b.SampleDimension(), 4);
+  EXPECT_EQ(b.LineDimension(), 3);
+  EXPECT_EQ(b.BandDimension(), 2);
+  EXPECT_EQ(b.size(), 24);
+
+  EXPECT_EQ(b.Sample(), 0);
+  EXPECT_EQ(b.Line(), 0);
+  EXPECT_EQ(b.Band(), 0);
+
+  int samp, line, band;
+  b.Position(0, samp, line, band);
+  EXPECT_EQ(samp, 0);
+  EXPECT_EQ(line, 0);
+  EXPECT_EQ(band, 0);
+
+  EXPECT_EQ(b.Index(samp, line, band), 0);
+
+  EXPECT_EQ(b.at(0), 0);
+  EXPECT_EQ(b.at(10), 10);
+  EXPECT_EQ(b.at(23), 23);
+  EXPECT_EQ(b[0], 0);
+  EXPECT_EQ(b[10], 10);
+  EXPECT_EQ(b[23], 23);
+  
+  // b.SetBasePosition(3, 2, 1);
+  // EXPECT_EQ(b.Sample(), 3);
+  // EXPECT_EQ(b.Line(), 2);
+  // EXPECT_EQ(b.Band(), 1);
+
+  // b.Position(0, samp, line, band);
+  // EXPECT_EQ(samp, 3);
+  // EXPECT_EQ(line, 2);
+  // EXPECT_EQ(band, 1);
+
+  // EXPECT_EQ(b.Index(samp, line, band), 0);
+  
+  // EXPECT_EQ(b.Sample(16), 3);
+  // EXPECT_EQ(b.Line(16), 3);
+  // EXPECT_EQ(b.Band(16), 2);
+
+  // b.Position(16, samp, line, band);
+  // EXPECT_EQ(samp, 3);
+  // EXPECT_EQ(line, 3);
+  // EXPECT_EQ(band, 2);
+
+  // EXPECT_EQ(b.Index(samp, line, band), 16);
+}
+
+TEST(BufferTest, TestBufferCopy) {
+  Buffer b(4, 3, 2, Isis::SignedInteger);
+
+  for(int i = 0; i < b.size(); i++) {
+    b[i] = i;
+  }
+
+  Buffer a = b;
+
+  EXPECT_TRUE(a.DoubleBuffer() != b.DoubleBuffer());
+
+  EXPECT_EQ(a.SampleDimension(), 4);
+  EXPECT_EQ(a.LineDimension(), 3);
+  EXPECT_EQ(a.BandDimension(), 2);
+  EXPECT_EQ(a.size(), 24);
+
+  EXPECT_EQ(a[0], 0);
+  EXPECT_EQ(a[23], 23);
+
+  EXPECT_EQ(Isis::PixelTypeName(a.PixelType()), "SignedInteger");
+}
+
+TEST(BufferTest, TestBufferDefaultConstructor) {
+  Buffer nullbuf;
+  EXPECT_EQ(nullbuf.size(), 0);
+}
+
+TEST(BufferTest, TestBufferAssignment) {
+  Buffer b(2, 2, 2, Isis::Double);
+  b = 999.0;
+
+  EXPECT_EQ(b.size(), 8);
+  EXPECT_EQ(b[0], 999.0);
+  EXPECT_EQ(b[2], 999.0);
+  EXPECT_EQ(b[b.size()-1], 999.0);
+}
+
+TEST(BufferTest, TestBufferOutOfBound) {
+  Buffer b(4, 3, 2, Isis::SignedInteger);
+  for(int i = 0; i < b.size(); i++) {
+    b[i] = i;
+  }
+
+  try {
+    b.at(-1);
+  }
+  catch(Isis::IException &e) {
+    EXPECT_TRUE(e.toString().toLatin1().contains("Array subscript [-1] is out of array bounds"))
+      << e.toString().toStdString();
+  }
+
+  try {
+    b.at(24);
+  }
+  catch(Isis::IException &e) {
+    EXPECT_TRUE(e.toString().toLatin1().contains("Array subscript [24] is out of array bounds"))
+      << e.toString().toStdString();
+  }
+}
+
+TEST(BufferTest, TestBufferScale) {
+  Buffer b(4, 3, 2, Isis::SignedInteger, 0.5);
+
+  EXPECT_EQ(b.SampleDimension(), 4);
+  EXPECT_EQ(b.LineDimension(), 3);
+  EXPECT_EQ(b.BandDimension(), 2);
+
+  EXPECT_EQ(b.SampleDimensionScaled(), 2);
+  EXPECT_EQ(b.LineDimensionScaled(), 1);
+
+  EXPECT_EQ(b.size(), 4);
+
+  for(int i = 0; i < b.size(); i++) {
+    b[i] = i;
+  }
+
+  Buffer d(4, 3, 2, Isis::SignedInteger, 1);
+  d.CopyOverlapFrom(b);
+  std::vector<int> truthBuffer = {0, 0, 1, 1,
+                                  0, 0, 1, 1,
+                                  0, 0, 1, 1,
+                                  // Second band
+                                  2, 2, 3, 3,
+                                  2, 2, 3, 3,
+                                  2, 2, 3, 3};
+  for (int i = 0; i < truthBuffer.size(); i++) {
+    EXPECT_EQ(truthBuffer[i], d[i]);
+  }
+}
\ No newline at end of file
diff --git a/isis/tests/CubeTests.cpp b/isis/tests/CubeTests.cpp
index 81261407067a1f1bd632a83dfc45472bd0ba6eba..538fcae47b6e733e6422520bb1c341b73cf73f43 100644
--- a/isis/tests/CubeTests.cpp
+++ b/isis/tests/CubeTests.cpp
@@ -30,7 +30,7 @@ void check_cube(Cube &cube,
                 double base, 
                 double multiplier, 
                 int pixelType, 
-                bool attached, 
+                int attachType, 
                 int format,
                 int isOpen,
                 int isReadOnly,
@@ -43,7 +43,7 @@ void check_cube(Cube &cube,
   EXPECT_EQ(cube.base(), base);
   EXPECT_EQ(cube.multiplier(), multiplier);
   EXPECT_EQ(cube.pixelType(), pixelType);
-  EXPECT_EQ(cube.labelsAttached(), attached);
+  EXPECT_EQ(cube.labelsAttached(), attachType);
   EXPECT_EQ(cube.format(), format);
   EXPECT_EQ(cube.isOpen(), isOpen);
   if (cube.isOpen()) {
@@ -270,7 +270,7 @@ TEST(CubeTest, TestCubeAttachSpiceFromIsd) {
   EXPECT_PRED_FORMAT2(AssertQStringsEqual, cam->instrumentNameLong(), "Visual Imaging Subsystem Camera B");
 }
 
-TEST(CubeTest, TestCubeAttachLineScanTableFromIsd) {
+TEST_F(TempTestingFiles, TestCubeAttachLineScanTableFromIsd) {
   std::istringstream labelStrm(R"(
    Object = IsisCube
     Object = Core
@@ -786,9 +786,8 @@ TEST(CubeTest, TestCubeAttachLineScanTableFromIsd) {
   Pvl label;
   labelStrm >> label;
 
-  QTemporaryFile tempFile;
   Cube testCube;
-  testCube.fromIsd(tempFile.fileName() + ".cub", label, isd, "rw");
+  testCube.fromIsd(tempDir.path() + "/test.cub", label, isd, "rw");
 
   PvlGroup kernels = testCube.group("Kernels");
 
@@ -820,11 +819,11 @@ TEST_F(SmallCube, TestCubeHasBlob) {
 TEST_F(TempTestingFiles, TestCubeCreateWriteCopy) {
   Cube out;
   QString file = "";
-  check_cube(out, file, 0, 0, 0, 0, 1, 7, 1, 1, 0, 0, 0, 65536);
+  check_cube(out, file, 0, 0, 0, 0, 1, 7, 0, 1, 0, 0, 0, 65536);
   out.setDimensions(150, 200, 2);
   file = QString(tempDir.path() + "/IsisCube_00.cub");
   out.create(file);
-  check_cube(out, file, 150, 200, 2, 0, 1, 7, 1, 1, 1, 0, 1, 65536);
+  check_cube(out, file, 150, 200, 2, 0, 1, 7, 0, 1, 1, 0, 1, 65536);
 
   LineManager line(out);
   long j = 0;
@@ -844,7 +843,7 @@ TEST_F(TempTestingFiles, TestCubeCreateWriteCopy) {
 
   // Test the open and read methods
   Cube in(file2);
-  check_cube(in, file2, 150, 200, 2, 0, 1, 7, 1, 1, 1, 1, 0, 6563);
+  check_cube(in, file2, 150, 200, 2, 0, 1, 7, 0, 1, 1, 1, 0, 6563);
 
   LineManager inLine(in);
   j = 0;
@@ -862,15 +861,15 @@ TEST_F(TempTestingFiles, TestCubeCreateWriteCopy) {
 TEST_F(TempTestingFiles, TestCubeCreateWrite8bit) {
   Cube out;
   QString file = "";
-  check_cube(out, file, 0, 0, 0, 0, 1, 7, 1, 1, 0, 0, 0, 65536);
+  check_cube(out, file, 0, 0, 0, 0, 1, 7, 0, 1, 0, 0, 0, 65536);
   out.setDimensions(150, 200, 1);
-  out.setLabelsAttached(0);
+  out.setLabelsAttached(Cube::DetachedLabel);
   out.setBaseMultiplier(200.0, -1.0);
   out.setByteOrder(ISIS_LITTLE_ENDIAN ? Msb : Lsb);
   out.setFormat(Cube::Bsq);
   out.setLabelSize(1000);
   out.setPixelType(UnsignedByte);
-  check_cube(out, file, 150, 200, 1, 200, -1, 1, 0, 0, 0, 0, 0, 1000);
+  check_cube(out, file, 150, 200, 1, 200, -1, 1, 1, 0, 0, 0, 0, 1000);
   file = QString(tempDir.path() + "/IsisCube_00.cub");
   out.create(file);
 
@@ -894,7 +893,7 @@ TEST_F(TempTestingFiles, TestCubeCreateWrite8bit) {
   catch (IException &e) {
     e.print();
   }
-  check_cube(in, file, 150, 200, 1, 200, -1, 1, 0, 0, 1, 1, 0, 419);
+  check_cube(in, file, 150, 200, 1, 200, -1, 1, 1, 0, 1, 1, 0, 419);
   j = 0;
   LineManager inLine(in);
   for(inLine.begin(); !inLine.end(); inLine++) {
@@ -911,12 +910,12 @@ TEST_F(TempTestingFiles, TestCubeCreateWrite8bit) {
 TEST_F(TempTestingFiles, TestCubeCreateWrite16bit) {
   Cube out;
   QString file = "";
-  check_cube(out, file, 0, 0, 0, 0, 1, 7, 1, 1, 0, 0, 0, 65536);
+  check_cube(out, file, 0, 0, 0, 0, 1, 7, 0, 1, 0, 0, 0, 65536);
   out.setDimensions(150, 200, 2);
   out.setBaseMultiplier(30000.0, -1.0);
   out.setByteOrder(ISIS_LITTLE_ENDIAN ? Msb : Lsb);
   out.setPixelType(SignedWord);
-  check_cube(out, file, 150, 200, 2, 30000, -1, 4, 1, 1, 0, 0, 0, 65536);
+  check_cube(out, file, 150, 200, 2, 30000, -1, 4, 0, 1, 0, 0, 0, 65536);
   file = QString(tempDir.path() + "/IsisCube.cub");
   out.create(file);
 
@@ -939,7 +938,7 @@ TEST_F(TempTestingFiles, TestCubeCreateWrite16bit) {
   catch (IException &e) {
     e.print();
   }
-  check_cube(in, file, 150, 200, 2, 30000, -1, 4, 1, 1, 1, 1, 0, 65536);
+  check_cube(in, file, 150, 200, 2, 30000, -1, 4, 0, 1, 1, 1, 0, 65536);
   j = 0;
   LineManager inLine(in);
   for(inLine.begin(); !inLine.end(); inLine++) {
@@ -1053,16 +1052,16 @@ TEST_F(SmallCube, TestCubeVirutalBands) {
 
 TEST_F(SmallCube, TestCubeReopenRW) {
   QString path = testCube->fileName();
-  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 1, 1, 1, 0, 1, 65536);
+  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 0, 1, 1, 0, 1, 65536);
   testCube->reopen("rw");
-  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 1, 1, 1, 0, 1, 65536);
+  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 0, 1, 1, 0, 1, 65536);
 }
 
 TEST_F(SmallCube, TestCubeReopenR) {
   QString path = testCube->fileName();
-  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 1, 1, 1, 0, 1, 65536);
+  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 0, 1, 1, 0, 1, 65536);
   testCube->reopen("r");
-  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 1, 1, 1, 1, 0, 65536);
+  check_cube(*testCube, path, 10, 10, 10, 0, 1, 7, 0, 1, 1, 1, 0, 65536);
 }
 
 TEST_F(SmallCube, TestCubeAlreadyOpenOpen) {
@@ -1374,14 +1373,15 @@ TEST_F(ECube, TestCubeECubeWrite) {
     for (int index = 0; index < writeBrick.size(); index++) {
       writeBrick[index] = 1.0;
     }
-    try {
-      testECube->write(writeBrick);
-      FAIL();
-    }
-    catch(IException &e) {
-      EXPECT_TRUE(e.toString().toLatin1().contains("The cube [external.ecub] does not support storing DN data because it is using an external file for DNs."))
-        << e.toString().toStdString();
+    testECube->write(writeBrick);
+
+    Brick readBrick(3, 3, 2, testECube->pixelType());
+    readBrick.SetBasePosition(1, 1, 1);
+    testECube->read(readBrick);
+    for (int index = 0; index < readBrick.size(); index++) {
+      EXPECT_EQ(readBrick[index], 1);
     }
+
 }
 
 TEST_F(ECube, TestCubeECubeFromECubeRead) {
diff --git a/isis/tests/FunctionalTestsCubeatt.cpp b/isis/tests/FunctionalTestsCubeatt.cpp
index 174474920cb636b19d31672c64dd14ccd8640bd9..673161d5291fc18533d178eea8cffdf21806d357 100644
--- a/isis/tests/FunctionalTestsCubeatt.cpp
+++ b/isis/tests/FunctionalTestsCubeatt.cpp
@@ -46,7 +46,7 @@ TEST_F(SmallCube, FunctionalTestCubeattNoChange) {
   // Check attributes: pixel type, storage format, label format, storage order, pixel range, bands
   EXPECT_EQ(outputCube.pixelType(), PixelType::Real);
   EXPECT_EQ(outputCube.format(), Cube::Format::Tile);
-  EXPECT_TRUE(outputCube.labelsAttached());
+  EXPECT_EQ(outputCube.labelsAttached(), Cube::AttachedLabel);
   EXPECT_EQ(outputCube.byteOrder(), ByteOrder::Lsb);
   // Setting the pixel range modifies the base/multiplier, so check those.
   EXPECT_EQ(outputCube.base(), 0);
diff --git a/isis/tests/FunctionalTestsShadow.cpp b/isis/tests/FunctionalTestsShadow.cpp
index 3efb78c57966e4d2510c58aaf98ace185842751d..c1c9acfa6e014bd52843c1d792d2c1cf20381e0f 100644
--- a/isis/tests/FunctionalTestsShadow.cpp
+++ b/isis/tests/FunctionalTestsShadow.cpp
@@ -37,10 +37,10 @@ TEST_F(DemCube, FunctionalTestShadowMatch) {
   EXPECT_DOUBLE_EQ(double(shadowStats["MinimumElevation"]), 90.0);
   EXPECT_DOUBLE_EQ(double(shadowStats["MaximumElevation"]), 90.0);
 
-  EXPECT_EQ(int(shadowStats["NumRays"]), 5551);
-  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumRays"]), 5550);
+  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 5550);
   EXPECT_DOUBLE_EQ(double(shadowStats["AverageRayDemIntersectionsPerRay"]), 1.0);
-  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5550);
   EXPECT_EQ(int(shadowStats["NumShadowedPixels"]), 0);
   EXPECT_EQ(int(shadowStats["NumSpecialPixels"]), 2800);
   EXPECT_EQ(int(shadowStats["NumPixelsShadowedByRays"]), 0);
@@ -49,10 +49,10 @@ TEST_F(DemCube, FunctionalTestShadowMatch) {
 
   std::unique_ptr<Histogram> hist (shadowCube.histogram());
 
-  EXPECT_NEAR(hist->Average(), 0.00084506527240706553, 1e-11);
-  EXPECT_NEAR(hist->Sum(), 4.6909573271316205, 1e-11);
-  ASSERT_EQ(hist->ValidPixels(), 5551);
-  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084740620921499, 1e-11);
+  EXPECT_NEAR(hist->Average(), 0.00084519596669997796, 1e-11);
+  EXPECT_NEAR(hist->Sum(), 4.6908376151848774, 1e-11);
+  EXPECT_EQ(hist->ValidPixels(), 5550);
+  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084895548001136, 1e-11);
 }
 
 TEST_F(DemCube, FunctionalTestShadowTime) {
@@ -77,8 +77,8 @@ TEST_F(DemCube, FunctionalTestShadowTime) {
   EXPECT_DOUBLE_EQ(double(shadowStats["MaximumElevation"]), 55.260883777776002);
 
   EXPECT_EQ(int(shadowStats["NumRays"]), 9604);
-  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 10177);
-  EXPECT_DOUBLE_EQ(double(shadowStats["AverageRayDemIntersectionsPerRay"]), 1.0596626405664);
+  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 10159);
+  EXPECT_DOUBLE_EQ(double(shadowStats["AverageRayDemIntersectionsPerRay"]), 1.0577884214910001);
   EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 9500);
   EXPECT_EQ(int(shadowStats["NumShadowedPixels"]), 104);
   EXPECT_EQ(int(shadowStats["NumSpecialPixels"]), 2800);
@@ -88,10 +88,10 @@ TEST_F(DemCube, FunctionalTestShadowTime) {
 
   std::unique_ptr<Histogram> hist (shadowCube.histogram());
 
-  EXPECT_NEAR(hist->Average(), 0.57755590112585775, 1e-11);
-  EXPECT_NEAR(hist->Sum(), 5486.7810606956482, 1e-11);
-  ASSERT_EQ(hist->ValidPixels(), 9500);
-  EXPECT_NEAR(hist->StandardDeviation(), 0.0027122379225963896, 1e-11);
+  EXPECT_NEAR(hist->Average(), 0.5775715160056164, 1e-11);
+  EXPECT_NEAR(hist->Sum(), 5486.9294020533562, 1e-11);
+  EXPECT_EQ(hist->ValidPixels(), 9500);
+  EXPECT_NEAR(hist->StandardDeviation(), 0.0027113945565730518, 1e-11);
 }
 
 TEST_F(DemCube, FunctionalTestShadowNoShadow) {
@@ -116,7 +116,7 @@ TEST_F(DemCube, FunctionalTestShadowNoShadow) {
 
   EXPECT_EQ(int(shadowStats["NumRays"]), 0);
   EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 0);
-  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5550);
   EXPECT_EQ(int(shadowStats["NumShadowedPixels"]), 0);
   EXPECT_EQ(int(shadowStats["NumSpecialPixels"]), 2800);
   EXPECT_EQ(int(shadowStats["NumPixelsShadowedByRays"]), 0);
@@ -125,10 +125,10 @@ TEST_F(DemCube, FunctionalTestShadowNoShadow) {
 
   std::unique_ptr<Histogram> hist (shadowCube.histogram());
 
-  EXPECT_NEAR(hist->Average(), 0.00084506527240706553, 1e-11);
-  EXPECT_NEAR(hist->Sum(), 4.6909573271316205, 1e-11);
-  EXPECT_EQ(hist->ValidPixels(), 5551);
-  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084740620921499, 1e-11);
+  EXPECT_NEAR(hist->Average(), 0.00084519596669997796, 1e-11);
+  EXPECT_NEAR(hist->Sum(), 4.6908376151848774, 1e-11);
+  EXPECT_EQ(hist->ValidPixels(), 5550);
+  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084895548001136, 1e-11);
 }
 
 TEST_F(DemCube, FunctionalTestShadowAccurate) {
@@ -149,10 +149,10 @@ TEST_F(DemCube, FunctionalTestShadowAccurate) {
   EXPECT_DOUBLE_EQ(double(shadowStats["MinimumElevation"]), 90.0);
   EXPECT_DOUBLE_EQ(double(shadowStats["MaximumElevation"]), 90.0);
 
-  EXPECT_EQ(int(shadowStats["NumRays"]), 5551);
-  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumRays"]), 5550);
+  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 5550);
   EXPECT_DOUBLE_EQ(double(shadowStats["AverageRayDemIntersectionsPerRay"]), 1.0);
-  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5550);
   EXPECT_EQ(int(shadowStats["NumShadowedPixels"]), 0);
   EXPECT_EQ(int(shadowStats["NumSpecialPixels"]), 2800);
   EXPECT_EQ(int(shadowStats["NumPixelsShadowedByRays"]), 0);
@@ -161,10 +161,10 @@ TEST_F(DemCube, FunctionalTestShadowAccurate) {
 
   std::unique_ptr<Histogram> hist (shadowCube.histogram());
 
-  EXPECT_NEAR(hist->Average(), 0.00084506527240706553, 1e-11);
-  EXPECT_NEAR(hist->Sum(), 4.6909573271316205, 1e-11);
-  ASSERT_EQ(hist->ValidPixels(), 5551);
-  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084740620921499, 1e-11);
+  EXPECT_NEAR(hist->Average(), 0.00084519596669997796, 1e-11);
+  EXPECT_NEAR(hist->Sum(), 4.6908376151848774, 1e-11);
+  EXPECT_EQ(hist->ValidPixels(), 5550);
+  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084895548001136, 1e-11);
 }
 
 TEST_F(DemCube, FunctionalTestShadowCustom) {
@@ -185,10 +185,10 @@ TEST_F(DemCube, FunctionalTestShadowCustom) {
   EXPECT_DOUBLE_EQ(double(shadowStats["MinimumElevation"]), 90.0);
   EXPECT_DOUBLE_EQ(double(shadowStats["MaximumElevation"]), 90.0);
 
-  EXPECT_EQ(int(shadowStats["NumRays"]), 5551);
-  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumRays"]), 5550);
+  EXPECT_EQ(int(shadowStats["NumRayDemIntersections"]), 5550);
   EXPECT_DOUBLE_EQ(double(shadowStats["AverageRayDemIntersectionsPerRay"]), 1.0);
-  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5551);
+  EXPECT_EQ(int(shadowStats["NumLightedPixels"]), 5550);
   EXPECT_EQ(int(shadowStats["NumShadowedPixels"]), 0);
   EXPECT_EQ(int(shadowStats["NumSpecialPixels"]), 2800);
   EXPECT_EQ(int(shadowStats["NumPixelsShadowedByRays"]), 0);
@@ -197,10 +197,10 @@ TEST_F(DemCube, FunctionalTestShadowCustom) {
 
   std::unique_ptr<Histogram> hist (shadowCube.histogram());
 
-  EXPECT_NEAR(hist->Average(), 0.00084506527240706553, 1e-11);
-  EXPECT_NEAR(hist->Sum(), 4.6909573271316205, 1e-11);
-  ASSERT_EQ(hist->ValidPixels(), 5551);
-  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084740620921499, 1e-11);
+  EXPECT_NEAR(hist->Average(), 0.00084519596669997796, 1e-11);
+  EXPECT_NEAR(hist->Sum(), 4.6908376151848774, 1e-11);
+  EXPECT_EQ(hist->ValidPixels(), 5550);
+  EXPECT_NEAR(hist->StandardDeviation(), 0.0010084895548001136, 1e-11);
 }
 
 TEST_F(DemCube, FunctionalTestShadowErrors) {
diff --git a/isis/tests/GTiffTests.cpp b/isis/tests/GTiffTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..704c8c0679af265a0f3cfde78f228eaa26056225
--- /dev/null
+++ b/isis/tests/GTiffTests.cpp
@@ -0,0 +1,225 @@
+#include <QTemporaryFile>
+#include <QString>
+#include <iostream>
+
+#include <nlohmann/json.hpp>
+using json = nlohmann::json;
+
+#include "Blob.h"
+#include "Brick.h"
+#include "Camera.h"
+#include "Cube.h"
+#include "CubeAttribute.h"
+#include "Histogram.h"
+#include "LineManager.h"
+#include "Statistics.h"
+#include "Table.h"
+#include "TableField.h"
+#include "TableRecord.h"
+
+#include "CubeFixtures.h"
+#include "TestUtilities.h"
+
+#include "gmock/gmock.h"
+
+using namespace Isis;
+
+void check_tiff(Cube &cube,
+                QString &file, 
+                int sampleCount, 
+                int lineCount, 
+                int bandCount, 
+                double base, 
+                double multiplier, 
+                int pixelType, 
+                int attachType, 
+                int format,
+                int isOpen,
+                int isReadOnly,
+                int isReadWrite,
+                int labelSize) {
+  EXPECT_EQ(cube.fileName().toStdString(), file.toStdString());
+  EXPECT_EQ(cube.sampleCount(), sampleCount);
+  EXPECT_EQ(cube.lineCount(), lineCount);
+  EXPECT_EQ(cube.bandCount(), bandCount);
+  EXPECT_EQ(cube.base(), base);
+  EXPECT_EQ(cube.multiplier(), multiplier);
+  EXPECT_EQ(cube.pixelType(), pixelType);
+  EXPECT_EQ(cube.labelsAttached(), attachType);
+  EXPECT_EQ(cube.format(), format);
+  EXPECT_EQ(cube.isOpen(), isOpen);
+  if (cube.isOpen()) {
+    EXPECT_EQ(cube.isReadOnly(), isReadOnly);
+    EXPECT_EQ(cube.isReadWrite(), isReadWrite);
+  }
+  EXPECT_EQ(cube.labelSize(), labelSize);
+}
+
+
+TEST_F(TempTestingFiles, TestGTiffCreateWriteCopy) {
+  Cube out;
+  QString file = "";
+  check_tiff(out, file, 0, 0, 0, 0, 1, 7, 0, 1, 0, 0, 0, 65536);
+  out.setDimensions(150, 200, 2);
+  out.setFormat(Isis::Cube::GTiff);
+  file = QString(tempDir.path() + "/IsisCube_00.tiff");
+  out.create(file);
+  check_tiff(out, file, 150, 200, 2, 0, 1, 7, 0, 2, 1, 0, 1, 65536);
+
+  LineManager line(out);
+  long j = 0;
+  for(line.begin(); !line.end(); line++) {
+    for(int i = 0; i < line.size(); i++) {
+      line[i] = (double) j;
+      j++;
+    }
+    j--;
+    out.write(line);
+  }
+
+  // Copy returns the resulting Cube, we don't care about it (but we need it to flush) so delete
+  QString file2 = tempDir.path() + "/IsisCube_01.tiff";
+  CubeAttributeOutput outAtt;
+  outAtt.setFileFormat(Isis::Cube::GTiff);
+  delete out.copy(file2, outAtt);
+  out.close();
+
+  // Test the open and read methods
+  Cube in(file2);
+  check_tiff(in, file2, 150, 200, 2, 0, 1, 7, 0, 2, 1, 1, 0, 65536);
+
+  LineManager inLine(in);
+  j = 0;
+  for(inLine.begin(); !inLine.end(); inLine++) {
+    in.read(inLine);
+    for(int i = 0; i < inLine.size(); i++) {
+      EXPECT_NEAR(inLine[i], (double) j, 1e-15);
+      j++;
+    }
+    j--;
+  }
+  in.close();
+}
+
+class GdalDnTypeGenerator: public TempTestingFiles, public ::testing::WithParamInterface<Isis::PixelType> {
+    // Intentionally left empty
+    void SetUp() {
+        TempTestingFiles::SetUp();
+    }
+};
+
+INSTANTIATE_TEST_SUITE_P (GdalDnPixelTypes,
+                          GdalDnTypeGenerator,
+                          ::testing::Values(Isis::UnsignedByte,
+                                            Isis::SignedByte,
+                                            Isis::UnsignedWord,
+                                            Isis::SignedWord,
+                                            Isis::UnsignedInteger,
+                                            Isis::SignedInteger,
+                                            Isis::Real,
+                                            Isis::Double));
+
+TEST_P(GdalDnTypeGenerator, TestGTiffCreateWrite) {
+  Cube out;
+  QString file = "";
+  check_tiff(out, file, 0, 0, 0, 0, 1, 7, 0, 1, 0, 0, 0, 65536);
+  int lines = 200;
+  int samples = 150;
+  int bands = 4;
+  out.setDimensions(samples, lines, bands);
+  out.setBaseMultiplier(0.0, 1.0);
+  out.setFormat(Cube::GTiff);
+  out.setPixelType(GetParam());
+  check_tiff(out, file, samples, lines, bands, 0, 1, GetParam(), 0, 2, 0, 0, 0, 65536);
+  file = QString(tempDir.path() + "/IsisCube_00.tiff");
+  out.create(file);
+
+  // Only write DNs between 3 and 127 as those are valid
+  // DN values for all pixel types
+  // All special pixel input and output tests are handled in the
+  // GdalIoHandler tests
+  int min = 3;
+  int max = 127;
+
+  long j = min;
+  LineManager oline(out);
+  for(oline.begin(); !oline.end(); oline++) {
+    for(int i = 0; i < oline.size(); i++) {
+      oline[i] = (double) j;
+    }
+    out.clearIoCache();
+    out.write(oline);
+    j++;
+    if (j > max) {
+      j = min;
+    }
+  }
+  out.close();
+
+  Cube in;
+  try {
+    in.open(file);
+  }
+  catch (IException &e) {
+    e.print();
+  }
+  check_tiff(in, file, samples, lines, bands, 0, 1, GetParam(), 0, 2, 1, 1, 0, 65536);
+
+  Statistics *cubeStats = in.statistics(0);
+  EXPECT_DOUBLE_EQ(cubeStats->Average(), 62.65625);
+  EXPECT_DOUBLE_EQ(cubeStats->StandardDeviation(), 36.277390383170371);
+  EXPECT_DOUBLE_EQ(cubeStats->TotalPixels(), 120000);
+  EXPECT_DOUBLE_EQ(cubeStats->NullPixels(), 0);
+  delete cubeStats;
+  in.close();
+}
+
+// Add Test for GTiff with no ISIS metadata
+
+TEST_F(TempTestingFiles, TableTestsWriteReadGdal) {
+  TableField f1("Column1", TableField::Integer);
+  TableField f2("Column2", TableField::Double);
+  TableField f3("Column3", TableField::Text, 10);
+  TableField f4("Column4", TableField::Double);
+  TableRecord rec;
+  rec += f1;
+  rec += f2;
+  rec += f3;
+  rec += f4;
+  Table t("UNITTEST", rec);
+
+  t.SetAssociation(Table::Lines);
+
+  rec[0] = 5;
+  rec[1] = 3.14;
+  rec[2] = "PI";
+  rec[3] = 3.14159;
+  t += rec;
+
+  rec[0] = -1;
+  rec[1] = 0.5;
+  rec[2] = "HI";
+  rec[3] = -0.55;
+  t += rec;
+
+  QString cubeFile = tempDir.path() + "/testTable.tiff";
+  Cube cube;
+  cube.setDimensions(10, 10, 1);
+  cube.setFormat(Isis::Cube::GTiff);
+  cube.create(cubeFile);
+  cube.write(t);
+  Blob tableBlob("UNITTEST", "Table");
+  cube.read(tableBlob);
+  Table t2(tableBlob);
+
+  EXPECT_EQ(t.RecordFields(), t2.RecordFields());
+  EXPECT_EQ(t.RecordSize(), t2.RecordSize());
+  EXPECT_EQ(t.IsSampleAssociated(), t2.IsSampleAssociated());
+  EXPECT_EQ(t.IsLineAssociated(), t2.IsLineAssociated());
+  EXPECT_EQ(t.IsBandAssociated(), t2.IsBandAssociated());
+
+  ASSERT_EQ(t.Records(), t2.Records());
+  for (int i = 0; i < t.Records(); i++) {
+    EXPECT_EQ(TableRecord::toString(t[i]).toStdString(), TableRecord::toString(t2[i]).toStdString());
+  }
+}
\ No newline at end of file
diff --git a/isis/tests/GdalIoHandlerTests.cpp b/isis/tests/GdalIoHandlerTests.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..166bbf84ec0beebd28ac43be3983109ab4bb22a6
--- /dev/null
+++ b/isis/tests/GdalIoHandlerTests.cpp
@@ -0,0 +1,672 @@
+#include <QTemporaryFile>
+#include <QString>
+#include <iostream>
+#include <iomanip>
+
+#include <nlohmann/json.hpp>
+using json = nlohmann::json;
+
+#include "Brick.h"
+#include "PixelType.h"
+#include "GdalIoHandler.h"
+#include "SpecialPixel.h"
+#include "TiffFixtures.h"
+
+#include "TestUtilities.h"
+
+#include "gmock/gmock.h"
+
+using namespace Isis;
+
+TEST_F(ReadWriteTiff, GdalIoTestsDefaulWrite) {
+  PixelType isisPixelType = Double;
+  createTiff(isisPixelType, false);
+
+  {
+    const QList<int> *bandList = new QList<int>;
+
+    GdalIoHandler handler(path, bandList, GDT_Float64, GA_Update);
+    Brick localBrick(6, 1, 1, isisPixelType);
+
+    localBrick.SetBasePosition(1, 1, 1);
+    // init everything to 100
+    for (int i = 0; i < localBrick.size(); i++) {
+      localBrick[i] = 100;
+    }
+
+    handler.write(localBrick);
+  } // file is closed  
+
+  // read it back 
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (double *) CPLMalloc(sizeof(double)*6*1);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0, 
+                                         6, 1,
+                                         dbuf, 
+                                         6, 1, 
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+  
+  for (int i = 0; i<6*1; i++) { 
+    ASSERT_EQ(((double *) dbuf)[i], 100);
+  }
+
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadFloat64) {
+  PixelType isisPixelType = Double;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[3], LOW_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 1000.0);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadFloat32) {
+  PixelType isisPixelType = Real;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[3], LOW_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 1000.0);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadInt32) {
+  PixelType isisPixelType = SignedInteger;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[3], LOW_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 1000.0);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadUInt32) {
+  PixelType isisPixelType = UnsignedInteger;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[3], LOW_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 1000.0);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadInt16) {
+  PixelType isisPixelType = SignedWord;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[3], LOW_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 1000.0);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadUInt16) {
+  PixelType isisPixelType = UnsignedWord;
+  createTiff(isisPixelType);
+ 
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[3], LOW_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 1000.0);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadInt8) {
+  PixelType isisPixelType = SignedByte;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], NULL8);
+  EXPECT_EQ(brickDoubleBuff[3], NULL8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 50);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadUInt8) {
+  PixelType isisPixelType = UnsignedByte;
+  createTiff(isisPixelType);
+
+  const QList<int> *bandList = new QList<int>;
+  GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType));
+  handler.read(*localBrick);
+
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  EXPECT_EQ(brickDoubleBuff[0], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(brickDoubleBuff[2], NULL8);
+  EXPECT_EQ(brickDoubleBuff[3], NULL8);
+  EXPECT_EQ(brickDoubleBuff[4], NULL8);
+  EXPECT_EQ(brickDoubleBuff[5], 50);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteFloat64) {
+  PixelType isisPixelType = Double;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 1000;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (double *)CPLMalloc(sizeof(double) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((double *)dbuf)[0], HIGH_INSTR_SAT8);
+  EXPECT_EQ(((double *)dbuf)[1], HIGH_REPR_SAT8);
+  EXPECT_EQ(((double *)dbuf)[2], LOW_INSTR_SAT8);
+  EXPECT_EQ(((double *)dbuf)[3], LOW_REPR_SAT8);
+  EXPECT_EQ(((double *)dbuf)[4], NULL8);
+  EXPECT_EQ(((double *)dbuf)[5], 1000);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteFloat32) {
+  PixelType isisPixelType = Real;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 1000;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (float *)CPLMalloc(sizeof(float) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((float *)dbuf)[0], HIGH_INSTR_SAT4);
+  EXPECT_EQ(((float *)dbuf)[1], HIGH_REPR_SAT4);
+  EXPECT_EQ(((float *)dbuf)[2], LOW_INSTR_SAT4);
+  EXPECT_EQ(((float *)dbuf)[3], LOW_REPR_SAT4);
+  EXPECT_EQ(((float *)dbuf)[4], NULL4);
+  EXPECT_EQ(((float *)dbuf)[5], 1000);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteInt32) {
+  PixelType isisPixelType = SignedInteger;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 1000;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (unsigned int *)CPLMalloc(sizeof(unsigned int) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                6, 1,
+                                dbuf,
+                                6, 1,
+                                IsisPixelToGdal(isisPixelType),
+                                0, 0);
+  
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((unsigned int *)dbuf)[0], HIGH_INSTR_SATI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[1], HIGH_REPR_SATI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[2], LOW_INSTR_SATI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[3], LOW_REPR_SATI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[4], NULLI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[5], 1000);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteUInt32) {
+  PixelType isisPixelType = UnsignedInteger;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 1000;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (unsigned int *)CPLMalloc(sizeof(unsigned int) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+ 
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((unsigned int *)dbuf)[0], HIGH_INSTR_SATUI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[1], HIGH_REPR_SATUI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[2], LOW_INSTR_SATUI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[3], LOW_REPR_SATUI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[4], NULLUI4);
+  EXPECT_EQ(((unsigned int *)dbuf)[5], 1000);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteInt16) {
+  PixelType isisPixelType = SignedWord;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 1000;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (short *)CPLMalloc(sizeof(short) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+  
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((short *)dbuf)[0], HIGH_INSTR_SAT2);
+  EXPECT_EQ(((short *)dbuf)[1], HIGH_REPR_SAT2);
+  EXPECT_EQ(((short *)dbuf)[2], LOW_INSTR_SAT2);
+  EXPECT_EQ(((short *)dbuf)[3], LOW_REPR_SAT2);
+  EXPECT_EQ(((short *)dbuf)[4], NULL2);
+  EXPECT_EQ(((short *)dbuf)[5], 1000);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteUInt16) {
+  PixelType isisPixelType = UnsignedWord;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 1000;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (unsigned short *)CPLMalloc(sizeof(unsigned short) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+  
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((unsigned short *)dbuf)[0], HIGH_INSTR_SATU2);
+  EXPECT_EQ(((unsigned short *)dbuf)[1], HIGH_REPR_SATU2);
+  EXPECT_EQ(((unsigned short *)dbuf)[2], LOW_INSTR_SATU2);
+  EXPECT_EQ(((unsigned short *)dbuf)[3], LOW_REPR_SATU2);
+  EXPECT_EQ(((unsigned short *)dbuf)[4], NULLU2);
+  EXPECT_EQ(((unsigned short *)dbuf)[5], 1000);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteInt8) {
+  PixelType isisPixelType = SignedByte;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 50;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (char *)CPLMalloc(sizeof(char) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+  
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((char *)dbuf)[0], HIGH_INSTR_SATS1);
+  EXPECT_EQ(((char *)dbuf)[1], HIGH_REPR_SATS1);
+  EXPECT_EQ(((char *)dbuf)[2], LOW_INSTR_SATS1);
+  EXPECT_EQ(((char *)dbuf)[3], LOW_REPR_SATS1);
+  EXPECT_EQ(((char *)dbuf)[4], NULLS1);
+  EXPECT_EQ(((char *)dbuf)[5], 50);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsWriteUInt8) {
+  PixelType isisPixelType = UnsignedByte;
+  createTiff(isisPixelType, false);
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    const QList<int> *bandList = new QList<int>;
+    GdalIoHandler handler(path, bandList, IsisPixelToGdal(isisPixelType), GA_Update);
+
+    localBrick = new Brick(6, 1, 1, isisPixelType);
+    localBrick->SetBasePosition(1, 1, 1);
+
+    double *brickDoubleBuff = localBrick->DoubleBuffer();
+
+    brickDoubleBuff[0] = HIGH_INSTR_SAT8;
+    brickDoubleBuff[1] = HIGH_REPR_SAT8;
+    brickDoubleBuff[2] = LOW_INSTR_SAT8;
+    brickDoubleBuff[3] = LOW_REPR_SAT8;
+    brickDoubleBuff[4] = NULL8;
+    brickDoubleBuff[5] = 50;
+
+    handler.write(*localBrick);
+  }
+
+  dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_ReadOnly));
+  dbuf = (unsigned char *)CPLMalloc(sizeof(unsigned char) * 6);
+  GDALRasterBand *poBand = dataset->GetRasterBand(1);
+  CPLErr err = poBand->RasterIO(GF_Read, 0, 0,
+                                         6, 1,
+                                         dbuf,
+                                         6, 1,
+                                         IsisPixelToGdal(isisPixelType),
+                                         0, 0);
+  
+  if (err > CE_Failure) {
+    FAIL() << "Failed to read tiff";
+  }
+
+  EXPECT_EQ(((unsigned char *)dbuf)[0], HIGH_INSTR_SAT1);
+  EXPECT_EQ(((unsigned char *)dbuf)[1], HIGH_REPR_SAT1);
+  EXPECT_EQ(((unsigned char *)dbuf)[2], LOW_INSTR_SAT1);
+  EXPECT_EQ(((unsigned char *)dbuf)[3], LOW_REPR_SAT1);
+  EXPECT_EQ(((unsigned char *)dbuf)[4], NULL1);
+  EXPECT_EQ(((unsigned char *)dbuf)[5], 50);
+}
+
+TEST_F(ReadWriteTiff, GdalIoTestsReadOutside) {
+  PixelType isisPixelType = SignedWord;
+  const QList<int> *bandList = new QList<int>;
+
+  // Create a context so the handler goes out of scope and closes the file
+  {
+    int samples = 5;
+    int lines = 5;
+    createSizedTiff(samples, lines, 1, isisPixelType);
+    dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_Update));
+    dbuf = (void *)CPLMalloc(sizeof(short) * samples * lines);
+
+    for (short i = 0; i < samples * lines; i++) {
+     ((short *)dbuf)[i] = i;
+    }
+    GDALRasterBand *poBand = dataset->GetRasterBand(1);
+    CPLErr err = poBand->RasterIO(GF_Write, 0, 0,
+                                            samples, lines,
+                                            dbuf,
+                                            samples, lines,
+                                            IsisPixelToGdal(isisPixelType),
+                                            0, 0);
+    
+    if (err > CE_Failure) {
+      FAIL() << "Failed to write tiff";
+    }
+
+    dataset->Close();
+  }
+
+  GdalIoHandler gdalHandler(path, bandList, IsisPixelToGdal(isisPixelType));
+
+  delete localBrick;
+  localBrick = NULL;
+  localBrick = new Brick(4, 4, 1, isisPixelType);
+  
+  // Test out of bounds read at the topleft
+  localBrick->SetBasePosition(-1, -1, 1);
+  gdalHandler.read(*localBrick);
+  double *brickDoubleBuff = localBrick->DoubleBuffer();
+  std::vector<double> resultVec = {NULL8, NULL8, NULL8, NULL8,
+                                   NULL8, NULL8, NULL8, NULL8,
+                                   NULL8, NULL8,     0,     1,
+                                   NULL8, NULL8,     5,     6};
+  for (short i = 0; i < 4 * 4; i++) {
+    EXPECT_EQ(brickDoubleBuff[i], resultVec[i]);
+  }
+
+  // Test out of bounds read at the bottom right
+  localBrick->SetBasePosition(4, 4, 1);
+  gdalHandler.read(*localBrick);
+  brickDoubleBuff = localBrick->DoubleBuffer();
+  resultVec = {   18,    19, NULL8, NULL8,
+                  23,    24, NULL8, NULL8,
+               NULL8, NULL8, NULL8, NULL8,
+               NULL8, NULL8, NULL8, NULL8};
+  for (short i = 0; i < 4 * 4; i++) {
+    EXPECT_EQ(brickDoubleBuff[i], resultVec[i]);
+  }
+
+  delete localBrick;
+  localBrick = NULL;
+  localBrick = new Brick(7, 7, 1, isisPixelType);
+  localBrick->SetBasePosition(0, 0, 1);
+
+  // Test filling a larger buffer than is in the image
+  gdalHandler.read(*localBrick);
+  brickDoubleBuff = localBrick->DoubleBuffer();
+  resultVec = {NULL8, NULL8, NULL8, NULL8, NULL8, NULL8, NULL8,
+               NULL8,     0,     1,     2,     3,     4, NULL8,
+               NULL8,     5,     6,     7,     8,     9, NULL8,
+               NULL8,    10,    11,    12,    13,    14, NULL8,
+               NULL8,    15,    16,    17,    18,    19, NULL8,
+               NULL8,    20,    21,    22,    23,    24, NULL8,
+               NULL8, NULL8, NULL8, NULL8, NULL8, NULL8, NULL8};
+  for (short i = 0; i < 7 * 7; i++) {
+    EXPECT_EQ(brickDoubleBuff[i], resultVec[i]);
+  }
+
+  // Test reading completely outside of an image
+  delete localBrick;
+  localBrick = NULL;
+  localBrick = new Brick(2, 2, 1, isisPixelType);
+  
+  // Read non-NULL8 data
+  localBrick->SetBasePosition(2, 2, 1);
+  gdalHandler.read(*localBrick);
+
+  // Read over to ensure brick is updated back to NULL8s
+  localBrick->SetBasePosition(8, 8, 1);
+  gdalHandler.read(*localBrick);
+  brickDoubleBuff = localBrick->DoubleBuffer();
+  resultVec = {NULL8, NULL8,
+               NULL8, NULL8};
+  for (short i = 0; i < 2 * 2; i++) {
+    EXPECT_EQ(brickDoubleBuff[i], resultVec[i]);
+  }
+  
+  delete localBrick;
+  localBrick = NULL;
+  localBrick = new Brick(2, 2, 1, isisPixelType);
+
+  // Read non-NULL8 data
+  localBrick->SetBasePosition(2, 2, 1);
+  gdalHandler.read(*localBrick);
+
+  // Read over to ensure brick is updated back to NULL8s
+  localBrick->SetBasePosition(-4, -4, 1);
+  gdalHandler.read(*localBrick);
+  brickDoubleBuff = localBrick->DoubleBuffer();
+  resultVec = {NULL8, NULL8,
+               NULL8, NULL8};
+  for (short i = 0; i < 2 * 2; i++) {
+    EXPECT_EQ(brickDoubleBuff[i], resultVec[i]);
+  }
+}
diff --git a/isis/tests/TiffFixtures.cpp b/isis/tests/TiffFixtures.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c7af4a096a334c5bfebd945e8c0e81b30ad6e483
--- /dev/null
+++ b/isis/tests/TiffFixtures.cpp
@@ -0,0 +1,177 @@
+#include "TiffFixtures.h"
+
+#include "Brick.h"
+#include "PixelType.h"
+#include "SpecialPixel.h"
+
+namespace Isis {
+
+  void ReadWriteTiff::SetUp() {
+    TempTestingFiles::SetUp();
+    
+    path = tempDir.path() + "/tiny.tiff";
+  }
+
+  void ReadWriteTiff::TearDown() {
+    if (dataset) {
+      delete dataset;
+    }
+    if (localBrick) {
+      delete localBrick;
+    }
+    if (dbuf) {
+      CPLFree(dbuf);
+    }
+  }
+
+  void ReadWriteTiff::createSizedTiff(int samples, int lines, int bands, PixelType pixelType) {
+    GDALAllRegister();
+    GDALDriver* driver = GetGDALDriverManager()->GetDriverByName("GTiff");
+    if (driver) {
+      char **papszOptions = NULL;
+      dataset = driver->Create(path.toStdString().c_str(), samples, lines, bands, IsisPixelToGdal(pixelType), papszOptions);
+      if (dataset) {
+        double noDataValue;
+        switch (pixelType) {
+          case UnsignedByte:
+            noDataValue = (double) NULL1;
+            break;
+          case SignedByte:
+            noDataValue = (double) NULLS1;
+            break;
+          case UnsignedWord:
+            noDataValue = (double) NULLU2;
+            break;
+          case SignedWord:
+            noDataValue = (double) NULL2;
+            break;
+          case UnsignedInteger:
+            noDataValue = (double) NULLUI4;
+            break;
+          case SignedInteger:
+            noDataValue = (double) NULLI4;
+            break;
+          case Real:
+            noDataValue = (double) NULL4;
+            break;
+
+          default:
+            noDataValue = NULL8;
+            break;
+        }
+        for (int i = 1; i <= bands; i++) {
+          GDALRasterBand *band = dataset->GetRasterBand(i);
+          band->SetScale(1);
+          band->SetOffset(0);
+          band->SetNoDataValue(noDataValue);
+        }
+        dataset->CreateMaskBand(GMF_ALPHA);
+        dataset->GetRasterBand(1)->GetMaskBand()->Fill(255);
+        dataset->Close();
+      }
+    }
+  }
+
+  void ReadWriteTiff::createTiff(PixelType pixelType, bool write) {
+    createSizedTiff(6, 1, 1, pixelType);
+    if (write) {
+      dataset = GDALDataset::FromHandle(GDALOpen(path.toStdString().c_str(), GA_Update));
+      if (pixelType == Double) {
+        dbuf = (void *)CPLMalloc(sizeof(double) * 6);
+
+        ((double *)dbuf)[0] = HIGH_INSTR_SAT8;
+        ((double *)dbuf)[1] = HIGH_REPR_SAT8;
+        ((double *)dbuf)[2] = LOW_INSTR_SAT8;
+        ((double *)dbuf)[3] = LOW_REPR_SAT8;
+        ((double *)dbuf)[4] = NULL8;
+        ((double *)dbuf)[5] = 1000;
+      }
+      else if (pixelType == Real) {
+        dbuf = (void *)CPLMalloc(sizeof(float) * 6);
+        
+        ((float *)dbuf)[0] = HIGH_INSTR_SAT4;
+        ((float *)dbuf)[1] = HIGH_REPR_SAT4;
+        ((float *)dbuf)[2] = LOW_INSTR_SAT4;
+        ((float *)dbuf)[3] = LOW_REPR_SAT4;
+        ((float *)dbuf)[4] = NULL4;
+        ((float *)dbuf)[5] = 1000;
+      }
+      else if (pixelType == SignedInteger) {
+        dbuf = (void *)CPLMalloc(sizeof(int) * 6);
+
+        ((int *)dbuf)[0] = HIGH_INSTR_SATI4;
+        ((int *)dbuf)[1] = HIGH_REPR_SATI4;
+        ((int *)dbuf)[2] = LOW_INSTR_SATI4;
+        ((int *)dbuf)[3] = LOW_REPR_SATI4;
+        ((int *)dbuf)[4] = NULLI4;
+        ((int *)dbuf)[5] = 1000;
+      }
+      else if (pixelType == UnsignedInteger) {
+        dbuf = (void *)CPLMalloc(sizeof(unsigned int) * 6);
+
+        ((unsigned int *)dbuf)[0] = HIGH_INSTR_SATUI4;
+        ((unsigned int *)dbuf)[1] = HIGH_REPR_SATUI4;
+        ((unsigned int *)dbuf)[2] = LOW_INSTR_SATUI4;
+        ((unsigned int *)dbuf)[3] = LOW_REPR_SATUI4;
+        ((unsigned int *)dbuf)[4] = NULLUI4;
+        ((unsigned int *)dbuf)[5] = 1000;
+      }
+      else if (pixelType == SignedWord) {
+        dbuf = (void *)CPLMalloc(sizeof(short) * 6);
+
+        ((short *)dbuf)[0] = HIGH_INSTR_SAT2;
+        ((short *)dbuf)[1] = HIGH_REPR_SAT2;
+        ((short *)dbuf)[2] = LOW_INSTR_SAT2;
+        ((short *)dbuf)[3] = LOW_REPR_SAT2;
+        ((short *)dbuf)[4] = NULL2;
+        ((short *)dbuf)[5] = 1000;
+      }
+      else if (pixelType == UnsignedWord) {
+        dbuf = (void *)CPLMalloc(sizeof(unsigned short) * 6);
+
+        ((unsigned short *)dbuf)[0] = HIGH_INSTR_SATU2;
+        ((unsigned short *)dbuf)[1] = HIGH_REPR_SATU2;
+        ((unsigned short *)dbuf)[2] = LOW_INSTR_SATU2;
+        ((unsigned short *)dbuf)[3] = LOW_REPR_SATU2;
+        ((unsigned short *)dbuf)[4] = NULLU2;
+        ((unsigned short *)dbuf)[5] = 1000;
+      }
+      else if (pixelType == SignedByte) {
+        dbuf = (void *)CPLMalloc(sizeof(char) * 6);
+
+        ((char *)dbuf)[0] = HIGH_INSTR_SATS1;
+        ((char *)dbuf)[1] = HIGH_REPR_SATS1;
+        ((char *)dbuf)[2] = LOW_INSTR_SATS1;
+        ((char *)dbuf)[3] = LOW_REPR_SATS1;
+        ((char *)dbuf)[4] = NULLS1;
+        ((char *)dbuf)[5] = 50;
+      }
+      else if (pixelType == UnsignedByte) {
+        dbuf = (void *)CPLMalloc(sizeof(unsigned char) * 6);
+
+        ((unsigned char *)dbuf)[0] = HIGH_INSTR_SAT1;
+        ((unsigned char *)dbuf)[1] = HIGH_REPR_SAT1;
+        ((unsigned char *)dbuf)[2] = LOW_INSTR_SAT1;
+        ((unsigned char *)dbuf)[3] = LOW_REPR_SAT1;
+        ((unsigned char *)dbuf)[4] = NULL1;
+        ((unsigned char *)dbuf)[5] = 50;
+      }
+
+      localBrick = new Brick(6, 1, 1, pixelType);
+      localBrick->SetBasePosition(1, 1, 1);
+
+      GDALRasterBand *poBand = dataset->GetRasterBand(1);
+      CPLErr err = poBand->RasterIO(GF_Write, 0, 0,
+                                              6, 1,
+                                              dbuf,
+                                              6, 1,
+                                              IsisPixelToGdal(pixelType),
+                                              0, 0);
+      if (err > CE_Failure) {
+        FAIL() << "Failed to write tiff";
+      }
+
+      dataset->Close();
+    }
+  }
+}
\ No newline at end of file
diff --git a/isis/tests/TiffFixtures.h b/isis/tests/TiffFixtures.h
new file mode 100644
index 0000000000000000000000000000000000000000..9a9819ce4baa710cf823d49de4bb8c04f5065fae
--- /dev/null
+++ b/isis/tests/TiffFixtures.h
@@ -0,0 +1,28 @@
+#ifndef TiffFixtures_h
+#define TiffFixtures_h
+
+#include "gtest/gtest.h"
+
+#include "Brick.h"
+#include "PixelType.h"
+#include "TempFixtures.h"
+
+#include "gdal_priv.h"
+
+namespace Isis {
+
+  class ReadWriteTiff : public TempTestingFiles {
+    protected:
+      void *dbuf = NULL;
+      GDALDataset *dataset = NULL;
+      Brick *localBrick = NULL;
+      QString path;
+
+      void SetUp() override;
+      void TearDown() override;
+      void createSizedTiff(int samples, int lines, int bands, PixelType pixelType);
+      void createTiff(PixelType pixelType, bool write=true);
+  };
+}
+
+#endif
\ No newline at end of file