/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#include <mrpt/config/CConfigFileMemory.h>
#include <mrpt/core/cpu.h>
#include <mrpt/img/CImage.h>
#include <mrpt/img/TStereoCamera.h>
#include <mrpt/random.h>
#include <mrpt/system/filesystem.h>
#include <mrpt/vision/CImagePyramid.h>
#include <mrpt/vision/CStereoRectifyMap.h>

#include "common.h"

using namespace mrpt;
using namespace mrpt::random;
using namespace mrpt::vision;
using namespace std;

extern void getTestImage(unsigned int img_index, mrpt::img::CImage& out_img);

// ------------------------------------------------------
//				Benchmark: image loading/saving
// ------------------------------------------------------
double image_test_1(int w, int img_quality)
{
  int h = 0;
  switch (w)
  {
    case 640:
      h = 480;
      break;
    case 800:
      h = 600;
      break;
    case 1024:
      h = 768;
      break;
    case 1280:
      h = 1024;
      break;
    default:
      THROW_EXCEPTION("Invalid 'w'!");
  }

  CImage img(w, h, mrpt::img::CH_RGB);

  for (int i = 0; i < 5000; i++)
    img.line(
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        TColor(getRandomGenerator().drawUniform32bit()));

  CTicTac tictac;

  const string fil = mrpt::system::getTempFileName() + ".jpg";

  const size_t N = 30;

  tictac.Tic();
  for (size_t i = 0; i < N; i++)
  {
    bool savedOk = img.saveToFile(fil, img_quality);
    ASSERT_(savedOk);
  }

  const double T = tictac.Tac() / N;
  mrpt::system::deleteFile(fil);
  return T;
}

// ------------------------------------------------------
//				Benchmark: save/load to disk vs shared mem
// ------------------------------------------------------
template <bool perf_load>
double image_saveload(int iFormat, int to_shm)
{
  const char* format;
  switch (iFormat)
  {
    case 0:
      format = "bmp";
      break;
    case 1:
      format = "png";
      break;
    case 2:
      format = "jpg";
      break;
    default:
      THROW_EXCEPTION("Wrong format");
  }

  const int w = 800, h = 600;
  CImage img(w, h, mrpt::img::CH_RGB);
  for (int i = 0; i < 5000; i++)
    img.line(
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        TColor(getRandomGenerator().drawUniform32bit()));

  CTicTac tictac;

  string fil;
  if (to_shm)
  {
    fil = string("/dev/shm/mrpt_perf_test.") + string(format);
  }
  else
  {
    fil = mrpt::system::getTempFileName() + string(".") + string(format);
  }

  double T;
  if (perf_load)
  {
    // LOAD:
    bool savedOk = img.saveToFile(fil);
    ASSERT_(savedOk);

    const size_t N = 30;

    tictac.Tic();
    for (size_t i = 0; i < N; i++)
    {
      CImage img_new;
      bool loadOk = img.loadFromFile(fil);
      ASSERT_(loadOk);
    }

    T = tictac.Tac() / N;
  }
  else
  {
    // WRITE:
    const size_t N = 30;

    tictac.Tic();
    for (size_t i = 0; i < N; i++)
    {
      bool savedOk = img.saveToFile(fil);
      ASSERT_(savedOk);
    }

    T = tictac.Tac() / N;
  }

  mrpt::system::deleteFile(fil);
  return T;
}

double image_test_2(int w, int h)
{
  CImage img(w, h, mrpt::img::CH_RGB), img2;

#if MRPT_HAS_OPENCV
//	int oldVal = cvUseOptimized(1);
#endif

  for (int i = 0; i < 5000; i++)
    img.line(
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        TColor(getRandomGenerator().drawUniform32bit()));

  CTicTac tictac;

  const size_t N = 50;

  tictac.Tic();
  for (size_t i = 0; i < N; i++) img.filterGaussian(img2, 7, 7);

  double R = tictac.Tac() / N;

#if MRPT_HAS_OPENCV
//	cvUseOptimized(oldVal);
#endif
  return R;
}

