Commit 459912c5 authored by Jesse Mapel's avatar Jesse Mapel Committed by Kristin
Browse files

Refactored ISD parsing into utilities and added warnings (#179)

* Added utility ISD parsing functions

* ISD Parsing Test stub

* Added ISD parsing method tests

* Added ISD Parsing utilities to Framer

* Replaced framer ISD parsing

* added check for warning output to bad fram ISD test

* Added a bit more testing
parent 80238a2e
Loading
Loading
Loading
Loading
+45 −5
Original line number Diff line number Diff line
@@ -4,6 +4,11 @@
#include <vector>
#include <math.h>
#include <tuple>
#include <string>

#include <json.hpp>

#include <Warning.h>

// methods pulled out of los2ecf and computeViewingPixel

@@ -44,5 +49,40 @@ void createCameraLookVector(
//  double attCorr[9]);
//

#endif
// Methods for checking/accessing the ISD

double metric_conversion(double val, std::string from, std::string to="m");
std::string getSensorModelName(nlohmann::json isd, csm::WarningList *list=nullptr);
std::string getImageId(nlohmann::json isd, csm::WarningList *list=nullptr);
std::string getSensorName(nlohmann::json isd, csm::WarningList *list=nullptr);
std::string getPlatformName(nlohmann::json isd, csm::WarningList *list=nullptr);
int getTotalLines(nlohmann::json isd, csm::WarningList *list=nullptr);
int getTotalSamples(nlohmann::json isd, csm::WarningList *list=nullptr);
double getStartingTime(nlohmann::json isd, csm::WarningList *list=nullptr);
double getCenterTime(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getIntegrationStartLines(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getIntegrationStartTimes(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getIntegrationTimes(nlohmann::json isd, csm::WarningList *list=nullptr);
int getSampleSumming(nlohmann::json isd, csm::WarningList *list=nullptr);
int getLineSumming(nlohmann::json isd, csm::WarningList *list=nullptr);
double getFocalLength(nlohmann::json isd, csm::WarningList *list=nullptr);
double getFocalLengthEpsilon(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getFocal2PixelLines(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getFocal2PixelSamples(nlohmann::json isd, csm::WarningList *list=nullptr);
double getDetectorCenterLine(nlohmann::json isd, csm::WarningList *list=nullptr);
double getDetectorCenterSample(nlohmann::json isd, csm::WarningList *list=nullptr);
double getDetectorStartingLine(nlohmann::json isd, csm::WarningList *list=nullptr);
double getDetectorStartingSample(nlohmann::json isd, csm::WarningList *list=nullptr);
double getMinHeight(nlohmann::json isd, csm::WarningList *list=nullptr);
double getMaxHeight(nlohmann::json isd, csm::WarningList *list=nullptr);
double getSemiMajorRadius(nlohmann::json isd, csm::WarningList *list=nullptr);
double getSemiMinorRadius(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getTransverseDistortionX(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getTransverseDistortionY(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getRadialDistortion(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getSunPositions(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getSensorPositions(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getSensorVelocities(nlohmann::json isd, csm::WarningList *list=nullptr);
std::vector<double> getSensorOrientations(nlohmann::json isd, csm::WarningList *list=nullptr);

#endif
+111 −135
Original line number Diff line number Diff line
#include "UsgsAstroFrameSensorModel.h"
#include "Distortion.h"

#include <iomanip>
#include <iostream>
@@ -10,6 +9,9 @@
#include <Error.h>
#include <Version.h>

#include "Distortion.h"
#include "Utilities.h"

using json = nlohmann::json;
using namespace std;

@@ -791,156 +793,120 @@ void UsgsAstroFrameSensorModel::replaceModelState(const std::string& stringState


std::string UsgsAstroFrameSensorModel::constructStateFromIsd(const std::string& jsonIsd, csm::WarningList* warnings) {
    auto metric_conversion = [](double val, std::string from, std::string to="m") {
        json typemap = {
          {"m", 0},
          {"km", 3}
        };

        // everything to lowercase
        std::transform(from.begin(), from.end(), from.begin(), ::tolower);
        std::transform(to.begin(), to.end(), to.begin(), ::tolower);
        return val*pow(10, typemap[from].get<int>() - typemap[to].get<int>());
    };

    json isd = json::parse(jsonIsd);
    json state = {};

    csm::WarningList* parsingWarnings = new csm::WarningList;

    try {
      state["m_modelName"] = isd.at("name_model");
      state["m_imageIdentifier"] = isd.at("image_identifier");
      state["m_sensorName"] = isd.at("name_sensor");
      state["m_platformName"] = isd.at("name_platform");
    state["m_modelName"] = getSensorModelName(isd, parsingWarnings);
    state["m_imageIdentifier"] = getImageId(isd, parsingWarnings);
    state["m_sensorName"] = getSensorName(isd, parsingWarnings);
    state["m_platformName"] = getPlatformName(isd, parsingWarnings);
    std::cerr << "Model Name Parsed!" << std::endl;

      state["m_startingDetectorSample"] = isd.at("starting_detector_sample");
      state["m_startingDetectorLine"] = isd.at("starting_detector_line");
    state["m_startingDetectorSample"] = getDetectorStartingSample(isd, parsingWarnings);
    state["m_startingDetectorLine"] = getDetectorStartingLine(isd, parsingWarnings);

    std::cerr << "Detector Starting Pixel Parsed!" << std::endl;

    // get focal length
      {
        json jayson = isd.at("focal_length_model");
        json focal_length = jayson.at("focal_length");
        json epsilon = jayson.at("focal_epsilon");

        state["m_focalLength"] = focal_length;
        state["m_focalLengthEpsilon"] = epsilon;
    state["m_focalLength"] = getFocalLength(isd, parsingWarnings);
    state["m_focalLengthEpsilon"] = getFocalLengthEpsilon(isd, parsingWarnings);

    std::cerr << "Focal Length Parsed!" << std::endl;
      }

    state["m_currentParameterValue"] = json();

    // get sensor_position
      {
        json jayson = isd.at("sensor_position");
        json positions = jayson.at("positions")[0];
        json velocities = jayson.at("velocities")[0];
        json unit = jayson.at("unit");
    std::vector<double> position = getSensorPositions(isd, parsingWarnings);
    if (!position.empty() && position.size() != 3) {
      parsingWarnings->push_back(
        csm::Warning(
          csm::Warning::DATA_NOT_AVAILABLE,
          "Sensor position does not have 3 values.",
          "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
      state["m_currentParameterValue"][0] = 0;
      state["m_currentParameterValue"][1] = 0;
      state["m_currentParameterValue"][2] = 0;
    }
    else {
      state["m_currentParameterValue"] = position;
    }

        unit = unit.get<std::string>();
        state["m_currentParameterValue"] = json();
        state["m_currentParameterValue"][0] = metric_conversion(positions[0].get<double>(), unit);
        state["m_currentParameterValue"][1] = metric_conversion(positions[1].get<double>(), unit);
        state["m_currentParameterValue"][2] = metric_conversion(positions[2].get<double>(), unit);
        state["m_spacecraftVelocity"] = velocities;
    // get sensor_velocity
    std::vector<double> velocity = getSensorVelocities(isd, parsingWarnings);
    if (!velocity.empty() && velocity.size() != 3) {
      parsingWarnings->push_back(
        csm::Warning(
          csm::Warning::DATA_NOT_AVAILABLE,
          "Sensor velocity does not have 3 values.",
          "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
    }
    else {
      state["m_spacecraftVelocity"] = velocity;
    }

    std::cerr << "Sensor Location Parsed!" << std::endl;
      }

    // get sun_position
    // sun position is not strictly necessary, but is required for getIlluminationDirection.
      {
        json jayson = isd.at("sun_position");
        json positions = jayson.at("positions")[0];
        json unit = jayson.at("unit");

        unit = unit.get<std::string>();
        state["m_sunPosition"] = json();
        state["m_sunPosition"][0] = metric_conversion(positions[0].get<double>(), unit);
        state["m_sunPosition"][1] = metric_conversion(positions[1].get<double>(), unit);
        state["m_sunPosition"][2] = metric_conversion(positions[2].get<double>(), unit);
    state["m_sunPosition"] = getSunPositions(isd);

    std::cerr << "Sun Position Parsed!" << std::endl;
      }

    // get sensor_orientation quaternion
      {
        json jayson = isd.at("sensor_orientation");
        json quaternion = jayson.at("quaternions")[0];

    std::vector<double> quaternion = getSensorOrientations(isd, parsingWarnings);
    if (!quaternion.empty() && quaternion.size() != 4) {
      parsingWarnings->push_back(
        csm::Warning(
          csm::Warning::DATA_NOT_AVAILABLE,
          "Sensor orientation quaternion does not have 4 values.",
          "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
    }
    else {
      state["m_currentParameterValue"][3] = quaternion[0];
      state["m_currentParameterValue"][4] = quaternion[1];
      state["m_currentParameterValue"][5] = quaternion[2];
      state["m_currentParameterValue"][6] = quaternion[3];
    }

    std::cerr << "Sensor Orientation Parsed!" << std::endl;
      }

    // get optical_distortion
      {
        json jayson = isd.at("optical_distortion");
        std::vector<double> xDistortion = jayson.at("transverse").at("x");
        std::vector<double> yDistortion = jayson.at("transverse").at("y");
        xDistortion.resize(10, 0.0);
        yDistortion.resize(10, 0.0);

        state["m_odtX"] = xDistortion;
        state["m_odtY"] = yDistortion;
    state["m_odtX"] = getTransverseDistortionX(isd, parsingWarnings);
    state["m_odtY"] = getTransverseDistortionY(isd, parsingWarnings);

    std::cerr << "Distortion Parsed!" << std::endl;
      }

    // get detector_center
      {
        json jayson = isd.at("detector_center");
        json sample = jayson.at("sample");
        json line = jayson.at("line");

        state["m_ccdCenter"][0] = line;
        state["m_ccdCenter"][1] = sample;
    state["m_ccdCenter"][0] = getDetectorCenterLine(isd, parsingWarnings);
    state["m_ccdCenter"][1] = getDetectorCenterSample(isd, parsingWarnings);

    std::cerr << "Detector Center Parsed!" << std::endl;
      }

    // get radii
      {
        json jayson = isd.at("radii");
        json semiminor = jayson.at("semiminor");
        json semimajor = jayson.at("semimajor");
        json unit = jayson.at("unit");

        unit = unit.get<std::string>();
        state["m_minorAxis"] = metric_conversion(semiminor.get<double>(), unit);
        state["m_majorAxis"] = metric_conversion(semimajor.get<double>(), unit);
    state["m_minorAxis"] = getSemiMinorRadius(isd, parsingWarnings);
    state["m_majorAxis"] = getSemiMajorRadius(isd, parsingWarnings);

    std::cerr << "Target Radii Parsed!" << std::endl;
      }

    // get reference_height
      {
        json reference_height = isd.at("reference_height");
        json maxheight = reference_height.at("maxheight");
        json minheight = reference_height.at("minheight");
        json unit = reference_height.at("unit");

        unit = unit.get<std::string>();
        state["m_minElevation"] = metric_conversion(minheight.get<double>(), unit);
        state["m_maxElevation"] = metric_conversion(maxheight.get<double>(), unit);
    state["m_minElevation"] = getMinHeight(isd, parsingWarnings);
    state["m_maxElevation"] = getMaxHeight(isd, parsingWarnings);

    std::cerr << "Reference Height Parsed!" << std::endl;
      }

      state["m_ephemerisTime"] = isd.at("center_ephemeris_time");
      state["m_nLines"] = isd.at("image_lines");
      state["m_nSamples"] = isd.at("image_samples");
    state["m_ephemerisTime"] = getCenterTime(isd, parsingWarnings);
    state["m_nLines"] = getTotalLines(isd, parsingWarnings);
    state["m_nSamples"] = getTotalSamples(isd, parsingWarnings);

      state["m_iTransL"] = isd.at("focal2pixel_lines");

      state["m_iTransS"] = isd.at("focal2pixel_samples");
    state["m_iTransL"] = getFocal2PixelLines(isd, parsingWarnings);
    state["m_iTransS"] = getFocal2PixelSamples(isd, parsingWarnings);

    // We don't pass the pixel to focal plane transformation so invert the
    // focal plane to pixel transformation
    try {
      double determinant = state["m_iTransL"][1].get<double>() * state["m_iTransS"][2].get<double>() -
                           state["m_iTransL"][2].get<double>() * state["m_iTransS"][1].get<double>();

@@ -953,6 +919,14 @@ std::string UsgsAstroFrameSensorModel::constructStateFromIsd(const std::string&
      state["m_transY"][2] =  state["m_iTransS"][2].get<double>() / determinant;
      state["m_transY"][0] = -(state["m_transY"][1].get<double>() * state["m_iTransL"][0].get<double>() +
                               state["m_transY"][2].get<double>() * state["m_iTransS"][0].get<double>());
    }
    catch (...) {
      parsingWarnings->push_back(
        csm::Warning(
          csm::Warning::DATA_NOT_AVAILABLE,
          "Could not compute detector pixel to focal plane coordinate transformation.",
          "UsgsAstroFrameSensorModel::constructStateFromIsd()"));
    }

    std::cerr << "Focal To Pixel Transformation Parsed!" << std::endl;

@@ -962,18 +936,20 @@ std::string UsgsAstroFrameSensorModel::constructStateFromIsd(const std::string&

    std::cerr << "Constants Set!" << std::endl;

    if (!parsingWarnings->empty()) {
      if (warnings) {
        warnings->insert(warnings->end(), parsingWarnings->begin(), parsingWarnings->end());
      }
    catch(std::out_of_range& e) {
      throw csm::Error(csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                       "ISD missing necessary keywords to create sensor model: " + std::string(e.what()),
                       "UsgsAstroFrameSensorModel::constructStateFromIsd");
    }
    catch(...) {
      delete parsingWarnings;
      parsingWarnings = nullptr;
      throw csm::Error(csm::Error::SENSOR_MODEL_NOT_CONSTRUCTIBLE,
                       "ISD is invalid for creating the sensor model.",
                       "UsgsAstroFrameSensorModel::constructStateFromIsd");
    }

    delete parsingWarnings;
    parsingWarnings = nullptr;

    return state.dump();

}
+604 −2

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1
Original line number Diff line number Diff line
@@ -5,7 +5,8 @@ add_executable(runCSMCameraModelTests
               PluginTests.cpp
               FrameCameraTests.cpp
               LineScanCameraTests.cpp
               DistortionTests.cpp)
               DistortionTests.cpp
               ISDParsingTests.cpp)
if(WIN32)
  option(CMAKE_USE_WIN32_THREADS_INIT "using WIN32 threads" ON)
  option(gtest_disable_pthreads "Disable uses of pthreads in gtest." ON)
+323 −0
Original line number Diff line number Diff line
#include "Utilities.h"

#include <gtest/gtest.h>

#include "Fixtures.h"

TEST(MetricConversion, DistanceConversion) {
  EXPECT_EQ(1,     metric_conversion(1000, "m", "km"));
  EXPECT_EQ(1000,  metric_conversion(1000, "m", "m"));
  EXPECT_EQ(1000,  metric_conversion(1, "km", "m"));
  EXPECT_EQ(1,     metric_conversion(1, "km", "km"));
  EXPECT_EQ(0,     metric_conversion(0, "m", "km"));
  EXPECT_EQ(0,     metric_conversion(0, "m", "m"));
  EXPECT_EQ(0,     metric_conversion(0, "km", "m"));
  EXPECT_EQ(0,     metric_conversion(0, "km", "km"));
  EXPECT_EQ(-1,    metric_conversion(-1000, "m", "km"));
  EXPECT_EQ(-1000, metric_conversion(-1000, "m", "m"));
  EXPECT_EQ(-1000, metric_conversion(-1, "km", "m"));
  EXPECT_EQ(-1,    metric_conversion(-1, "km", "km"));
}

TEST(ISDParsing, ModelName) {
  json isd = {
    {"name_model", "Test"}
  };
  EXPECT_EQ("Test", getSensorModelName(isd));
}

TEST(ISDParsing, ImageIdentifier) {
  json isd = {
    {"image_identifier", "Test"}
  };
  EXPECT_EQ("Test", getImageId(isd));
}

TEST(ISDParsing, SensorName) {
  json isd = {
    {"name_sensor", "Test"}
  };
  EXPECT_EQ("Test", getSensorName(isd));
}

TEST(ISDParsing, PlatformName) {
  json isd = {
    {"name_platform", "Test"}
  };
  EXPECT_EQ("Test", getPlatformName(isd));
}

TEST(ISDParsing, TotalLines) {
  json isd = {
    {"image_lines", 16}
  };
  EXPECT_EQ(16, getTotalLines(isd));
}

TEST(ISDParsing, TotalSamples) {
  json isd = {
    {"image_samples", 16}
  };
  EXPECT_EQ(16, getTotalSamples(isd));
}

TEST(ISDParsing, StartTime) {
  json isd = {
    {"starting_ephemeris_time", -10}
  };
  EXPECT_EQ(-10, getStartingTime(isd));
}

TEST(ISDParsing, CenterTime) {
  json isd = {
    {"center_ephemeris_time", -10}
  };
  EXPECT_EQ(-10, getCenterTime(isd));
}

TEST(ISDParsing, IntegrationStartLines) {
  json isd = {
    {"line_scan_rate", {
      {0, 1, 2},
      {3, 4, 5},
      {6, 7, 8}}
    }
  };
  std::vector<double> startLines = {0, 3, 6};
  EXPECT_EQ(startLines, getIntegrationStartLines(isd));
}

TEST(ISDParsing, IntegrationStartTimes) {
  json isd = {
    {"line_scan_rate", {
      {0, 1, 2},
      {3, 4, 5},
      {6, 7, 8}}
    }
  };
  std::vector<double> startTimes = {1, 4, 7};
  EXPECT_EQ(startTimes, getIntegrationStartTimes(isd));
}

TEST(ISDParsing, IntegrationTimes) {
  json isd = {
    {"line_scan_rate", {
      {0, 1, 2},
      {3, 4, 5},
      {6, 7, 8}}
    }
  };
  std::vector<double> times = {2, 5, 8};
  EXPECT_EQ(times, getIntegrationTimes(isd));
}

TEST(ISDParsing, SampleSumming) {
  json isd = {
    {"detector_sample_summing", 4}
  };
  EXPECT_EQ(4, getSampleSumming(isd));
}

TEST(ISDParsing, LineSumming) {
  json isd = {
    {"detector_line_summing", 4}
  };
  EXPECT_EQ(4, getLineSumming(isd));
}

TEST(ISDParsing, FocalLength) {
  json isd = {
    {"focal_length_model", {
      {"focal_length", 2}}
    }
  };
  EXPECT_EQ(2, getFocalLength(isd));
}

TEST(ISDParsing, FocalLengthEpsilon) {
  json isd = {
    {"focal_length_model", {
      {"focal_epsilon", 0.1}}
    }
  };
  EXPECT_EQ(0.1, getFocalLengthEpsilon(isd));
}

TEST(ISDParsing, Focal2PixelLines) {
  json isd = {
    {"focal2pixel_lines", {0, 1, 2}}
  };
  std::vector<double> coefficients = {0, 1, 2};
  EXPECT_EQ(coefficients, getFocal2PixelLines(isd));
}

TEST(ISDParsing, Focal2PixelSamples) {
  json isd = {
    {"focal2pixel_samples", {0, 1, 2}}
  };
  std::vector<double> coefficients = {0, 1, 2};
  EXPECT_EQ(coefficients, getFocal2PixelSamples(isd));
}

TEST(ISDParsing, DetectorCenterLine) {
  json isd = {
    {"detector_center", {
      {"line", 2}}
    }
  };
  EXPECT_EQ(2, getDetectorCenterLine(isd));
}

TEST(ISDParsing, DetectorCenterSample) {
  json isd = {
    {"detector_center", {
      {"sample", 3}}
    }
  };
  EXPECT_EQ(3, getDetectorCenterSample(isd));
}

TEST(ISDParsing, DetectorStartingLine) {
  json isd = {
    {"starting_detector_line", 1}
  };
  EXPECT_EQ(1, getDetectorStartingLine(isd));
}

TEST(ISDParsing, DetectorStartingSample) {
  json isd = {
    {"starting_detector_sample", 2}
  };
  EXPECT_EQ(2, getDetectorStartingSample(isd));
}

TEST(ISDParsing, MinHeight) {
  json isd = {
    {"reference_height", {
      {"minheight", -1},
      {"unit", "km"}}
    }
  };
  EXPECT_EQ(-1000, getMinHeight(isd));
}

TEST(ISDParsing, MaxHeight) {
  json isd = {
    {"reference_height", {
      {"maxheight", 10},
      {"unit", "km"}}
    }
  };
  EXPECT_EQ(10000, getMaxHeight(isd));
}

TEST(ISDParsing, SemiMajor) {
  json isd = {
    {"radii", {
      {"semimajor", 2},
      {"unit", "km"}}
    }
  };
  EXPECT_EQ(2000, getSemiMajorRadius(isd));
}

TEST(ISDParsing, SemiMinor) {
  json isd = {
    {"radii", {
      {"semiminor", 1},
      {"unit", "km"}}
    }
  };
  EXPECT_EQ(1000, getSemiMinorRadius(isd));
}

TEST(ISDParsing, TransverseX) {
  json isd = {
    {"optical_distortion", {
      {"transverse", {
        {"x", {-1, 2, 4}}}}
      }
    }
  };
  std::vector<double> coefficients = {-1, 2, 4, 0, 0, 0, 0, 0, 0, 0};
  EXPECT_EQ(coefficients, getTransverseDistortionX(isd));
}

TEST(ISDParsing, TransverseY) {
  json isd = {
    {"optical_distortion", {
      {"transverse", {
        {"y", {-11, 21, 24, 16, 20}}}}
      }
    }
  };
  std::vector<double> coefficients = {-11, 21, 24, 16, 20, 0, 0, 0, 0, 0};
  EXPECT_EQ(coefficients, getTransverseDistortionY(isd));
}

TEST(ISDParsing, Radial) {
  json isd = {
    {"optical_distortion", {
      {"radial", {
        {"coefficients", {0, 1, 2}}}}
      }
    }
  };
  std::vector<double> coefficients = {0, 1, 2};
  EXPECT_EQ(coefficients, getRadialDistortion(isd));
}

TEST(ISDParsing, SunPosition) {
  json isd = {
    {"sun_position", {
      {"positions", {
        {0, 1, 2},
        {3, 4, 5},
        {6, 7, 8}}},
      {"unit", "km"}}
    }
  };
  std::vector<double> positions = {0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000};
  EXPECT_EQ(positions, getSunPositions(isd));
}

TEST(ISDParsing, SensorPosition) {
  json isd = {
    {"sensor_position", {
      {"positions", {
        {0, 1, 2},
        {3, 4, 5},
        {6, 7, 8}}},
      {"unit", "km"}}
    }
  };
  std::vector<double> positions = {0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000};
  EXPECT_EQ(positions, getSensorPositions(isd));
}

TEST(ISDParsing, SensorVelocities) {
  json isd = {
    {"sensor_position", {
      {"velocities", {
        {0, 1, 2},
        {3, 4, 5},
        {6, 7, 8}}},
      {"unit", "km"}}
    }
  };
  std::vector<double> velocity = {0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000};
  EXPECT_EQ(velocity, getSensorVelocities(isd));
}

TEST(ISDParsing, SensorOrientations) {
  json isd = {
    {"sensor_orientation", {
      {"quaternions", {
        {0, 1, 2, 3},
        {4, 5, 6, 7},
        {8, 9, 10, 11}}}}
    }
  };
  std::vector<double> rotations = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
  EXPECT_EQ(rotations, getSensorOrientations(isd));
}
Loading