template <TImageChannels IMG_CHANNELS, bool DISABLE_SIMD = false>
double image_halfsample(int w, int h)
{
  CImage img(w, h, IMG_CHANNELS), img2;
  CTicTac tictac;

  const bool savedFeatSSE2 = mrpt::cpu::supports(mrpt::cpu::feature::SSE2);
  const bool savedFeatSSSE3 = mrpt::cpu::supports(mrpt::cpu::feature::SSSE3);
  if (DISABLE_SIMD)
  {
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSE2, false);
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSSE3, false);
  }

  const size_t N = 300;

  tictac.Tic();
  for (size_t i = 0; i < N; i++) img.scaleHalf(img2, mrpt::img::IMG_INTERP_NN);

  if (DISABLE_SIMD)
  {
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSE2, savedFeatSSE2);
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSSE3, savedFeatSSSE3);
  }
  return tictac.Tac() / N;
}

template <TImageChannels IMG_CHANNELS, bool DISABLE_SIMD = false>
double image_halfsample_smooth(int w, int h)
{
  CImage img(w, h, IMG_CHANNELS), img2;

  const bool savedFeatSSE2 = mrpt::cpu::supports(mrpt::cpu::feature::SSE2);
  const bool savedFeatSSSE3 = mrpt::cpu::supports(mrpt::cpu::feature::SSSE3);
  if (DISABLE_SIMD)
  {
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSE2, false);
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSSE3, false);
  }

  CTicTac tictac;

  const size_t N = 300;

  tictac.Tic();
  for (size_t i = 0; i < N; i++) img.scaleHalf(img2, mrpt::img::IMG_INTERP_LINEAR);

  if (DISABLE_SIMD)
  {
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSE2, savedFeatSSE2);
    mrpt::cpu::overrideDetectedFeature(mrpt::cpu::feature::SSSE3, savedFeatSSSE3);
  }

  return tictac.Tac() / N;
}

double image_rgb2gray_8u(int w, int h)
{
  CImage img(w, h, CH_RGB), img2;

  CTicTac tictac;

  const size_t N = 300;

  tictac.Tic();
  for (size_t i = 0; i < N; i++) img.grayscale(img2);

  return tictac.Tac() / N;
}

double image_KLTscore(int WIN, int N)
{
  static const size_t w = 800;
  static const size_t h = 800;
  CImage img(w, h, CH_GRAY);

  for (int i = 0; i < 5000; i++)
    img.line(
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        getRandomGenerator().drawUniform(0, w - 1), getRandomGenerator().drawUniform(0, h - 1),
        TColor(getRandomGenerator().drawUniform32bit()));

  ASSERT_LT_(WIN, 128);
  int x = 0;
  int y = 0;

  CTicTac tictac;
  tictac.Tic();
  for (int i = 0; i < N; i++)
  {
    float r = img.KLT_response(x | 128, y | 128, WIN);
    (void)r;
    x++;
    x &= 0x1FF;
    y++;
    y &= 0x1FF;
  }

  double R = tictac.Tac() / N;

  return R;
}

template <bool DO_SMOOTH, bool CONVERT_GRAY, bool SOURCE_GRAY>
double image_buildPyramid(int N, int NOCTS)
{
  // Get a real image for testing:
  CImage img;
  getTestImage(0, img);

  if (SOURCE_GRAY) img = img.grayscale();

  mrpt::vision::CImagePyramid pyr;
  // Run once in advance not to count the memory reservation:
  pyr.buildPyramid(img, NOCTS, DO_SMOOTH, CONVERT_GRAY);

  CTicTac tictac;
  tictac.Tic();
  for (int i = 0; i < N; i++)
  {
    pyr.buildPyramid(img, NOCTS, DO_SMOOTH, CONVERT_GRAY);
  }
  double R = tictac.Tac() / N;
  return R;
}

const char* EXAMPLE_STEREO_CALIB =
    "[CAMERA_PARAMS_LEFT]\n"
    "resolution = [1024 768]\n"
    "cx         = 523.098290\n"
    "cy         = 402.599910\n"
    "fx         = 839.161865\n"
    "fy         = 841.168710\n"
    "dist       = [-3.747584e-001 2.468493e-001 0.000000e+000 0.000000e+000 "
    "-1.281381e-001]    // The order is: [K1 K2 T1 T2 K3]\n"
    "[CAMERA_PARAMS_RIGHT]\n"
    "resolution = [1024 768]\n"
    "cx         = 511.173904\n"
    "cy         = 388.440870\n"
    "fx         = 836.761931\n"
    "fy         = 838.133088\n"
    "dist       = [-3.606276e-001 2.116929e-001 0.000000e+000 0.000000e+000 "
    "-9.216787e-002]    // The order is: [K1 K2 T1 T2 K3]\n"
    "[CAMERA_PARAMS_LEFT2RIGHT_POSE]\n"
    "translation_only     = [1.193203e-001 -3.361637e-004 3.729054e-004]\n"
    "rotation_matrix_only = [9.9997534298507e-001 -6.2827062556904e-003 "
    "-3.1370406439725e-003 ;6.2847721003742e-003 9.9998003994880e-001 "
    "6.4910988856303e-004 ;3.1328998617231e-003 -6.6880946896783e-004 "
    "9.9999486880301e-001 ]\n"
    "pose_yaw_pitch_roll  = [0.119320 -0.000336 0.000373 0.360095 -0.179502 "
    "-0.038320]\n"
    "pose_quaternion      = [0.119320 -0.000336 0.000373 0.999994 -0.000329 "
    "-0.001567 0.003142]\n";

template <int IMG_CHANNELS, int w, int h, int w2, int h2>
double stereoimage_rectify_prepare_map(int, int)
{
  mrpt::img::TStereoCamera params;
  params.loadFromConfigFile(
      "CAMERA_PARAMS", mrpt::config::CConfigFileMemory(std::string(EXAMPLE_STEREO_CALIB)));
  params.scaleToResolution(w, h);

  mrpt::vision::CStereoRectifyMap rectify_map;
  rectify_map.enableResizeOutput((w2 != w || h2 != h), w2, h2);

  CTicTac tictac;

  const size_t N = 100;
  tictac.Tic();
  for (size_t i = 0; i < N; i++)
  {
    rectify_map.setAlpha(-1);  // This invalidates the old map & forces recomputing
    rectify_map.setFromCamParams(params);
  }

  return tictac.Tac() / N;
}

template <TImageChannels IMG_CHANNELS, int w, int h, int w2, int h2>
double stereoimage_rectify(int, int)
{
  const CImage imgL(w, h, IMG_CHANNELS), imgR(w, h, IMG_CHANNELS);
  CImage imgL2, imgR2;

  mrpt::img::TStereoCamera params;
  params.loadFromConfigFile(
      "CAMERA_PARAMS", mrpt::config::CConfigFileMemory(std::string(EXAMPLE_STEREO_CALIB)));
  params.scaleToResolution(w, h);

  mrpt::vision::CStereoRectifyMap rectify_map;
  rectify_map.enableResizeOutput((w2 != w || h2 != h), w2, h2);
  rectify_map.setFromCamParams(params);

  CTicTac tictac;
  const size_t N = 20;
  tictac.Tic();
  for (size_t i = 0; i < N; i++)
  {
    rectify_map.rectify(imgL, imgR, imgL2, imgR2);
  }

  return tictac.Tac() / N;
}

// ------------------------------------------------------
// register_tests_image
// ------------------------------------------------------
void register_tests_image()
{
  lstTests.emplace_back("images: Save as JPEG (640x480, quality=95%)", image_test_1, 640, 95);
  lstTests.emplace_back("images: Save as JPEG (800x600, quality=95%)", image_test_1, 800, 95);
  lstTests.emplace_back("images: Save as JPEG (1024x768, quality=95%)", image_test_1, 1024, 95);

  lstTests.emplace_back("images: Save as JPEG (640x480, quality=75%)", image_test_1, 640, 75);
  lstTests.emplace_back("images: Save as JPEG (800x600, quality=75%)", image_test_1, 800, 75);
  lstTests.emplace_back("images: Save as JPEG (1024x768, quality=75%)", image_test_1, 1024, 75);

  lstTests.emplace_back("images: Save BMP 800x600 disk", image_saveload<false>, 0, 0);
  lstTests.emplace_back("images: Save PNG 800x600 disk", image_saveload<false>, 1, 0);
  lstTests.emplace_back("images: Save JPG 800x600 disk", image_saveload<false>, 2, 0);
  lstTests.emplace_back("images: Load BMP 800x600 disk", image_saveload<true>, 0, 0);
  lstTests.emplace_back("images: Load PNG 800x600 disk", image_saveload<true>, 1, 0);
  lstTests.emplace_back("images: Load JPG 800x600 disk", image_saveload<true>, 2, 0);

  if (mrpt::system::directoryExists("/dev/shm"))
  {
    lstTests.emplace_back("images: Save BMP 800x600 shared mem", image_saveload<false>, 0, 1);
    lstTests.emplace_back("images: Save PNG 800x600 shared mem", image_saveload<false>, 1, 1);
    lstTests.emplace_back("images: Save JPG 800x600 shared mem", image_saveload<false>, 2, 1);
    lstTests.emplace_back("images: Load BMP 800x600 shared mem", image_saveload<true>, 0, 1);
    lstTests.emplace_back("images: Load PNG 800x600 shared mem", image_saveload<true>, 1, 1);
    lstTests.emplace_back("images: Load JPG 800x600 shared mem", image_saveload<true>, 2, 1);
  }

  lstTests.emplace_back("images: Gauss filter (640x480)", image_test_2, 640, 480);
  lstTests.emplace_back("images: Gauss filter (800x600)", image_test_2, 800, 600);
  lstTests.emplace_back("images: Gauss filter (1024x768)", image_test_2, 1024, 768);

  lstTests.emplace_back("images: Half sample GRAY (160x120)", image_halfsample<CH_GRAY>, 160, 120);
  lstTests.emplace_back("images: Half sample GRAY (320x240)", image_halfsample<CH_GRAY>, 320, 240);
  lstTests.emplace_back("images: Half sample GRAY (640x480)", image_halfsample<CH_GRAY>, 640, 480);
  lstTests.emplace_back("images: Half sample GRAY (800x600)", image_halfsample<CH_GRAY>, 800, 600);
  lstTests.emplace_back(
      "images: Half sample GRAY (1024x768)", image_halfsample<CH_GRAY>, 1024, 768);

  lstTests.emplace_back(
      "images: Half sample GRAY (1280x1024)", image_halfsample<CH_GRAY>, 1280, 1024);
  lstTests.emplace_back(
      "images: Half sample GRAY (1280x1024) [SSE2 disabled]", image_halfsample<CH_GRAY, true>, 1280,
      1024);

  lstTests.emplace_back("images: Half sample RGB (160x120)", image_halfsample<CH_RGB>, 160, 120);
  lstTests.emplace_back("images: Half sample RGB (320x240)", image_halfsample<CH_RGB>, 320, 240);
  lstTests.emplace_back("images: Half sample RGB (640x480)", image_halfsample<CH_RGB>, 640, 480);
  lstTests.emplace_back("images: Half sample RGB (800x600)", image_halfsample<CH_RGB>, 800, 600);
  lstTests.emplace_back("images: Half sample RGB (1024x768)", image_halfsample<CH_RGB>, 1024, 768);
  lstTests.emplace_back(
      "images: Half sample RGB (1280x1024)", image_halfsample<CH_RGB>, 1280, 1024);
  lstTests.emplace_back(
      "images: Half sample RGB (1280x1024) [SSSE3 disabled]", image_halfsample<CH_RGB>, 1280, 1024);

  lstTests.emplace_back(
      "images: Half sample smooth GRAY (160x120)", image_halfsample_smooth<CH_GRAY>, 160, 120);
  lstTests.emplace_back(
      "images: Half sample smooth GRAY (320x240)", image_halfsample_smooth<CH_GRAY>, 320, 240);
  lstTests.emplace_back(
      "images: Half sample smooth GRAY (640x480)", image_halfsample_smooth<CH_GRAY>, 640, 480);
  lstTests.emplace_back(
      "images: Half sample smooth GRAY (800x600)", image_halfsample_smooth<CH_GRAY>, 800, 600);
  lstTests.emplace_back(
      "images: Half sample smooth GRAY (1024x768)", image_halfsample_smooth<CH_GRAY>, 1024, 768);
  lstTests.emplace_back(
      "images: Half sample smooth GRAY (1280x1024)", image_halfsample_smooth<CH_GRAY>, 1280, 1024);
  lstTests.emplace_back(
      "images: Half sample smooth GRAY (1280x1024) [SSSE3 disabled]",
      image_halfsample_smooth<CH_GRAY, true>, 1280, 1024);

  lstTests.emplace_back(
      "images: Half sample smooth RGB (160x120)", image_halfsample_smooth<CH_RGB>, 160, 120);
  lstTests.emplace_back(
      "images: Half sample smooth RGB (320x240)", image_halfsample_smooth<CH_RGB>, 320, 240);
  lstTests.emplace_back(
      "images: Half sample smooth RGB (640x480)", image_halfsample_smooth<CH_RGB>, 640, 480);
  lstTests.emplace_back(
      "images: Half sample smooth RGB (800x600)", image_halfsample_smooth<CH_RGB>, 800, 600);
  lstTests.emplace_back(
      "images: Half sample smooth RGB (1024x768)", image_halfsample_smooth<CH_RGB>, 1024, 768);
  lstTests.emplace_back(
      "images: Half sample smooth RGB (1280x1024)", image_halfsample_smooth<CH_RGB>, 1280, 1024);

  lstTests.emplace_back("images: RGB->GRAY 8u (40x30)", image_rgb2gray_8u, 40, 30);
  lstTests.emplace_back("images: RGB->GRAY 8u (80x60)", image_rgb2gray_8u, 80, 60);
  lstTests.emplace_back("images: RGB->GRAY 8u (160x120)", image_rgb2gray_8u, 160, 120);
  lstTests.emplace_back("images: RGB->GRAY 8u (320x240)", image_rgb2gray_8u, 320, 240);
  lstTests.emplace_back("images: RGB->GRAY 8u (640x480)", image_rgb2gray_8u, 640, 480);
  lstTests.emplace_back("images: RGB->GRAY 8u (800x600)", image_rgb2gray_8u, 800, 600);
  lstTests.emplace_back("images: RGB->GRAY 8u (1024x768)", image_rgb2gray_8u, 1024, 768);
  lstTests.emplace_back("images: RGB->GRAY 8u (1280x1024)", image_rgb2gray_8u, 1280, 1024);

  lstTests.emplace_back("images: KLT score (WIN=2 5x5)", image_KLTscore, 2, 1e7);
  lstTests.emplace_back("images: KLT score (WIN=3 7x7)", image_KLTscore, 3, 1e7);
  lstTests.emplace_back("images: KLT score (WIN=4 9x9)", image_KLTscore, 4, 1e7);
  lstTests.emplace_back("images: KLT score (WIN=5 10x10)", image_KLTscore, 5, 1e7);
  lstTests.emplace_back("images: KLT score (WIN=6 13x13)", image_KLTscore, 6, 1e7);
  lstTests.emplace_back("images: KLT score (WIN=7 15x15)", image_KLTscore, 7, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=8 17x17)", image_KLTscore, 8, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=9 19x19)", image_KLTscore, 9, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=10 21x21)", image_KLTscore, 10, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=11 23x23)", image_KLTscore, 11, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=12 25x25)", image_KLTscore, 12, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=13 27x27)", image_KLTscore, 13, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=14 29x29)", image_KLTscore, 14, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=15 31x31)", image_KLTscore, 15, 1e6);
  lstTests.emplace_back("images: KLT score (WIN=16 33x33)", image_KLTscore, 16, 1e6);

  lstTests.emplace_back(
      "images: buildPyramid 640x480,4 levs,no smooth,rgb->rgb",
      image_buildPyramid<false, false, false>, 500, 4);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,4 levs,   smooth,rgb->rgb",
      image_buildPyramid<true, false, false>, 500, 4);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,4 levs,no smooth,rgb->gray",
      image_buildPyramid<false, true, false>, 500, 4);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,4 levs,   smooth,rgb->gray",
      image_buildPyramid<true, true, false>, 500, 4);

  lstTests.emplace_back(
      "images: buildPyramid 640x480,8 levs,no smooth,rgb->rgb",
      image_buildPyramid<false, false, false>, 500, 8);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,8 levs,   smooth,rgb->rgb",
      image_buildPyramid<true, false, false>, 500, 8);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,8 levs,no smooth,rgb->gray",
      image_buildPyramid<false, true, false>, 500, 8);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,8 levs,   smooth,rgb->gray",
      image_buildPyramid<true, true, false>, 500, 8);

  lstTests.emplace_back(
      "images: buildPyramid 640x480,4 levs,no smooth,gray", image_buildPyramid<false, false, true>,
      500, 4);
  lstTests.emplace_back(
      "images: buildPyramid 640x480,4 levs,   smooth,gray", image_buildPyramid<true, false, true>,
      500, 4);

  lstTests.emplace_back(
      "stereo: prepare rectify map 640x480 RGB",
      stereoimage_rectify_prepare_map<CH_RGB, 640, 480, 640, 480>);
  lstTests.emplace_back(
      "stereo: prepare rectify map 800x600 RGB",
      stereoimage_rectify_prepare_map<CH_RGB, 800, 600, 800, 600>);
  lstTests.emplace_back(
      "stereo: prepare rectify map 1024x768 RGB",
      stereoimage_rectify_prepare_map<CH_RGB, 1024, 768, 1024, 768>);
  lstTests.emplace_back(
      "stereo: prepare rectify map 1024x768->800x600 RGB",
      stereoimage_rectify_prepare_map<CH_RGB, 1024, 768, 800, 600>);
  lstTests.emplace_back(
      "stereo: prepare rectify map 1024x768->640x480 RGB",
      stereoimage_rectify_prepare_map<CH_RGB, 1024, 768, 640, 480>);

  lstTests.emplace_back(
      "stereo: rectify 640x480 RGB", stereoimage_rectify<CH_RGB, 640, 480, 640, 480>);
  lstTests.emplace_back(
      "stereo: rectify 800x600 RGB", stereoimage_rectify<CH_RGB, 800, 600, 800, 600>);
  lstTests.emplace_back(
      "stereo: rectify 1024x768 RGB", stereoimage_rectify<CH_RGB, 1024, 768, 1024, 768>);
  lstTests.emplace_back(
      "stereo: rectify 1024x768->800x600 RGB", stereoimage_rectify<CH_RGB, 1024, 768, 800, 600>);
  lstTests.emplace_back(
      "stereo: rectify 1024x768->640x480 RGB", stereoimage_rectify<CH_RGB, 1024, 768, 640, 480>);

  lstTests.emplace_back(
      "stereo: rectify 640x480 GRAY", stereoimage_rectify<CH_GRAY, 640, 480, 640, 480>);
  lstTests.emplace_back(
      "stereo: rectify 800x600 GRAY", stereoimage_rectify<CH_GRAY, 800, 600, 800, 600>);
  lstTests.emplace_back(
      "stereo: rectify 1024x768 GRAY", stereoimage_rectify<CH_GRAY, 1024, 768, 1024, 768>);
  lstTests.emplace_back(
      "stereo: rectify 1024x768->800x600 GRAY", stereoimage_rectify<CH_GRAY, 1024, 768, 800, 600>);
  lstTests.emplace_back(
      "stereo: rectify 1024x768->640x480 GRAY", stereoimage_rectify<CH_GRAY, 1024, 768, 640, 480>);
}
